mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-28 21:01:30 +00:00
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:
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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. -->
|
||||
@@ -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.
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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
|
||||
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
|
||||
|
||||
- Keep `sky_phone` independent. Do not add dependencies or integrations with
|
||||
|
||||
@@ -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
@@ -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.
|
||||
@@ -15,6 +15,11 @@
|
||||
"test": "vitest run && pnpm test:browser-mocks",
|
||||
"test:browser-mocks": "node testserver/smoke.cjs"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"nanoid@<3.3.18": "3.3.18"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@tiptap/core": "^3.29.2",
|
||||
"@tiptap/extension-placeholder": "^3.29.2",
|
||||
|
||||
Generated
+7
-4
@@ -4,6 +4,9 @@ settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
nanoid@<3.3.18: 3.3.18
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
@@ -1940,8 +1943,8 @@ packages:
|
||||
muggle-string@0.4.1:
|
||||
resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
|
||||
|
||||
nanoid@3.3.17:
|
||||
resolution: {integrity: sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==}
|
||||
nanoid@3.3.18:
|
||||
resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
@@ -4351,7 +4354,7 @@ snapshots:
|
||||
|
||||
muggle-string@0.4.1: {}
|
||||
|
||||
nanoid@3.3.17: {}
|
||||
nanoid@3.3.18: {}
|
||||
|
||||
natural-compare@1.4.0: {}
|
||||
|
||||
@@ -4449,7 +4452,7 @@ snapshots:
|
||||
|
||||
postcss@8.5.25:
|
||||
dependencies:
|
||||
nanoid: 3.3.17
|
||||
nanoid: 3.3.18
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
|
||||
Reference in New Issue
Block a user