@open-agent-toolkit/cli 0.2.73 → 0.2.74
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/assets/bundle-metadata.json +1 -1
- package/assets/docs/cli-utilities/configuration.md +3 -56
- package/assets/docs/cli-utilities/tool-packs.md +4 -4
- package/assets/docs/contributing/code.md +2 -2
- package/assets/docs/contributing/index.md +0 -1
- package/assets/docs/reference/cli-reference.md +1 -1
- package/assets/docs/reference/troubleshooting.md +17 -40
- package/assets/docs/workflows/projects/artifacts.md +16 -34
- package/assets/docs/workflows/projects/lifecycle.md +20 -19
- package/assets/docs/workflows/skills/explainer-kit.md +180 -440
- package/assets/docs/workflows/skills/index.md +1 -2
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/explainer-kit/SKILL.md +96 -171
- package/assets/skills/explainer-kit/recipes/engineer-tour.json +1 -22
- package/assets/skills/explainer-kit/recipes/program-recap.json +1 -29
- package/assets/skills/explainer-kit/recipes/project-explainer.json +1 -22
- package/assets/skills/explainer-kit/recipes/project-recap.v2.json +1 -43
- package/assets/skills/explainer-kit/references/fact-base-contract.md +76 -101
- package/assets/skills/explainer-kit/references/recap-authoring.md +99 -0
- package/assets/skills/explainer-kit/schemas/manifest.schema.json +13 -135
- package/assets/skills/explainer-kit/scripts/bundle.mjs +712 -0
- package/assets/skills/explainer-kit/scripts/lib/claim-subject.mjs +40 -0
- package/assets/skills/explainer-kit/scripts/lib/contracts.mjs +113 -1504
- package/assets/skills/explainer-kit/scripts/lib/failure.mjs +13 -0
- package/assets/skills/explainer-kit/scripts/lib/package-coverage.mjs +57 -403
- package/assets/skills/explainer-kit/scripts/lib/qa-result.mjs +168 -0
- package/assets/skills/explainer-kit/scripts/lib/qa.mjs +90 -488
- package/assets/skills/explainer-kit/scripts/lib/recipes.mjs +28 -545
- package/assets/skills/explainer-kit/scripts/lib/run-package.mjs +120 -0
- package/assets/skills/explainer-kit/scripts/lib/sanitize.mjs +42 -0
- package/assets/skills/explainer-kit/scripts/record.mjs +335 -0
- package/assets/skills/explainer-kit/scripts/verify.mjs +648 -0
- package/assets/skills/oat-explainer-kit/SKILL.md +57 -117
- package/assets/skills/oat-explainer-kit/references/config-contract.md +20 -64
- package/assets/skills/oat-explainer-kit/references/lifecycle-contract.md +85 -237
- package/assets/skills/oat-explainer-kit/scripts/bind-project-sources.mjs +18 -10
- package/assets/skills/oat-explainer-kit/scripts/check-core.mjs +1 -0
- package/assets/skills/oat-explainer-kit/scripts/check-terminal-outcome.mjs +256 -21
- package/assets/skills/oat-explainer-kit/scripts/persist-intent.mjs +82 -3
- package/assets/skills/oat-explainer-kit/scripts/resolve-config.mjs +0 -162
- package/assets/skills/oat-explainer-kit/scripts/resolve-intent.mjs +57 -123
- package/assets/skills/oat-project-autonomous/SKILL.md +14 -13
- package/assets/skills/oat-project-autonomous/references/gate-inventory.md +165 -165
- package/assets/skills/oat-project-complete/SKILL.md +168 -349
- package/assets/skills/oat-project-complete/scripts/consume-persisted-recap-intent.mjs +125 -0
- package/assets/skills/oat-project-complete/scripts/execute-synced-archive-entry.mjs +0 -30
- package/assets/skills/oat-project-complete/scripts/parse-completion-retry-fields.mjs +4 -37
- package/assets/skills/oat-project-complete/scripts/parse-synced-archive-resume-fields.mjs +17 -16
- package/assets/skills/oat-project-complete/scripts/recover-completion-receipts.mjs +9 -174
- package/assets/skills/oat-project-complete/scripts/resolve-completion-retry.mjs +1 -2
- package/assets/skills/oat-project-document/SKILL.md +1 -1
- package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +165 -165
- package/assets/skills/oat-project-implement/SKILL.md +1 -1
- package/assets/skills/oat-project-implement/references/completion-and-closeout.md +44 -56
- package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +165 -165
- package/assets/skills/oat-project-lite/SKILL.md +1 -1
- package/assets/skills/oat-project-lite/references/docs/autonomy-contract.md +165 -165
- package/assets/skills/oat-project-plan/SKILL.md +5 -6
- package/assets/skills/oat-project-pr-final/SKILL.md +1 -1
- package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +165 -165
- package/assets/skills/oat-project-quick-start/SKILL.md +1 -1
- package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +165 -165
- package/assets/skills/oat-project-summary/SKILL.md +35 -25
- package/assets/skills/oat-wave-execute/SKILL.md +11 -36
- package/assets/skills/oat-wave-program/SKILL.md +13 -37
- package/dist/commands/config/index.d.ts.map +1 -1
- package/dist/commands/config/index.js +9 -113
- package/dist/commands/project/archive/archive-utils.d.ts.map +1 -1
- package/dist/commands/project/archive/archive-utils.js +32 -184
- package/dist/commands/project/archive/explainer-package-coverage.d.ts +3 -8
- package/dist/commands/project/archive/explainer-package-coverage.d.ts.map +1 -1
- package/dist/commands/project/archive/explainer-package-coverage.js +1 -1
- package/dist/config/oat-config.d.ts +0 -11
- package/dist/config/oat-config.d.ts.map +1 -1
- package/dist/config/oat-config.js +0 -34
- package/dist/config/resolve.d.ts.map +1 -1
- package/dist/config/resolve.js +0 -8
- package/dist/release/public-package-contract.d.ts.map +1 -1
- package/dist/release/public-package-contract.js +2 -10
- package/dist/validation/project-state.d.ts.map +1 -1
- package/dist/validation/project-state.js +42 -3
- package/package.json +2 -2
- package/assets/docs/contributing/explainer-kit-verification.md +0 -133
- package/assets/docs/workflows/skills/explainer-kit-providers.md +0 -155
- package/assets/skills/explainer-kit/briefs/deep-dive.md +0 -35
- package/assets/skills/explainer-kit/briefs/project-page.md +0 -38
- package/assets/skills/explainer-kit/briefs/supporting-diagram.md +0 -28
- package/assets/skills/explainer-kit/briefs/walkthrough-deck.md +0 -35
- package/assets/skills/explainer-kit/examples/project-explainer/content.md +0 -31
- package/assets/skills/explainer-kit/examples/project-explainer/fact-base.md +0 -22
- package/assets/skills/explainer-kit/examples/project-recap/content.md +0 -77
- package/assets/skills/explainer-kit/examples/project-recap/fact-base.json +0 -104
- package/assets/skills/explainer-kit/examples/project-recap/fact-base.md +0 -44
- package/assets/skills/explainer-kit/examples/theme-bundle.json +0 -87
- package/assets/skills/explainer-kit/recipes/project-recap.v1.json +0 -93
- package/assets/skills/explainer-kit/references/contracts.md +0 -324
- package/assets/skills/explainer-kit/references/destination-contract.md +0 -210
- package/assets/skills/explainer-kit/references/extension-contract.md +0 -80
- package/assets/skills/explainer-kit/references/golden-conformance.md +0 -80
- package/assets/skills/explainer-kit/references/visual-review.md +0 -71
- package/assets/skills/explainer-kit/schemas/author-request.v2.schema.json +0 -212
- package/assets/skills/explainer-kit/schemas/author-request.v3.schema.json +0 -241
- package/assets/skills/explainer-kit/schemas/author-result.v2.schema.json +0 -52
- package/assets/skills/explainer-kit/schemas/build-record.schema.json +0 -98
- package/assets/skills/explainer-kit/schemas/durability-evidence.schema.json +0 -53
- package/assets/skills/explainer-kit/schemas/publish-receipt.v1.schema.json +0 -97
- package/assets/skills/explainer-kit/schemas/publish-receipt.v2.schema.json +0 -215
- package/assets/skills/explainer-kit/schemas/publish-request.v1.schema.json +0 -32
- package/assets/skills/explainer-kit/schemas/publish-request.v2.schema.json +0 -34
- package/assets/skills/explainer-kit/schemas/run-request.schema.json +0 -147
- package/assets/skills/explainer-kit/schemas/set-plan.v1.schema.json +0 -149
- package/assets/skills/explainer-kit/schemas/terminal-evidence.v1.schema.json +0 -81
- package/assets/skills/explainer-kit/schemas/visual-review-evidence.v1.schema.json +0 -66
- package/assets/skills/explainer-kit/schemas/visual-review-request.v1.schema.json +0 -117
- package/assets/skills/explainer-kit/schemas/visual-review-result.v1.schema.json +0 -80
- package/assets/skills/explainer-kit/scripts/lib/catalog.mjs +0 -349
- package/assets/skills/explainer-kit/scripts/lib/content-approval.mjs +0 -418
- package/assets/skills/explainer-kit/scripts/lib/diagram.mjs +0 -514
- package/assets/skills/explainer-kit/scripts/lib/durability.mjs +0 -643
- package/assets/skills/explainer-kit/scripts/lib/fact-base.mjs +0 -664
- package/assets/skills/explainer-kit/scripts/lib/internal-references.mjs +0 -538
- package/assets/skills/explainer-kit/scripts/lib/markdown.mjs +0 -414
- package/assets/skills/explainer-kit/scripts/lib/publication-policy.mjs +0 -54
- package/assets/skills/explainer-kit/scripts/lib/records.mjs +0 -954
- package/assets/skills/explainer-kit/scripts/lib/render.mjs +0 -703
- package/assets/skills/explainer-kit/scripts/lib/s3-roots.mjs +0 -353
- package/assets/skills/explainer-kit/scripts/lib/s3-static.mjs +0 -759
- package/assets/skills/explainer-kit/scripts/lib/set-plan.mjs +0 -209
- package/assets/skills/explainer-kit/scripts/lib/source-backlinks.mjs +0 -218
- package/assets/skills/explainer-kit/scripts/lib/terminal-evidence.mjs +0 -157
- package/assets/skills/explainer-kit/scripts/lib/visual-review.mjs +0 -400
- package/assets/skills/explainer-kit/scripts/publish.mjs +0 -76
- package/assets/skills/explainer-kit/scripts/record-durability.mjs +0 -64
- package/assets/skills/explainer-kit/scripts/render-qa.mjs +0 -461
- package/assets/skills/explainer-kit/scripts/run.mjs +0 -3038
- package/assets/skills/explainer-kit/scripts/validate.mjs +0 -59
- package/assets/skills/oat-explainer-kit/references/author-callback.md +0 -53
- package/assets/skills/oat-explainer-kit/references/migration.md +0 -144
- package/assets/skills/oat-explainer-kit/references/visual-review-callback.md +0 -83
- package/assets/skills/oat-explainer-kit/scripts/derive-destination.mjs +0 -91
- package/assets/skills/oat-explainer-kit/scripts/finalize-tracked-run.mjs +0 -531
- package/assets/skills/oat-explainer-kit/scripts/probe-recap-seams.mjs +0 -371
- package/assets/skills/oat-explainer-kit/scripts/run.mjs +0 -905
- package/dist/commands/project/archive/explainer-source-backlinks.d.ts +0 -18
- package/dist/commands/project/archive/explainer-source-backlinks.d.ts.map +0 -1
- package/dist/commands/project/archive/explainer-source-backlinks.js +0 -27
- package/dist/commands/project/archive/explainer-terminal-evidence.d.ts +0 -29
- package/dist/commands/project/archive/explainer-terminal-evidence.d.ts.map +0 -1
- package/dist/commands/project/archive/explainer-terminal-evidence.js +0 -37
|
@@ -1,461 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { readFile } from 'node:fs/promises';
|
|
4
|
-
import { createServer } from 'node:http';
|
|
5
|
-
import { basename, posix, resolve, sep } from 'node:path';
|
|
6
|
-
import { pathToFileURL } from 'node:url';
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
RENDER_QA_WARNING_IDS,
|
|
10
|
-
auditArtifactSet,
|
|
11
|
-
renderQaWarningIds,
|
|
12
|
-
runBrowserProbes,
|
|
13
|
-
} from './lib/qa.mjs';
|
|
14
|
-
import { renderArtifact } from './lib/render.mjs';
|
|
15
|
-
import { resolveTheme } from './lib/theme.mjs';
|
|
16
|
-
|
|
17
|
-
export const RELEASE_PALETTES = Object.freeze([
|
|
18
|
-
'neutral',
|
|
19
|
-
'ocean',
|
|
20
|
-
'ember',
|
|
21
|
-
'forest',
|
|
22
|
-
'violet',
|
|
23
|
-
]);
|
|
24
|
-
export const RELEASE_MODES = Object.freeze(['light', 'dark']);
|
|
25
|
-
export const RELEASE_PROFILES = Object.freeze([
|
|
26
|
-
'clean',
|
|
27
|
-
'editorial',
|
|
28
|
-
'technical',
|
|
29
|
-
]);
|
|
30
|
-
export const RELEASE_VIEWPORTS = Object.freeze([320, 768, 1440]);
|
|
31
|
-
export const RELEASE_ARTIFACTS = Object.freeze([
|
|
32
|
-
Object.freeze({
|
|
33
|
-
id: 'release-hub',
|
|
34
|
-
type: 'hub',
|
|
35
|
-
template: 'house-style',
|
|
36
|
-
required: true,
|
|
37
|
-
}),
|
|
38
|
-
Object.freeze({
|
|
39
|
-
id: 'release-diagram',
|
|
40
|
-
type: 'diagram',
|
|
41
|
-
template: 'diagram-shell',
|
|
42
|
-
required: true,
|
|
43
|
-
}),
|
|
44
|
-
Object.freeze({
|
|
45
|
-
id: 'release-explainer',
|
|
46
|
-
type: 'explainer',
|
|
47
|
-
template: 'engineer-tour',
|
|
48
|
-
required: true,
|
|
49
|
-
}),
|
|
50
|
-
Object.freeze({
|
|
51
|
-
id: 'release-deck',
|
|
52
|
-
type: 'deck',
|
|
53
|
-
template: 'deck-shell',
|
|
54
|
-
required: true,
|
|
55
|
-
}),
|
|
56
|
-
]);
|
|
57
|
-
|
|
58
|
-
export async function runRenderQaStage({
|
|
59
|
-
siteDir,
|
|
60
|
-
artifacts,
|
|
61
|
-
browserProbe,
|
|
62
|
-
widths,
|
|
63
|
-
evidenceRoot,
|
|
64
|
-
requireBrowserEvidence,
|
|
65
|
-
} = {}) {
|
|
66
|
-
assertStageInput(siteDir, artifacts);
|
|
67
|
-
// Render QA is opt-in: the stage drives a probe the caller supplies and never
|
|
68
|
-
// launches a runtime of its own.
|
|
69
|
-
if (typeof browserProbe !== 'function') {
|
|
70
|
-
return {
|
|
71
|
-
valid: true,
|
|
72
|
-
skipped: true,
|
|
73
|
-
warnings: [RENDER_QA_WARNING_IDS.skippedNoProbe],
|
|
74
|
-
issues: [],
|
|
75
|
-
probes: 0,
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return probeSiteArtifacts({
|
|
80
|
-
siteDir,
|
|
81
|
-
artifacts,
|
|
82
|
-
probe: browserProbe,
|
|
83
|
-
widths,
|
|
84
|
-
evidenceRoot,
|
|
85
|
-
requireBrowserEvidence,
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async function probeSiteArtifacts({
|
|
90
|
-
siteDir,
|
|
91
|
-
artifacts,
|
|
92
|
-
probe,
|
|
93
|
-
widths,
|
|
94
|
-
evidenceRoot,
|
|
95
|
-
requireBrowserEvidence,
|
|
96
|
-
}) {
|
|
97
|
-
return withSiteServer(siteDir, async (origin) => {
|
|
98
|
-
const probeArtifacts = await Promise.all(
|
|
99
|
-
artifacts.map(async (artifact) => {
|
|
100
|
-
const relativePath = siteRelativePath(artifact.renderedPath);
|
|
101
|
-
return {
|
|
102
|
-
id: artifact.id,
|
|
103
|
-
type: artifact.type,
|
|
104
|
-
html: await readFile(
|
|
105
|
-
resolve(siteDir, ...relativePath.split('/')),
|
|
106
|
-
'utf8',
|
|
107
|
-
),
|
|
108
|
-
url: `${origin}/${relativePath
|
|
109
|
-
.split('/')
|
|
110
|
-
.map(encodeURIComponent)
|
|
111
|
-
.join('/')}`,
|
|
112
|
-
};
|
|
113
|
-
}),
|
|
114
|
-
);
|
|
115
|
-
const browser = await runBrowserProbes({
|
|
116
|
-
artifacts: probeArtifacts,
|
|
117
|
-
probe,
|
|
118
|
-
...(widths && { widths }),
|
|
119
|
-
...(evidenceRoot && { evidenceRoot }),
|
|
120
|
-
...(requireBrowserEvidence && { requireEvidence: true }),
|
|
121
|
-
});
|
|
122
|
-
return {
|
|
123
|
-
valid: true,
|
|
124
|
-
skipped: false,
|
|
125
|
-
warnings: renderQaWarningIds(browser.issues),
|
|
126
|
-
issues: browser.issues,
|
|
127
|
-
probes: browser.probes,
|
|
128
|
-
...(browser.evidence && { evidence: browser.evidence }),
|
|
129
|
-
};
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export function selectReleaseVisualMatrix() {
|
|
134
|
-
const paletteModeCases = RELEASE_PALETTES.flatMap((palette, paletteIndex) =>
|
|
135
|
-
RELEASE_MODES.map((mode, modeIndex) => ({
|
|
136
|
-
id: `palette-${palette}-${mode}`,
|
|
137
|
-
axis: 'palette-mode',
|
|
138
|
-
palette,
|
|
139
|
-
mode,
|
|
140
|
-
visualProfile: 'clean',
|
|
141
|
-
renderStrategy:
|
|
142
|
-
(paletteIndex + modeIndex) % 2 === 0
|
|
143
|
-
? 'default-only'
|
|
144
|
-
: 'user-switchable',
|
|
145
|
-
artifact: {
|
|
146
|
-
...RELEASE_ARTIFACTS[0],
|
|
147
|
-
id: `palette-${palette}-${mode}`,
|
|
148
|
-
},
|
|
149
|
-
viewports: [RELEASE_VIEWPORTS[0], RELEASE_VIEWPORTS[2]],
|
|
150
|
-
scenarios: ['default'],
|
|
151
|
-
})),
|
|
152
|
-
);
|
|
153
|
-
const profileArtifactCases = RELEASE_PROFILES.flatMap(
|
|
154
|
-
(visualProfile, profileIndex) =>
|
|
155
|
-
RELEASE_ARTIFACTS.map((artifact, artifactIndex) => {
|
|
156
|
-
const mode =
|
|
157
|
-
RELEASE_MODES[(profileIndex + artifactIndex) % RELEASE_MODES.length];
|
|
158
|
-
return {
|
|
159
|
-
id: `profile-${visualProfile}-${artifact.type}`,
|
|
160
|
-
axis: 'profile-artifact',
|
|
161
|
-
palette:
|
|
162
|
-
RELEASE_PALETTES[
|
|
163
|
-
(profileIndex + artifactIndex) % RELEASE_PALETTES.length
|
|
164
|
-
],
|
|
165
|
-
mode,
|
|
166
|
-
visualProfile,
|
|
167
|
-
renderStrategy:
|
|
168
|
-
(profileIndex + artifactIndex) % 2 === 0
|
|
169
|
-
? 'default-only'
|
|
170
|
-
: 'user-switchable',
|
|
171
|
-
artifact: {
|
|
172
|
-
...artifact,
|
|
173
|
-
id: `profile-${visualProfile}-${artifact.type}`,
|
|
174
|
-
},
|
|
175
|
-
viewports:
|
|
176
|
-
artifact.type === 'deck'
|
|
177
|
-
? [...RELEASE_VIEWPORTS]
|
|
178
|
-
: [RELEASE_VIEWPORTS[0], RELEASE_VIEWPORTS[2]],
|
|
179
|
-
scenarios:
|
|
180
|
-
artifact.type === 'deck'
|
|
181
|
-
? ['horizontal', 'no-js', 'print']
|
|
182
|
-
: ['default'],
|
|
183
|
-
};
|
|
184
|
-
}),
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
return [...paletteModeCases, ...profileArtifactCases];
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export async function runReleaseVisualMatrix({
|
|
191
|
-
matrix = selectReleaseVisualMatrix(),
|
|
192
|
-
browserProbe,
|
|
193
|
-
browserSession,
|
|
194
|
-
evidenceRoot,
|
|
195
|
-
onProbeResult,
|
|
196
|
-
} = {}) {
|
|
197
|
-
if (!Array.isArray(matrix) || matrix.length === 0) {
|
|
198
|
-
throw new TypeError('Release visual QA requires a non-empty matrix.');
|
|
199
|
-
}
|
|
200
|
-
if (browserSession !== undefined && browserProbe !== undefined) {
|
|
201
|
-
throw new TypeError(
|
|
202
|
-
'Release visual QA accepts either a trusted browser session or a bare non-retaining probe, not both.',
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
if (browserSession === undefined && typeof browserProbe !== 'function') {
|
|
206
|
-
throw new TypeError(
|
|
207
|
-
'Release visual QA requires a trusted browser session or browser probe callback.',
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
if (onProbeResult !== undefined && typeof onProbeResult !== 'function') {
|
|
211
|
-
throw new TypeError('Release visual QA probe observer must be a callback.');
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const issues = [];
|
|
215
|
-
const cases = [];
|
|
216
|
-
const evidence = [];
|
|
217
|
-
for (const entry of matrix) {
|
|
218
|
-
const { theme } = await resolveTheme({
|
|
219
|
-
palette: entry.palette,
|
|
220
|
-
visualProfile: entry.visualProfile,
|
|
221
|
-
defaultMode: entry.mode,
|
|
222
|
-
renderStrategy: entry.renderStrategy,
|
|
223
|
-
});
|
|
224
|
-
const rendered = await renderArtifact({
|
|
225
|
-
recipeArtifact: entry.artifact,
|
|
226
|
-
content: releaseContent(entry.artifact.id),
|
|
227
|
-
theme,
|
|
228
|
-
renderStrategy: entry.renderStrategy,
|
|
229
|
-
});
|
|
230
|
-
const report = await auditArtifactSet({
|
|
231
|
-
artifacts: [
|
|
232
|
-
{
|
|
233
|
-
id: entry.id,
|
|
234
|
-
type: entry.artifact.type,
|
|
235
|
-
html: rendered.html,
|
|
236
|
-
},
|
|
237
|
-
],
|
|
238
|
-
widths: entry.viewports,
|
|
239
|
-
...(browserSession === undefined ? { browserProbe } : { browserSession }),
|
|
240
|
-
onProbeResult: async (observation) => {
|
|
241
|
-
if (
|
|
242
|
-
entry.artifact.type === 'deck' &&
|
|
243
|
-
observation.scenario === 'default' &&
|
|
244
|
-
observation.result?.deckLayout?.flow !== 'horizontal'
|
|
245
|
-
) {
|
|
246
|
-
issues.push({
|
|
247
|
-
artifactId: entry.id,
|
|
248
|
-
width: observation.viewport.width,
|
|
249
|
-
scenario: 'default',
|
|
250
|
-
code: 'deck-horizontal-layout',
|
|
251
|
-
message: 'Interactive deck must page horizontally by default.',
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
await onProbeResult?.(observation);
|
|
255
|
-
},
|
|
256
|
-
...(evidenceRoot && {
|
|
257
|
-
evidenceRoot,
|
|
258
|
-
requireBrowserEvidence: true,
|
|
259
|
-
}),
|
|
260
|
-
});
|
|
261
|
-
issues.push(...report.issues);
|
|
262
|
-
evidence.push(...(report.browser?.evidence ?? []));
|
|
263
|
-
cases.push({
|
|
264
|
-
id: entry.id,
|
|
265
|
-
artifactType: entry.artifact.type,
|
|
266
|
-
palette: entry.palette,
|
|
267
|
-
mode: entry.mode,
|
|
268
|
-
visualProfile: entry.visualProfile,
|
|
269
|
-
renderStrategy: entry.renderStrategy,
|
|
270
|
-
probes: report.browser?.probes ?? 0,
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
return {
|
|
275
|
-
valid: issues.length === 0,
|
|
276
|
-
issues,
|
|
277
|
-
cases,
|
|
278
|
-
...(evidenceRoot && { evidence }),
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
export async function runRenderQaCli(
|
|
283
|
-
argv = process.argv.slice(2),
|
|
284
|
-
io = console,
|
|
285
|
-
options = {},
|
|
286
|
-
) {
|
|
287
|
-
try {
|
|
288
|
-
const parsed = parseArguments(argv);
|
|
289
|
-
const denylist = [...parsed.denylist, ...(options.denylist ?? [])];
|
|
290
|
-
if (parsed.paths.length === 0) {
|
|
291
|
-
throw new Error(
|
|
292
|
-
'Usage: render-qa.mjs [--deny <string>] [--type <type>] <html-path> [...]',
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const artifacts = await Promise.all(
|
|
297
|
-
parsed.paths.map(async ({ path, type }, index) => ({
|
|
298
|
-
id: `${basename(path)}-${index + 1}`,
|
|
299
|
-
type,
|
|
300
|
-
html: await readFile(path, 'utf8'),
|
|
301
|
-
})),
|
|
302
|
-
);
|
|
303
|
-
const report = await auditArtifactSet({
|
|
304
|
-
artifacts,
|
|
305
|
-
denylist,
|
|
306
|
-
...(options.browserProbe && { browserProbe: options.browserProbe }),
|
|
307
|
-
...(options.widths && { widths: options.widths }),
|
|
308
|
-
});
|
|
309
|
-
io.log(JSON.stringify(report, null, 2));
|
|
310
|
-
return report.valid ? 0 : 1;
|
|
311
|
-
} catch (error) {
|
|
312
|
-
io.log(
|
|
313
|
-
JSON.stringify(
|
|
314
|
-
{
|
|
315
|
-
valid: false,
|
|
316
|
-
issues: [
|
|
317
|
-
{
|
|
318
|
-
code: 'input',
|
|
319
|
-
message: error instanceof Error ? error.message : String(error),
|
|
320
|
-
},
|
|
321
|
-
],
|
|
322
|
-
},
|
|
323
|
-
null,
|
|
324
|
-
2,
|
|
325
|
-
),
|
|
326
|
-
);
|
|
327
|
-
return 1;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
function parseArguments(argv) {
|
|
332
|
-
const paths = [];
|
|
333
|
-
const denylist = [];
|
|
334
|
-
let nextType;
|
|
335
|
-
|
|
336
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
337
|
-
const value = argv[index];
|
|
338
|
-
if (value === '--deny') {
|
|
339
|
-
const denied = argv[index + 1];
|
|
340
|
-
if (!denied) throw new Error('--deny requires a string.');
|
|
341
|
-
denylist.push(denied);
|
|
342
|
-
index += 1;
|
|
343
|
-
} else if (value === '--type') {
|
|
344
|
-
nextType = argv[index + 1];
|
|
345
|
-
if (!['hub', 'diagram', 'explainer', 'deck'].includes(nextType)) {
|
|
346
|
-
throw new Error('--type requires hub, diagram, explainer, or deck.');
|
|
347
|
-
}
|
|
348
|
-
index += 1;
|
|
349
|
-
} else if (value.startsWith('--')) {
|
|
350
|
-
throw new Error(`Unknown option: ${value}.`);
|
|
351
|
-
} else {
|
|
352
|
-
paths.push({ path: value, ...(nextType && { type: nextType }) });
|
|
353
|
-
nextType = undefined;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
if (nextType) throw new Error('--type must precede an HTML path.');
|
|
357
|
-
return { paths, denylist };
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
function releaseContent(artifactId) {
|
|
361
|
-
return {
|
|
362
|
-
artifactId,
|
|
363
|
-
slug: 'release-qa',
|
|
364
|
-
title: 'Release QA',
|
|
365
|
-
description: 'Representative release artifact.',
|
|
366
|
-
sections: [
|
|
367
|
-
{ id: 'overview', title: 'Overview', content: 'Release candidate.' },
|
|
368
|
-
{ id: 'evidence', title: 'Evidence', content: 'Checks are complete.' },
|
|
369
|
-
],
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
function assertStageInput(siteDir, artifacts) {
|
|
374
|
-
if (typeof siteDir !== 'string' || siteDir.length === 0) {
|
|
375
|
-
throw new TypeError('Render QA stage requires a built site directory.');
|
|
376
|
-
}
|
|
377
|
-
if (
|
|
378
|
-
!Array.isArray(artifacts) ||
|
|
379
|
-
artifacts.length === 0 ||
|
|
380
|
-
artifacts.some(
|
|
381
|
-
(artifact) =>
|
|
382
|
-
typeof artifact?.id !== 'string' ||
|
|
383
|
-
typeof artifact?.type !== 'string' ||
|
|
384
|
-
typeof artifact?.renderedPath !== 'string',
|
|
385
|
-
)
|
|
386
|
-
) {
|
|
387
|
-
throw new TypeError(
|
|
388
|
-
'Render QA stage requires artifacts with id, type, and renderedPath.',
|
|
389
|
-
);
|
|
390
|
-
}
|
|
391
|
-
for (const artifact of artifacts) siteRelativePath(artifact.renderedPath);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
function siteRelativePath(renderedPath) {
|
|
395
|
-
const withoutPrefix = renderedPath.startsWith('site/')
|
|
396
|
-
? renderedPath.slice('site/'.length)
|
|
397
|
-
: renderedPath;
|
|
398
|
-
const normalized = posix.normalize(withoutPrefix);
|
|
399
|
-
if (
|
|
400
|
-
normalized === '.' ||
|
|
401
|
-
normalized.startsWith('../') ||
|
|
402
|
-
posix.isAbsolute(normalized)
|
|
403
|
-
) {
|
|
404
|
-
throw new TypeError(`Unsafe render QA artifact path: ${renderedPath}`);
|
|
405
|
-
}
|
|
406
|
-
return normalized;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
async function withSiteServer(siteDir, callback) {
|
|
410
|
-
const root = resolve(siteDir);
|
|
411
|
-
const server = createServer(async (request, response) => {
|
|
412
|
-
try {
|
|
413
|
-
const pathname = decodeURIComponent(
|
|
414
|
-
new URL(request.url ?? '/', 'http://127.0.0.1').pathname,
|
|
415
|
-
);
|
|
416
|
-
const candidate = resolve(root, `.${pathname}`);
|
|
417
|
-
if (candidate !== root && !candidate.startsWith(`${root}${sep}`)) {
|
|
418
|
-
response.writeHead(403).end();
|
|
419
|
-
return;
|
|
420
|
-
}
|
|
421
|
-
const body = await readFile(candidate);
|
|
422
|
-
response.writeHead(200, {
|
|
423
|
-
'content-type': candidate.endsWith('.html')
|
|
424
|
-
? 'text/html; charset=utf-8'
|
|
425
|
-
: 'application/octet-stream',
|
|
426
|
-
'cache-control': 'no-store',
|
|
427
|
-
});
|
|
428
|
-
response.end(body);
|
|
429
|
-
} catch {
|
|
430
|
-
response.writeHead(404).end();
|
|
431
|
-
}
|
|
432
|
-
});
|
|
433
|
-
await new Promise((resolveListen, rejectListen) => {
|
|
434
|
-
server.once('error', rejectListen);
|
|
435
|
-
server.listen(0, '127.0.0.1', resolveListen);
|
|
436
|
-
});
|
|
437
|
-
const address = server.address();
|
|
438
|
-
if (!address || typeof address === 'string') {
|
|
439
|
-
await closeServer(server);
|
|
440
|
-
throw new Error('Render QA server did not expose a TCP address.');
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
try {
|
|
444
|
-
return await callback(`http://127.0.0.1:${address.port}`);
|
|
445
|
-
} finally {
|
|
446
|
-
await closeServer(server);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
function closeServer(server) {
|
|
451
|
-
return new Promise((resolveClose, rejectClose) => {
|
|
452
|
-
server.close((error) => (error ? rejectClose(error) : resolveClose()));
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
if (
|
|
457
|
-
process.argv[1] &&
|
|
458
|
-
pathToFileURL(process.argv[1]).href === import.meta.url
|
|
459
|
-
) {
|
|
460
|
-
process.exitCode = await runRenderQaCli();
|
|
461
|
-
}
|