@agent-scope/cli 1.8.0 → 1.9.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 +409 -53
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +350 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +89 -1
- package/dist/index.d.ts +89 -1
- package/dist/index.js +350 -5
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.cjs
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
var fs = require('fs');
|
|
4
4
|
var path = require('path');
|
|
5
|
-
var
|
|
5
|
+
var readline = require('readline');
|
|
6
6
|
var commander = require('commander');
|
|
7
|
+
var manifest = require('@agent-scope/manifest');
|
|
7
8
|
var playwright = require('@agent-scope/playwright');
|
|
8
9
|
var playwright$1 = require('playwright');
|
|
9
10
|
var render = require('@agent-scope/render');
|
|
@@ -29,9 +30,353 @@ function _interopNamespace(e) {
|
|
|
29
30
|
return Object.freeze(n);
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
var readline__namespace = /*#__PURE__*/_interopNamespace(readline);
|
|
32
34
|
var esbuild__namespace = /*#__PURE__*/_interopNamespace(esbuild);
|
|
33
35
|
|
|
34
|
-
// src/
|
|
36
|
+
// src/init/index.ts
|
|
37
|
+
function hasConfigFile(dir, stem) {
|
|
38
|
+
if (!fs.existsSync(dir)) return false;
|
|
39
|
+
try {
|
|
40
|
+
const entries = fs.readdirSync(dir);
|
|
41
|
+
return entries.some((f) => f === stem || f.startsWith(`${stem}.`));
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function readSafe(path) {
|
|
47
|
+
try {
|
|
48
|
+
return fs.readFileSync(path, "utf-8");
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function detectFramework(rootDir, packageDeps) {
|
|
54
|
+
if (hasConfigFile(rootDir, "next.config")) return "next";
|
|
55
|
+
if (hasConfigFile(rootDir, "vite.config")) return "vite";
|
|
56
|
+
if (hasConfigFile(rootDir, "remix.config")) return "remix";
|
|
57
|
+
if ("react-scripts" in packageDeps) return "cra";
|
|
58
|
+
return "unknown";
|
|
59
|
+
}
|
|
60
|
+
function detectPackageManager(rootDir) {
|
|
61
|
+
if (fs.existsSync(path.join(rootDir, "bun.lock"))) return "bun";
|
|
62
|
+
if (fs.existsSync(path.join(rootDir, "yarn.lock"))) return "yarn";
|
|
63
|
+
if (fs.existsSync(path.join(rootDir, "pnpm-lock.yaml"))) return "pnpm";
|
|
64
|
+
if (fs.existsSync(path.join(rootDir, "package-lock.json"))) return "npm";
|
|
65
|
+
return "npm";
|
|
66
|
+
}
|
|
67
|
+
function detectTypeScript(rootDir) {
|
|
68
|
+
const candidate = path.join(rootDir, "tsconfig.json");
|
|
69
|
+
if (fs.existsSync(candidate)) {
|
|
70
|
+
return { typescript: true, tsconfigPath: candidate };
|
|
71
|
+
}
|
|
72
|
+
return { typescript: false, tsconfigPath: null };
|
|
73
|
+
}
|
|
74
|
+
var COMPONENT_DIRS = ["src/components", "src/app", "src/pages", "src/ui", "src/features", "src"];
|
|
75
|
+
var COMPONENT_EXTS = [".tsx", ".jsx"];
|
|
76
|
+
function detectComponentPatterns(rootDir, typescript) {
|
|
77
|
+
const patterns = [];
|
|
78
|
+
const ext = typescript ? "tsx" : "jsx";
|
|
79
|
+
const altExt = typescript ? "jsx" : "jsx";
|
|
80
|
+
for (const dir of COMPONENT_DIRS) {
|
|
81
|
+
const absDir = path.join(rootDir, dir);
|
|
82
|
+
if (!fs.existsSync(absDir)) continue;
|
|
83
|
+
let hasComponents = false;
|
|
84
|
+
try {
|
|
85
|
+
const entries = fs.readdirSync(absDir, { withFileTypes: true });
|
|
86
|
+
hasComponents = entries.some(
|
|
87
|
+
(e) => e.isFile() && COMPONENT_EXTS.some((x) => e.name.endsWith(x))
|
|
88
|
+
);
|
|
89
|
+
if (!hasComponents) {
|
|
90
|
+
hasComponents = entries.some(
|
|
91
|
+
(e) => e.isDirectory() && (() => {
|
|
92
|
+
try {
|
|
93
|
+
return fs.readdirSync(path.join(absDir, e.name)).some(
|
|
94
|
+
(f) => COMPONENT_EXTS.some((x) => f.endsWith(x))
|
|
95
|
+
);
|
|
96
|
+
} catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
})()
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
} catch {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (hasComponents) {
|
|
106
|
+
patterns.push(`${dir}/**/*.${ext}`);
|
|
107
|
+
if (altExt !== ext) {
|
|
108
|
+
patterns.push(`${dir}/**/*.${altExt}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const unique = [...new Set(patterns)];
|
|
113
|
+
if (unique.length === 0) {
|
|
114
|
+
return [`**/*.${ext}`];
|
|
115
|
+
}
|
|
116
|
+
return unique;
|
|
117
|
+
}
|
|
118
|
+
var TAILWIND_STEMS = ["tailwind.config"];
|
|
119
|
+
var CSS_EXTS = [".css", ".scss", ".sass", ".less"];
|
|
120
|
+
var THEME_SUFFIXES = [".theme.ts", ".theme.js", ".theme.tsx"];
|
|
121
|
+
var CSS_CUSTOM_PROPS_RE = /:root\s*\{[^}]*--[a-zA-Z]/;
|
|
122
|
+
function detectTokenSources(rootDir) {
|
|
123
|
+
const sources = [];
|
|
124
|
+
for (const stem of TAILWIND_STEMS) {
|
|
125
|
+
if (hasConfigFile(rootDir, stem)) {
|
|
126
|
+
try {
|
|
127
|
+
const entries = fs.readdirSync(rootDir);
|
|
128
|
+
const match = entries.find((f) => f === stem || f.startsWith(`${stem}.`));
|
|
129
|
+
if (match) {
|
|
130
|
+
sources.push({ kind: "tailwind-config", path: path.join(rootDir, match) });
|
|
131
|
+
}
|
|
132
|
+
} catch {
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const srcDir = path.join(rootDir, "src");
|
|
137
|
+
const dirsToScan = fs.existsSync(srcDir) ? [srcDir] : [];
|
|
138
|
+
for (const scanDir of dirsToScan) {
|
|
139
|
+
try {
|
|
140
|
+
const entries = fs.readdirSync(scanDir, { withFileTypes: true });
|
|
141
|
+
for (const entry of entries) {
|
|
142
|
+
if (entry.isFile() && CSS_EXTS.some((x) => entry.name.endsWith(x))) {
|
|
143
|
+
const filePath = path.join(scanDir, entry.name);
|
|
144
|
+
const content = readSafe(filePath);
|
|
145
|
+
if (content !== null && CSS_CUSTOM_PROPS_RE.test(content)) {
|
|
146
|
+
sources.push({ kind: "css-custom-properties", path: filePath });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
} catch {
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (fs.existsSync(srcDir)) {
|
|
154
|
+
try {
|
|
155
|
+
const entries = fs.readdirSync(srcDir);
|
|
156
|
+
for (const entry of entries) {
|
|
157
|
+
if (THEME_SUFFIXES.some((s) => entry.endsWith(s))) {
|
|
158
|
+
sources.push({ kind: "theme-file", path: path.join(srcDir, entry) });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
} catch {
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return sources;
|
|
165
|
+
}
|
|
166
|
+
function detectProject(rootDir) {
|
|
167
|
+
const pkgPath = path.join(rootDir, "package.json");
|
|
168
|
+
let packageDeps = {};
|
|
169
|
+
const pkgContent = readSafe(pkgPath);
|
|
170
|
+
if (pkgContent !== null) {
|
|
171
|
+
try {
|
|
172
|
+
const pkg = JSON.parse(pkgContent);
|
|
173
|
+
packageDeps = {
|
|
174
|
+
...pkg.dependencies,
|
|
175
|
+
...pkg.devDependencies
|
|
176
|
+
};
|
|
177
|
+
} catch {
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const framework = detectFramework(rootDir, packageDeps);
|
|
181
|
+
const { typescript, tsconfigPath } = detectTypeScript(rootDir);
|
|
182
|
+
const packageManager = detectPackageManager(rootDir);
|
|
183
|
+
const componentPatterns = detectComponentPatterns(rootDir, typescript);
|
|
184
|
+
const tokenSources = detectTokenSources(rootDir);
|
|
185
|
+
return {
|
|
186
|
+
framework,
|
|
187
|
+
typescript,
|
|
188
|
+
tsconfigPath,
|
|
189
|
+
componentPatterns,
|
|
190
|
+
tokenSources,
|
|
191
|
+
packageManager
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function buildDefaultConfig(detected, tokenFile, outputDir) {
|
|
195
|
+
const include = detected.componentPatterns.length > 0 ? detected.componentPatterns : ["src/**/*.tsx"];
|
|
196
|
+
return {
|
|
197
|
+
components: {
|
|
198
|
+
include,
|
|
199
|
+
exclude: ["**/*.test.tsx", "**/*.stories.tsx"],
|
|
200
|
+
wrappers: { providers: [], globalCSS: [] }
|
|
201
|
+
},
|
|
202
|
+
render: {
|
|
203
|
+
viewport: { default: { width: 1280, height: 800 } },
|
|
204
|
+
theme: "light",
|
|
205
|
+
warmBrowser: true
|
|
206
|
+
},
|
|
207
|
+
tokens: {
|
|
208
|
+
file: tokenFile,
|
|
209
|
+
compliance: { threshold: 90 }
|
|
210
|
+
},
|
|
211
|
+
output: {
|
|
212
|
+
dir: outputDir,
|
|
213
|
+
sprites: { format: "png", cellPadding: 8, labelAxes: true },
|
|
214
|
+
json: { pretty: true }
|
|
215
|
+
},
|
|
216
|
+
ci: {
|
|
217
|
+
complianceThreshold: 90,
|
|
218
|
+
failOnA11yViolations: true,
|
|
219
|
+
failOnConsoleErrors: false,
|
|
220
|
+
baselinePath: `${outputDir}baseline/`
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function createRL() {
|
|
225
|
+
return readline__namespace.createInterface({
|
|
226
|
+
input: process.stdin,
|
|
227
|
+
output: process.stdout
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
async function ask(rl, question) {
|
|
231
|
+
return new Promise((resolve6) => {
|
|
232
|
+
rl.question(question, (answer) => {
|
|
233
|
+
resolve6(answer.trim());
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
async function askWithDefault(rl, label, defaultValue) {
|
|
238
|
+
const answer = await ask(rl, ` ${label} [${defaultValue}]: `);
|
|
239
|
+
return answer.length > 0 ? answer : defaultValue;
|
|
240
|
+
}
|
|
241
|
+
function ensureGitignoreEntry(rootDir, entry) {
|
|
242
|
+
const gitignorePath = path.join(rootDir, ".gitignore");
|
|
243
|
+
if (fs.existsSync(gitignorePath)) {
|
|
244
|
+
const content = fs.readFileSync(gitignorePath, "utf-8");
|
|
245
|
+
const normalised = entry.replace(/\/$/, "");
|
|
246
|
+
const lines = content.split("\n").map((l) => l.trim());
|
|
247
|
+
if (lines.includes(entry) || lines.includes(normalised)) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
const suffix = content.endsWith("\n") ? "" : "\n";
|
|
251
|
+
fs.appendFileSync(gitignorePath, `${suffix}${entry}
|
|
252
|
+
`);
|
|
253
|
+
} else {
|
|
254
|
+
fs.writeFileSync(gitignorePath, `${entry}
|
|
255
|
+
`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function scaffoldConfig(rootDir, config) {
|
|
259
|
+
const path$1 = path.join(rootDir, "reactscope.config.json");
|
|
260
|
+
fs.writeFileSync(path$1, `${JSON.stringify(config, null, 2)}
|
|
261
|
+
`);
|
|
262
|
+
return path$1;
|
|
263
|
+
}
|
|
264
|
+
function scaffoldTokenFile(rootDir, tokenFile) {
|
|
265
|
+
const path$1 = path.join(rootDir, tokenFile);
|
|
266
|
+
if (!fs.existsSync(path$1)) {
|
|
267
|
+
const stub = {
|
|
268
|
+
$schema: "https://raw.githubusercontent.com/FlatFilers/Scope/main/packages/tokens/schema.json",
|
|
269
|
+
tokens: {}
|
|
270
|
+
};
|
|
271
|
+
fs.writeFileSync(path$1, `${JSON.stringify(stub, null, 2)}
|
|
272
|
+
`);
|
|
273
|
+
}
|
|
274
|
+
return path$1;
|
|
275
|
+
}
|
|
276
|
+
function scaffoldOutputDir(rootDir, outputDir) {
|
|
277
|
+
const dirPath = path.join(rootDir, outputDir);
|
|
278
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
279
|
+
const keepPath = path.join(dirPath, ".gitkeep");
|
|
280
|
+
if (!fs.existsSync(keepPath)) {
|
|
281
|
+
fs.writeFileSync(keepPath, "");
|
|
282
|
+
}
|
|
283
|
+
return dirPath;
|
|
284
|
+
}
|
|
285
|
+
async function runInit(options) {
|
|
286
|
+
const rootDir = options.cwd ?? process.cwd();
|
|
287
|
+
const configPath = path.join(rootDir, "reactscope.config.json");
|
|
288
|
+
const created = [];
|
|
289
|
+
if (fs.existsSync(configPath) && !options.force) {
|
|
290
|
+
const msg = "reactscope.config.json already exists. Run with --force to overwrite.";
|
|
291
|
+
process.stderr.write(`\u26A0\uFE0F ${msg}
|
|
292
|
+
`);
|
|
293
|
+
return { success: false, message: msg, created: [], skipped: true };
|
|
294
|
+
}
|
|
295
|
+
const detected = detectProject(rootDir);
|
|
296
|
+
const defaultTokenFile = "reactscope.tokens.json";
|
|
297
|
+
const defaultOutputDir = ".reactscope/";
|
|
298
|
+
let config = buildDefaultConfig(detected, defaultTokenFile, defaultOutputDir);
|
|
299
|
+
if (options.yes) {
|
|
300
|
+
process.stdout.write("\n\u{1F50D} Detected project settings:\n");
|
|
301
|
+
process.stdout.write(` Framework : ${detected.framework}
|
|
302
|
+
`);
|
|
303
|
+
process.stdout.write(` TypeScript : ${detected.typescript}
|
|
304
|
+
`);
|
|
305
|
+
process.stdout.write(` Include globs : ${config.components.include.join(", ")}
|
|
306
|
+
`);
|
|
307
|
+
process.stdout.write(` Token file : ${config.tokens.file}
|
|
308
|
+
`);
|
|
309
|
+
process.stdout.write(` Output dir : ${config.output.dir}
|
|
310
|
+
|
|
311
|
+
`);
|
|
312
|
+
} else {
|
|
313
|
+
const rl = createRL();
|
|
314
|
+
process.stdout.write("\n\u{1F680} scope init \u2014 project configuration\n");
|
|
315
|
+
process.stdout.write(" Press Enter to accept the detected value shown in brackets.\n\n");
|
|
316
|
+
try {
|
|
317
|
+
process.stdout.write(` Detected framework: ${detected.framework}
|
|
318
|
+
`);
|
|
319
|
+
const includeRaw = await askWithDefault(
|
|
320
|
+
rl,
|
|
321
|
+
"Component include patterns (comma-separated)",
|
|
322
|
+
config.components.include.join(", ")
|
|
323
|
+
);
|
|
324
|
+
config.components.include = includeRaw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
325
|
+
const excludeRaw = await askWithDefault(
|
|
326
|
+
rl,
|
|
327
|
+
"Component exclude patterns (comma-separated)",
|
|
328
|
+
config.components.exclude.join(", ")
|
|
329
|
+
);
|
|
330
|
+
config.components.exclude = excludeRaw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
331
|
+
const tokenFile = await askWithDefault(rl, "Token file location", config.tokens.file);
|
|
332
|
+
config.tokens.file = tokenFile;
|
|
333
|
+
config.ci.baselinePath = `${config.output.dir}baseline/`;
|
|
334
|
+
const outputDir = await askWithDefault(rl, "Output directory", config.output.dir);
|
|
335
|
+
config.output.dir = outputDir.endsWith("/") ? outputDir : `${outputDir}/`;
|
|
336
|
+
config.ci.baselinePath = `${config.output.dir}baseline/`;
|
|
337
|
+
config = buildDefaultConfig(detected, config.tokens.file, config.output.dir);
|
|
338
|
+
config.components.include = includeRaw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
339
|
+
config.components.exclude = excludeRaw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
340
|
+
} finally {
|
|
341
|
+
rl.close();
|
|
342
|
+
}
|
|
343
|
+
process.stdout.write("\n");
|
|
344
|
+
}
|
|
345
|
+
const cfgPath = scaffoldConfig(rootDir, config);
|
|
346
|
+
created.push(cfgPath);
|
|
347
|
+
const tokPath = scaffoldTokenFile(rootDir, config.tokens.file);
|
|
348
|
+
created.push(tokPath);
|
|
349
|
+
const outDirPath = scaffoldOutputDir(rootDir, config.output.dir);
|
|
350
|
+
created.push(outDirPath);
|
|
351
|
+
ensureGitignoreEntry(rootDir, config.output.dir);
|
|
352
|
+
process.stdout.write("\u2705 Scope project initialised!\n\n");
|
|
353
|
+
process.stdout.write(" Created files:\n");
|
|
354
|
+
for (const p of created) {
|
|
355
|
+
process.stdout.write(` ${p}
|
|
356
|
+
`);
|
|
357
|
+
}
|
|
358
|
+
process.stdout.write("\n Next steps: run `scope manifest` to scan your components.\n\n");
|
|
359
|
+
return {
|
|
360
|
+
success: true,
|
|
361
|
+
message: "Project initialised successfully.",
|
|
362
|
+
created,
|
|
363
|
+
skipped: false
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
function createInitCommand() {
|
|
367
|
+
return new commander.Command("init").description("Initialise a Scope project \u2014 scaffold reactscope.config.json and friends").option("-y, --yes", "Accept all detected defaults without prompting", false).option("--force", "Overwrite existing reactscope.config.json if present", false).action(async (opts) => {
|
|
368
|
+
try {
|
|
369
|
+
const result = await runInit({ yes: opts.yes, force: opts.force });
|
|
370
|
+
if (!result.success && !result.skipped) {
|
|
371
|
+
process.exit(1);
|
|
372
|
+
}
|
|
373
|
+
} catch (err) {
|
|
374
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
375
|
+
`);
|
|
376
|
+
process.exit(1);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
}
|
|
35
380
|
|
|
36
381
|
// src/manifest-formatter.ts
|
|
37
382
|
function isTTY() {
|
|
@@ -2384,13 +2729,16 @@ function createProgram(options = {}) {
|
|
|
2384
2729
|
program.addCommand(createRenderCommand());
|
|
2385
2730
|
program.addCommand(createTokensCommand());
|
|
2386
2731
|
program.addCommand(createInstrumentCommand());
|
|
2732
|
+
program.addCommand(createInitCommand());
|
|
2387
2733
|
return program;
|
|
2388
2734
|
}
|
|
2389
2735
|
|
|
2736
|
+
exports.createInitCommand = createInitCommand;
|
|
2390
2737
|
exports.createManifestCommand = createManifestCommand;
|
|
2391
2738
|
exports.createProgram = createProgram;
|
|
2392
2739
|
exports.createTokensCommand = createTokensCommand;
|
|
2393
2740
|
exports.isTTY = isTTY;
|
|
2394
2741
|
exports.matchGlob = matchGlob;
|
|
2742
|
+
exports.runInit = runInit;
|
|
2395
2743
|
//# sourceMappingURL=index.cjs.map
|
|
2396
2744
|
//# sourceMappingURL=index.cjs.map
|