@agent-scope/cli 1.13.0 → 1.14.0
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/cli.js +547 -50
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +499 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +501 -23
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/program.ts
|
|
4
|
-
import { readFileSync as
|
|
4
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
5
5
|
import { generateTest, loadTrace } from "@agent-scope/playwright";
|
|
6
6
|
import { Command as Command8 } from "commander";
|
|
7
7
|
|
|
@@ -255,9 +255,9 @@ function createRL() {
|
|
|
255
255
|
});
|
|
256
256
|
}
|
|
257
257
|
async function ask(rl, question) {
|
|
258
|
-
return new Promise((
|
|
258
|
+
return new Promise((resolve14) => {
|
|
259
259
|
rl.question(question, (answer) => {
|
|
260
|
-
|
|
260
|
+
resolve14(answer.trim());
|
|
261
261
|
});
|
|
262
262
|
});
|
|
263
263
|
}
|
|
@@ -2874,8 +2874,8 @@ Available: ${available}`
|
|
|
2874
2874
|
`
|
|
2875
2875
|
);
|
|
2876
2876
|
if (opts.sprite !== void 0) {
|
|
2877
|
-
const { SpriteSheetGenerator } = await import("@agent-scope/render");
|
|
2878
|
-
const gen = new
|
|
2877
|
+
const { SpriteSheetGenerator: SpriteSheetGenerator2 } = await import("@agent-scope/render");
|
|
2878
|
+
const gen = new SpriteSheetGenerator2();
|
|
2879
2879
|
const sheet = await gen.generate(result);
|
|
2880
2880
|
const spritePath = resolve7(process.cwd(), opts.sprite);
|
|
2881
2881
|
writeFileSync4(spritePath, sheet.png);
|
|
@@ -2884,8 +2884,8 @@ Available: ${available}`
|
|
|
2884
2884
|
}
|
|
2885
2885
|
const fmt = resolveMatrixFormat(opts.format, opts.sprite !== void 0);
|
|
2886
2886
|
if (fmt === "file") {
|
|
2887
|
-
const { SpriteSheetGenerator } = await import("@agent-scope/render");
|
|
2888
|
-
const gen = new
|
|
2887
|
+
const { SpriteSheetGenerator: SpriteSheetGenerator2 } = await import("@agent-scope/render");
|
|
2888
|
+
const gen = new SpriteSheetGenerator2();
|
|
2889
2889
|
const sheet = await gen.generate(result);
|
|
2890
2890
|
const dir = resolve7(process.cwd(), DEFAULT_OUTPUT_DIR);
|
|
2891
2891
|
mkdirSync3(dir, { recursive: true });
|
|
@@ -2902,8 +2902,8 @@ Available: ${available}`
|
|
|
2902
2902
|
} else if (fmt === "png") {
|
|
2903
2903
|
if (opts.sprite !== void 0) {
|
|
2904
2904
|
} else {
|
|
2905
|
-
const { SpriteSheetGenerator } = await import("@agent-scope/render");
|
|
2906
|
-
const gen = new
|
|
2905
|
+
const { SpriteSheetGenerator: SpriteSheetGenerator2 } = await import("@agent-scope/render");
|
|
2906
|
+
const gen = new SpriteSheetGenerator2();
|
|
2907
2907
|
const sheet = await gen.generate(result);
|
|
2908
2908
|
process.stdout.write(sheet.png);
|
|
2909
2909
|
}
|
|
@@ -3217,12 +3217,12 @@ async function runBaseline(options = {}) {
|
|
|
3217
3217
|
mkdirSync4(rendersDir, { recursive: true });
|
|
3218
3218
|
let manifest;
|
|
3219
3219
|
if (manifestPath !== void 0) {
|
|
3220
|
-
const { readFileSync:
|
|
3220
|
+
const { readFileSync: readFileSync10 } = await import("fs");
|
|
3221
3221
|
const absPath = resolve8(rootDir, manifestPath);
|
|
3222
3222
|
if (!existsSync5(absPath)) {
|
|
3223
3223
|
throw new Error(`Manifest not found at ${absPath}.`);
|
|
3224
3224
|
}
|
|
3225
|
-
manifest = JSON.parse(
|
|
3225
|
+
manifest = JSON.parse(readFileSync10(absPath, "utf-8"));
|
|
3226
3226
|
process.stderr.write(`Loaded manifest from ${manifestPath}
|
|
3227
3227
|
`);
|
|
3228
3228
|
} else {
|
|
@@ -4144,47 +4144,223 @@ function buildStructuredReport(report) {
|
|
|
4144
4144
|
}
|
|
4145
4145
|
|
|
4146
4146
|
// src/tokens/commands.ts
|
|
4147
|
-
import { existsSync as
|
|
4148
|
-
import { resolve as
|
|
4147
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
|
|
4148
|
+
import { resolve as resolve13 } from "path";
|
|
4149
4149
|
import {
|
|
4150
4150
|
parseTokenFileSync as parseTokenFileSync2,
|
|
4151
4151
|
TokenParseError,
|
|
4152
|
-
TokenResolver as
|
|
4152
|
+
TokenResolver as TokenResolver7,
|
|
4153
4153
|
TokenValidationError,
|
|
4154
4154
|
validateTokenFile
|
|
4155
4155
|
} from "@agent-scope/tokens";
|
|
4156
4156
|
import { Command as Command7 } from "commander";
|
|
4157
4157
|
|
|
4158
|
-
// src/tokens/
|
|
4159
|
-
import { existsSync as existsSync7, readFileSync as readFileSync6
|
|
4158
|
+
// src/tokens/compliance.ts
|
|
4159
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
|
|
4160
4160
|
import { resolve as resolve10 } from "path";
|
|
4161
|
+
import {
|
|
4162
|
+
ComplianceEngine as ComplianceEngine3,
|
|
4163
|
+
TokenResolver as TokenResolver3
|
|
4164
|
+
} from "@agent-scope/tokens";
|
|
4165
|
+
var DEFAULT_STYLES_PATH = ".reactscope/compliance-styles.json";
|
|
4166
|
+
function loadStylesFile(stylesPath) {
|
|
4167
|
+
const absPath = resolve10(process.cwd(), stylesPath);
|
|
4168
|
+
if (!existsSync7(absPath)) {
|
|
4169
|
+
throw new Error(
|
|
4170
|
+
`Compliance styles file not found at ${absPath}.
|
|
4171
|
+
Run \`scope render all\` first to generate component styles, or use --styles to specify a path.
|
|
4172
|
+
Expected format: { "ComponentName": { colors: {}, spacing: {}, typography: {}, borders: {}, shadows: {} } }`
|
|
4173
|
+
);
|
|
4174
|
+
}
|
|
4175
|
+
const raw = readFileSync6(absPath, "utf-8");
|
|
4176
|
+
let parsed;
|
|
4177
|
+
try {
|
|
4178
|
+
parsed = JSON.parse(raw);
|
|
4179
|
+
} catch (err) {
|
|
4180
|
+
throw new Error(`Failed to parse compliance styles file as JSON: ${String(err)}`);
|
|
4181
|
+
}
|
|
4182
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
4183
|
+
throw new Error(
|
|
4184
|
+
`Compliance styles file must be a JSON object mapping component names to ComputedStyles.`
|
|
4185
|
+
);
|
|
4186
|
+
}
|
|
4187
|
+
return parsed;
|
|
4188
|
+
}
|
|
4189
|
+
function categoryForProperty(property) {
|
|
4190
|
+
const lower = property.toLowerCase();
|
|
4191
|
+
if (lower.includes("shadow")) return "shadow";
|
|
4192
|
+
if (lower.includes("color") || lower === "background" || lower === "fill" || lower === "stroke")
|
|
4193
|
+
return "color";
|
|
4194
|
+
if (lower.includes("padding") || lower.includes("margin") || lower === "gap" || lower === "width" || lower === "height" || lower === "top" || lower === "right" || lower === "bottom" || lower === "left")
|
|
4195
|
+
return "spacing";
|
|
4196
|
+
if (lower.includes("border")) return "border";
|
|
4197
|
+
if (lower.includes("font") || lower.includes("line") || lower.includes("letter") || lower === "texttransform" || lower === "textdecoration")
|
|
4198
|
+
return "typography";
|
|
4199
|
+
return "spacing";
|
|
4200
|
+
}
|
|
4201
|
+
function buildCategorySummary(batch) {
|
|
4202
|
+
const cats = {
|
|
4203
|
+
color: { total: 0, onSystem: 0, offSystem: 0, compliance: 1 },
|
|
4204
|
+
spacing: { total: 0, onSystem: 0, offSystem: 0, compliance: 1 },
|
|
4205
|
+
typography: { total: 0, onSystem: 0, offSystem: 0, compliance: 1 },
|
|
4206
|
+
border: { total: 0, onSystem: 0, offSystem: 0, compliance: 1 },
|
|
4207
|
+
shadow: { total: 0, onSystem: 0, offSystem: 0, compliance: 1 }
|
|
4208
|
+
};
|
|
4209
|
+
for (const report of Object.values(batch.components)) {
|
|
4210
|
+
for (const [property, result] of Object.entries(report.properties)) {
|
|
4211
|
+
const cat = categoryForProperty(property);
|
|
4212
|
+
const summary = cats[cat];
|
|
4213
|
+
if (summary === void 0) continue;
|
|
4214
|
+
summary.total++;
|
|
4215
|
+
if (result.status === "on_system") {
|
|
4216
|
+
summary.onSystem++;
|
|
4217
|
+
} else {
|
|
4218
|
+
summary.offSystem++;
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
4221
|
+
}
|
|
4222
|
+
for (const summary of Object.values(cats)) {
|
|
4223
|
+
summary.compliance = summary.total === 0 ? 1 : summary.onSystem / summary.total;
|
|
4224
|
+
}
|
|
4225
|
+
return cats;
|
|
4226
|
+
}
|
|
4227
|
+
function collectOffenders(batch, limit = 10) {
|
|
4228
|
+
const offenders = [];
|
|
4229
|
+
const componentEntries = Object.entries(batch.components).map(([name, report]) => ({
|
|
4230
|
+
name,
|
|
4231
|
+
report,
|
|
4232
|
+
offSystemCount: report.offSystem
|
|
4233
|
+
}));
|
|
4234
|
+
componentEntries.sort((a, b) => b.offSystemCount - a.offSystemCount);
|
|
4235
|
+
for (const { name, report, offSystemCount } of componentEntries) {
|
|
4236
|
+
if (offSystemCount === 0) continue;
|
|
4237
|
+
for (const [property, result] of Object.entries(report.properties)) {
|
|
4238
|
+
if (result.status !== "OFF_SYSTEM") continue;
|
|
4239
|
+
offenders.push({
|
|
4240
|
+
component: name,
|
|
4241
|
+
property,
|
|
4242
|
+
value: result.value,
|
|
4243
|
+
nearestToken: result.nearest?.token ?? "\u2014",
|
|
4244
|
+
nearestValue: result.nearest?.value ?? "\u2014",
|
|
4245
|
+
offSystemCount
|
|
4246
|
+
});
|
|
4247
|
+
if (offenders.length >= limit) break;
|
|
4248
|
+
}
|
|
4249
|
+
if (offenders.length >= limit) break;
|
|
4250
|
+
}
|
|
4251
|
+
return offenders;
|
|
4252
|
+
}
|
|
4253
|
+
function formatPct(n) {
|
|
4254
|
+
return `${Math.round(n * 100)}%`;
|
|
4255
|
+
}
|
|
4256
|
+
function truncate(s, max) {
|
|
4257
|
+
return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
|
|
4258
|
+
}
|
|
4259
|
+
function formatComplianceReport(batch, threshold) {
|
|
4260
|
+
const pct = Math.round(batch.aggregateCompliance * 100);
|
|
4261
|
+
const lines = [];
|
|
4262
|
+
const thresholdLabel = threshold !== void 0 ? pct >= threshold ? " \u2713 (pass)" : ` \u2717 (below threshold ${threshold}%)` : "";
|
|
4263
|
+
lines.push(`Overall compliance score: ${pct}%${thresholdLabel}`);
|
|
4264
|
+
lines.push("");
|
|
4265
|
+
const cats = buildCategorySummary(batch);
|
|
4266
|
+
const catEntries = Object.entries(cats).filter(([, s]) => s.total > 0);
|
|
4267
|
+
if (catEntries.length > 0) {
|
|
4268
|
+
lines.push("By category:");
|
|
4269
|
+
const catWidth = Math.max(...catEntries.map(([k]) => k.length));
|
|
4270
|
+
for (const [cat, summary] of catEntries) {
|
|
4271
|
+
const label = cat.padEnd(catWidth);
|
|
4272
|
+
lines.push(
|
|
4273
|
+
` ${label} ${formatPct(summary.compliance).padStart(4)} (${summary.offSystem} off-system value${summary.offSystem !== 1 ? "s" : ""})`
|
|
4274
|
+
);
|
|
4275
|
+
}
|
|
4276
|
+
lines.push("");
|
|
4277
|
+
}
|
|
4278
|
+
const offenders = collectOffenders(batch);
|
|
4279
|
+
if (offenders.length > 0) {
|
|
4280
|
+
lines.push("Top off-system offenders (sorted by count):");
|
|
4281
|
+
const nameWidth = Math.max(9, ...offenders.map((o) => o.component.length));
|
|
4282
|
+
const propWidth = Math.max(8, ...offenders.map((o) => o.property.length));
|
|
4283
|
+
const valWidth = Math.max(5, ...offenders.map((o) => truncate(o.value, 40).length));
|
|
4284
|
+
for (const offender of offenders) {
|
|
4285
|
+
const name = offender.component.padEnd(nameWidth);
|
|
4286
|
+
const prop = offender.property.padEnd(propWidth);
|
|
4287
|
+
const val = truncate(offender.value, 40).padEnd(valWidth);
|
|
4288
|
+
const nearest = `${offender.nearestToken} (${truncate(offender.nearestValue, 30)})`;
|
|
4289
|
+
lines.push(` ${name} ${prop}: ${val} \u2192 nearest: ${nearest}`);
|
|
4290
|
+
}
|
|
4291
|
+
} else {
|
|
4292
|
+
lines.push("No off-system values detected. \u{1F389}");
|
|
4293
|
+
}
|
|
4294
|
+
return lines.join("\n");
|
|
4295
|
+
}
|
|
4296
|
+
function registerCompliance(tokensCmd) {
|
|
4297
|
+
tokensCmd.command("compliance").description("Aggregate token compliance report across all components (Token Spec \xA73.3 format)").option("--file <path>", "Path to token file (overrides config)").option("--styles <path>", `Path to compliance styles JSON (default: ${DEFAULT_STYLES_PATH})`).option("--threshold <n>", "Exit code 1 if compliance score is below this percentage (0-100)").option("--format <fmt>", "Output format: json or text (default: auto-detect)").action((opts) => {
|
|
4298
|
+
try {
|
|
4299
|
+
const tokenFilePath = resolveTokenFilePath(opts.file);
|
|
4300
|
+
const { tokens } = loadTokens(tokenFilePath);
|
|
4301
|
+
const resolver = new TokenResolver3(tokens);
|
|
4302
|
+
const engine = new ComplianceEngine3(resolver);
|
|
4303
|
+
const stylesPath = opts.styles ?? DEFAULT_STYLES_PATH;
|
|
4304
|
+
const stylesFile = loadStylesFile(stylesPath);
|
|
4305
|
+
const componentMap = /* @__PURE__ */ new Map();
|
|
4306
|
+
for (const [name, styles] of Object.entries(stylesFile)) {
|
|
4307
|
+
componentMap.set(name, styles);
|
|
4308
|
+
}
|
|
4309
|
+
if (componentMap.size === 0) {
|
|
4310
|
+
process.stderr.write(`Warning: No components found in styles file at ${stylesPath}
|
|
4311
|
+
`);
|
|
4312
|
+
}
|
|
4313
|
+
const batch = engine.auditBatch(componentMap);
|
|
4314
|
+
const useJson = opts.format === "json" || opts.format !== "text" && !isTTY();
|
|
4315
|
+
const threshold = opts.threshold !== void 0 ? Number.parseInt(opts.threshold, 10) : void 0;
|
|
4316
|
+
if (useJson) {
|
|
4317
|
+
process.stdout.write(`${JSON.stringify(batch, null, 2)}
|
|
4318
|
+
`);
|
|
4319
|
+
} else {
|
|
4320
|
+
process.stdout.write(`${formatComplianceReport(batch, threshold)}
|
|
4321
|
+
`);
|
|
4322
|
+
}
|
|
4323
|
+
if (threshold !== void 0 && Math.round(batch.aggregateCompliance * 100) < threshold) {
|
|
4324
|
+
process.exit(1);
|
|
4325
|
+
}
|
|
4326
|
+
} catch (err) {
|
|
4327
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
4328
|
+
`);
|
|
4329
|
+
process.exit(1);
|
|
4330
|
+
}
|
|
4331
|
+
});
|
|
4332
|
+
}
|
|
4333
|
+
|
|
4334
|
+
// src/tokens/export.ts
|
|
4335
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "fs";
|
|
4336
|
+
import { resolve as resolve11 } from "path";
|
|
4161
4337
|
import {
|
|
4162
4338
|
exportTokens,
|
|
4163
4339
|
parseTokenFileSync,
|
|
4164
4340
|
ThemeResolver,
|
|
4165
|
-
TokenResolver as
|
|
4341
|
+
TokenResolver as TokenResolver4
|
|
4166
4342
|
} from "@agent-scope/tokens";
|
|
4167
4343
|
import { Command as Command6 } from "commander";
|
|
4168
4344
|
var DEFAULT_TOKEN_FILE = "reactscope.tokens.json";
|
|
4169
4345
|
var CONFIG_FILE = "reactscope.config.json";
|
|
4170
4346
|
var SUPPORTED_FORMATS = ["css", "ts", "scss", "tailwind", "flat-json", "figma"];
|
|
4171
|
-
function
|
|
4347
|
+
function resolveTokenFilePath2(fileFlag) {
|
|
4172
4348
|
if (fileFlag !== void 0) {
|
|
4173
|
-
return
|
|
4349
|
+
return resolve11(process.cwd(), fileFlag);
|
|
4174
4350
|
}
|
|
4175
|
-
const configPath =
|
|
4176
|
-
if (
|
|
4351
|
+
const configPath = resolve11(process.cwd(), CONFIG_FILE);
|
|
4352
|
+
if (existsSync8(configPath)) {
|
|
4177
4353
|
try {
|
|
4178
|
-
const raw =
|
|
4354
|
+
const raw = readFileSync7(configPath, "utf-8");
|
|
4179
4355
|
const config = JSON.parse(raw);
|
|
4180
4356
|
if (typeof config === "object" && config !== null && "tokens" in config && typeof config.tokens === "object" && config.tokens !== null && typeof config.tokens?.file === "string") {
|
|
4181
4357
|
const file = config.tokens.file;
|
|
4182
|
-
return
|
|
4358
|
+
return resolve11(process.cwd(), file);
|
|
4183
4359
|
}
|
|
4184
4360
|
} catch {
|
|
4185
4361
|
}
|
|
4186
4362
|
}
|
|
4187
|
-
return
|
|
4363
|
+
return resolve11(process.cwd(), DEFAULT_TOKEN_FILE);
|
|
4188
4364
|
}
|
|
4189
4365
|
function createTokensExportCommand() {
|
|
4190
4366
|
return new Command6("export").description("Export design tokens to a downstream format").requiredOption("--format <fmt>", `Output format: ${SUPPORTED_FORMATS.join(", ")}`).option("--file <path>", "Path to token file (overrides config)").option("--out <path>", "Write output to file instead of stdout").option("--prefix <prefix>", "CSS/SCSS: prefix for variable names (e.g. 'scope')").option("--selector <selector>", "CSS: custom root selector (default: ':root')").option(
|
|
@@ -4202,14 +4378,14 @@ Supported formats: ${SUPPORTED_FORMATS.join(", ")}
|
|
|
4202
4378
|
}
|
|
4203
4379
|
const format = opts.format;
|
|
4204
4380
|
try {
|
|
4205
|
-
const filePath =
|
|
4206
|
-
if (!
|
|
4381
|
+
const filePath = resolveTokenFilePath2(opts.file);
|
|
4382
|
+
if (!existsSync8(filePath)) {
|
|
4207
4383
|
throw new Error(
|
|
4208
4384
|
`Token file not found at ${filePath}.
|
|
4209
4385
|
Create a reactscope.tokens.json file or use --file to specify a path.`
|
|
4210
4386
|
);
|
|
4211
4387
|
}
|
|
4212
|
-
const raw =
|
|
4388
|
+
const raw = readFileSync7(filePath, "utf-8");
|
|
4213
4389
|
const { tokens, rawFile } = parseTokenFileSync(raw);
|
|
4214
4390
|
let themesMap;
|
|
4215
4391
|
if (opts.theme !== void 0) {
|
|
@@ -4220,7 +4396,7 @@ Create a reactscope.tokens.json file or use --file to specify a path.`
|
|
|
4220
4396
|
Available themes: ${available}`
|
|
4221
4397
|
);
|
|
4222
4398
|
}
|
|
4223
|
-
const baseResolver = new
|
|
4399
|
+
const baseResolver = new TokenResolver4(tokens);
|
|
4224
4400
|
const themeResolver = ThemeResolver.fromTokenFile(
|
|
4225
4401
|
baseResolver,
|
|
4226
4402
|
rawFile
|
|
@@ -4248,7 +4424,7 @@ Available themes: ${themeNames.join(", ")}`
|
|
|
4248
4424
|
themes: themesMap
|
|
4249
4425
|
});
|
|
4250
4426
|
if (opts.out !== void 0) {
|
|
4251
|
-
const outPath =
|
|
4427
|
+
const outPath = resolve11(process.cwd(), opts.out);
|
|
4252
4428
|
writeFileSync7(outPath, output, "utf-8");
|
|
4253
4429
|
process.stderr.write(`Exported ${tokens.length} tokens to ${outPath}
|
|
4254
4430
|
`);
|
|
@@ -4267,6 +4443,324 @@ Available themes: ${themeNames.join(", ")}`
|
|
|
4267
4443
|
);
|
|
4268
4444
|
}
|
|
4269
4445
|
|
|
4446
|
+
// src/tokens/impact.ts
|
|
4447
|
+
import {
|
|
4448
|
+
ComplianceEngine as ComplianceEngine4,
|
|
4449
|
+
ImpactAnalyzer,
|
|
4450
|
+
TokenResolver as TokenResolver5
|
|
4451
|
+
} from "@agent-scope/tokens";
|
|
4452
|
+
var DEFAULT_STYLES_PATH2 = ".reactscope/compliance-styles.json";
|
|
4453
|
+
var SEVERITY_EMOJI = {
|
|
4454
|
+
none: "\u25CB",
|
|
4455
|
+
subtle: "\u25D4",
|
|
4456
|
+
moderate: "\u25D1",
|
|
4457
|
+
significant: "\u25CF"
|
|
4458
|
+
};
|
|
4459
|
+
function formatImpactReport(report) {
|
|
4460
|
+
const lines = [];
|
|
4461
|
+
const newValueSuffix = report.newValue !== report.oldValue ? ` \u2192 ${report.newValue}` : "";
|
|
4462
|
+
lines.push(`Token: ${report.tokenPath} (${report.oldValue})${newValueSuffix}`);
|
|
4463
|
+
if (report.components.length === 0) {
|
|
4464
|
+
lines.push("");
|
|
4465
|
+
lines.push("No components reference this token.");
|
|
4466
|
+
return lines.join("\n");
|
|
4467
|
+
}
|
|
4468
|
+
lines.push("");
|
|
4469
|
+
const nameWidth = Math.max(9, ...report.components.map((c) => c.name.length));
|
|
4470
|
+
const propWidth = Math.max(
|
|
4471
|
+
8,
|
|
4472
|
+
...report.components.flatMap((c) => c.affectedProperties.map((p) => p.length))
|
|
4473
|
+
);
|
|
4474
|
+
for (const comp of report.components) {
|
|
4475
|
+
for (const property of comp.affectedProperties) {
|
|
4476
|
+
const name = comp.name.padEnd(nameWidth);
|
|
4477
|
+
const prop = property.padEnd(propWidth);
|
|
4478
|
+
const severityIcon2 = SEVERITY_EMOJI[comp.severity] ?? "?";
|
|
4479
|
+
lines.push(` ${name} ${prop} ${severityIcon2} ${comp.severity}`);
|
|
4480
|
+
}
|
|
4481
|
+
}
|
|
4482
|
+
lines.push("");
|
|
4483
|
+
const countLabel = `${report.affectedComponentCount} component${report.affectedComponentCount !== 1 ? "s" : ""}`;
|
|
4484
|
+
const severityIcon = SEVERITY_EMOJI[report.overallSeverity] ?? "?";
|
|
4485
|
+
lines.push(
|
|
4486
|
+
`${countLabel} affected \u2014 overall severity: ${severityIcon} ${report.overallSeverity}`
|
|
4487
|
+
);
|
|
4488
|
+
if (report.colorDelta !== void 0) {
|
|
4489
|
+
lines.push(`Color delta: \u0394E ${report.colorDelta.toFixed(2)}`);
|
|
4490
|
+
}
|
|
4491
|
+
return lines.join("\n");
|
|
4492
|
+
}
|
|
4493
|
+
function formatImpactSummary(report) {
|
|
4494
|
+
if (report.components.length === 0) {
|
|
4495
|
+
return `No components reference token "${report.tokenPath}".`;
|
|
4496
|
+
}
|
|
4497
|
+
const parts = report.components.map(
|
|
4498
|
+
(c) => `${c.name} (${c.affectedProperties.length} element${c.affectedProperties.length !== 1 ? "s" : ""})`
|
|
4499
|
+
);
|
|
4500
|
+
return `\u2192 ${parts.join(", ")}`;
|
|
4501
|
+
}
|
|
4502
|
+
function registerImpact(tokensCmd) {
|
|
4503
|
+
tokensCmd.command("impact <path>").description("List all components and elements that consume a given token (Token Spec \xA74.3)").option("--file <path>", "Path to token file (overrides config)").option("--styles <path>", `Path to compliance styles JSON (default: ${DEFAULT_STYLES_PATH2})`).option("--new-value <value>", "Proposed new value \u2014 report visual severity of the change").option("--format <fmt>", "Output format: json or text (default: auto-detect)").action(
|
|
4504
|
+
(tokenPath, opts) => {
|
|
4505
|
+
try {
|
|
4506
|
+
const tokenFilePath = resolveTokenFilePath(opts.file);
|
|
4507
|
+
const { tokens } = loadTokens(tokenFilePath);
|
|
4508
|
+
const resolver = new TokenResolver5(tokens);
|
|
4509
|
+
const engine = new ComplianceEngine4(resolver);
|
|
4510
|
+
const stylesPath = opts.styles ?? DEFAULT_STYLES_PATH2;
|
|
4511
|
+
const stylesFile = loadStylesFile(stylesPath);
|
|
4512
|
+
const componentMap = new Map(Object.entries(stylesFile));
|
|
4513
|
+
const batchReport = engine.auditBatch(componentMap);
|
|
4514
|
+
const complianceReports = new Map(Object.entries(batchReport.components));
|
|
4515
|
+
const analyzer = new ImpactAnalyzer(resolver, complianceReports);
|
|
4516
|
+
const currentValue = resolver.resolve(tokenPath);
|
|
4517
|
+
const newValue = opts.newValue ?? currentValue;
|
|
4518
|
+
const report = analyzer.impactOf(tokenPath, newValue);
|
|
4519
|
+
const useJson = opts.format === "json" || opts.format !== "text" && !isTTY();
|
|
4520
|
+
if (useJson) {
|
|
4521
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}
|
|
4522
|
+
`);
|
|
4523
|
+
} else {
|
|
4524
|
+
process.stdout.write(`${formatImpactReport(report)}
|
|
4525
|
+
`);
|
|
4526
|
+
if (isTTY()) {
|
|
4527
|
+
process.stdout.write(`
|
|
4528
|
+
${formatImpactSummary(report)}
|
|
4529
|
+
`);
|
|
4530
|
+
}
|
|
4531
|
+
}
|
|
4532
|
+
} catch (err) {
|
|
4533
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
4534
|
+
`);
|
|
4535
|
+
process.exit(1);
|
|
4536
|
+
}
|
|
4537
|
+
}
|
|
4538
|
+
);
|
|
4539
|
+
}
|
|
4540
|
+
|
|
4541
|
+
// src/tokens/preview.ts
|
|
4542
|
+
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
4543
|
+
import { resolve as resolve12 } from "path";
|
|
4544
|
+
import { BrowserPool as BrowserPool6, SpriteSheetGenerator } from "@agent-scope/render";
|
|
4545
|
+
import { ComplianceEngine as ComplianceEngine5, ImpactAnalyzer as ImpactAnalyzer2, TokenResolver as TokenResolver6 } from "@agent-scope/tokens";
|
|
4546
|
+
var DEFAULT_STYLES_PATH3 = ".reactscope/compliance-styles.json";
|
|
4547
|
+
var DEFAULT_MANIFEST_PATH = ".reactscope/manifest.json";
|
|
4548
|
+
var DEFAULT_OUTPUT_DIR2 = ".reactscope/previews";
|
|
4549
|
+
async function renderComponentWithCssOverride(filePath, componentName, cssOverride, vpWidth, vpHeight, timeoutMs) {
|
|
4550
|
+
const htmlHarness = await buildComponentHarness(
|
|
4551
|
+
filePath,
|
|
4552
|
+
componentName,
|
|
4553
|
+
{},
|
|
4554
|
+
// no props
|
|
4555
|
+
vpWidth,
|
|
4556
|
+
cssOverride
|
|
4557
|
+
// injected as <style>
|
|
4558
|
+
);
|
|
4559
|
+
const pool = new BrowserPool6({
|
|
4560
|
+
size: { browsers: 1, pagesPerBrowser: 1 },
|
|
4561
|
+
viewportWidth: vpWidth,
|
|
4562
|
+
viewportHeight: vpHeight
|
|
4563
|
+
});
|
|
4564
|
+
await pool.init();
|
|
4565
|
+
const slot = await pool.acquire();
|
|
4566
|
+
const { page } = slot;
|
|
4567
|
+
try {
|
|
4568
|
+
await page.setContent(htmlHarness, { waitUntil: "load" });
|
|
4569
|
+
await page.waitForFunction(
|
|
4570
|
+
() => {
|
|
4571
|
+
const w = window;
|
|
4572
|
+
return w.__SCOPE_RENDER_COMPLETE__ === true;
|
|
4573
|
+
},
|
|
4574
|
+
{ timeout: timeoutMs }
|
|
4575
|
+
);
|
|
4576
|
+
const rootLocator = page.locator("[data-reactscope-root]");
|
|
4577
|
+
const bb = await rootLocator.boundingBox();
|
|
4578
|
+
const PAD = 16;
|
|
4579
|
+
const MIN_W = 320;
|
|
4580
|
+
const MIN_H = 120;
|
|
4581
|
+
const clipX = Math.max(0, (bb?.x ?? 0) - PAD);
|
|
4582
|
+
const clipY = Math.max(0, (bb?.y ?? 0) - PAD);
|
|
4583
|
+
const rawW = (bb?.width ?? MIN_W) + PAD * 2;
|
|
4584
|
+
const rawH = (bb?.height ?? MIN_H) + PAD * 2;
|
|
4585
|
+
const clipW = Math.min(Math.max(rawW, MIN_W), vpWidth - clipX);
|
|
4586
|
+
const clipH = Math.min(Math.max(rawH, MIN_H), vpHeight - clipY);
|
|
4587
|
+
const screenshot = await page.screenshot({
|
|
4588
|
+
clip: { x: clipX, y: clipY, width: clipW, height: clipH },
|
|
4589
|
+
type: "png"
|
|
4590
|
+
});
|
|
4591
|
+
return { screenshot, width: Math.round(clipW), height: Math.round(clipH) };
|
|
4592
|
+
} finally {
|
|
4593
|
+
pool.release(slot);
|
|
4594
|
+
await pool.close().catch(() => void 0);
|
|
4595
|
+
}
|
|
4596
|
+
}
|
|
4597
|
+
function registerPreview(tokensCmd) {
|
|
4598
|
+
tokensCmd.command("preview <path>").description("Render before/after sprite sheet for components affected by a token change").requiredOption("--new-value <value>", "The proposed new resolved value for the token").option("--sprite", "Output a PNG sprite sheet (default when TTY)", false).option("-o, --output <path>", "Output PNG path (default: .reactscope/previews/<token>.png)").option("--file <path>", "Path to token file (overrides config)").option("--styles <path>", `Path to compliance styles JSON (default: ${DEFAULT_STYLES_PATH3})`).option("--manifest <path>", "Path to manifest.json", DEFAULT_MANIFEST_PATH).option("--format <fmt>", "Output format: json or text (default: auto-detect)").option("--timeout <ms>", "Browser timeout per render (ms)", "10000").option("--viewport-width <px>", "Viewport width in pixels", "1280").option("--viewport-height <px>", "Viewport height in pixels", "720").action(
|
|
4599
|
+
async (tokenPath, opts) => {
|
|
4600
|
+
try {
|
|
4601
|
+
const tokenFilePath = resolveTokenFilePath(opts.file);
|
|
4602
|
+
const { tokens } = loadTokens(tokenFilePath);
|
|
4603
|
+
const resolver = new TokenResolver6(tokens);
|
|
4604
|
+
const engine = new ComplianceEngine5(resolver);
|
|
4605
|
+
const stylesPath = opts.styles ?? DEFAULT_STYLES_PATH3;
|
|
4606
|
+
const stylesFile = loadStylesFile(stylesPath);
|
|
4607
|
+
const componentMap = new Map(Object.entries(stylesFile));
|
|
4608
|
+
const batchReport = engine.auditBatch(componentMap);
|
|
4609
|
+
const complianceReports = new Map(Object.entries(batchReport.components));
|
|
4610
|
+
const analyzer = new ImpactAnalyzer2(resolver, complianceReports);
|
|
4611
|
+
const currentValue = resolver.resolve(tokenPath);
|
|
4612
|
+
const impactReport = analyzer.impactOf(tokenPath, opts.newValue);
|
|
4613
|
+
if (impactReport.components.length === 0) {
|
|
4614
|
+
process.stdout.write(
|
|
4615
|
+
`No components reference token "${tokenPath}". Nothing to preview.
|
|
4616
|
+
`
|
|
4617
|
+
);
|
|
4618
|
+
return;
|
|
4619
|
+
}
|
|
4620
|
+
const affectedNames = impactReport.components.map((c) => c.name);
|
|
4621
|
+
process.stderr.write(
|
|
4622
|
+
`Rendering ${affectedNames.length} component(s): ${affectedNames.join(", ")}
|
|
4623
|
+
`
|
|
4624
|
+
);
|
|
4625
|
+
const manifest = loadManifest(opts.manifest);
|
|
4626
|
+
const vpWidth = Number.parseInt(opts.viewportWidth, 10);
|
|
4627
|
+
const vpHeight = Number.parseInt(opts.viewportHeight, 10);
|
|
4628
|
+
const timeout = Number.parseInt(opts.timeout, 10);
|
|
4629
|
+
const tokenCssVar = `--token-${tokenPath.replace(/\./g, "-")}`;
|
|
4630
|
+
const beforeCss = `:root { ${tokenCssVar}: ${currentValue}; }`;
|
|
4631
|
+
const afterCss = `:root { ${tokenCssVar}: ${opts.newValue}; }`;
|
|
4632
|
+
const renders = [];
|
|
4633
|
+
for (const componentName of affectedNames) {
|
|
4634
|
+
const descriptor = manifest.components[componentName];
|
|
4635
|
+
if (descriptor === void 0) {
|
|
4636
|
+
process.stderr.write(
|
|
4637
|
+
`Warning: "${componentName}" not found in manifest \u2014 skipping
|
|
4638
|
+
`
|
|
4639
|
+
);
|
|
4640
|
+
continue;
|
|
4641
|
+
}
|
|
4642
|
+
process.stderr.write(` Rendering ${componentName} (before)...
|
|
4643
|
+
`);
|
|
4644
|
+
const before = await renderComponentWithCssOverride(
|
|
4645
|
+
descriptor.filePath,
|
|
4646
|
+
componentName,
|
|
4647
|
+
beforeCss,
|
|
4648
|
+
vpWidth,
|
|
4649
|
+
vpHeight,
|
|
4650
|
+
timeout
|
|
4651
|
+
);
|
|
4652
|
+
process.stderr.write(` Rendering ${componentName} (after)...
|
|
4653
|
+
`);
|
|
4654
|
+
const after = await renderComponentWithCssOverride(
|
|
4655
|
+
descriptor.filePath,
|
|
4656
|
+
componentName,
|
|
4657
|
+
afterCss,
|
|
4658
|
+
vpWidth,
|
|
4659
|
+
vpHeight,
|
|
4660
|
+
timeout
|
|
4661
|
+
);
|
|
4662
|
+
renders.push({ name: componentName, before, after });
|
|
4663
|
+
}
|
|
4664
|
+
if (renders.length === 0) {
|
|
4665
|
+
process.stderr.write(
|
|
4666
|
+
"Warning: No components could be rendered (all missing from manifest).\n"
|
|
4667
|
+
);
|
|
4668
|
+
return;
|
|
4669
|
+
}
|
|
4670
|
+
const cellW = Math.max(...renders.flatMap((r) => [r.before.width, r.after.width]));
|
|
4671
|
+
const cellH = Math.max(...renders.flatMap((r) => [r.before.height, r.after.height]));
|
|
4672
|
+
const cells = renders.flatMap((r, colIdx) => [
|
|
4673
|
+
{
|
|
4674
|
+
props: { version: "before", component: r.name },
|
|
4675
|
+
result: {
|
|
4676
|
+
screenshot: r.before.screenshot,
|
|
4677
|
+
width: cellW,
|
|
4678
|
+
height: cellH,
|
|
4679
|
+
renderTimeMs: 0,
|
|
4680
|
+
computedStyles: {}
|
|
4681
|
+
},
|
|
4682
|
+
index: colIdx * 2,
|
|
4683
|
+
axisIndices: [0, colIdx]
|
|
4684
|
+
},
|
|
4685
|
+
{
|
|
4686
|
+
props: { version: "after", component: r.name },
|
|
4687
|
+
result: {
|
|
4688
|
+
screenshot: r.after.screenshot,
|
|
4689
|
+
width: cellW,
|
|
4690
|
+
height: cellH,
|
|
4691
|
+
renderTimeMs: 0,
|
|
4692
|
+
computedStyles: {}
|
|
4693
|
+
},
|
|
4694
|
+
index: colIdx * 2 + 1,
|
|
4695
|
+
axisIndices: [1, colIdx]
|
|
4696
|
+
}
|
|
4697
|
+
]);
|
|
4698
|
+
const matrixResult = {
|
|
4699
|
+
cells,
|
|
4700
|
+
axes: [
|
|
4701
|
+
{ name: "component", values: renders.map((r) => r.name) },
|
|
4702
|
+
{ name: "version", values: ["before", "after"] }
|
|
4703
|
+
],
|
|
4704
|
+
axisLabels: [renders.map((r) => r.name), ["before", "after"]],
|
|
4705
|
+
rows: 2,
|
|
4706
|
+
cols: renders.length,
|
|
4707
|
+
stats: {
|
|
4708
|
+
totalCells: cells.length,
|
|
4709
|
+
totalRenderTimeMs: 0,
|
|
4710
|
+
avgRenderTimeMs: 0,
|
|
4711
|
+
minRenderTimeMs: 0,
|
|
4712
|
+
maxRenderTimeMs: 0,
|
|
4713
|
+
wallClockTimeMs: 0
|
|
4714
|
+
}
|
|
4715
|
+
};
|
|
4716
|
+
const generator = new SpriteSheetGenerator({
|
|
4717
|
+
cellPadding: 8,
|
|
4718
|
+
borderWidth: 1,
|
|
4719
|
+
labelHeight: 32,
|
|
4720
|
+
labelWidth: 120
|
|
4721
|
+
});
|
|
4722
|
+
const spriteResult = await generator.generate(matrixResult);
|
|
4723
|
+
const tokenLabel = tokenPath.replace(/\./g, "-");
|
|
4724
|
+
const outputPath = opts.output ?? resolve12(process.cwd(), DEFAULT_OUTPUT_DIR2, `preview-${tokenLabel}.png`);
|
|
4725
|
+
const outputDir = resolve12(outputPath, "..");
|
|
4726
|
+
mkdirSync5(outputDir, { recursive: true });
|
|
4727
|
+
writeFileSync8(outputPath, spriteResult.png);
|
|
4728
|
+
const useJson = opts.format === "json" || opts.format !== "text" && !isTTY();
|
|
4729
|
+
if (useJson) {
|
|
4730
|
+
process.stdout.write(
|
|
4731
|
+
`${JSON.stringify(
|
|
4732
|
+
{
|
|
4733
|
+
tokenPath,
|
|
4734
|
+
oldValue: currentValue,
|
|
4735
|
+
newValue: opts.newValue,
|
|
4736
|
+
outputPath,
|
|
4737
|
+
width: spriteResult.width,
|
|
4738
|
+
height: spriteResult.height,
|
|
4739
|
+
components: renders.map((r) => r.name),
|
|
4740
|
+
cells: spriteResult.coordinates.length
|
|
4741
|
+
},
|
|
4742
|
+
null,
|
|
4743
|
+
2
|
|
4744
|
+
)}
|
|
4745
|
+
`
|
|
4746
|
+
);
|
|
4747
|
+
} else {
|
|
4748
|
+
process.stdout.write(
|
|
4749
|
+
`Preview written to ${outputPath} (${spriteResult.width}\xD7${spriteResult.height}px)
|
|
4750
|
+
`
|
|
4751
|
+
);
|
|
4752
|
+
process.stdout.write(`Components: ${renders.map((r) => r.name).join(", ")}
|
|
4753
|
+
`);
|
|
4754
|
+
}
|
|
4755
|
+
} catch (err) {
|
|
4756
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
4757
|
+
`);
|
|
4758
|
+
process.exit(1);
|
|
4759
|
+
}
|
|
4760
|
+
}
|
|
4761
|
+
);
|
|
4762
|
+
}
|
|
4763
|
+
|
|
4270
4764
|
// src/tokens/commands.ts
|
|
4271
4765
|
var DEFAULT_TOKEN_FILE2 = "reactscope.tokens.json";
|
|
4272
4766
|
var CONFIG_FILE2 = "reactscope.config.json";
|
|
@@ -4287,32 +4781,32 @@ function buildTable2(headers, rows) {
|
|
|
4287
4781
|
);
|
|
4288
4782
|
return [headerRow, divider, ...dataRows].join("\n");
|
|
4289
4783
|
}
|
|
4290
|
-
function
|
|
4784
|
+
function resolveTokenFilePath(fileFlag) {
|
|
4291
4785
|
if (fileFlag !== void 0) {
|
|
4292
|
-
return
|
|
4786
|
+
return resolve13(process.cwd(), fileFlag);
|
|
4293
4787
|
}
|
|
4294
|
-
const configPath =
|
|
4295
|
-
if (
|
|
4788
|
+
const configPath = resolve13(process.cwd(), CONFIG_FILE2);
|
|
4789
|
+
if (existsSync9(configPath)) {
|
|
4296
4790
|
try {
|
|
4297
|
-
const raw =
|
|
4791
|
+
const raw = readFileSync8(configPath, "utf-8");
|
|
4298
4792
|
const config = JSON.parse(raw);
|
|
4299
4793
|
if (typeof config === "object" && config !== null && "tokens" in config && typeof config.tokens === "object" && config.tokens !== null && typeof config.tokens?.file === "string") {
|
|
4300
4794
|
const file = config.tokens.file;
|
|
4301
|
-
return
|
|
4795
|
+
return resolve13(process.cwd(), file);
|
|
4302
4796
|
}
|
|
4303
4797
|
} catch {
|
|
4304
4798
|
}
|
|
4305
4799
|
}
|
|
4306
|
-
return
|
|
4800
|
+
return resolve13(process.cwd(), DEFAULT_TOKEN_FILE2);
|
|
4307
4801
|
}
|
|
4308
4802
|
function loadTokens(absPath) {
|
|
4309
|
-
if (!
|
|
4803
|
+
if (!existsSync9(absPath)) {
|
|
4310
4804
|
throw new Error(
|
|
4311
4805
|
`Token file not found at ${absPath}.
|
|
4312
4806
|
Create a reactscope.tokens.json file or use --file to specify a path.`
|
|
4313
4807
|
);
|
|
4314
4808
|
}
|
|
4315
|
-
const raw =
|
|
4809
|
+
const raw = readFileSync8(absPath, "utf-8");
|
|
4316
4810
|
return parseTokenFileSync2(raw);
|
|
4317
4811
|
}
|
|
4318
4812
|
function getRawValue(node, segments) {
|
|
@@ -4350,9 +4844,9 @@ function buildResolutionChain(startPath, rawTokens) {
|
|
|
4350
4844
|
function registerGet2(tokensCmd) {
|
|
4351
4845
|
tokensCmd.command("get <path>").description("Resolve a token path to its computed value").option("--file <path>", "Path to token file (overrides config)").option("--format <fmt>", "Output format: json or text (default: auto-detect)").action((tokenPath, opts) => {
|
|
4352
4846
|
try {
|
|
4353
|
-
const filePath =
|
|
4847
|
+
const filePath = resolveTokenFilePath(opts.file);
|
|
4354
4848
|
const { tokens } = loadTokens(filePath);
|
|
4355
|
-
const resolver = new
|
|
4849
|
+
const resolver = new TokenResolver7(tokens);
|
|
4356
4850
|
const resolvedValue = resolver.resolve(tokenPath);
|
|
4357
4851
|
const useJson = opts.format === "json" || opts.format !== "text" && !isTTY2();
|
|
4358
4852
|
if (useJson) {
|
|
@@ -4376,9 +4870,9 @@ function registerList2(tokensCmd) {
|
|
|
4376
4870
|
tokensCmd.command("list [category]").description("List tokens, optionally filtered by category or type").option("--type <type>", "Filter by token type (color, dimension, fontFamily, etc.)").option("--file <path>", "Path to token file (overrides config)").option("--format <fmt>", "Output format: json or table (default: auto-detect)").action(
|
|
4377
4871
|
(category, opts) => {
|
|
4378
4872
|
try {
|
|
4379
|
-
const filePath =
|
|
4873
|
+
const filePath = resolveTokenFilePath(opts.file);
|
|
4380
4874
|
const { tokens } = loadTokens(filePath);
|
|
4381
|
-
const resolver = new
|
|
4875
|
+
const resolver = new TokenResolver7(tokens);
|
|
4382
4876
|
const filtered = resolver.list(opts.type, category);
|
|
4383
4877
|
const useJson = opts.format === "json" || opts.format !== "table" && !isTTY2();
|
|
4384
4878
|
if (useJson) {
|
|
@@ -4406,9 +4900,9 @@ function registerSearch(tokensCmd) {
|
|
|
4406
4900
|
tokensCmd.command("search <value>").description("Find which token(s) match a computed value (supports fuzzy color matching)").option("--type <type>", "Restrict search to a specific token type").option("--fuzzy", "Return nearest match even if no exact match exists", false).option("--file <path>", "Path to token file (overrides config)").option("--format <fmt>", "Output format: json or table (default: auto-detect)").action(
|
|
4407
4901
|
(value, opts) => {
|
|
4408
4902
|
try {
|
|
4409
|
-
const filePath =
|
|
4903
|
+
const filePath = resolveTokenFilePath(opts.file);
|
|
4410
4904
|
const { tokens } = loadTokens(filePath);
|
|
4411
|
-
const resolver = new
|
|
4905
|
+
const resolver = new TokenResolver7(tokens);
|
|
4412
4906
|
const useJson = opts.format === "json" || opts.format !== "table" && !isTTY2();
|
|
4413
4907
|
const typesToSearch = opts.type ? [opts.type] : [
|
|
4414
4908
|
"color",
|
|
@@ -4488,10 +4982,10 @@ Tip: use --fuzzy for nearest-match search.
|
|
|
4488
4982
|
function registerResolve(tokensCmd) {
|
|
4489
4983
|
tokensCmd.command("resolve <path>").description("Show the full resolution chain for a token").option("--file <path>", "Path to token file (overrides config)").option("--format <fmt>", "Output format: json or text (default: auto-detect)").action((tokenPath, opts) => {
|
|
4490
4984
|
try {
|
|
4491
|
-
const filePath =
|
|
4985
|
+
const filePath = resolveTokenFilePath(opts.file);
|
|
4492
4986
|
const absFilePath = filePath;
|
|
4493
4987
|
const { tokens, rawFile } = loadTokens(absFilePath);
|
|
4494
|
-
const resolver = new
|
|
4988
|
+
const resolver = new TokenResolver7(tokens);
|
|
4495
4989
|
resolver.resolve(tokenPath);
|
|
4496
4990
|
const chain = buildResolutionChain(tokenPath, rawFile.tokens);
|
|
4497
4991
|
const useJson = opts.format === "json" || opts.format !== "text" && !isTTY2();
|
|
@@ -4525,14 +5019,14 @@ function registerValidate(tokensCmd) {
|
|
|
4525
5019
|
"Validate the token file for errors (circular refs, missing refs, type mismatches)"
|
|
4526
5020
|
).option("--file <path>", "Path to token file (overrides config)").option("--format <fmt>", "Output format: json or text (default: auto-detect)").action((opts) => {
|
|
4527
5021
|
try {
|
|
4528
|
-
const filePath =
|
|
4529
|
-
if (!
|
|
5022
|
+
const filePath = resolveTokenFilePath(opts.file);
|
|
5023
|
+
if (!existsSync9(filePath)) {
|
|
4530
5024
|
throw new Error(
|
|
4531
5025
|
`Token file not found at ${filePath}.
|
|
4532
5026
|
Create a reactscope.tokens.json file or use --file to specify a path.`
|
|
4533
5027
|
);
|
|
4534
5028
|
}
|
|
4535
|
-
const raw =
|
|
5029
|
+
const raw = readFileSync8(filePath, "utf-8");
|
|
4536
5030
|
const useJson = opts.format === "json" || opts.format !== "text" && !isTTY2();
|
|
4537
5031
|
const errors = [];
|
|
4538
5032
|
let parsed;
|
|
@@ -4609,6 +5103,9 @@ function createTokensCommand() {
|
|
|
4609
5103
|
registerResolve(tokensCmd);
|
|
4610
5104
|
registerValidate(tokensCmd);
|
|
4611
5105
|
tokensCmd.addCommand(createTokensExportCommand());
|
|
5106
|
+
registerCompliance(tokensCmd);
|
|
5107
|
+
registerImpact(tokensCmd);
|
|
5108
|
+
registerPreview(tokensCmd);
|
|
4612
5109
|
return tokensCmd;
|
|
4613
5110
|
}
|
|
4614
5111
|
|
|
@@ -4687,7 +5184,7 @@ function createProgram(options = {}) {
|
|
|
4687
5184
|
}
|
|
4688
5185
|
);
|
|
4689
5186
|
program2.command("generate").description("Generate a Playwright test from a Scope trace file").argument("<trace>", "Path to a serialized Scope trace (.json)").option("-o, --output <path>", "Output file path", "scope.spec.ts").option("-d, --description <text>", "Test description").action((tracePath, opts) => {
|
|
4690
|
-
const raw =
|
|
5187
|
+
const raw = readFileSync9(tracePath, "utf-8");
|
|
4691
5188
|
const trace = loadTrace(raw);
|
|
4692
5189
|
const source = generateTest(trace, {
|
|
4693
5190
|
description: opts.description,
|