@rm-industries/create-forge 0.4.0-beta.5 → 1.0.0-rc.1

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.
package/README.md CHANGED
@@ -54,8 +54,14 @@ To exercise the packed CLI across the complete generator fixture matrix:
54
54
  npm run test:generator:e2e
55
55
  ```
56
56
 
57
- This generates default, fully specified, scoped-package-name, no-install, and
58
- conflict fixtures in an OS temporary directory. The default fixture installs,
59
- type-checks, and builds outside the Forge workspace. The conflict fixture also
60
- proves that a failed invocation leaves existing files unchanged. Every fixture
61
- is removed after the run.
57
+ This generates default, fully specified, scoped-package-name, current-directory,
58
+ no-install, and conflict fixtures in an OS temporary directory. The default
59
+ fixture installs and runs the complete generated-project quality pipeline
60
+ outside the Forge workspace. The conflict fixture also proves that a failed
61
+ invocation leaves existing files unchanged. Every fixture is removed after the
62
+ run.
63
+
64
+ The release compatibility matrix runs the same packed fixtures with the
65
+ generated project's core quality gate on every supported Node line on Ubuntu
66
+ and on macOS. The complete end-to-end job adds the multi-browser and Lighthouse
67
+ checks once on the primary Node runtime.
@@ -12,6 +12,9 @@ updates:
12
12
  open-pull-requests-limit: 5
13
13
  commit-message:
14
14
  prefix: chore
15
+ labels:
16
+ - dependencies
17
+ - github-actions
15
18
  groups:
16
19
  github-actions:
17
20
  patterns:
@@ -30,6 +33,9 @@ updates:
30
33
  open-pull-requests-limit: 5
31
34
  commit-message:
32
35
  prefix: chore
36
+ labels:
37
+ - dependencies
38
+ - npm
33
39
  groups:
34
40
  production-dependencies:
35
41
  dependency-type: production
@@ -173,8 +173,8 @@ jobs:
173
173
  with:
174
174
  node-version: '26.x'
175
175
 
176
- - name: Install Chromium
177
- run: npx playwright install --with-deps chromium
176
+ - name: Install supported browsers
177
+ run: npx playwright install --with-deps chromium firefox webkit
178
178
 
179
179
  - name: Run browser and accessibility tests
180
180
  run: npm run test:e2e
@@ -259,3 +259,8 @@ $RECYCLE.BIN/
259
259
  *.lnk
260
260
 
261
261
  # End of https://www.toptal.com/developers/gitignore/api/vim,node,astro,linux,macos,windows,visualstudiocode
262
+
263
+ ### Forge test output ###
264
+ .lighthouseci/
265
+ playwright-report/
266
+ test-results/
@@ -1,12 +1,9 @@
1
1
  {
2
2
  "ci": {
3
3
  "collect": {
4
- "staticDistDir": "./dist",
5
- "url": [
6
- "http://localhost/",
7
- "http://localhost/articles/",
8
- "http://localhost/articles/designing-a-calm-starting-point/"
9
- ],
4
+ "startServerCommand": "npm run preview -- --host 127.0.0.1",
5
+ "startServerReadyPattern": "Local",
6
+ "startServerReadyTimeout": 30000,
10
7
  "numberOfRuns": 3
11
8
  },
12
9
  "assert": {
@@ -69,7 +69,8 @@ the complete Playwright and axe suite, and Lighthouse budgets in that order.
69
69
  Each stage is joined with `&&`, so the command stops immediately and returns a
70
70
  non-zero status when any stage fails. Browser tests and Lighthouse start from
71
71
  their own production build so they remain independently runnable and cannot
72
- accidentally inspect stale output.
72
+ accidentally inspect stale output. Both use Astro's preview server and derive
73
+ any deployment base path from the configured canonical site URL.
73
74
 
74
75
  The complete command is intentionally high cost. Use `npm run quality:static`
75
76
  for the quickest source-only feedback or `npm run quality:core` for every check
@@ -124,6 +125,21 @@ tested content-model release first. No version is ignored, security updates
124
125
  remain enabled, and every update must pass the complete project and security
125
126
  workflows before merging.
126
127
 
128
+ Dependabot pull requests use `dependencies` plus the readable ecosystem label
129
+ `npm` or `github-actions`. GitHub ignores configured custom labels that do not
130
+ exist, so initialize them once after creating the repository:
131
+
132
+ ```sh
133
+ gh label create dependencies --color 0366d6 --description "Pull requests that update a dependency file" --force
134
+ gh label create npm --color cb3837 --description "Pull requests that update npm dependencies" --force
135
+ gh label create github-actions --color 2088ff --description "Pull requests that update GitHub Actions dependencies" --force
136
+ ```
137
+
138
+ Run these commands from the generated repository while authenticated with the
139
+ GitHub CLI. They are idempotent because `--force` updates an existing label. If
140
+ they are not run, Dependabot still opens update pull requests but omits any
141
+ configured label that is missing.
142
+
127
143
  GitHub chooses the Dependabot run times, distributing update checks across its
128
144
  available schedule. Forge derives distinct weekly CodeQL and workflow-validation
129
145
  minutes from the generated package name. This keeps generation reproducible
@@ -147,10 +163,12 @@ alternative-text quality.
147
163
 
148
164
  Lighthouse CI runs three times each against the production home, article
149
165
  listing, and article detail pages, then enforces median category scores and
150
- transfer budgets. The measured baseline, headroom, exclusions, and update rules
151
- are documented in `docs/performance-budget.md`. Reports are written to the
152
- generated `.lighthouseci/` directory; CI retains them for seven days even when
153
- an assertion fails.
166
+ transfer budgets. Its typed runner prefixes those routes with a configured
167
+ project path, so local checks exercise GitHub Pages project sites with the same
168
+ asset URLs used in production. The measured baseline, headroom, exclusions, and
169
+ update rules are documented in `docs/performance-budget.md`. Reports are written
170
+ to the generated `.lighthouseci/` directory; CI retains them for seven days even
171
+ when an assertion fails.
154
172
 
155
173
  The configured exclusions cover only generated output, dependency directories,
156
174
  and tool artifacts. Stylelint's exceptions recognize Tailwind and DaisyUI
@@ -286,6 +304,13 @@ reference in `robots.txt`. Replace `public/favicon.svg` and
286
304
  `public/social-card.svg` when establishing a project identity, and update
287
305
  `socialImage` in `src/config/site.ts` if the sharing image path changes.
288
306
 
307
+ Every HTML page, including `/admin/`, also includes the nonvisual, standard
308
+ `generator` metadata value `Forge by RM Industries`. It makes Forge-generated
309
+ sites identifiable in page source without adding telemetry, network requests,
310
+ client-side JavaScript, accessibility-tree content, or visible branding. This
311
+ generated project is owned source: remove or change the element in
312
+ `src/components/seo/SeoHead.astro` if you do not want to retain the attribution.
313
+
289
314
  ## Content manager
290
315
 
291
316
  Run the development server and open `/admin/` to use Sveltia CMS. Sveltia
@@ -3,7 +3,10 @@
3
3
  Lighthouse CI protects the generated site's public-page baseline. Run
4
4
  `npm run lighthouse:ci` after changes that can affect rendering, assets,
5
5
  metadata, or client behavior. The command builds the production site, collects
6
- three reports per route, and evaluates the median result.
6
+ three reports per route from Astro's preview server, and evaluates the median
7
+ result. The typed runner derives Astro's optional deployment base from
8
+ `src/config/site.ts`, ensuring project-path assets and routes are measured at
9
+ their production-relative locations.
7
10
 
8
11
  ## Enforced routes
9
12
 
@@ -12,7 +12,7 @@
12
12
  "@astrojs/sitemap": "^3.7.3",
13
13
  "@fontsource/fira-code": "^5.3.0",
14
14
  "@fontsource/fira-sans": "^5.3.0",
15
- "@rm-industries/content-model": "0.2.0-alpha.0",
15
+ "@rm-industries/content-model": "1.0.0-rc.1",
16
16
  "@sveltia/cms": "0.203.2",
17
17
  "astro": "^7.2.9"
18
18
  },
@@ -5389,9 +5389,9 @@
5389
5389
  }
5390
5390
  },
5391
5391
  "node_modules/@rm-industries/content-model": {
5392
- "version": "0.2.0-alpha.0",
5393
- "resolved": "https://registry.npmjs.org/@rm-industries/content-model/-/content-model-0.2.0-alpha.0.tgz",
5394
- "integrity": "sha512-DC5MkcUQ4iYIO40mFwNzlawkDEy2eleba8Ui48Xg9g5B6YWzEao6Mqu8FvEa923iVw127t1ZskhZEEh1+47njA==",
5392
+ "version": "1.0.0-rc.1",
5393
+ "resolved": "https://registry.npmjs.org/@rm-industries/content-model/-/content-model-1.0.0-rc.1.tgz",
5394
+ "integrity": "sha512-AqsJ8X0WLjR2VXEpl45Thbfl54w4PzVAUqf2WXexpppOVEELl0VvgCVgkyIUbHpmlxb2ZbClNifY8qJoMbw4kw==",
5395
5395
  "license": "MIT",
5396
5396
  "engines": {
5397
5397
  "node": "^22.22.2 || ^24.0.0 || ^26.0.0",
@@ -9235,9 +9235,9 @@
9235
9235
  }
9236
9236
  },
9237
9237
  "node_modules/fast-uri": {
9238
- "version": "3.1.5",
9239
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz",
9240
- "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==",
9238
+ "version": "3.1.7",
9239
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz",
9240
+ "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==",
9241
9241
  "dev": true,
9242
9242
  "funding": [
9243
9243
  {
@@ -21,7 +21,8 @@
21
21
  "lint:css:fix": "stylelint --fix \"**/*.css\" --ignore-path .gitignore",
22
22
  "lint:markdown": "markdownlint \"**/*.md\" --ignore-path .gitignore",
23
23
  "lint:markdown:fix": "markdownlint --fix \"**/*.md\" --ignore-path .gitignore",
24
- "lighthouse:ci": "npm run build && lhci autorun",
24
+ "lighthouse:autorun": "lhci autorun",
25
+ "lighthouse:ci": "npm run build && node scripts/run-lighthouse.ts",
25
26
  "spellcheck": "cspell \"**\"",
26
27
  "typecheck": "astro sync && tsc --noEmit",
27
28
  "test": "npm run test:unit",
@@ -39,7 +40,7 @@
39
40
  "@astrojs/sitemap": "^3.7.3",
40
41
  "@fontsource/fira-code": "^5.3.0",
41
42
  "@fontsource/fira-sans": "^5.3.0",
42
- "@rm-industries/content-model": "0.2.0-alpha.0",
43
+ "@rm-industries/content-model": "1.0.0-rc.1",
43
44
  "@sveltia/cms": "0.203.2",
44
45
  "astro": "^7.2.9"
45
46
  },
@@ -19,6 +19,14 @@ export default defineConfig({
19
19
  name: 'chromium',
20
20
  use: { ...devices['Desktop Chrome'] },
21
21
  },
22
+ {
23
+ name: 'firefox',
24
+ use: { ...devices['Desktop Firefox'] },
25
+ },
26
+ {
27
+ name: 'webkit',
28
+ use: { ...devices['Desktop Safari'] },
29
+ },
22
30
  ],
23
31
  webServer: {
24
32
  command: 'npm run build && npm run preview -- --host 127.0.0.1',
@@ -0,0 +1,21 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { getLighthouseUrls } from './run-lighthouse';
4
+
5
+ describe('getLighthouseUrls', () => {
6
+ it('audits root deployments from the preview origin', () => {
7
+ expect(getLighthouseUrls('https://example.com')).toEqual([
8
+ 'http://127.0.0.1:4321/',
9
+ 'http://127.0.0.1:4321/articles/',
10
+ 'http://127.0.0.1:4321/articles/designing-a-calm-starting-point/',
11
+ ]);
12
+ });
13
+
14
+ it('audits project deployments below their configured base path', () => {
15
+ expect(getLighthouseUrls('https://example.github.io/example-project')).toEqual([
16
+ 'http://127.0.0.1:4321/example-project/',
17
+ 'http://127.0.0.1:4321/example-project/articles/',
18
+ 'http://127.0.0.1:4321/example-project/articles/designing-a-calm-starting-point/',
19
+ ]);
20
+ });
21
+ });
@@ -0,0 +1,30 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { createRequire } from 'node:module';
3
+ import { pathToFileURL } from 'node:url';
4
+
5
+ import { getDeploymentConfig } from '../src/config/deployment.ts';
6
+ import { site } from '../src/config/site.ts';
7
+ import { resolveSiteHref } from '../src/lib/paths.ts';
8
+
9
+ const previewOrigin = 'http://127.0.0.1:4321';
10
+ const auditedPaths = ['/', '/articles/', '/articles/designing-a-calm-starting-point/'] as const;
11
+
12
+ export const getLighthouseUrls = (siteUrl: string, origin = previewOrigin): string[] => {
13
+ const deployment = getDeploymentConfig(siteUrl);
14
+
15
+ return auditedPaths.map((path) => new URL(resolveSiteHref(path, deployment.base ?? '/'), origin).href);
16
+ };
17
+
18
+ export const runLighthouse = (): void => {
19
+ const lighthouseCli = createRequire(import.meta.url).resolve('@lhci/cli/src/cli.js');
20
+ const urlArguments = getLighthouseUrls(site.url).map((url) => `--collect.url=${url}`);
21
+ const result = spawnSync(process.execPath, [lighthouseCli, 'autorun', ...urlArguments], {
22
+ env: { ...process.env, ASTRO_PREVIEW_BACKGROUND: '0' },
23
+ stdio: 'inherit',
24
+ });
25
+
26
+ if (result.error) throw result.error;
27
+ if (result.status !== 0) process.exitCode = result.status ?? 1;
28
+ };
29
+
30
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) runLighthouse();
@@ -28,6 +28,7 @@ const socialImageUrl = new URL(resolveSiteHref(site.socialImage), new URL(site.u
28
28
  ---
29
29
 
30
30
  <title>{pageTitle}</title>
31
+ <meta name="generator" content="Forge by RM Industries" />
31
32
  <meta name="description" content={description} />
32
33
  <link rel="canonical" href={canonicalUrl} />
33
34
  <link rel="sitemap" href={resolveSiteHref('/sitemap-index.xml')} />
@@ -49,9 +49,9 @@ test('identifies the current primary navigation item', async ({ page }) => {
49
49
  await expect(page.getByRole('link', { name: 'Home' })).toHaveAttribute('aria-current', 'page');
50
50
  });
51
51
 
52
- test('moves keyboard users directly to the main content', async ({ page }) => {
52
+ test('moves keyboard users directly to the main content', async ({ browserName, page }) => {
53
53
  await page.goto(resolvePreviewPath('/'));
54
- await page.keyboard.press('Tab');
54
+ await page.keyboard.press(browserName === 'webkit' && process.platform === 'darwin' ? 'Alt+Tab' : 'Tab');
55
55
 
56
56
  const skipLink = page.getByRole('link', { name: 'Skip to content' });
57
57
  await expect(skipLink).toBeFocused();
@@ -11,6 +11,7 @@ test('serves every baseline page with configured canonical and social metadata',
11
11
 
12
12
  const canonicalUrl = new URL(resolvePreviewPath(route), site.url).href;
13
13
 
14
+ await expect(page.locator('meta[name="generator"]')).toHaveAttribute('content', 'Forge by RM Industries');
14
15
  await expect(page.locator('link[rel="canonical"]')).toHaveAttribute('href', canonicalUrl);
15
16
  await expect(page.locator('meta[property="og:site_name"]')).toHaveAttribute('content', site.name);
16
17
  await expect(page.locator('meta[property="og:url"]')).toHaveAttribute('content', canonicalUrl);
@@ -33,14 +33,14 @@ test('makes every Catppuccin flavor available explicitly', async ({ page }) => {
33
33
  }
34
34
  });
35
35
 
36
- test('serves fonts locally and presents a visible keyboard focus indicator', async ({ page }) => {
36
+ test('serves fonts locally and presents a visible keyboard focus indicator', async ({ browserName, page }) => {
37
37
  const fontRequests: string[] = [];
38
38
  page.on('request', (request) => {
39
39
  if (request.resourceType() === 'font') fontRequests.push(request.url());
40
40
  });
41
41
 
42
42
  await page.goto(resolvePreviewPath('/'));
43
- await page.keyboard.press('Tab');
43
+ await page.keyboard.press(browserName === 'webkit' && process.platform === 'darwin' ? 'Alt+Tab' : 'Tab');
44
44
 
45
45
  await expect(page.locator('a').first()).toBeFocused();
46
46
  await expect(page.locator('a').first()).not.toHaveCSS('outline-style', 'none');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rm-industries/create-forge",
3
- "version": "0.4.0-beta.5",
3
+ "version": "1.0.0-rc.1",
4
4
  "description": "Create a content-driven Forge website",
5
5
  "keywords": [
6
6
  "astro",
@@ -33,6 +33,7 @@
33
33
  "build": "tsdown && node scripts/copy-template.ts",
34
34
  "prepack": "npm run typecheck && npm test && npm run build",
35
35
  "test": "vitest run",
36
+ "test:compatibility": "npm run build && node scripts/test-generator-e2e.ts --compatibility",
36
37
  "test:e2e": "npm run build && node scripts/test-generator-e2e.ts",
37
38
  "typecheck": "tsc --project tsconfig.json --noEmit",
38
39
  "verify:package": "npm run build && node scripts/verify-package.ts"