@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 +11 -5
- package/dist/template/.github/dependabot.yml +6 -0
- package/dist/template/.github/workflows/project.yml +2 -2
- package/dist/template/.gitignore.template +5 -0
- package/dist/template/.lighthouserc.json +3 -6
- package/dist/template/README.md +30 -5
- package/dist/template/docs/performance-budget.md +4 -1
- package/dist/template/package-lock.json +7 -7
- package/dist/template/package.json +3 -2
- package/dist/template/playwright.config.ts +8 -0
- package/dist/template/scripts/run-lighthouse.test.ts +21 -0
- package/dist/template/scripts/run-lighthouse.ts +30 -0
- package/dist/template/src/components/seo/SeoHead.astro +1 -0
- package/dist/template/tests/accessibility.spec.ts +2 -2
- package/dist/template/tests/routes.spec.ts +1 -0
- package/dist/template/tests/theme.spec.ts +2 -2
- package/package.json +2 -1
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,
|
|
58
|
-
conflict fixtures in an OS temporary directory. The default
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
is removed after the
|
|
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
|
|
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
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"ci": {
|
|
3
3
|
"collect": {
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
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": {
|
package/dist/template/README.md
CHANGED
|
@@ -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.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
|
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.
|
|
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.
|
|
5393
|
-
"resolved": "https://registry.npmjs.org/@rm-industries/content-model/-/content-model-0.
|
|
5394
|
-
"integrity": "sha512-
|
|
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.
|
|
9239
|
-
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.
|
|
9240
|
-
"integrity": "sha512-
|
|
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:
|
|
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.
|
|
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.
|
|
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"
|