create-raredays-app 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.md +1 -1
  2. package/package.json +7 -3
  3. package/templates/client/.node-version +1 -0
  4. package/templates/client/.semgrepignore +12 -0
  5. package/templates/client/_github/actions/setup/action.yml +37 -0
  6. package/templates/client/_github/workflows/ci.yml +70 -36
  7. package/templates/client/_github/workflows/coverage.yml +40 -0
  8. package/templates/client/_github/workflows/sast.yml +42 -0
  9. package/templates/client/_github/workflows/security.yml +33 -0
  10. package/templates/client/_github/workflows/zizmor.yml +43 -0
  11. package/templates/client/package.json +4 -1
  12. package/templates/full-stack/.node-version +1 -0
  13. package/templates/full-stack/.semgrepignore +12 -0
  14. package/templates/full-stack/_github/actions/setup/action.yml +37 -0
  15. package/templates/full-stack/_github/workflows/ci.yml +70 -37
  16. package/templates/full-stack/_github/workflows/coverage.yml +40 -0
  17. package/templates/full-stack/_github/workflows/db-check.yml +8 -6
  18. package/templates/full-stack/_github/workflows/db-migrate.yml +8 -6
  19. package/templates/full-stack/_github/workflows/sast.yml +42 -0
  20. package/templates/full-stack/_github/workflows/security.yml +33 -0
  21. package/templates/full-stack/_github/workflows/zizmor.yml +43 -0
  22. package/templates/full-stack/package.json +4 -1
  23. package/templates/static/.node-version +1 -0
  24. package/templates/static/.semgrepignore +12 -0
  25. package/templates/static/_github/actions/setup/action.yml +37 -0
  26. package/templates/static/_github/workflows/ci.yml +64 -31
  27. package/templates/static/_github/workflows/sast.yml +42 -0
  28. package/templates/static/_github/workflows/security.yml +33 -0
  29. package/templates/static/_github/workflows/zizmor.yml +43 -0
  30. package/templates/static/package.json +3 -1
package/README.md CHANGED
@@ -49,7 +49,7 @@ Presets set the template, modules, and brand color. You can still customise disp
49
49
  ## What it does
50
50
 
51
51
  1. Copies `templates/<name>/` into the target directory.
52
- 2. Renames stub files (`_gitignore` -> `.gitignore`, `_npmrc` -> `.npmrc`).
52
+ 2. Renames stub files (`_gitignore` -> `.gitignore`, `_github` -> `.github`).
53
53
  3. Sets `package.json:name` to the project slug.
54
54
  4. Substitutes `__SLUG__` in `site.spec.ts` and applies display name, domain, and brand color.
55
55
  5. Loads scaffold fragments for selected modules (imports, config, ports, file overlays).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-raredays-app",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Scaffold a new RareDays Next.js app.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "Jeremy Harper <jeremy@raredays.com>",
@@ -14,13 +14,16 @@
14
14
  "skillex"
15
15
  ],
16
16
  "type": "module",
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
17
20
  "dependencies": {
18
21
  "@clack/prompts": "1.4.0"
19
22
  },
20
23
  "devDependencies": {
21
24
  "typescript": "5.9.3",
22
- "vitest": "4.1.5",
23
- "@raredays/lint": "0.1.0"
25
+ "vitest": "4.1.9",
26
+ "@raredays/lint": "0.2.0"
24
27
  },
