BLD - add GitHub contribution and release automation (#3)

* BLD - add GitHub contribution and release automation

* BLD - restrict dev merges to maintainers

* BLD - add automated review and PR test resources

* DOC - require AI governance checks

* FIX - pin patched nanoid dependency

* TRY - trigger webhook delivery

* TRY - verify webhook routing

* TRY - rerun pull request checks
This commit is contained in:
DerEchteAlec
2026-08-19 15:26:50 +02:00
committed by GitHub
parent c766cb5044
commit 7644dec3a3
19 changed files with 1173 additions and 4 deletions
+114
View File
@@ -0,0 +1,114 @@
name: Bug report
description: Report a reproducible problem in Sky Phone.
title: "[Bug] "
body:
- type: markdown
attributes:
value: |
Thanks for helping improve Sky Phone. Search existing issues first and remove secrets, tokens, player identifiers, and private URLs from all logs.
- type: input
id: version
attributes:
label: Sky Phone version
description: Use the release tag or exact commit SHA. "Latest" is not a version.
placeholder: 0.1.0 or 0123456789abcdef...
validations:
required: true
- type: dropdown
id: framework
attributes:
label: Framework
options:
- ESX Legacy
- QBCore
- Qbox
- Other or custom bridge
validations:
required: true
- type: input
id: artifacts
attributes:
label: FiveM server artifact
description: Provide the exact artifact build number.
placeholder: "12345"
validations:
required: true
- type: input
id: database
attributes:
label: Database
description: Include product and version, for example MariaDB 11.4.
placeholder: MariaDB 11.4
validations:
required: true
- type: textarea
id: integrations
attributes:
label: Relevant integrations
description: List the configured inventory, voice, housing, garage, media, and other providers involved.
placeholder: |
Inventory: ox_inventory 2.x
Voice: pma-voice 1.x
validations:
required: false
- type: textarea
id: description
attributes:
label: Problem description
description: Describe what happens and which player or server state is affected.
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: Reproduction steps
description: Provide the smallest reliable sequence, starting from a clean resource restart where possible.
placeholder: |
1. Start ...
2. Open ...
3. Select ...
4. Observe ...
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behavior
validations:
required: true
- type: textarea
id: logs
attributes:
label: Client, server, and NUI evidence
description: Attach relevant F8, server console, NUI console, network, or database output. Include the first error, not only follow-up errors.
validations:
required: true
- type: textarea
id: regression
attributes:
label: Regression information
description: State the last known working tag or commit, if applicable.
validations:
required: false
- type: checkboxes
id: confirmations
attributes:
label: Confirmation
options:
- label: I reproduced this with the resource from this repository and included its exact version.
required: true
- label: I removed secrets, credentials, private URLs, and player-identifying data.
required: true
- label: This is not an exploitable security vulnerability that should be reported privately.
required: true
+8
View File
@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Security vulnerability
url: https://github.com/sky-systems/sky_phone/security/advisories/new
about: Report exploitable vulnerabilities privately. Do not open a public issue.
- name: Installation and configuration support
url: https://discord.gg/sky-systems
about: Ask for setup help in the Sky-Systems community.
+35
View File
@@ -0,0 +1,35 @@
name: Documentation improvement
description: Report missing, incorrect, or unclear project documentation.
title: "[Docs] "
body:
- type: input
id: location
attributes:
label: Documentation location
description: Link the section or provide the repository path and heading.
placeholder: README.md, Quick installation
validations:
required: true
- type: textarea
id: problem
attributes:
label: What is unclear or incorrect?
validations:
required: true
- type: textarea
id: correction
attributes:
label: Suggested correction
description: Include verified runtime, configuration, or version context where relevant.
validations:
required: true
- type: checkboxes
id: confirmations
attributes:
label: Confirmation
options:
- label: I checked the current default branch before reporting this.
required: true
@@ -0,0 +1,82 @@
name: Feature request
description: Propose a focused improvement to Sky Phone.
title: "[Feature] "
body:
- type: markdown
attributes:
value: |
Explain the user problem before the proposed implementation. New features must preserve Sky Phone's standalone architecture and server-authoritative state changes.
- type: textarea
id: problem
attributes:
label: Problem or opportunity
description: Who needs this, and what can they not do today?
validations:
required: true
- type: textarea
id: proposal
attributes:
label: Proposed behavior
description: Describe the user-visible result and the expected client, server, NUI, or persistence flow.
validations:
required: true
- type: dropdown
id: scope
attributes:
label: Primary area
options:
- Phone app or NUI
- Client behavior
- Server behavior
- Framework or third-party integration
- Configuration or localization
- Database or migration
- Developer API or custom apps
- Build, release, or documentation
validations:
required: true
- type: textarea
id: authority
attributes:
label: Authority and data ownership
description: For state-changing behavior, explain what the server validates and which sky_phone-owned data changes.
validations:
required: false
- type: textarea
id: alternatives
attributes:
label: Alternatives considered
description: Describe existing configuration, integrations, or workflows that were considered.
validations:
required: false
- type: textarea
id: compatibility
attributes:
label: Compatibility impact
description: Note affected frameworks, providers, configs, locales, schemas, exports, or public events.
validations:
required: false
- type: textarea
id: references
attributes:
label: References or mockups
description: Attach concise examples, diagrams, or screenshots when they materially clarify the request.
validations:
required: false
- type: checkboxes
id: confirmations
attributes:
label: Confirmation
options:
- label: This proposal does not require sky_base, sky_jobs_base, or another Sky resource.
required: true
- label: I searched for an existing issue covering the same request.
required: true
+46
View File
@@ -0,0 +1,46 @@
## Summary
<!-- What user or developer problem does this PR solve? -->
Closes #
## Root cause and approach
<!-- For fixes: trace the real failing path. For features: explain the chosen boundary and behavior. -->
## Changes
-
## Compatibility and migrations
<!-- List config, locale, SQL, framework, public API, event, export, or deployment impact. Write "None" when not applicable. -->
## Validation
- [ ] I ran the narrowest relevant automated tests.
- [ ] I ran `pnpm typecheck`, `pnpm lint`, and `pnpm test` for frontend changes.
- [ ] I ran a production frontend build for frontend changes.
- [ ] I tested affected Lua/native behavior with experimental OAL enabled, or marked the live runtime test as pending below.
- [ ] I verified every changed NUI callback responds on every reachable path.
- [ ] I reviewed the final diff and excluded unrelated work and generated-only edits.
Commands and results:
```text
```
## Runtime evidence
<!-- Distinguish local/static/build checks from an actual restarted FiveM runtime test. Add screenshots for visible UI changes. -->
## Security and architecture
- [ ] Consequential actions remain server-authoritative and validate identity, permissions, ownership, limits, and payloads.
- [ ] This change introduces no dependency, event, export, global, config, persistence, or fallback connection to another `sky_*` resource.
- [ ] No secret, credential, private URL, or personal player data is included.
## Reviewer notes
<!-- Call out risky areas, intentional tradeoffs, or follow-up work. -->
+22
View File
@@ -0,0 +1,22 @@
# Repository rulesets
These JSON files are import-ready repository rulesets for `sky-systems/sky_phone`.
## Activation order
1. Merge the governance files and workflows into the default `dev` branch.
2. Let `CI`, `Automated code review`, and `Pull request policy` run once so GitHub registers the check names.
3. Open **Settings > Rules > Rulesets > New ruleset > Import a ruleset**.
4. Import `protect-dev.json`, review its target and required checks, then activate it.
5. Import `protect-release-tags.json`, review the numeric tag pattern, then activate it.
6. Open a test pull request and confirm all five required checks and the test-resource comment are reported before relying on the ruleset.
`protect-dev.json` blocks deletion and force pushes, restricts all updates of `dev` to the built-in `Maintain` role, requires one approval, dismisses stale reviews, requires approval after the last push, resolves review conversations, and requires the `Repository policy`, `Frontend`, `CodeQL`, `Dependency review`, and `Pull request policy` checks. Maintainers can bypass rules only through a pull request, so they cannot use this bypass for a direct push to `dev`.
Successful pull requests receive a 14-day test-resource artifact containing the deployable `sky_phone` folder and built NUI. `Pull request test resource link` runs after `CI` and maintains one download comment in the pull request. It does not check out, download, or execute pull-request content with its write-capable token.
`protect-release-tags.json` accepts stable numeric semantic versions such as `0.2.0`, rejects a leading `v`, and makes created tags immutable.
The built-in repository `Maintain` role uses `RepositoryRole` actor ID `2`. Its branch bypass is limited to pull requests and is what permits maintainers to merge; its tag bypass is always available so maintainers can create and recover releases. Changing the committed JSON alone does not update an already imported ruleset.
Rulesets are GitHub settings, not live configuration files. Committing or editing these JSON files does not activate or update protection automatically; an administrator must import or reconcile them in GitHub.
+68
View File
@@ -0,0 +1,68 @@
{
"name": "Protect the default development branch",
"target": "branch",
"source_type": "Repository",
"enforcement": "active",
"conditions": {
"ref_name": {
"include": ["~DEFAULT_BRANCH"],
"exclude": []
}
},
"rules": [
{
"type": "deletion"
},
{
"type": "non_fast_forward"
},
{
"type": "update",
"parameters": {
"update_allows_fetch_and_merge": false
}
},
{
"type": "pull_request",
"parameters": {
"allowed_merge_methods": ["merge", "squash", "rebase"],
"dismiss_stale_reviews_on_push": true,
"require_code_owner_review": false,
"require_last_push_approval": true,
"required_approving_review_count": 1,
"required_review_thread_resolution": true
}
},
{
"type": "required_status_checks",
"parameters": {
"do_not_enforce_on_create": true,
"required_status_checks": [
{
"context": "Repository policy"
},
{
"context": "Frontend"
},
{
"context": "CodeQL"
},
{
"context": "Dependency review"
},
{
"context": "Pull request policy"
}
],
"strict_required_status_checks_policy": true
}
}
],
"bypass_actors": [
{
"actor_id": 2,
"actor_type": "RepositoryRole",
"bypass_mode": "pull_request"
}
]
}
@@ -0,0 +1,36 @@
{
"name": "Protect semantic release tags",
"target": "tag",
"source_type": "Repository",
"enforcement": "active",
"conditions": {
"ref_name": {
"include": ["~ALL"],
"exclude": []
}
},
"rules": [
{
"type": "deletion"
},
{
"type": "non_fast_forward"
},
{
"type": "tag_name_pattern",
"parameters": {
"name": "Numeric semantic version without v prefix",
"negate": false,
"operator": "regex",
"pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+\\n?$"
}
}
],
"bypass_actors": [
{
"actor_id": 2,
"actor_type": "RepositoryRole",
"bypass_mode": "always"
}
]
}
+161
View File
@@ -0,0 +1,161 @@
import { readFile, readdir } from "node:fs/promises";
import { dirname, extname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const scriptDirectory = dirname(fileURLToPath(import.meta.url));
const repositoryRoot = resolve(scriptDirectory, "..", "..");
const failures = [];
function fail(message) {
failures.push(message);
}
const manifestPath = join(repositoryRoot, "sky_phone", "fxmanifest.lua");
const manifest = await readFile(manifestPath, "utf8");
for (const requiredFragment of [
"fx_version 'cerulean'",
"node_version '22'",
"use_experimental_fxv2_oal 'yes'",
"ui_page 'source/html/index.html'",
]) {
if (!manifest.includes(requiredFragment)) {
fail(`fxmanifest.lua is missing required contract: ${requiredFragment}`);
}
}
const rulesetDirectory = join(repositoryRoot, ".github", "rulesets");
const rulesetFiles = (await readdir(rulesetDirectory)).filter((file) =>
file.endsWith(".json"),
);
const requiredContexts = new Set([
"Repository policy",
"Frontend",
"CodeQL",
"Dependency review",
"Pull request policy",
]);
for (const file of rulesetFiles) {
const path = join(rulesetDirectory, file);
let ruleset;
try {
ruleset = JSON.parse(await readFile(path, "utf8"));
} catch (error) {
fail(`${file} is not valid JSON: ${error.message}`);
continue;
}
if (!ruleset.name || !["branch", "tag"].includes(ruleset.target)) {
fail(`${file} must define a name and a branch or tag target`);
}
if (!Array.isArray(ruleset.rules) || ruleset.rules.length === 0) {
fail(`${file} must contain at least one rule`);
}
const expectedBypassMode =
ruleset.target === "branch" ? "pull_request" : "always";
const maintainBypass = ruleset.bypass_actors?.some(
(actor) =>
actor.actor_type === "RepositoryRole" &&
actor.actor_id === 2 &&
actor.bypass_mode === expectedBypassMode,
);
if (!maintainBypass) {
fail(`${file} must retain the expected Maintain role bypass`);
}
if (
ruleset.target === "branch" &&
!ruleset.rules?.some((rule) => rule.type === "update")
) {
fail(`${file} must restrict default-branch updates to bypass actors`);
}
const statusRule = ruleset.rules?.find(
(rule) => rule.type === "required_status_checks",
);
if (statusRule) {
const contexts = new Set(
statusRule.parameters?.required_status_checks?.map(
(check) => check.context,
) ?? [],
);
for (const context of requiredContexts) {
if (!contexts.has(context)) {
fail(`${file} is missing required status context: ${context}`);
}
}
}
}
const sourceRoots = [
join(repositoryRoot, "sky_phone"),
join(repositoryRoot, "frontend", "src"),
];
const inspectedExtensions = new Set([
".cjs",
".js",
".json",
".lua",
".mjs",
".sql",
".ts",
".vue",
]);
const forbiddenPatterns = [
{
label: "a forbidden Sky resource reference",
pattern: /\bsky_(?:base|jobs_base)(?::|\b)/i,
},
{
label: "a forbidden shared Sky global",
pattern: /\bSky\.(?:FW|Cb|DB|Query)\b/,
},
];
async function inspectDirectory(directory) {
for (const entry of await readdir(directory, { withFileTypes: true })) {
if (
entry.name === "html" &&
directory.endsWith(join("sky_phone", "source"))
) {
continue;
}
const path = join(directory, entry.name);
if (entry.isDirectory()) {
await inspectDirectory(path);
continue;
}
if (!inspectedExtensions.has(extname(entry.name))) {
continue;
}
const content = await readFile(path, "utf8");
for (const { label, pattern } of forbiddenPatterns) {
if (pattern.test(content)) {
fail(`${path.slice(repositoryRoot.length + 1)} contains ${label}`);
}
}
}
}
for (const sourceRoot of sourceRoots) {
await inspectDirectory(sourceRoot);
}
if (failures.length > 0) {
console.error("Repository policy validation failed:");
for (const failure of failures) {
console.error(`- ${failure}`);
}
process.exit(1);
}
console.log(
`Repository policy validation passed (${rulesetFiles.length} rulesets checked).`,
);
@@ -0,0 +1,52 @@
name: Automated code review
on:
pull_request:
push:
branches:
- dev
workflow_dispatch:
concurrency:
group: automated-review-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
codeql:
name: CodeQL
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
packages: read
security-events: write
steps:
- name: Check out repository
uses: actions/checkout@v6
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: javascript-typescript
- name: Analyze JavaScript and TypeScript
uses: github/codeql-action/analyze@v4
with:
category: /language:javascript-typescript
dependency-review:
name: Dependency review
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check out repository
uses: actions/checkout@v6
- name: Review dependency changes
uses: actions/dependency-review-action@v5
with:
fail-on-severity: high
+119
View File
@@ -0,0 +1,119 @@
name: CI
on:
pull_request:
push:
branches:
- dev
workflow_dispatch:
concurrency:
group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
repository-policy:
name: Repository policy
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check out repository
uses: actions/checkout@v6
- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version: 22
- name: Validate repository contracts
run: node .github/scripts/validate-repository.mjs
- name: Install Lua compiler
run: |
sudo apt-get update
sudo apt-get install --yes lua5.4
- name: Check Lua syntax
shell: bash
run: |
set -euo pipefail
while IFS= read -r -d '' file; do
luac5.4 -p "$file"
done < <(find sky_phone tests -type f -name '*.lua' -print0)
frontend:
name: Frontend
runs-on: ubuntu-latest
timeout-minutes: 20
defaults:
run:
working-directory: frontend
steps:
- name: Check out repository
uses: actions/checkout@v6
- name: Set up pnpm
uses: pnpm/action-setup@v6
with:
version: 10.33.0
- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version: 22
cache: pnpm
cache-dependency-path: frontend/pnpm-lock.yaml
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Check repository formatting and schemas
run: >-
pnpm exec prettier --check
"../.github/**/*.{yml,yaml,json,md}"
"../.github/scripts/*.mjs"
"../CONTRIBUTING.md"
"../SECURITY.md"
- name: Lint frontend
run: pnpm lint
- name: Typecheck frontend
run: pnpm typecheck
- name: Test frontend
run: pnpm test
- name: Build deployable NUI
run: pnpm build-only
- name: Verify published NUI entrypoint
run: test -f ../sky_phone/source/html/index.html
- name: Package pull request test resource
if: github.event_name == 'pull_request'
shell: bash
working-directory: .
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
artifact_name="sky_phone-pr-${PR_NUMBER}-${HEAD_SHA}"
mkdir -p artifacts
zip -r "artifacts/${artifact_name}.zip" sky_phone
unzip -t "artifacts/${artifact_name}.zip"
unzip -Z1 "artifacts/${artifact_name}.zip" | grep -Fxq 'sky_phone/fxmanifest.lua'
unzip -Z1 "artifacts/${artifact_name}.zip" | grep -Fxq 'sky_phone/source/html/index.html'
- name: Upload pull request test resource
if: github.event_name == 'pull_request'
uses: actions/upload-artifact@v7
with:
path: artifacts/sky_phone-pr-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }}.zip
archive: false
if-no-files-found: error
retention-days: 14
+61
View File
@@ -0,0 +1,61 @@
name: Pull request policy
on:
pull_request:
types:
- opened
- edited
- reopened
- synchronize
- ready_for_review
permissions:
contents: read
jobs:
policy:
name: Pull request policy
runs-on: ubuntu-latest
timeout-minutes: 5
env:
BASE_REF: ${{ github.base_ref }}
HEAD_REF: ${{ github.head_ref }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
steps:
- name: Validate target branch
shell: bash
run: |
set -euo pipefail
if [[ "$BASE_REF" != "dev" ]]; then
echo "Pull requests must target dev; received: $BASE_REF"
exit 1
fi
- name: Validate source branch
shell: bash
run: |
set -euo pipefail
branch_pattern='^(feat|feature|fix|hotfix|docs|refactor|perf|test|build|ci|chore|release)/[a-z0-9]+([._-][a-z0-9]+)*$'
if [[ "$HEAD_REF" == dependabot/* ]]; then
exit 0
fi
if [[ ! "$HEAD_REF" =~ $branch_pattern ]]; then
echo "Invalid branch name: $HEAD_REF"
echo "Expected type/lowercase-kebab-case; see CONTRIBUTING.md."
exit 1
fi
- name: Validate pull request title
shell: bash
run: |
set -euo pipefail
if [[ "$PR_AUTHOR" == "dependabot[bot]" ]]; then
exit 0
fi
title_pattern='^(ENH|ADD|FIX|DOC|BLD|PERF|CLN|TRY) - [^[:space:]].{4,72}$'
if [[ ! "$PR_TITLE" =~ $title_pattern ]] || (( ${#PR_TITLE} > 80 )); then
echo "Invalid pull request title: $PR_TITLE"
echo "Expected: TAG - short imperative summary (maximum 80 characters)."
exit 1
fi
@@ -0,0 +1,103 @@
name: Pull request test resource link
on:
workflow_run:
workflows:
- CI
types:
- completed
permissions:
actions: read
contents: read
pull-requests: write
jobs:
comment:
name: Link test resource
if: github.event.workflow_run.event == 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Create or update pull request comment
uses: actions/github-script@v9
with:
script: |
const marker = '<!-- sky-phone-test-resource -->';
const run = context.payload.workflow_run;
const pullRequest = run.pull_requests?.[0];
if (!pullRequest) {
core.notice('The CI run is not associated with a pull request.');
return;
}
const { owner, repo } = context.repo;
const runUrl = `https://github.com/${owner}/${repo}/actions/runs/${run.id}`;
let body;
if (run.conclusion === 'success') {
const response = await github.rest.actions.listWorkflowRunArtifacts({
owner,
repo,
run_id: run.id,
per_page: 100,
});
const prefix = `sky_phone-pr-${pullRequest.number}-`;
const artifact = response.data.artifacts.find(
(candidate) =>
!candidate.expired &&
candidate.name.startsWith(prefix) &&
/^sky_phone-pr-\d+-[0-9a-f]{40}\.zip$/.test(candidate.name),
);
if (!artifact) {
core.setFailed('CI succeeded without a pull request test resource artifact.');
return;
}
const artifactUrl = `https://github.com/${owner}/${repo}/actions/runs/${run.id}/artifacts/${artifact.id}`;
body = [
marker,
'## Sky Phone Test-Resource',
'',
`✅ Der aktuelle Pull Request wurde erfolgreich geprüft und gebaut. [Test-Resource herunterladen](${artifactUrl}).`,
'',
'Das ZIP enthält den deploybaren `sky_phone`-Ordner inklusive gebautem Frontend und bleibt 14 Tage verfügbar.',
'',
`[Workflow-Lauf öffnen](${runUrl})`,
].join('\n');
} else {
body = [
marker,
'## Sky Phone Test-Resource',
'',
`❌ Für den aktuellen Stand wurde keine freigegebene Test-Resource erzeugt, weil der [CI-Lauf](${runUrl}) nicht erfolgreich war.`,
].join('\n');
}
const comments = await github.paginate(github.rest.issues.listComments, {
owner,
repo,
issue_number: pullRequest.number,
per_page: 100,
});
const existing = comments.find(
(comment) => comment.user?.type === 'Bot' && comment.body?.includes(marker),
);
if (existing) {
await github.rest.issues.updateComment({
owner,
repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner,
repo,
issue_number: pullRequest.number,
body,
});
}
+115
View File
@@ -0,0 +1,115 @@
name: Release
on:
push:
tags:
- "*.*.*"
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: write
jobs:
release:
name: Build and publish release
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Check out tagged source
uses: actions/checkout@v6
- name: Validate tag and manifest version
shell: bash
run: |
set -euo pipefail
if [[ ! "$GITHUB_REF_NAME" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Release tags must use numeric semantic versions without v: $GITHUB_REF_NAME"
exit 1
fi
manifest_version=$(sed -n "s/^version '\([^']*\)'/\1/p" sky_phone/fxmanifest.lua)
if [[ "$manifest_version" != "$GITHUB_REF_NAME" ]]; then
echo "Tag $GITHUB_REF_NAME does not match fxmanifest version $manifest_version"
exit 1
fi
- name: Set up pnpm
uses: pnpm/action-setup@v6
with:
version: 10.33.0
- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version: 22
cache: pnpm
cache-dependency-path: frontend/pnpm-lock.yaml
- name: Install frontend dependencies
working-directory: frontend
run: pnpm install --frozen-lockfile
- name: Validate, test, and build frontend
working-directory: frontend
run: |
pnpm lint
pnpm test
pnpm build
- name: Verify deployable phone resource
shell: bash
run: |
set -euo pipefail
test -f sky_phone/fxmanifest.lua
test -f sky_phone/source/html/index.html
test -d sky_phone/source/html/assets
- name: Validate repository contracts
run: node .github/scripts/validate-repository.mjs
- name: Install Lua compiler
run: |
sudo apt-get update
sudo apt-get install --yes lua5.4
- name: Check Lua syntax
shell: bash
run: |
set -euo pipefail
while IFS= read -r -d '' file; do
luac5.4 -p "$file"
done < <(find sky_phone tests -type f -name '*.lua' -print0)
- name: Create release archive
shell: bash
run: |
set -euo pipefail
archive="sky_phone-${GITHUB_REF_NAME}.zip"
zip -r "$archive" sky_phone
unzip -t "$archive"
unzip -Z1 "$archive" | grep -Fx "sky_phone/fxmanifest.lua"
unzip -Z1 "$archive" | grep -Fx "sky_phone/source/html/index.html"
sha256sum "$archive" > "${archive}.sha256"
- name: Upload workflow artifact
uses: actions/upload-artifact@v7
with:
name: sky_phone-${{ github.ref_name }}
path: |
sky_phone-${{ github.ref_name }}.zip
sky_phone-${{ github.ref_name }}.zip.sha256
if-no-files-found: error
retention-days: 30
- name: Publish GitHub release
env:
GH_TOKEN: ${{ github.token }}
run: >-
gh release create "$GITHUB_REF_NAME"
"sky_phone-${GITHUB_REF_NAME}.zip"
"sky_phone-${GITHUB_REF_NAME}.zip.sha256"
--verify-tag
--generate-notes
--title "Sky Phone $GITHUB_REF_NAME"
+38
View File
@@ -6,6 +6,44 @@ This repository contains the standalone FiveM resource `sky_phone`. These rules
apply throughout the repository unless a more specific `AGENTS.md` adds stricter apply throughout the repository unless a more specific `AGENTS.md` adds stricter
requirements. requirements.
## Repository governance for AI contributors
AI agents and automated coding tools must treat the repository governance files
as project instructions, not as optional documentation.
- Before changing code, read `CONTRIBUTING.md` and every more specific
`AGENTS.md` that applies to the target files. For issue or pull-request work,
also read the matching file under `.github/ISSUE_TEMPLATE` and
`.github/PULL_REQUEST_TEMPLATE.md`.
- Before changing CI, packaging, releases, contribution policy, branch policy,
or dependency handling, inspect all relevant files under `.github/workflows`,
`.github/rulesets`, and `.github/scripts`. Keep their contracts synchronized.
- Preserve the required check names `Repository policy`, `Frontend`, `CodeQL`,
`Dependency review`, and `Pull request policy`. If a task intentionally
renames or replaces one, update `.github/rulesets/protect-dev.json`,
`.github/scripts/validate-repository.mjs`, `.github/rulesets/README.md`, and
the corresponding workflow in the same change.
- Anyone may open a pull request, but only collaborators with GitHub's built-in
`Maintain` role may merge into `dev`, and maintainers must merge through a
pull request. Preserve this behavior in branch rules and documentation.
- Treat `.github/rulesets/*.json` as the version-controlled ruleset baseline.
Editing or merging these files does not update GitHub settings automatically;
report that an administrator must import or reconcile the live ruleset.
- Preserve the secure PR artifact boundary: untrusted pull-request code may
build the test resource only with read permissions. A workflow with write
permissions may inspect trusted GitHub metadata and maintain the PR comment,
but must never check out, download, extract, import, or execute PR-controlled
code or artifacts.
- PR and release packages must contain one top-level `sky_phone` directory with
`fxmanifest.lua` and the built NUI at `source/html/index.html`. Build generated
NUI from `frontend`; never hand-edit `sky_phone/source/html`.
- Keep automated review and test-resource findings visible. Do not weaken,
skip, or silence validation merely to make a check pass. Automated review
complements and never replaces the required human maintainer review.
- After governance changes, run the repository validator, Prettier, and
actionlint where available. Inspect the final diff and stage only files that
belong to the task.
## Architecture and security ## Architecture and security
- Keep `sky_phone` independent. Do not add dependencies or integrations with - Keep `sky_phone` independent. Do not add dependencies or integrations with
+86
View File
@@ -0,0 +1,86 @@
# Contributing to Sky Phone
Sky Phone uses `dev` as its default integration branch. All normal changes reach `dev` through a pull request; do not push feature work directly to it.
## Branches
Create a short-lived branch from an up-to-date `dev` branch. Use lowercase kebab-case after one of these prefixes:
| Prefix | Purpose |
| ----------- | ------------------------------------------------ |
| `feat/` | New behavior or app capability |
| `fix/` | Bug fix |
| `hotfix/` | Urgent release repair |
| `docs/` | Documentation only |
| `refactor/` | Internal change without intended behavior change |
| `perf/` | Performance work |
| `test/` | Test-only work |
| `build/` | Build or packaging work |
| `ci/` | GitHub Actions and repository automation |
| `chore/` | Focused maintenance |
| `release/` | Release preparation |
`feature/` remains accepted for existing branches, but new feature branches should use `feat/`.
Examples: `feat/mail-signatures`, `fix/radio-focus`, `ci/release-package`. Avoid personal names, issue titles, uppercase characters, spaces, and branches that combine unrelated work.
## Commits and pull requests
Use the repository commit format:
```text
TAG - short imperative summary
```
Allowed tags are `ENH`, `ADD`, `FIX`, `DOC`, `BLD`, `PERF`, `CLN`, and `TRY`. Examples:
```text
FIX - validate mail ownership before deletion
ENH - add per-account notification settings
DOC - clarify Qbox installation order
```
Use the same format for the pull request title. Keep commits focused, stage only task files, link the issue, and complete the pull request template with actual commands and results.
## Architecture and security rules
- Sky Phone is standalone. It must not depend on or exchange state with `sky_base`, `sky_jobs_base`, or another `sky_*` resource.
- The server validates and decides permissions, identity, money, inventory, ownership, proximity, limits, and all other consequential state.
- Treat NUI and client payloads as untrusted. Every NUI callback must invoke its response callback on every reachable path.
- Parameterize SQL, keep persistence owned by `sky_phone`, and send only the required data over the network.
- Keep credentials, tokens, private endpoints, and player-identifying data out of source, fixtures, logs, issues, and pull requests.
## Schema, configuration, and compatibility
- Prefix new resource-owned tables, persistent keys, convars, callbacks, and events with `sky_phone` where the technology permits it.
- Keep the runtime schema in `sky_phone/source/server/db_migrate.lua` and the clean-install schema in `sky_phone/sql/install.sql` aligned.
- Prefer additive, idempotent migrations. Destructive or lossy migrations require an explicit migration plan, backup guidance, and reviewer approval.
- Do not force a database charset or collation unless a documented compatibility requirement has been reviewed.
- Preserve public events, callbacks, exports, configuration defaults, and stored data unless the linked issue explicitly authorizes a breaking change.
- Update both English and German locales for user-facing text. Logs and developer diagnostics remain in English.
- New or substantially changed NUI screens use the public Sky UI components and semantic tokens under `frontend/src/ui`.
## Local validation
Install frontend dependencies with pnpm, then run the checks relevant to the change:
```powershell
cd frontend
pnpm install --frozen-lockfile
pnpm typecheck
pnpm lint
pnpm test
pnpm build
```
The build publishes the generated NUI into `sky_phone/source/html`. Do not hand-edit generated output. A successful build proves source/build consistency, not behavior inside FiveM; report live runtime testing separately.
Every pull request also receives an automated CodeQL scan and dependency review. After the full CI run succeeds, GitHub packages the deployable `sky_phone` folder as a test-resource ZIP and adds or updates a download link in the pull request. The artifact is retained for 14 days. It is suitable for manual testing on a test server, but it is not a release and does not replace live FiveM validation.
Lua, config, manifest, locale, SQL, and native changes must also be tested in a restarted FiveM resource with experimental OAL enabled. Pass native coordinates as separate numeric arguments and verify native signatures against authoritative documentation.
## Review and merge
A pull request is ready when the repository policy, frontend, CodeQL, dependency review, and pull-request policy checks pass; review conversations are resolved; the latest push is approved by someone other than its author; and migrations or operational steps are explicit. Automated findings complement rather than replace the human maintainer review. Anyone may open a pull request, but only collaborators with the built-in GitHub `Maintain` role may merge into `dev`. Maintainers must merge through a pull request; the ruleset does not permit direct pushes to `dev`. The default ruleset allows merge, squash, and rebase so maintainers can preserve meaningful merge history when needed.
Release tags use numeric semantic versions without a `v` prefix, for example `0.2.0`. Tags are immutable after creation.
+15
View File
@@ -0,0 +1,15 @@
# Security policy
## Reporting a vulnerability
Do not open a public issue for an exploitable vulnerability or include exploit details in Discord, logs, screenshots, or pull requests.
Use [GitHub private vulnerability reporting](https://github.com/sky-systems/sky_phone/security/advisories/new). If that form is unavailable, use the private contact listed on the [official Sky-Systems contact page](https://www.sky-systems.net/impressum).
Include the affected release tag or commit, framework and integration context, impact, minimal reproduction, and any proposed mitigation. Remove credentials, tokens, private server addresses, and player-identifying data.
We will acknowledge the report, reproduce and assess the impact, coordinate a fix, and publish details after affected users have a reasonable update path. Please do not disclose the issue publicly before that coordination is complete.
## Supported versions
Security fixes target the latest published release and the current `dev` branch. Older releases may require upgrading before a fix can be applied.
+5
View File
@@ -15,6 +15,11 @@
"test": "vitest run && pnpm test:browser-mocks", "test": "vitest run && pnpm test:browser-mocks",
"test:browser-mocks": "node testserver/smoke.cjs" "test:browser-mocks": "node testserver/smoke.cjs"
}, },
"pnpm": {
"overrides": {
"nanoid@<3.3.18": "3.3.18"
}
},
"dependencies": { "dependencies": {
"@tiptap/core": "^3.29.2", "@tiptap/core": "^3.29.2",
"@tiptap/extension-placeholder": "^3.29.2", "@tiptap/extension-placeholder": "^3.29.2",
+7 -4
View File
@@ -4,6 +4,9 @@ settings:
autoInstallPeers: true autoInstallPeers: true
excludeLinksFromLockfile: false excludeLinksFromLockfile: false
overrides:
nanoid@<3.3.18: 3.3.18
importers: importers:
.: .:
@@ -1940,8 +1943,8 @@ packages:
muggle-string@0.4.1: muggle-string@0.4.1:
resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
nanoid@3.3.17: nanoid@3.3.18:
resolution: {integrity: sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==} resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true hasBin: true
@@ -4351,7 +4354,7 @@ snapshots:
muggle-string@0.4.1: {} muggle-string@0.4.1: {}
nanoid@3.3.17: {} nanoid@3.3.18: {}
natural-compare@1.4.0: {} natural-compare@1.4.0: {}
@@ -4449,7 +4452,7 @@ snapshots:
postcss@8.5.25: postcss@8.5.25:
dependencies: dependencies:
nanoid: 3.3.17 nanoid: 3.3.18
picocolors: 1.1.1 picocolors: 1.1.1
source-map-js: 1.2.1 source-map-js: 1.2.1