@reqvire-org/ads 0.3.9 → 0.3.11
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/dist/mcp/build-status.mjs +14 -4
- package/dist/mcp/preview-watch.mjs +7 -5
- package/dist/mcp/server.mjs +11 -8
- package/dist/preview-inputs.mjs +24 -0
- package/dist/preview-resources.mjs +2 -1
- package/dist/scenarios-compiler.mjs +26 -20
- package/dist/scenarios.mjs +2 -1
- package/dist/showcase/client.bundle.js +17 -17
- package/dist/skills/ads-break-ui/references/scenarios.md +31 -1
- package/dist/skills/ads-designer/references/scenarios.md +31 -1
- package/dist/skills/ads-import/references/scenarios.md +31 -1
- package/dist/skills/ads-migrate/references/scenarios.md +31 -1
- package/dist/skills/ads-motion/references/scenarios.md +31 -1
- package/dist/verification.mjs +5 -1
- package/docs/ADS.md +5 -0
- package/docs/ARCHITECTURE.md +12 -3
- package/docs/CHANGELOG.md +22 -0
- package/docs/MCP.md +24 -1
- package/package.json +1 -1
|
@@ -2,7 +2,8 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import { isInside, projectPath } from "../config.mjs";
|
|
5
|
+
import { isInside, projectPath, loadConfig } from "../config.mjs";
|
|
6
|
+
import { previewSourceRevision } from "../preview-inputs.mjs";
|
|
6
7
|
import { readPreviewManifest } from "../preview-resources.mjs";
|
|
7
8
|
import { previewArtifactPath, withPreviewSession } from "../preview-scope.mjs";
|
|
8
9
|
|
|
@@ -10,7 +11,7 @@ export const BUILD_URI = "ads://project/build";
|
|
|
10
11
|
export const buildStatusFile = (root) => path.join(root, ".ds/build-status.json");
|
|
11
12
|
const maxBytes = 256 * 1024;
|
|
12
13
|
const diagnostic = z.object({ message: z.string().max(4000), file: z.string().max(1000).optional(), line: z.number().int().positive().optional(), column: z.number().int().positive().optional(), plugin: z.string().max(200).optional(), code: z.string().max(100).optional() }).strict();
|
|
13
|
-
const preview = z.object({ id: z.string().max(256), state: z.enum(["idle", "building", "ready", "error"]), revision: z.number().int().nonnegative(), lastSuccessfulRevision: z.number().int().positive().nullable(), artifactDigest: z.string().regex(/^sha256:[a-f0-9]{64}$/).nullable(), diagnostics: z.array(diagnostic).max(20), diagnosticSet: z.number().int().nonnegative().optional() }).strict();
|
|
14
|
+
const preview = z.object({ id: z.string().max(256), state: z.enum(["idle", "building", "ready", "error"]), revision: z.number().int().nonnegative(), lastSuccessfulRevision: z.number().int().positive().nullable(), artifactDigest: z.string().regex(/^sha256:[a-f0-9]{64}$/).nullable(), sourceRevision:z.string().regex(/^[a-f0-9]{64}$/).optional(), diagnostics: z.array(diagnostic).max(20), diagnosticSet: z.number().int().nonnegative().optional() }).strict();
|
|
14
15
|
const progressSchema = z.object({ phase: z.enum(["queued", "validation", "compilation", "validation-and-compilation", "publication", "idle"]), startedAt: z.string().datetime(), updatedAt: z.string().datetime() }).strict();
|
|
15
16
|
const snapshot = z.object({ schemaVersion: z.literal(1), session: z.string().regex(/^[\w-]{1,100}$/), sequence: z.number().int().nonnegative(), updatedAt: z.string().datetime(), progress: progressSchema.optional(), previews: z.array(preview), diagnostics: z.array(diagnostic).max(20).optional(), diagnosticSets: z.array(z.array(diagnostic).max(20)).optional() }).strict().refine(value => value.previews.every(entry => entry.diagnosticSet === undefined || value.diagnosticSets?.[entry.diagnosticSet]), "Invalid shared diagnostic reference.");
|
|
16
17
|
export const artifactDigest = (file) => {
|
|
@@ -93,8 +94,9 @@ export function createBuildReporter(directory, { apps, session = process.env.ADS
|
|
|
93
94
|
apps = next;
|
|
94
95
|
save();
|
|
95
96
|
},
|
|
96
|
-
begin(id) {
|
|
97
|
+
begin(id,{sourceRevision = previewSourceRevision(loadConfig(root),apps)} = {}) {
|
|
97
98
|
const value = entry(id);
|
|
99
|
+
value.sourceRevision = sourceRevision;
|
|
98
100
|
value.state = "building";
|
|
99
101
|
value.revision++;
|
|
100
102
|
value.diagnostics = [];
|
|
@@ -137,6 +139,11 @@ export function inspectBuildStatus(root, config, { session, previewSession, proc
|
|
|
137
139
|
}
|
|
138
140
|
} catch (error) { reportError = `Invalid preview build status: ${error.message.slice(0, 500)}`; }
|
|
139
141
|
let graphError;
|
|
142
|
+
let sourceRevision, sourceError;
|
|
143
|
+
if (report?.previews.some(entry=>entry.state==="ready")) {
|
|
144
|
+
try { sourceRevision = previewSourceRevision(loadConfig(root),config.apps); }
|
|
145
|
+
catch (error) { sourceError = `Cannot verify preview inputs: ${error.message}`; }
|
|
146
|
+
}
|
|
140
147
|
if (config.previewManifest && report?.previews.some(entry => entry.state === "ready")) {
|
|
141
148
|
try {
|
|
142
149
|
if (!readPreviewManifest(options, { verify: true })) throw new Error("Preview resource manifest is missing.");
|
|
@@ -154,13 +161,16 @@ export function inspectBuildStatus(root, config, { session, previewSession, proc
|
|
|
154
161
|
if (state === "ready") {
|
|
155
162
|
try {
|
|
156
163
|
if (value.lastSuccessfulRevision !== value.revision || artifactDigest(artifact) !== value.artifactDigest) throw new Error("Preview output does not match its successful build. Rebuild before reviewing it.");
|
|
164
|
+
if (sourceError) throw new Error(sourceError);
|
|
165
|
+
if (!value.sourceRevision) throw new Error("Preview builder did not record its source revision. Rebuild using the current ADS build reporter.");
|
|
166
|
+
if (value.sourceRevision !== sourceRevision) { state = "idle"; diagnostics = [{code:"ADS_PREVIEW_STALE_SOURCE",message:"Project inputs changed after this preview was built. Waiting for a build of the current source."}]; }
|
|
157
167
|
} catch (error) { state = "error"; diagnostics = [{ message: error.message }]; }
|
|
158
168
|
}
|
|
159
169
|
if (processState?.state === "failed") { state = "error"; diagnostics = [{ message: processState.error ?? "Preview watcher exited." }, ...diagnostics].slice(0, 20); }
|
|
160
170
|
if (["stopped", "stopping", "idle"].includes(processState?.state)) state = "unreported";
|
|
161
171
|
const current = state === "ready";
|
|
162
172
|
return { id: app.id, state, session: report?.session ?? null, revision: value?.revision ?? null, lastSuccessfulRevision: value?.lastSuccessfulRevision ?? null,
|
|
163
|
-
current, stale: state === "unreported" ? null : !current, available, diagnostics };
|
|
173
|
+
current, stale: state === "unreported" ? null : !current, available, ...(value?.sourceRevision ? {sourceRevision:value.sourceRevision} : {}), diagnostics };
|
|
164
174
|
});
|
|
165
175
|
const state = reportError || report?.diagnostics?.length || previews.some((entry) => entry.state === "error") ? "error" : previews.some((entry) => entry.state === "building") ? "building" : previews.some((entry) => entry.state === "unreported") ? "unreported" : previews.some(entry => entry.current) ? "ready" : previews.length ? "idle" : "disabled";
|
|
166
176
|
return { state, reported: Boolean(report), session: report?.session ?? null, sequence: report?.sequence ?? null, updatedAt: report?.updatedAt ?? null, ...(report?.progress ? { progress: report.progress } : {}), previews, ...(report?.diagnostics?.length ? { diagnostics: report.diagnostics } : {}), resourceUri: BUILD_URI };
|
|
@@ -31,16 +31,17 @@ export function createPreviewWatch(root, config, { log = process.stderr, buildTi
|
|
|
31
31
|
const ready = new Set();
|
|
32
32
|
const requests = new Set();
|
|
33
33
|
let active;
|
|
34
|
+
let invalidation;
|
|
34
35
|
let requestFile = path.join(root, ".ds", `preview-requests-${session}.json`);
|
|
35
36
|
let reportFile = path.join(root, ".ds", `build-status-${session}.json`);
|
|
36
|
-
function request(file) {
|
|
37
|
-
if (!command || requests.has(file) && active === file) return;
|
|
37
|
+
function request(file,force = false) {
|
|
38
|
+
if (!command || !force && requests.has(file) && active === file) return;
|
|
38
39
|
requests.add(file);
|
|
39
40
|
active = file;
|
|
40
41
|
mkdirSync(path.dirname(requestFile), { recursive: true });
|
|
41
42
|
if (realpathSync(path.dirname(requestFile)) !== path.dirname(requestFile)) throw new Error("Preview requests cannot resolve through a symlink.");
|
|
42
43
|
const temporary = `${requestFile}.tmp`;
|
|
43
|
-
writeFileSync(temporary, JSON.stringify({ session, active: path.relative(root, active).replaceAll("\\", "/"), files: [...requests].map((file) => path.relative(root, file).replaceAll("\\", "/")) }));
|
|
44
|
+
writeFileSync(temporary, JSON.stringify({ session, active: path.relative(root, active).replaceAll("\\", "/"), files: [...requests].map((file) => path.relative(root, file).replaceAll("\\", "/")), ...(invalidation ? {invalidation} : {}) }));
|
|
44
45
|
renameSync(temporary, requestFile);
|
|
45
46
|
}
|
|
46
47
|
|
|
@@ -135,7 +136,7 @@ export function createPreviewWatch(root, config, { log = process.stderr, buildTi
|
|
|
135
136
|
if (state === "failed" || restarting || stalled) await restart();
|
|
136
137
|
if (disposed) throw new Error("Preview watcher is stopped.");
|
|
137
138
|
start();
|
|
138
|
-
request(file);
|
|
139
|
+
request(file,buildStatus().previews.some(entry=>entry.diagnostics.some(item=>item.code==="ADS_PREVIEW_STALE_SOURCE")));
|
|
139
140
|
if (signal?.aborted) throw new Error("Preview request cancelled.");
|
|
140
141
|
const app = config.apps.find((entry) => path.resolve(root, entry.file) === file);
|
|
141
142
|
let baseline = baselines.get(file) ?? null;
|
|
@@ -181,7 +182,7 @@ export function createPreviewWatch(root, config, { log = process.stderr, buildTi
|
|
|
181
182
|
})();
|
|
182
183
|
return stopping;
|
|
183
184
|
}
|
|
184
|
-
return { start, ensure, async stop() { disposed = true; await restarting; return stopChild(); }, kill: () => kill("SIGKILL"), status, resourceSession, buildStatus, setApps(apps) {
|
|
185
|
+
return { start, ensure, invalidate() { invalidation = randomUUID(); if (active) request(active,true); }, async stop() { disposed = true; await restarting; return stopChild(); }, kill: () => kill("SIGKILL"), status, resourceSession, buildStatus, setApps(apps) {
|
|
185
186
|
for (const app of apps) {
|
|
186
187
|
const file = path.resolve(root, app.file);
|
|
187
188
|
if (!baselines.has(file)) baselines.set(file, stamp(file));
|
|
@@ -202,6 +203,7 @@ export function createReconfigurablePreviewWatch(root, config, options) {
|
|
|
202
203
|
status() { return replacing ? { state: "reconfiguring" } : current.status(); },
|
|
203
204
|
resourceSession() { return current.resourceSession(); },
|
|
204
205
|
buildStatus() { return current.buildStatus(); },
|
|
206
|
+
async invalidate() { await pending; if (!stopped) current.invalidate(); },
|
|
205
207
|
setApps(next) { apps = next; current.setApps(next); },
|
|
206
208
|
kill() { current.kill(); },
|
|
207
209
|
reconfigure(next, force = false) {
|
package/dist/mcp/server.mjs
CHANGED
|
@@ -46,6 +46,14 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
46
46
|
cache.registry = cache.catalog = cache.documents = undefined;
|
|
47
47
|
}
|
|
48
48
|
const getShowcaseUrl = () => showcaseUrl?.() ?? options.config.mcp.showcaseUrl;
|
|
49
|
+
function scenarioUrl(id) {
|
|
50
|
+
const base = getShowcaseUrl();
|
|
51
|
+
if (!base) return null;
|
|
52
|
+
const url = new URL(base);
|
|
53
|
+
url.searchParams.delete("category");
|
|
54
|
+
url.searchParams.set("scenario", id);
|
|
55
|
+
return url.href;
|
|
56
|
+
}
|
|
49
57
|
const project = () => { const context = projectContext(options); context.showcase.url = getShowcaseUrl(); return context; };
|
|
50
58
|
const subscribed = new Set();
|
|
51
59
|
const server = new Server({ name: "reqvire-ads", version: pkg.version }, {
|
|
@@ -171,6 +179,7 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
171
179
|
if (result.applied) {
|
|
172
180
|
try {
|
|
173
181
|
if (result.changes.some(change => change.path === "ds.config.json") || result.unit === "configuration" || result.units?.includes("configuration")) await previewWatch?.reconfigure?.(loadConfig(root).config.mcp,true);
|
|
182
|
+
await previewWatch?.invalidate?.();
|
|
174
183
|
await refresh(true);
|
|
175
184
|
} catch (error) {result.warnings=[...(result.warnings ?? []),{rule:"mutation-notification",message:`Changes were applied, but preview refresh failed: ${error.message.slice(0,2000)}. Recheck ads_status and reopen the scenario.`}];}
|
|
176
185
|
}
|
|
@@ -197,10 +206,7 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
197
206
|
const build = await previewBuild(app, signal);
|
|
198
207
|
const graph = graphSelection(id);
|
|
199
208
|
if (!options.config.mcp.previewManifest) projectApp(options, app, build);
|
|
200
|
-
|
|
201
|
-
url?.searchParams.delete("category");
|
|
202
|
-
url?.searchParams.set("scenario", id);
|
|
203
|
-
return json({ widget: { resourceUri: scenarioWidget() }, scenario: { ...entry, resourceUri: appUri(id) }, ...graph, build, showcaseUrl: url?.href ?? null });
|
|
209
|
+
return json({ widget: { resourceUri: scenarioWidget() }, scenario: { ...entry, resourceUri: appUri(id) }, ...graph, build, showcaseUrl: scenarioUrl(id) });
|
|
204
210
|
} },
|
|
205
211
|
{ name: "ads_tokens", description: "Use before styling, theme, brand or motion-token changes to inspect existing semantic definitions/values. Query specific token names with a small limit. ads_show_tokens renders project token scenarios; definition inspection does not validate component layout.", schema: query, call: ({ query: search = "", limit }) => {
|
|
206
212
|
const inventory = inspectTokens(root);
|
|
@@ -233,10 +239,7 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
233
239
|
const build = await previewBuild(app, signal);
|
|
234
240
|
const graph = graphSelection(category);
|
|
235
241
|
if (!options.config.mcp.previewManifest) projectApp(options, app, build);
|
|
236
|
-
|
|
237
|
-
url?.searchParams.delete("scenario");
|
|
238
|
-
url?.searchParams.set("category", category);
|
|
239
|
-
return json({ widget: { resourceUri: options.config.mcp.previewManifest ? PREVIEW_APP_URI : TOKENS_APP_URI }, category, scenario: { ...entry, resourceUri: appUri(category) }, ...graph, build, showcaseUrl: url?.href ?? null });
|
|
242
|
+
return json({ widget: { resourceUri: options.config.mcp.previewManifest ? PREVIEW_APP_URI : TOKENS_APP_URI }, category, scenario: { ...entry, resourceUri: appUri(category) }, ...graph, build, showcaseUrl: scenarioUrl(category) });
|
|
240
243
|
}
|
|
241
244
|
return json({ widget: { resourceUri: TOKENS_APP_URI }, category: null, tokenCount: inspectTokens(root).tokens.length, dataUri: `${PROJECT_URI}/tokens`, showcaseUrl: getShowcaseUrl() });
|
|
242
245
|
} },
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { isInside } from "./config.mjs";
|
|
5
|
+
|
|
6
|
+
/** Shared source evidence for compilation, watch recovery and status reads. */
|
|
7
|
+
export function previewSourceRevision({root,config}, apps = config.mcp.apps ?? []) {
|
|
8
|
+
const rows = [], generated = config.assets.generatedPaths.map(file => path.resolve(root,file));
|
|
9
|
+
const outputs = new Set(apps.map(app => path.resolve(root,app.file)));
|
|
10
|
+
function digest(file) { return createHash("sha256").update(readFileSync(file)).digest("hex"); }
|
|
11
|
+
function visit(directory) {
|
|
12
|
+
for (const entry of readdirSync(directory,{withFileTypes:true})) {
|
|
13
|
+
if (entry.isSymbolicLink() || ["node_modules",".git",".ds",".vite","coverage"].includes(entry.name)) continue;
|
|
14
|
+
const file = path.join(directory,entry.name);
|
|
15
|
+
if (outputs.has(file) || generated.some(base => isInside(base,file))) continue;
|
|
16
|
+
if (entry.isDirectory()) visit(file);
|
|
17
|
+
else if (entry.isFile() && /\.(?:[cm]?[jt]sx?|css|html|json|md|svg|png|jpe?g|webp|gif|woff2?|ttf|otf|lock|lockb)$/.test(file)) rows.push([path.relative(root,file),digest(file)]);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
visit(root);
|
|
21
|
+
const installed = path.join(root,"node_modules/.package-lock.json");
|
|
22
|
+
rows.push(["installed-packages",existsSync(installed) ? digest(installed) : null]);
|
|
23
|
+
return createHash("sha256").update(JSON.stringify(rows.sort((a,b)=>a[0].localeCompare(b[0])))).digest("hex");
|
|
24
|
+
}
|
|
@@ -28,7 +28,8 @@ export function previewEntriesPlugin(entries, mountEntry) {
|
|
|
28
28
|
load(id) {
|
|
29
29
|
const entry = ids.get(id.slice(1));
|
|
30
30
|
if (!entry) return;
|
|
31
|
-
|
|
31
|
+
const styles = (entry.stylesheets ?? []).map(file => `import ${JSON.stringify(path.resolve(entry.root, file))};`).join("");
|
|
32
|
+
return `import { mountScenario } from ${JSON.stringify(mountEntry)};${styles}import Scenario from ${JSON.stringify(path.resolve(entry.root, entry.file))};export const mount = container => mountScenario(Scenario,container);`;
|
|
32
33
|
},
|
|
33
34
|
};
|
|
34
35
|
}
|
|
@@ -6,6 +6,7 @@ import { inspectScenarios } from "./scenarios.mjs";
|
|
|
6
6
|
import { artifactDigest, createBuildReporter } from "./mcp/build-status.mjs";
|
|
7
7
|
import { cachedPreviewPublication, previewArtifactHtml, readPreviewManifest } from "./preview-resources.mjs";
|
|
8
8
|
import { previewArtifactPath, withPreviewSession } from "./preview-scope.mjs";
|
|
9
|
+
import { previewSourceRevision } from "./preview-inputs.mjs";
|
|
9
10
|
|
|
10
11
|
const digest = value => createHash("sha256").update(value).digest("hex");
|
|
11
12
|
const fileDigest = file => { try { return digest(readFileSync(file)); } catch { return null; } };
|
|
@@ -13,8 +14,8 @@ const fileDigest = file => { try { return digest(readFileSync(file)); } catch {
|
|
|
13
14
|
/** Cache only successful validation + matching source/dependency/output content.
|
|
14
15
|
* Cache state is development output, never proof that a failed build is current. */
|
|
15
16
|
export function createPreviewCache(options, compilerFiles = []) {
|
|
16
|
-
const { root
|
|
17
|
-
const adapterFiles = [import.meta.filename, ...["preview-resources.mjs", "preview-scope.mjs", "scenarios-vite.mjs", "linaria.mjs", "linaria/atomic-css.cjs", "linaria/atomic-styled.cjs", "linaria/preserve-values.cjs"].map(file => path.join(import.meta.dirname, file)), ...compilerFiles,
|
|
17
|
+
const { root } = options;
|
|
18
|
+
const adapterFiles = [import.meta.filename, ...["preview-inputs.mjs", "preview-resources.mjs", "preview-scope.mjs", "scenarios-vite.mjs", "linaria.mjs", "linaria/atomic-css.cjs", "linaria/atomic-styled.cjs", "linaria/preserve-values.cjs"].map(file => path.join(import.meta.dirname, file)), ...compilerFiles,
|
|
18
19
|
...["package.json", "package-lock.json", "bun.lock", "bun.lockb", "node_modules/.package-lock.json"].map(file => path.join(root, file))];
|
|
19
20
|
const adapterDigest = () => digest(JSON.stringify(adapterFiles.map(file => [file, fileDigest(file)])));
|
|
20
21
|
const loadedAdapter = adapterDigest();
|
|
@@ -27,20 +28,7 @@ export function createPreviewCache(options, compilerFiles = []) {
|
|
|
27
28
|
}
|
|
28
29
|
} catch { /* Missing or corrupt cache means validate and rebuild. */ }
|
|
29
30
|
function inputs() {
|
|
30
|
-
const rows = [];
|
|
31
|
-
const generated = config.assets.generatedPaths.map(file => path.resolve(root, file));
|
|
32
|
-
function visit(directory) {
|
|
33
|
-
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
34
|
-
if (entry.isSymbolicLink() || ["node_modules", ".git", ".ds", ".vite", "coverage"].includes(entry.name)) continue;
|
|
35
|
-
const file = path.join(directory, entry.name);
|
|
36
|
-
if (generated.some(base => isInside(base, file))) continue;
|
|
37
|
-
if (entry.isDirectory()) visit(file);
|
|
38
|
-
else if (entry.isFile() && /\.(?:[cm]?[jt]sx?|css|html|json|md|svg|png|jpe?g|webp|gif|woff2?|ttf|otf|lock|lockb)$/.test(file)) rows.push([path.relative(root, file), fileDigest(file)]);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
visit(root);
|
|
42
|
-
rows.push(["installed-packages", fileDigest(path.join(root, "node_modules/.package-lock.json"))]);
|
|
43
|
-
rows.push(["node-runtime", `${process.version}/${process.platform}/${process.arch}`]);
|
|
31
|
+
const rows = [["project-inputs",previewSourceRevision(options)],["node-runtime",`${process.version}/${process.platform}/${process.arch}`]];
|
|
44
32
|
for (const file of compilerFiles) rows.push([file, fileDigest(file)]);
|
|
45
33
|
// Tool upgrades invalidate validation even when the project's lockfile is unchanged.
|
|
46
34
|
for (const name of readdirSync(import.meta.dirname).filter(name => name.endsWith(".mjs"))) {
|
|
@@ -119,6 +107,8 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
119
107
|
let generation = 0;
|
|
120
108
|
let publishedStamp;
|
|
121
109
|
let validation;
|
|
110
|
+
let observedSourceRevision = previewSourceRevision(options);
|
|
111
|
+
let invalidation;
|
|
122
112
|
function failure(error) {
|
|
123
113
|
try { reporter.failure(error); } catch (reportError) { console.error(reportError); }
|
|
124
114
|
console.error(error);
|
|
@@ -144,7 +134,7 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
144
134
|
}
|
|
145
135
|
async function compile(entry) {
|
|
146
136
|
if (buildGraph) return compileGraph();
|
|
147
|
-
const revision = reporter.begin(entry.id);
|
|
137
|
+
const revision = reporter.begin(entry.id,{sourceRevision:previewSourceRevision(options)});
|
|
148
138
|
const started = generation;
|
|
149
139
|
try {
|
|
150
140
|
const stamp = cache.inputs();
|
|
@@ -187,7 +177,8 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
187
177
|
}
|
|
188
178
|
}
|
|
189
179
|
async function compileGraph() {
|
|
190
|
-
const
|
|
180
|
+
const sourceRevision = previewSourceRevision(options);
|
|
181
|
+
const revisions = new Map(entries.map(entry => [entry.id, reporter.begin(entry.id,{sourceRevision})]));
|
|
191
182
|
const started = generation;
|
|
192
183
|
try {
|
|
193
184
|
const stamp = cache.inputs();
|
|
@@ -257,6 +248,10 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
257
248
|
try {
|
|
258
249
|
const value = JSON.parse(readFileSync(file, "utf8"));
|
|
259
250
|
if (value.session !== process.env.ADS_MCP_BUILD_SESSION) return;
|
|
251
|
+
if (value.invalidation !== invalidation) {
|
|
252
|
+
invalidation = value.invalidation;
|
|
253
|
+
if (requested.size) sourceChanged("invalidate",options.config.dsDir,true);
|
|
254
|
+
}
|
|
260
255
|
active = entries.find(entry => entry.artifact === value.active)?.id;
|
|
261
256
|
for (const file of value.files) {
|
|
262
257
|
const entry = entries.find(entry => entry.artifact === file);
|
|
@@ -288,6 +283,7 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
288
283
|
const changes = [...changed]; changed.clear();
|
|
289
284
|
try {
|
|
290
285
|
entries = registry();
|
|
286
|
+
observedSourceRevision = previewSourceRevision(options);
|
|
291
287
|
registryFailure = undefined;
|
|
292
288
|
reporter.setApps(buildApps());
|
|
293
289
|
reporter.clearFailure();
|
|
@@ -297,7 +293,7 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
297
293
|
// compilation. Check active first, then rebuild only changed dependencies.
|
|
298
294
|
for (const id of requested) {
|
|
299
295
|
const entry = entries.find(entry => entry.id === id);
|
|
300
|
-
if (entry) { reporter.begin(id); pending.add(id); }
|
|
296
|
+
if (entry) { reporter.begin(id,{sourceRevision:observedSourceRevision}); pending.add(id); }
|
|
301
297
|
}
|
|
302
298
|
if (changes.length) void drain();
|
|
303
299
|
} catch (error) {
|
|
@@ -344,7 +340,17 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
344
340
|
} catch (error) { failure(error); }
|
|
345
341
|
}
|
|
346
342
|
reconcileWatches(true);
|
|
347
|
-
const reconcileTimer = setInterval(
|
|
343
|
+
const reconcileTimer = setInterval(() => {
|
|
344
|
+
reconcileWatches();
|
|
345
|
+
try {
|
|
346
|
+
const current = previewSourceRevision(options);
|
|
347
|
+
if (current !== observedSourceRevision) {
|
|
348
|
+
observedSourceRevision = current;
|
|
349
|
+
sourceChanged("reconcile",options.config.dsDir,true);
|
|
350
|
+
}
|
|
351
|
+
request();
|
|
352
|
+
} catch (error) { failure(error); }
|
|
353
|
+
}, 1000);
|
|
348
354
|
request();
|
|
349
355
|
console.log(`Scenario compiler ready: ${entries.length} entries; builds are requested on demand.`);
|
|
350
356
|
for (const signal of ["SIGINT", "SIGTERM"]) process.once(signal, () => {
|
package/dist/scenarios.mjs
CHANGED
|
@@ -110,7 +110,8 @@ export function inspectScenarios(options, suppliedProgram) {
|
|
|
110
110
|
const owner = scoped.inventory.owner(relative);
|
|
111
111
|
for (const name of tokens) if (tokenInventory.tokens.find(token => token.name === name)?.owner !== owner) report(file, `Token ${name} needs a preview beside its defining owner. Shared tokens belong under the configured token directory; local tokens stay with their component/pattern.`);
|
|
112
112
|
}
|
|
113
|
-
|
|
113
|
+
const stylesheets = kind === "tokens" ? scoped.definitions.filter(entry => entry.unit === scoped.inventory.owner(relative)).map(entry => entry.file) : [];
|
|
114
|
+
scenarios.push({ id, kind, title: metadata.title, covers: [...new Set(metadata.covers)], ...(Object.hasOwn(metadata, "exercises") ? { exercises: [...new Set(exercises)] } : {}), tokens, ...(metadata.samples ? { samples: metadata.samples } : {}), ...(stylesheets.length ? { stylesheets } : {}), file: path.relative(root, file).replaceAll("\\", "/"), artifact: path.relative(root, scenarioArtifact(root, id)).replaceAll("\\", "/") });
|
|
114
115
|
}
|
|
115
116
|
return { scenarios: scenarios.sort((a, b) => a.id.localeCompare(b.id)), diagnostics };
|
|
116
117
|
}
|