From 05169acbfecd253d215dba825a2cb32b15ac4d81 Mon Sep 17 00:00:00 2001 From: DerEchteAlec Date: Wed, 19 Aug 2026 14:20:46 +0200 Subject: [PATCH] BLD - add GitHub contribution and release automation --- .github/ISSUE_TEMPLATE/bug_report.yml | 114 ++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 8 ++ .github/ISSUE_TEMPLATE/documentation.yml | 35 +++++ .github/ISSUE_TEMPLATE/feature_request.yml | 82 +++++++++++ .github/PULL_REQUEST_TEMPLATE.md | 46 +++++++ .github/rulesets/README.md | 20 +++ .github/rulesets/protect-dev.json | 56 ++++++++ .github/rulesets/protect-release-tags.json | 36 +++++ .github/scripts/validate-repository.mjs | 150 +++++++++++++++++++++ .github/workflows/ci.yml | 94 +++++++++++++ .github/workflows/pr-policy.yml | 61 +++++++++ .github/workflows/release.yml | 115 ++++++++++++++++ CONTRIBUTING.md | 84 ++++++++++++ SECURITY.md | 15 +++ 14 files changed, 916 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/documentation.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/rulesets/README.md create mode 100644 .github/rulesets/protect-dev.json create mode 100644 .github/rulesets/protect-release-tags.json create mode 100644 .github/scripts/validate-repository.mjs create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/pr-policy.yml create mode 100644 .github/workflows/release.yml create mode 100644 CONTRIBUTING.md create mode 100644 SECURITY.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..fed07a1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -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 diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..d6deb4c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -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. diff --git a/.github/ISSUE_TEMPLATE/documentation.yml b/.github/ISSUE_TEMPLATE/documentation.yml new file mode 100644 index 0000000..e14eec7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.yml @@ -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 diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..9d2a194 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -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 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..f891ced --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,46 @@ +## Summary + + + +Closes # + +## Root cause and approach + + + +## Changes + +- + +## Compatibility and migrations + + + +## 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 + + + +## 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 + + diff --git a/.github/rulesets/README.md b/.github/rulesets/README.md new file mode 100644 index 0000000..0ed0b49 --- /dev/null +++ b/.github/rulesets/README.md @@ -0,0 +1,20 @@ +# 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` 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 three required checks are reported before relying on the ruleset. + +`protect-dev.json` blocks deletion and force pushes, requires one approval, dismisses stale reviews, requires approval after the last push, resolves review conversations, and requires the `Repository policy`, `Frontend`, and `Pull request policy` checks. + +`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 (`RepositoryRole` actor ID `2`) has an explicit emergency bypass in both files. Use it only for audited recovery work. Remove the bypass in GitHub if maintainers should remain subject to every rule; 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. diff --git a/.github/rulesets/protect-dev.json b/.github/rulesets/protect-dev.json new file mode 100644 index 0000000..c07ead8 --- /dev/null +++ b/.github/rulesets/protect-dev.json @@ -0,0 +1,56 @@ +{ + "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": "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": "Pull request policy" + } + ], + "strict_required_status_checks_policy": true + } + } + ], + "bypass_actors": [ + { + "actor_id": 2, + "actor_type": "RepositoryRole", + "bypass_mode": "always" + } + ] +} diff --git a/.github/rulesets/protect-release-tags.json b/.github/rulesets/protect-release-tags.json new file mode 100644 index 0000000..53fa4d1 --- /dev/null +++ b/.github/rulesets/protect-release-tags.json @@ -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" + } + ] +} diff --git a/.github/scripts/validate-repository.mjs b/.github/scripts/validate-repository.mjs new file mode 100644 index 0000000..3706f53 --- /dev/null +++ b/.github/scripts/validate-repository.mjs @@ -0,0 +1,150 @@ +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", + "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 maintainBypass = ruleset.bypass_actors?.some( + (actor) => + actor.actor_type === "RepositoryRole" && + actor.actor_id === 2 && + actor.bypass_mode === "always", + ); + if (!maintainBypass) { + fail(`${file} must retain the Maintain role emergency bypass`); + } + + 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).`, +); diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..de6b06b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,94 @@ +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 diff --git a/.github/workflows/pr-policy.yml b/.github/workflows/pr-policy.yml new file mode 100644 index 0000000..a607110 --- /dev/null +++ b/.github/workflows/pr-policy.yml @@ -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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..39b271f --- /dev/null +++ b/.github/workflows/release.yml @@ -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" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..66683c4 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,84 @@ +# 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. + +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 required checks pass, review conversations are resolved, the latest push is approved by someone other than its author, and migrations or operational steps are explicit. 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. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..e2a0d18 --- /dev/null +++ b/SECURITY.md @@ -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.