25
28
  "engines": {
26
29
  "node": ">=20"
@@ -34,6 +37,7 @@
34
37
  "style": "raredays style",
35
38
  "style:fix": "raredays style:fix",
36
39
  "fix": "raredays fix",
40
+ "publint": "publint run --strict",
37
41
  "check": "raredays check",
38
42
  "test": "vitest run",
39
43
  "test:watch": "vitest",
@@ -0,0 +1 @@
1
+ 24.15.0
@@ -0,0 +1,12 @@
1
+ # Semgrep ignore — paths excluded from SAST scans.
2
+ #
3
+ # Add specific paths or globs here when a rule produces unavoidable
4
+ # false positives (e.g. mock servers using http://localhost, generated
5
+ # code that trips security patterns).
6
+ #
7
+ # Example:
8
+ # src/mocks/*.test.ts
9
+ #
10
+ # Prefer inline `// nosemgrep: <rule-id> -- <reason>` over path-level
11
+ # exclusions when the case is local; reserve this file for whole files
12
+ # that legitimately need to be skipped.
@@ -0,0 +1,37 @@
1
+ name: Setup
2
+ description: >-
3
+ Install pnpm and Node, authenticate to the private @raredays npm registry, then
4
+ install dependencies. The @raredays/* packages are published with restricted
5
+ access, so the registry token must be present before install. pnpm ignores
6
+ ${VAR} interpolation in a committed project .npmrc (it could leak the token to
7
+ an attacker-controlled registry), so the token is written to the user-level
8
+ ~/.npmrc at runtime instead.
9
+
10
+ inputs:
11
+ node-auth-token:
12
+ description: >-
13
+ npm token with read access to the private @raredays registry. Pass it from
14
+ the NODE_AUTH_TOKEN repository secret.
15
+ required: true
16
+
17
+ runs:
18
+ using: composite
19
+ steps:
20
+ - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
21
+ - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
22
+ with:
23
+ node-version: 24
24
+ cache: pnpm
25
+ - name: Authenticate to the npm registry
26
+ shell: bash
27
+ env:
28
+ NODE_AUTH_TOKEN: ${{ inputs.node-auth-token }}
29
+ run: |
30
+ if [ -z "$NODE_AUTH_TOKEN" ]; then
31
+ echo "::error::NODE_AUTH_TOKEN is empty. Add a repository secret with an npm token that has read access to the private @raredays registry."
32
+ exit 1
33
+ fi
34
+ echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" >> ~/.npmrc
35
+ - name: Install dependencies
36
+ shell: bash
37
+ run: pnpm install --frozen-lockfile
@@ -6,6 +6,9 @@ on:
6
6
  pull_request:
7
7
  branches: [main]
8
8
 
9
+ permissions:
10
+ contents: read
11
+
9
12
  concurrency:
10
13
  group: ci-${{ github.ref }}
11
14
  cancel-in-progress: true
@@ -20,11 +23,12 @@ jobs:
20
23
  runs-on: ubuntu-latest
21
24
  timeout-minutes: 10
22
25
  steps:
23
- - uses: actions/checkout@v4
24
- - uses: pnpm/action-setup@v4
25
- - uses: actions/setup-node@v4
26
- with: { node-version: 24, cache: pnpm }
27
- - run: pnpm install --frozen-lockfile
26
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
27
+ with:
28
+ persist-credentials: false
29
+ - uses: ./.github/actions/setup
30
+ with:
31
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
28
32
  - run: pnpm lint
29
33
 
30
34
  format-check:
@@ -32,11 +36,12 @@ jobs:
32
36
  runs-on: ubuntu-latest
33
37
  timeout-minutes: 10
34
38
  steps:
35
- - uses: actions/checkout@v4
36
- - uses: pnpm/action-setup@v4
37
- - uses: actions/setup-node@v4
38
- with: { node-version: 24, cache: pnpm }
39
- - run: pnpm install --frozen-lockfile
39
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
40
+ with:
41
+ persist-credentials: false
42
+ - uses: ./.github/actions/setup
43
+ with:
44
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
40
45
  - run: pnpm format:check
41
46
 
42
47
  style:
@@ -44,11 +49,12 @@ jobs:
44
49
  runs-on: ubuntu-latest
45
50
  timeout-minutes: 10
46
51
  steps:
47
- - uses: actions/checkout@v4
48
- - uses: pnpm/action-setup@v4
49
- - uses: actions/setup-node@v4
50
- with: { node-version: 24, cache: pnpm }
51
- - run: pnpm install --frozen-lockfile
52
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
53
+ with:
54
+ persist-credentials: false
55
+ - uses: ./.github/actions/setup
56
+ with:
57
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
52
58
  - run: pnpm style
53
59
 
54
60
  typecheck:
@@ -56,11 +62,12 @@ jobs:
56
62
  runs-on: ubuntu-latest
57
63
  timeout-minutes: 10
58
64
  steps:
59
- - uses: actions/checkout@v4
60
- - uses: pnpm/action-setup@v4
61
- - uses: actions/setup-node@v4
62
- with: { node-version: 24, cache: pnpm }
63
- - run: pnpm install --frozen-lockfile
65
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
66
+ with:
67
+ persist-credentials: false
68
+ - uses: ./.github/actions/setup
69
+ with:
70
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
64
71
  - run: pnpm theme:build
65
72
  - run: pnpm typecheck
66
73
 
@@ -69,11 +76,12 @@ jobs:
69
76
  runs-on: ubuntu-latest
70
77
  timeout-minutes: 15
71
78
  steps:
72
- - uses: actions/checkout@v4
73
- - uses: pnpm/action-setup@v4
74
- - uses: actions/setup-node@v4
75
- with: { node-version: 24, cache: pnpm }
76
- - run: pnpm install --frozen-lockfile
79
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
80
+ with:
81
+ persist-credentials: false
82
+ - uses: ./.github/actions/setup
83
+ with:
84
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
77
85
  - run: pnpm theme:build
78
86
  - run: pnpm test
79
87
 
@@ -82,23 +90,49 @@ jobs:
82
90
  runs-on: ubuntu-latest
83
91
  timeout-minutes: 15
84
92
  steps:
85
- - uses: actions/checkout@v4
86
- - uses: pnpm/action-setup@v4
87
- - uses: actions/setup-node@v4
88
- with: { node-version: 24, cache: pnpm }
89
- - run: pnpm install --frozen-lockfile
93
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
94
+ with:
95
+ persist-credentials: false
96
+ - uses: ./.github/actions/setup
97
+ with:
98
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
90
99
  - run: pnpm build
91
100
 
92
101
  storybook:
93
102
  name: Storybook build
94
103
  runs-on: ubuntu-latest
95
104
  timeout-minutes: 15
96
- if: hashFiles('.storybook/main.ts', '.storybook/main.js') != ''
97
105
  steps:
98
- - uses: actions/checkout@v4
99
- - uses: pnpm/action-setup@v4
100
- - uses: actions/setup-node@v4
101
- with: { node-version: 24, cache: pnpm }
102
- - run: pnpm install --frozen-lockfile
106
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
107
+ with:
108
+ persist-credentials: false
109
+ - uses: ./.github/actions/setup
110
+ with:
111
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
103
112
  - run: pnpm theme:build
104
113
  - run: pnpm storybook:build
114
+
115
+ # Single aggregation gate — make THIS the only required status check in
116
+ # branch protection so you don't have to enumerate every job (skipped
117
+ # jobs are tolerated by the gate).
118
+ ci-result:
119
+ name: ci-result
120
+ needs: [lint, format-check, style, typecheck, test, build, storybook]
121
+ if: ${{ !cancelled() }}
122
+ runs-on: ubuntu-latest
123
+ permissions: {}
124
+ steps:
125
+ - name: Aggregate CI results
126
+ env:
127
+ NEEDS: ${{ toJson(needs) }}
128
+ run: |
129
+ echo "$NEEDS" | jq -r 'to_entries[] | "\(.key)\t\(.value.result)"'
130
+ failed=$(echo "$NEEDS" | jq -r '
131
+ to_entries[]
132
+ | select(.value.result == "failure" or .value.result == "cancelled")
133
+ | .key')
134
+ if [ -n "$failed" ]; then
135
+ echo "::error::Failed or cancelled jobs: $failed"
136
+ exit 1
137
+ fi
138
+ echo "All required CI jobs passed (or were skipped)."
@@ -0,0 +1,40 @@
1
+ name: Coverage
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: coverage-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ # Runs Vitest with --coverage and uploads the HTML report as an artifact.
17
+ # Coverage thresholds are not enforced by default — opt in by configuring
18
+ # `test.coverage.thresholds` in vitest.config.ts when your suite is ready.
19
+
20
+ jobs:
21
+ coverage:
22
+ name: Coverage
23
+ runs-on: ubuntu-latest
24
+ timeout-minutes: 15
25
+ steps:
26
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
27
+ with:
28
+ persist-credentials: false
29
+ - uses: ./.github/actions/setup
30
+ with:
31
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
32
+ - run: pnpm theme:build
33
+ - run: pnpm coverage
34
+ - name: Upload coverage report
35
+ if: always()
36
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
37
+ with:
38
+ name: coverage-report
39
+ path: coverage/
40
+ retention-days: 14
@@ -0,0 +1,42 @@
1
+ name: SAST
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: sast-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ # Static Application Security Testing via Semgrep. Scans first-party
17
+ # source against TypeScript, JavaScript, React, and OWASP top-ten
18
+ # rulesets. Findings fail the build — silence false positives with
19
+ # `.semgrepignore` (path-level) or inline `// nosemgrep: <rule-id>` with
20
+ # a justification.
21
+
22
+ jobs:
23
+ semgrep:
24
+ name: Semgrep
25
+ runs-on: ubuntu-latest
26
+ timeout-minutes: 10
27
+ if: github.actor != 'dependabot[bot]'
28
+ container:
29
+ image: semgrep/semgrep:1.168.0@sha256:59fbed6127ea7c5dde3ba6a85142733bb20ea9aaa36120c953904f1539aaf66e
30
+ steps:
31
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
32
+ with:
33
+ persist-credentials: false
34
+ - name: Run Semgrep
35
+ run: |
36
+ semgrep scan \
37
+ --config=p/typescript \
38
+ --config=p/javascript \
39
+ --config=p/react \
40
+ --config=p/owasp-top-ten \
41
+ --error \
42
+ --metrics=off
@@ -0,0 +1,33 @@
1
+ name: Security
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: security-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ # Audits production dependencies for known vulnerabilities. Fails the
17
+ # build on high or critical severity findings; moderate and low findings
18
+ # appear in the log but don't block merges.
19
+
20
+ jobs:
21
+ audit:
22
+ name: pnpm audit
23
+ runs-on: ubuntu-latest
24
+ timeout-minutes: 5
25
+ steps:
26
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
27
+ with:
28
+ persist-credentials: false
29
+ - uses: ./.github/actions/setup
30
+ with:
31
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
32
+ - name: Audit production dependencies
33
+ run: pnpm audit --audit-level=high --prod
@@ -0,0 +1,43 @@
1
+ name: Zizmor
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths: [".github/**"]
7
+ pull_request:
8
+ branches: [main]
9
+ paths: [".github/**"]
10
+
11
+ permissions: {}
12
+
13
+ concurrency:
14
+ group: zizmor-${{ github.ref }}
15
+ cancel-in-progress: true
16
+
17
+ # Static security analysis of this app's GitHub Actions workflows
18
+ # (template-injection, unpinned actions, excessive token permissions,
19
+ # credential persistence). Free runner-hosted SAST — no GitHub Advanced
20
+ # Security needed. Findings surface as inline PR annotations. Starts at
21
+ # min-severity: high; lower to medium/low once your workflows are clean.
22
+
23
+ jobs:
24
+ zizmor:
25
+ name: GitHub Actions security (zizmor)
26
+ runs-on: ubuntu-latest
27
+ timeout-minutes: 10
28
+ permissions:
29
+ contents: read
30
+ actions: read
31
+ steps:
32
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
33
+ with:
34
+ persist-credentials: false
35
+ - uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
36
+ with:
37
+ min-severity: high
38
+ min-confidence: medium
39
+ # This app's repo may not have GitHub Advanced Security; disable the
40
+ # SARIF/code-scanning upload (mutually exclusive with annotations) and
41
+ # surface findings as inline PR annotations instead.
42
+ advanced-security: false
43
+ annotations: true
@@ -12,6 +12,7 @@
12
12
  "theme:build": "node ./scripts/build-theme.mjs",
13
13
  "test": "vitest run",
14
14
  "test:watch": "vitest",
15
+ "coverage": "vitest run --coverage",
15
16
  "storybook": "storybook dev -p 6006",
16
17
  "storybook:build": "storybook build -o storybook-static",
17
18
  "lint": "raredays lint",
@@ -48,6 +49,7 @@
48
49
  "@types/react": "19.2.14",
49
50
  "@types/react-dom": "19.2.3",
50
51
  "@vitejs/plugin-react": "6.0.1",
52
+ "@vitest/coverage-v8": "4.1.5",
51
53
  "happy-dom": "15.11.7",
52
54
  "storybook": "10.3.6",
53
55
  "tailwindcss": "4.2.4",
@@ -57,5 +59,6 @@
57
59
  },
58
60
  "engines": {
59
61
  "node": ">=24"
60
- }
62
+ },
63
+ "packageManager": "pnpm@11.9.0"
61
64
  }
@@ -0,0 +1 @@
1
+ 24.15.0
@@ -0,0 +1,12 @@
1
+ # Semgrep ignore — paths excluded from SAST scans.
2
+ #
3
+ # Add specific paths or globs here when a rule produces unavoidable
4
+ # false positives (e.g. mock servers using http://localhost, generated
5
+ # code that trips security patterns).
6
+ #
7
+ # Example:
8
+ # src/mocks/*.test.ts
9
+ #
10
+ # Prefer inline `// nosemgrep: <rule-id> -- <reason>` over path-level
11
+ # exclusions when the case is local; reserve this file for whole files
12
+ # that legitimately need to be skipped.
@@ -0,0 +1,37 @@
1
+ name: Setup
2
+ description: >-
3
+ Install pnpm and Node, authenticate to the private @raredays npm registry, then
4
+ install dependencies. The @raredays/* packages are published with restricted
5
+ access, so the registry token must be present before install. pnpm ignores
6
+ ${VAR} interpolation in a committed project .npmrc (it could leak the token to
7
+ an attacker-controlled registry), so the token is written to the user-level
8
+ ~/.npmrc at runtime instead.
9
+
10
+ inputs:
11
+ node-auth-token:
12
+ description: >-
13
+ npm token with read access to the private @raredays registry. Pass it from
14
+ the NODE_AUTH_TOKEN repository secret.
15
+ required: true
16
+
17
+ runs:
18
+ using: composite
19
+ steps:
20
+ - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
21
+ - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
22
+ with:
23
+ node-version: 24
24
+ cache: pnpm
25
+ - name: Authenticate to the npm registry
26
+ shell: bash
27
+ env:
28
+ NODE_AUTH_TOKEN: ${{ inputs.node-auth-token }}
29
+ run: |
30
+ if [ -z "$NODE_AUTH_TOKEN" ]; then
31
+ echo "::error::NODE_AUTH_TOKEN is empty. Add a repository secret with an npm token that has read access to the private @raredays registry."
32
+ exit 1
33
+ fi
34
+ echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" >> ~/.npmrc
35
+ - name: Install dependencies
36
+ shell: bash
37
+ run: pnpm install --frozen-lockfile
@@ -6,6 +6,9 @@ on:
6
6
  pull_request:
7
7
  branches: [main]
8
8
 
9
+ permissions:
10
+ contents: read
11
+
9
12
  concurrency:
10
13
  group: ci-${{ github.ref }}
11
14
  cancel-in-progress: true
@@ -24,11 +27,12 @@ jobs:
24
27
  runs-on: ubuntu-latest
25
28
  timeout-minutes: 10
26
29
  steps:
27
- - uses: actions/checkout@v4
28
- - uses: pnpm/action-setup@v4
29
- - uses: actions/setup-node@v4
30
- with: { node-version: 24, cache: pnpm }
31
- - run: pnpm install --frozen-lockfile
30
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
31
+ with:
32
+ persist-credentials: false
33
+ - uses: ./.github/actions/setup
34
+ with:
35
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
32
36
  - run: pnpm lint
33
37
 
34
38
  format-check:
@@ -36,11 +40,12 @@ jobs:
36
40
  runs-on: ubuntu-latest
37
41
  timeout-minutes: 10
38
42
  steps:
39
- - uses: actions/checkout@v4
40
- - uses: pnpm/action-setup@v4
41
- - uses: actions/setup-node@v4
42
- with: { node-version: 24, cache: pnpm }
43
- - run: pnpm install --frozen-lockfile
43
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
44
+ with:
45
+ persist-credentials: false
46
+ - uses: ./.github/actions/setup
47
+ with:
48
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
44
49
  - run: pnpm format:check
45
50
 
46
51
  style:
@@ -48,11 +53,12 @@ jobs:
48
53
  runs-on: ubuntu-latest
49
54
  timeout-minutes: 10
50
55
  steps:
51
- - uses: actions/checkout@v4
52
- - uses: pnpm/action-setup@v4
53
- - uses: actions/setup-node@v4
54
- with: { node-version: 24, cache: pnpm }
55
- - run: pnpm install --frozen-lockfile
56
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
57
+ with:
58
+ persist-credentials: false
59
+ - uses: ./.github/actions/setup
60
+ with:
61
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
56
62
  - run: pnpm style
57
63
 
58
64
  typecheck:
@@ -60,11 +66,12 @@ jobs:
60
66
  runs-on: ubuntu-latest
61
67
  timeout-minutes: 10
62
68
  steps:
63
- - uses: actions/checkout@v4
64
- - uses: pnpm/action-setup@v4
65
- - uses: actions/setup-node@v4
66
- with: { node-version: 24, cache: pnpm }
67
- - run: pnpm install --frozen-lockfile
69
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
70
+ with:
71
+ persist-credentials: false
72
+ - uses: ./.github/actions/setup
73
+ with:
74
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
68
75
  - run: pnpm theme:build
69
76
  - run: pnpm typecheck
70
77
 
@@ -73,11 +80,12 @@ jobs:
73
80
  runs-on: ubuntu-latest
74
81
  timeout-minutes: 15
75
82
  steps:
76
- - uses: actions/checkout@v4
77
- - uses: pnpm/action-setup@v4
78
- - uses: actions/setup-node@v4
79
- with: { node-version: 24, cache: pnpm }
80
- - run: pnpm install --frozen-lockfile
83
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
84
+ with:
85
+ persist-credentials: false
86
+ - uses: ./.github/actions/setup
87
+ with:
88
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
81
89
  - run: pnpm theme:build
82
90
  - run: pnpm test
83
91
 
@@ -86,24 +94,49 @@ jobs:
86
94
  runs-on: ubuntu-latest
87
95
  timeout-minutes: 15
88
96
  steps:
89
- - uses: actions/checkout@v4
90
- - uses: pnpm/action-setup@v4
91
- - uses: actions/setup-node@v4
92
- with: { node-version: 24, cache: pnpm }
93
- - run: pnpm install --frozen-lockfile
97
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
98
+ with:
99
+ persist-credentials: false
100
+ - uses: ./.github/actions/setup
101
+ with:
102
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
94
103
  - run: pnpm build
95
104
 
96
105
  storybook:
97
106
  name: Storybook build
98
107
  runs-on: ubuntu-latest
99
108
  timeout-minutes: 15
100
- # Only run if storybook is configured.
101
- if: hashFiles('.storybook/main.ts', '.storybook/main.js') != ''
102
109
  steps:
103
- - uses: actions/checkout@v4
104
- - uses: pnpm/action-setup@v4
105
- - uses: actions/setup-node@v4
106
- with: { node-version: 24, cache: pnpm }
107
- - run: pnpm install --frozen-lockfile
110
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
111
+ with:
112
+ persist-credentials: false
113
+ - uses: ./.github/actions/setup
114
+ with:
115
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
108
116
  - run: pnpm theme:build
109
117
  - run: pnpm storybook:build
118
+
119
+ # Single aggregation gate — make THIS the only required status check in
120
+ # branch protection so you don't have to enumerate every job (skipped
121
+ # jobs are tolerated by the gate).
122
+ ci-result:
123
+ name: ci-result
124
+ needs: [lint, format-check, style, typecheck, test, build, storybook]
125
+ if: ${{ !cancelled() }}
126
+ runs-on: ubuntu-latest
127
+ permissions: {}
128
+ steps:
129
+ - name: Aggregate CI results
130
+ env:
131
+ NEEDS: ${{ toJson(needs) }}
132
+ run: |
133
+ echo "$NEEDS" | jq -r 'to_entries[] | "\(.key)\t\(.value.result)"'
134
+ failed=$(echo "$NEEDS" | jq -r '
135
+ to_entries[]
136
+ | select(.value.result == "failure" or .value.result == "cancelled")
137
+ | .key')
138
+ if [ -n "$failed" ]; then
139
+ echo "::error::Failed or cancelled jobs: $failed"
140
+ exit 1
141
+ fi
142
+ echo "All required CI jobs passed (or were skipped)."
@@ -0,0 +1,40 @@
1
+ name: Coverage
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: coverage-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ # Runs Vitest with --coverage and uploads the HTML report as an artifact.
17
+ # Coverage thresholds are not enforced by default — opt in by configuring
18
+ # `test.coverage.thresholds` in vitest.config.ts when your suite is ready.
19
+
20
+ jobs:
21
+ coverage:
22
+ name: Coverage
23
+ runs-on: ubuntu-latest
24
+ timeout-minutes: 15
25
+ steps:
26
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
27
+ with:
28
+ persist-credentials: false
29
+ - uses: ./.github/actions/setup
30
+ with:
31
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
32
+ - run: pnpm theme:build
33
+ - run: pnpm coverage
34
+ - name: Upload coverage report
35
+ if: always()
36
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
37
+ with:
38
+ name: coverage-report
39
+ path: coverage/
40
+ retention-days: 14
@@ -19,17 +19,19 @@ on:
19
19
  - "package.json"
20
20
  - ".github/workflows/db-check.yml"
21
21
 
22
+ permissions:
23
+ contents: read
24
+
22
25
  jobs:
23
26
  db-check:
24
27
  runs-on: ubuntu-latest
25
28
  steps:
26
- - uses: actions/checkout@v4
27
- - uses: pnpm/action-setup@v4
28
- - uses: actions/setup-node@v4
29
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
30
+ with:
31
+ persist-credentials: false
32
+ - uses: ./.github/actions/setup
29
33
  with:
30
- node-version: 24
31
- cache: pnpm
32
- - run: pnpm install --frozen-lockfile
34
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
33
35
  - name: Validate migrations
34
36
  env:
35
37
  DATABASE_URL: ${{ secrets.DATABASE_URL_STAGING }}
@@ -15,6 +15,9 @@ on:
15
15
  - ".github/workflows/db-migrate.yml"
16
16
  workflow_dispatch:
17
17
 
18
+ permissions:
19
+ contents: read
20
+
18
21
  concurrency:
19
22
  # Advisory lock at the workflow layer too — defense in depth alongside
20
23
  # safeMigrate's per-database advisory lock.
@@ -26,13 +29,12 @@ jobs:
26
29
  runs-on: ubuntu-latest
27
30
  environment: production
28
31
  steps:
29
- - uses: actions/checkout@v4
30
- - uses: pnpm/action-setup@v4
31
- - uses: actions/setup-node@v4
32
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
33
+ with:
34
+ persist-credentials: false
35
+ - uses: ./.github/actions/setup
32
36
  with:
33
- node-version: 24
34
- cache: pnpm
35
- - run: pnpm install --frozen-lockfile
37
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
36
38
  - name: Apply migrations
37
39
  env:
38
40
  DATABASE_URL: ${{ secrets.DATABASE_URL }}
@@ -0,0 +1,42 @@
1
+ name: SAST
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: sast-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ # Static Application Security Testing via Semgrep. Scans first-party
17
+ # source against TypeScript, JavaScript, React, and OWASP top-ten
18
+ # rulesets. Findings fail the build — silence false positives with
19
+ # `.semgrepignore` (path-level) or inline `// nosemgrep: <rule-id>` with
20
+ # a justification.
21
+
22
+ jobs:
23
+ semgrep:
24
+ name: Semgrep
25
+ runs-on: ubuntu-latest
26
+ timeout-minutes: 10
27
+ if: github.actor != 'dependabot[bot]'
28
+ container:
29
+ image: semgrep/semgrep:1.168.0@sha256:59fbed6127ea7c5dde3ba6a85142733bb20ea9aaa36120c953904f1539aaf66e
30
+ steps:
31
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
32
+ with:
33
+ persist-credentials: false
34
+ - name: Run Semgrep
35
+ run: |
36
+ semgrep scan \
37
+ --config=p/typescript \
38
+ --config=p/javascript \
39
+ --config=p/react \
40
+ --config=p/owasp-top-ten \
41
+ --error \
42
+ --metrics=off
@@ -0,0 +1,33 @@
1
+ name: Security
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: security-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ # Audits production dependencies for known vulnerabilities. Fails the
17
+ # build on high or critical severity findings; moderate and low findings
18
+ # appear in the log but don't block merges.
19
+
20
+ jobs:
21
+ audit:
22
+ name: pnpm audit
23
+ runs-on: ubuntu-latest
24
+ timeout-minutes: 5
25
+ steps:
26
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
27
+ with:
28
+ persist-credentials: false
29
+ - uses: ./.github/actions/setup
30
+ with:
31
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
32
+ - name: Audit production dependencies
33
+ run: pnpm audit --audit-level=high --prod
@@ -0,0 +1,43 @@
1
+ name: Zizmor
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths: [".github/**"]
7
+ pull_request:
8
+ branches: [main]
9
+ paths: [".github/**"]
10
+
11
+ permissions: {}
12
+
13
+ concurrency:
14
+ group: zizmor-${{ github.ref }}
15
+ cancel-in-progress: true
16
+
17
+ # Static security analysis of this app's GitHub Actions workflows
18
+ # (template-injection, unpinned actions, excessive token permissions,
19
+ # credential persistence). Free runner-hosted SAST — no GitHub Advanced
20
+ # Security needed. Findings surface as inline PR annotations. Starts at
21
+ # min-severity: high; lower to medium/low once your workflows are clean.
22
+
23
+ jobs:
24
+ zizmor:
25
+ name: GitHub Actions security (zizmor)
26
+ runs-on: ubuntu-latest
27
+ timeout-minutes: 10
28
+ permissions:
29
+ contents: read
30
+ actions: read
31
+ steps:
32
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
33
+ with:
34
+ persist-credentials: false
35
+ - uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
36
+ with:
37
+ min-severity: high
38
+ min-confidence: medium
39
+ # This app's repo may not have GitHub Advanced Security; disable the
40
+ # SARIF/code-scanning upload (mutually exclusive with annotations) and
41
+ # surface findings as inline PR annotations instead.
42
+ advanced-security: false
43
+ annotations: true
@@ -12,6 +12,7 @@
12
12
  "theme:build": "node ./scripts/build-theme.mjs",
13
13
  "test": "vitest run",
14
14
  "test:watch": "vitest",
15
+ "coverage": "vitest run --coverage",
15
16
  "storybook": "storybook dev -p 6006",
16
17
  "storybook:build": "storybook build -o storybook-static",
17
18
  "db:generate": "drizzle-kit generate",
@@ -58,6 +59,7 @@
58
59
  "@storybook/react-vite": "10.3.6",
59
60
  "@tailwindcss/vite": "4.2.4",
60
61
  "@vitejs/plugin-react": "6.0.1",
62
+ "@vitest/coverage-v8": "4.1.5",
61
63
  "drizzle-kit": "0.31.10",
62
64
  "happy-dom": "15.11.7",
63
65
  "storybook": "10.3.6",
@@ -68,5 +70,6 @@
68
70
  },
69
71
  "engines": {
70
72
  "node": ">=24"
71
- }
73
+ },
74
+ "packageManager": "pnpm@11.9.0"
72
75
  }
@@ -0,0 +1 @@
1
+ 24.15.0
@@ -0,0 +1,12 @@
1
+ # Semgrep ignore — paths excluded from SAST scans.
2
+ #
3
+ # Add specific paths or globs here when a rule produces unavoidable
4
+ # false positives (e.g. mock servers using http://localhost, generated
5
+ # code that trips security patterns).
6
+ #
7
+ # Example:
8
+ # src/mocks/*.test.ts
9
+ #
10
+ # Prefer inline `// nosemgrep: <rule-id> -- <reason>` over path-level
11
+ # exclusions when the case is local; reserve this file for whole files
12
+ # that legitimately need to be skipped.
@@ -0,0 +1,37 @@
1
+ name: Setup
2
+ description: >-
3
+ Install pnpm and Node, authenticate to the private @raredays npm registry, then
4
+ install dependencies. The @raredays/* packages are published with restricted
5
+ access, so the registry token must be present before install. pnpm ignores
6
+ ${VAR} interpolation in a committed project .npmrc (it could leak the token to
7
+ an attacker-controlled registry), so the token is written to the user-level
8
+ ~/.npmrc at runtime instead.
9
+
10
+ inputs:
11
+ node-auth-token:
12
+ description: >-
13
+ npm token with read access to the private @raredays registry. Pass it from
14
+ the NODE_AUTH_TOKEN repository secret.
15
+ required: true
16
+
17
+ runs:
18
+ using: composite
19
+ steps:
20
+ - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
21
+ - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
22
+ with:
23
+ node-version: 24
24
+ cache: pnpm
25
+ - name: Authenticate to the npm registry
26
+ shell: bash
27
+ env:
28
+ NODE_AUTH_TOKEN: ${{ inputs.node-auth-token }}
29
+ run: |
30
+ if [ -z "$NODE_AUTH_TOKEN" ]; then
31
+ echo "::error::NODE_AUTH_TOKEN is empty. Add a repository secret with an npm token that has read access to the private @raredays registry."
32
+ exit 1
33
+ fi
34
+ echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" >> ~/.npmrc
35
+ - name: Install dependencies
36
+ shell: bash
37
+ run: pnpm install --frozen-lockfile
@@ -6,6 +6,9 @@ on:
6
6
  pull_request:
7
7
  branches: [main]
8
8
 
9
+ permissions:
10
+ contents: read
11
+
9
12
  concurrency:
10
13
  group: ci-${{ github.ref }}
11
14
  cancel-in-progress: true
@@ -20,11 +23,12 @@ jobs:
20
23
  runs-on: ubuntu-latest
21
24
  timeout-minutes: 10
22
25
  steps:
23
- - uses: actions/checkout@v4
24
- - uses: pnpm/action-setup@v4
25
- - uses: actions/setup-node@v4
26
- with: { node-version: 24, cache: pnpm }
27
- - run: pnpm install --frozen-lockfile
26
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
27
+ with:
28
+ persist-credentials: false
29
+ - uses: ./.github/actions/setup
30
+ with:
31
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
28
32
  - run: pnpm lint
29
33
 
30
34
  format-check:
@@ -32,11 +36,12 @@ jobs:
32
36
  runs-on: ubuntu-latest
33
37
  timeout-minutes: 10
34
38
  steps:
35
- - uses: actions/checkout@v4
36
- - uses: pnpm/action-setup@v4
37
- - uses: actions/setup-node@v4
38
- with: { node-version: 24, cache: pnpm }
39
- - run: pnpm install --frozen-lockfile
39
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
40
+ with:
41
+ persist-credentials: false
42
+ - uses: ./.github/actions/setup
43
+ with:
44
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
40
45
  - run: pnpm format:check
41
46
 
42
47
  style:
@@ -44,11 +49,12 @@ jobs:
44
49
  runs-on: ubuntu-latest
45
50
  timeout-minutes: 10
46
51
  steps:
47
- - uses: actions/checkout@v4
48
- - uses: pnpm/action-setup@v4
49
- - uses: actions/setup-node@v4
50
- with: { node-version: 24, cache: pnpm }
51
- - run: pnpm install --frozen-lockfile
52
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
53
+ with:
54
+ persist-credentials: false
55
+ - uses: ./.github/actions/setup
56
+ with:
57
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
52
58
  - run: pnpm style
53
59
 
54
60
  typecheck:
@@ -56,11 +62,12 @@ jobs:
56
62
  runs-on: ubuntu-latest
57
63
  timeout-minutes: 10
58
64
  steps:
59
- - uses: actions/checkout@v4
60
- - uses: pnpm/action-setup@v4
61
- - uses: actions/setup-node@v4
62
- with: { node-version: 24, cache: pnpm }
63
- - run: pnpm install --frozen-lockfile
65
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
66
+ with:
67
+ persist-credentials: false
68
+ - uses: ./.github/actions/setup
69
+ with:
70
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
64
71
  - run: pnpm theme:build
65
72
  - run: pnpm typecheck
66
73
 
@@ -69,23 +76,49 @@ jobs:
69
76
  runs-on: ubuntu-latest
70
77
  timeout-minutes: 15
71
78
  steps:
72
- - uses: actions/checkout@v4
73
- - uses: pnpm/action-setup@v4
74
- - uses: actions/setup-node@v4
75
- with: { node-version: 24, cache: pnpm }
76
- - run: pnpm install --frozen-lockfile
79
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
80
+ with:
81
+ persist-credentials: false
82
+ - uses: ./.github/actions/setup
83
+ with:
84
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
77
85
  - run: pnpm build
78
86
 
79
87
  storybook:
80
88
  name: Storybook build
81
89
  runs-on: ubuntu-latest
82
90
  timeout-minutes: 15
83
- if: hashFiles('.storybook/main.ts', '.storybook/main.js') != ''
84
91
  steps:
85
- - uses: actions/checkout@v4
86
- - uses: pnpm/action-setup@v4
87
- - uses: actions/setup-node@v4
88
- with: { node-version: 24, cache: pnpm }
89
- - run: pnpm install --frozen-lockfile
92
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
93
+ with:
94
+ persist-credentials: false
95
+ - uses: ./.github/actions/setup
96
+ with:
97
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
90
98
  - run: pnpm theme:build
91
99
  - run: pnpm storybook:build
100
+
101
+ # Single aggregation gate — make THIS the only required status check in
102
+ # branch protection so you don't have to enumerate every job (skipped
103
+ # jobs are tolerated by the gate).
104
+ ci-result:
105
+ name: ci-result
106
+ needs: [lint, format-check, style, typecheck, build, storybook]
107
+ if: ${{ !cancelled() }}
108
+ runs-on: ubuntu-latest
109
+ permissions: {}
110
+ steps:
111
+ - name: Aggregate CI results
112
+ env:
113
+ NEEDS: ${{ toJson(needs) }}
114
+ run: |
115
+ echo "$NEEDS" | jq -r 'to_entries[] | "\(.key)\t\(.value.result)"'
116
+ failed=$(echo "$NEEDS" | jq -r '
117
+ to_entries[]
118
+ | select(.value.result == "failure" or .value.result == "cancelled")
119
+ | .key')
120
+ if [ -n "$failed" ]; then
121
+ echo "::error::Failed or cancelled jobs: $failed"
122
+ exit 1
123
+ fi
124
+ echo "All required CI jobs passed (or were skipped)."
@@ -0,0 +1,42 @@
1
+ name: SAST
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: sast-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ # Static Application Security Testing via Semgrep. Scans first-party
17
+ # source against TypeScript, JavaScript, React, and OWASP top-ten
18
+ # rulesets. Findings fail the build — silence false positives with
19
+ # `.semgrepignore` (path-level) or inline `// nosemgrep: <rule-id>` with
20
+ # a justification.
21
+
22
+ jobs:
23
+ semgrep:
24
+ name: Semgrep
25
+ runs-on: ubuntu-latest
26
+ timeout-minutes: 10
27
+ if: github.actor != 'dependabot[bot]'
28
+ container:
29
+ image: semgrep/semgrep:1.168.0@sha256:59fbed6127ea7c5dde3ba6a85142733bb20ea9aaa36120c953904f1539aaf66e
30
+ steps:
31
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
32
+ with:
33
+ persist-credentials: false
34
+ - name: Run Semgrep
35
+ run: |
36
+ semgrep scan \
37
+ --config=p/typescript \
38
+ --config=p/javascript \
39
+ --config=p/react \
40
+ --config=p/owasp-top-ten \
41
+ --error \
42
+ --metrics=off
@@ -0,0 +1,33 @@
1
+ name: Security
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: security-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ # Audits production dependencies for known vulnerabilities. Fails the
17
+ # build on high or critical severity findings; moderate and low findings
18
+ # appear in the log but don't block merges.
19
+
20
+ jobs:
21
+ audit:
22
+ name: pnpm audit
23
+ runs-on: ubuntu-latest
24
+ timeout-minutes: 5
25
+ steps:
26
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
27
+ with:
28
+ persist-credentials: false
29
+ - uses: ./.github/actions/setup
30
+ with:
31
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
32
+ - name: Audit production dependencies
33
+ run: pnpm audit --audit-level=high --prod
@@ -0,0 +1,43 @@
1
+ name: Zizmor
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths: [".github/**"]
7
+ pull_request:
8
+ branches: [main]
9
+ paths: [".github/**"]
10
+
11
+ permissions: {}
12
+
13
+ concurrency:
14
+ group: zizmor-${{ github.ref }}
15
+ cancel-in-progress: true
16
+
17
+ # Static security analysis of this app's GitHub Actions workflows
18
+ # (template-injection, unpinned actions, excessive token permissions,
19
+ # credential persistence). Free runner-hosted SAST — no GitHub Advanced
20
+ # Security needed. Findings surface as inline PR annotations. Starts at
21
+ # min-severity: high; lower to medium/low once your workflows are clean.
22
+
23
+ jobs:
24
+ zizmor:
25
+ name: GitHub Actions security (zizmor)
26
+ runs-on: ubuntu-latest
27
+ timeout-minutes: 10
28
+ permissions:
29
+ contents: read
30
+ actions: read
31
+ steps:
32
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
33
+ with:
34
+ persist-credentials: false
35
+ - uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
36
+ with:
37
+ min-severity: high
38
+ min-confidence: medium
39
+ # This app's repo may not have GitHub Advanced Security; disable the
40
+ # SARIF/code-scanning upload (mutually exclusive with annotations) and
41
+ # surface findings as inline PR annotations instead.
42
+ advanced-security: false
43
+ annotations: true
@@ -25,6 +25,7 @@
25
25
  "@raredays/components": "^0.1.0",
26
26
  "@raredays/core": "^0.1.0",
27
27
  "@raredays/style": "^0.1.0",
28
+ "lucide-react": "1.14.0",
28
29
  "next": "16.2.4",
29
30
  "react": "19.2.5",
30
31
  "react-dom": "19.2.5"
@@ -49,5 +50,6 @@
49
50
  },
50
51
  "engines": {
51
52
  "node": ">=24"
52
- }
53
+ },
54
+ "packageManager": "pnpm@11.9.0"
53
55
  }