create-quiver 0.4.0 → 0.6.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/README.md +76 -22
- package/README_FOR_AI.md +30 -10
- package/docs/AI_CONTEXT.md.template +59 -0
- package/docs/AI_ONBOARDING_PROMPT.md.template +56 -0
- package/docs/CONTEXTO.md.template +1 -1
- package/docs/DOCUMENTATION_GUIDE.md.template +9 -7
- package/docs/INDEX.md.template +4 -0
- package/docs/WORKFLOW.md.template +7 -1
- package/package.json +2 -1
- package/package.template.json +2 -1
- package/scripts/init-docs.sh +209 -35
- package/scripts/package-quiver.sh +2 -0
- package/specs/quiver-v07-ai-context-pack/EVIDENCE_REPORT.md +24 -0
- package/specs/quiver-v07-ai-context-pack/SPEC.md +40 -0
- package/specs/quiver-v07-ai-context-pack/STATUS.md +24 -0
- package/specs/quiver-v07-ai-context-pack/slices/slice-01-ai-context-pack/slice.json +79 -0
- package/specs/quiver-v08-agent-onboarding-analysis/EVIDENCE_REPORT.md +49 -0
- package/specs/quiver-v08-agent-onboarding-analysis/SPEC.md +53 -0
- package/specs/quiver-v08-agent-onboarding-analysis/STATUS.md +26 -0
- package/specs/quiver-v08-agent-onboarding-analysis/slices/slice-01-project-scan-command/slice.json +73 -0
- package/specs/quiver-v08-agent-onboarding-analysis/slices/slice-02-ai-onboarding-prompt/slice.json +82 -0
- package/specs/quiver-v08-agent-onboarding-analysis/slices/slice-03-doctor-readme-adoption-flow/slice.json +76 -0
- package/specs/quiver-v09-onboarding-readme-flow/EVIDENCE_REPORT.md +33 -0
- package/specs/quiver-v09-onboarding-readme-flow/SPEC.md +44 -0
- package/specs/quiver-v09-onboarding-readme-flow/STATUS.md +25 -0
- package/specs/quiver-v09-onboarding-readme-flow/slices/slice-01-developer-readme-onboarding-flow/slice.json +69 -0
- package/specs/quiver-v09-onboarding-readme-flow/slices/slice-02-ai-handoff-doctor-guidance/slice.json +71 -0
- package/specs/quiver-v10-local-project-installation-guidance/EVIDENCE_REPORT.md +25 -0
- package/specs/quiver-v10-local-project-installation-guidance/SPEC.md +42 -0
- package/specs/quiver-v10-local-project-installation-guidance/STATUS.md +24 -0
- package/specs/quiver-v10-local-project-installation-guidance/slices/slice-01-local-project-installation-guidance/slice.json +75 -0
- package/specs/quiver-v11-existing-project-migration/EVIDENCE_REPORT.md +38 -0
- package/specs/quiver-v11-existing-project-migration/SPEC.md +59 -0
- package/specs/quiver-v11-existing-project-migration/STATUS.md +26 -0
- package/specs/quiver-v11-existing-project-migration/slices/slice-01-non-destructive-migrate-command/slice.json +73 -0
- package/specs/quiver-v11-existing-project-migration/slices/slice-02-version-metadata-doctor-upgrade-checks/slice.json +71 -0
- package/specs/quiver-v11-existing-project-migration/slices/slice-03-upgrade-docs-legacy-project-smokes/slice.json +78 -0
- package/src/create-quiver/index.js +757 -9
|
@@ -2,6 +2,8 @@ const fs = require('fs');
|
|
|
2
2
|
const os = require('os');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const { execFileSync } = require('child_process');
|
|
5
|
+
const cliPackageJson = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../..', 'package.json'), 'utf8'));
|
|
6
|
+
const CLI_VERSION = cliPackageJson.version || '0.0.0';
|
|
5
7
|
|
|
6
8
|
function formatError(message) {
|
|
7
9
|
return `create-quiver: ${message}`;
|
|
@@ -10,6 +12,8 @@ function formatError(message) {
|
|
|
10
12
|
function printUsage() {
|
|
11
13
|
console.log(`Usage:
|
|
12
14
|
npx create-quiver [options]
|
|
15
|
+
npx create-quiver analyze [options]
|
|
16
|
+
npx create-quiver migrate [options]
|
|
13
17
|
npx create-quiver doctor [options]
|
|
14
18
|
|
|
15
19
|
Options:
|
|
@@ -21,6 +25,8 @@ Options:
|
|
|
21
25
|
Examples:
|
|
22
26
|
npx create-quiver --name "My Project"
|
|
23
27
|
npx create-quiver --name "My Project" --dir ./my-project
|
|
28
|
+
npx create-quiver analyze --dir ./my-project
|
|
29
|
+
npx create-quiver migrate --dir ./my-project
|
|
24
30
|
npx create-quiver doctor --dir ./my-project
|
|
25
31
|
node bin/create-quiver.js doctor --dir ./my-project
|
|
26
32
|
`);
|
|
@@ -36,7 +42,16 @@ function parseArgs(argv) {
|
|
|
36
42
|
};
|
|
37
43
|
|
|
38
44
|
const args = [...argv];
|
|
39
|
-
if (args[0] === 'doctor') {
|
|
45
|
+
if (args[0] === 'doctor' || args[0] === 'analyze' || args[0] === 'migrate') {
|
|
46
|
+
result.mode = args[0];
|
|
47
|
+
args.shift();
|
|
48
|
+
} else if (args[0] === '--analyze') {
|
|
49
|
+
result.mode = 'analyze';
|
|
50
|
+
args.shift();
|
|
51
|
+
} else if (args[0] === '--migrate') {
|
|
52
|
+
result.mode = 'migrate';
|
|
53
|
+
args.shift();
|
|
54
|
+
} else if (args[0] === '--doctor') {
|
|
40
55
|
result.mode = 'doctor';
|
|
41
56
|
args.shift();
|
|
42
57
|
}
|
|
@@ -61,6 +76,11 @@ function parseArgs(argv) {
|
|
|
61
76
|
continue;
|
|
62
77
|
}
|
|
63
78
|
|
|
79
|
+
if (arg === '--migrate') {
|
|
80
|
+
result.mode = 'migrate';
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
64
84
|
if (arg === '-n' || arg === '--name' || arg === '--project-name') {
|
|
65
85
|
const value = args[++index];
|
|
66
86
|
if (!value) {
|
|
@@ -169,12 +189,99 @@ function copyTemplate(templateRoot, targetDir) {
|
|
|
169
189
|
return docsTemplateDir;
|
|
170
190
|
}
|
|
171
191
|
|
|
192
|
+
function mergeDirectoryTree(sourceDir, targetDir) {
|
|
193
|
+
if (!fs.existsSync(sourceDir)) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
198
|
+
fs.cpSync(sourceDir, targetDir, {
|
|
199
|
+
recursive: true,
|
|
200
|
+
force: false,
|
|
201
|
+
errorOnExist: false,
|
|
202
|
+
preserveTimestamps: true,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
172
206
|
function runInitDocs(repoRoot, projectName) {
|
|
173
207
|
runCommand('bash', ['docs-template/scripts/init-docs.sh', projectName], {
|
|
174
208
|
cwd: repoRoot,
|
|
175
209
|
});
|
|
176
210
|
}
|
|
177
211
|
|
|
212
|
+
function runInitDocsWithMode(repoRoot, projectName, mode) {
|
|
213
|
+
return runCommand('bash', ['docs-template/scripts/init-docs.sh', projectName], {
|
|
214
|
+
cwd: repoRoot,
|
|
215
|
+
env: {
|
|
216
|
+
...process.env,
|
|
217
|
+
QUIVER_PROJECT_NAME: projectName,
|
|
218
|
+
QUIVER_MIGRATE: mode === 'migrate' ? '1' : '0',
|
|
219
|
+
QUIVER_VERSION: CLI_VERSION,
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function writeQuiverState(projectRoot, nextState) {
|
|
225
|
+
const stateDir = path.join(projectRoot, '.quiver');
|
|
226
|
+
const statePath = path.join(stateDir, 'state.json');
|
|
227
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
228
|
+
fs.writeFileSync(statePath, `${JSON.stringify(nextState, null, 2)}\n`);
|
|
229
|
+
return statePath;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function readQuiverState(projectRoot) {
|
|
233
|
+
return readJsonIfExists(path.join(projectRoot, '.quiver', 'state.json'));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function createInitialQuiverState(projectName) {
|
|
237
|
+
const now = new Date().toISOString();
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
project_name: projectName,
|
|
241
|
+
quiver_version: CLI_VERSION,
|
|
242
|
+
initialized_version: CLI_VERSION,
|
|
243
|
+
migrated_version: null,
|
|
244
|
+
last_initialized_at: now,
|
|
245
|
+
last_migration_at: null,
|
|
246
|
+
last_analysis_at: null,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function updateQuiverStateForAnalyze(projectRoot) {
|
|
251
|
+
const currentState = readQuiverState(projectRoot);
|
|
252
|
+
|
|
253
|
+
if (!currentState) {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const nextState = {
|
|
258
|
+
...currentState,
|
|
259
|
+
quiver_version: CLI_VERSION,
|
|
260
|
+
last_analysis_at: new Date().toISOString(),
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
writeQuiverState(projectRoot, nextState);
|
|
264
|
+
return nextState;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function updateQuiverStateForMigrate(projectRoot, projectName) {
|
|
268
|
+
const currentState = readQuiverState(projectRoot);
|
|
269
|
+
const now = new Date().toISOString();
|
|
270
|
+
const nextState = {
|
|
271
|
+
...(currentState || {}),
|
|
272
|
+
project_name: projectName,
|
|
273
|
+
quiver_version: CLI_VERSION,
|
|
274
|
+
initialized_version: currentState?.initialized_version ?? null,
|
|
275
|
+
migrated_version: CLI_VERSION,
|
|
276
|
+
last_initialized_at: currentState?.last_initialized_at ?? null,
|
|
277
|
+
last_migration_at: now,
|
|
278
|
+
last_analysis_at: currentState?.last_analysis_at ?? null,
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
writeQuiverState(projectRoot, nextState);
|
|
282
|
+
return nextState;
|
|
283
|
+
}
|
|
284
|
+
|
|
178
285
|
function listGeneratedSpecDirs(projectRoot) {
|
|
179
286
|
const specsDir = path.join(projectRoot, 'specs');
|
|
180
287
|
|
|
@@ -215,6 +322,621 @@ function loadPackageJson(projectRoot) {
|
|
|
215
322
|
return JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
216
323
|
}
|
|
217
324
|
|
|
325
|
+
function readJsonIfExists(filePath) {
|
|
326
|
+
if (!fs.existsSync(filePath)) {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function readTextIfExists(filePath) {
|
|
334
|
+
if (!fs.existsSync(filePath)) {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function toRelativePath(root, absolutePath) {
|
|
342
|
+
return path.relative(root, absolutePath).split(path.sep).join('/');
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function escapeMarkdownCell(value) {
|
|
346
|
+
return String(value).replace(/\|/g, '\\|');
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function collectPackageManagers(projectRoot) {
|
|
350
|
+
const packageManagerField = readJsonIfExists(path.join(projectRoot, 'package.json'))?.packageManager;
|
|
351
|
+
|
|
352
|
+
if (typeof packageManagerField === 'string' && packageManagerField.length > 0) {
|
|
353
|
+
return packageManagerField.split('@')[0];
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const priority = [
|
|
357
|
+
['pnpm', 'pnpm-lock.yaml'],
|
|
358
|
+
['yarn', 'yarn.lock'],
|
|
359
|
+
['bun', 'bun.lockb'],
|
|
360
|
+
['bun', 'bun.lock'],
|
|
361
|
+
['npm', 'package-lock.json'],
|
|
362
|
+
];
|
|
363
|
+
|
|
364
|
+
for (const [manager, filename] of priority) {
|
|
365
|
+
if (fs.existsSync(path.join(projectRoot, filename))) {
|
|
366
|
+
return manager;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return 'unknown';
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function collectProjectFiles(projectRoot, maxDepth = 2) {
|
|
374
|
+
const files = [];
|
|
375
|
+
const skippedPaths = [];
|
|
376
|
+
const ignoredDirs = new Set([
|
|
377
|
+
'.git',
|
|
378
|
+
'node_modules',
|
|
379
|
+
'dist',
|
|
380
|
+
'build',
|
|
381
|
+
'.next',
|
|
382
|
+
'coverage',
|
|
383
|
+
'vendor',
|
|
384
|
+
'.turbo',
|
|
385
|
+
'.cache',
|
|
386
|
+
'out',
|
|
387
|
+
'tmp',
|
|
388
|
+
'docs-template',
|
|
389
|
+
]);
|
|
390
|
+
const allowedHiddenDirs = new Set(['.github', '.vscode', '.devcontainer']);
|
|
391
|
+
|
|
392
|
+
function walk(currentDir, depth, relativeDir = '') {
|
|
393
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
394
|
+
|
|
395
|
+
for (const entry of entries) {
|
|
396
|
+
const entryRelativePath = relativeDir ? path.posix.join(relativeDir, entry.name) : entry.name;
|
|
397
|
+
const absolutePath = path.join(currentDir, entry.name);
|
|
398
|
+
|
|
399
|
+
if (entry.isDirectory()) {
|
|
400
|
+
if (ignoredDirs.has(entry.name)) {
|
|
401
|
+
skippedPaths.push(entryRelativePath);
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (entry.name.startsWith('.') && !allowedHiddenDirs.has(entry.name)) {
|
|
406
|
+
skippedPaths.push(entryRelativePath);
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (depth < maxDepth) {
|
|
411
|
+
walk(absolutePath, depth + 1, entryRelativePath);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
files.push(entryRelativePath);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
walk(projectRoot, 0);
|
|
422
|
+
|
|
423
|
+
return { files, skippedPaths };
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function collectRootEntries(projectRoot) {
|
|
427
|
+
return fs.readdirSync(projectRoot, { withFileTypes: true }).map((entry) => ({
|
|
428
|
+
name: entry.name,
|
|
429
|
+
type: entry.isDirectory() ? 'directory' : 'file',
|
|
430
|
+
}));
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function detectSourceDirectories(rootEntries) {
|
|
434
|
+
const commonNames = new Set([
|
|
435
|
+
'src',
|
|
436
|
+
'app',
|
|
437
|
+
'pages',
|
|
438
|
+
'components',
|
|
439
|
+
'lib',
|
|
440
|
+
'server',
|
|
441
|
+
'client',
|
|
442
|
+
'api',
|
|
443
|
+
'packages',
|
|
444
|
+
'services',
|
|
445
|
+
'modules',
|
|
446
|
+
'tests',
|
|
447
|
+
'test',
|
|
448
|
+
'spec',
|
|
449
|
+
'stories',
|
|
450
|
+
]);
|
|
451
|
+
|
|
452
|
+
return rootEntries
|
|
453
|
+
.filter((entry) => entry.type === 'directory' && commonNames.has(entry.name))
|
|
454
|
+
.map((entry) => entry.name);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function collectLanguageSignals(files) {
|
|
458
|
+
const extensions = new Map();
|
|
459
|
+
|
|
460
|
+
for (const file of files) {
|
|
461
|
+
const ext = path.extname(file).toLowerCase();
|
|
462
|
+
|
|
463
|
+
if (!ext) {
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
extensions.set(ext, (extensions.get(ext) || 0) + 1);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const languages = [];
|
|
471
|
+
const extToLanguage = new Map([
|
|
472
|
+
['.ts', 'typescript'],
|
|
473
|
+
['.tsx', 'typescript'],
|
|
474
|
+
['.mts', 'typescript'],
|
|
475
|
+
['.cts', 'typescript'],
|
|
476
|
+
['.js', 'javascript'],
|
|
477
|
+
['.jsx', 'javascript'],
|
|
478
|
+
['.mjs', 'javascript'],
|
|
479
|
+
['.cjs', 'javascript'],
|
|
480
|
+
['.py', 'python'],
|
|
481
|
+
['.go', 'go'],
|
|
482
|
+
['.php', 'php'],
|
|
483
|
+
['.rb', 'ruby'],
|
|
484
|
+
['.rs', 'rust'],
|
|
485
|
+
['.java', 'java'],
|
|
486
|
+
['.kt', 'kotlin'],
|
|
487
|
+
['.swift', 'swift'],
|
|
488
|
+
['.cs', 'csharp'],
|
|
489
|
+
['.sh', 'shell'],
|
|
490
|
+
['.toml', 'toml'],
|
|
491
|
+
['.yaml', 'yaml'],
|
|
492
|
+
['.yml', 'yaml'],
|
|
493
|
+
]);
|
|
494
|
+
|
|
495
|
+
for (const [ext, language] of extToLanguage.entries()) {
|
|
496
|
+
if (extensions.has(ext)) {
|
|
497
|
+
languages.push(language);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return languages;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function collectWorkspaces(packageJson) {
|
|
505
|
+
if (!packageJson) {
|
|
506
|
+
return [];
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const workspaces = packageJson.workspaces;
|
|
510
|
+
|
|
511
|
+
if (Array.isArray(workspaces)) {
|
|
512
|
+
return workspaces.filter((workspace) => typeof workspace === 'string');
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (workspaces && Array.isArray(workspaces.packages)) {
|
|
516
|
+
return workspaces.packages.filter((workspace) => typeof workspace === 'string');
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return [];
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function collectDependencies(packageJson) {
|
|
523
|
+
const dependencySets = [
|
|
524
|
+
packageJson?.dependencies,
|
|
525
|
+
packageJson?.devDependencies,
|
|
526
|
+
packageJson?.peerDependencies,
|
|
527
|
+
packageJson?.optionalDependencies,
|
|
528
|
+
];
|
|
529
|
+
const dependencies = new Set();
|
|
530
|
+
|
|
531
|
+
for (const set of dependencySets) {
|
|
532
|
+
if (!set) {
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
for (const name of Object.keys(set)) {
|
|
537
|
+
dependencies.add(name);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return dependencies;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function detectFrameworks(projectRoot, files, rootEntries, packageJson) {
|
|
545
|
+
const dependencies = collectDependencies(packageJson);
|
|
546
|
+
const rootFileSet = new Set(rootEntries.filter((entry) => entry.type === 'file').map((entry) => entry.name));
|
|
547
|
+
const rootDirSet = new Set(rootEntries.filter((entry) => entry.type === 'directory').map((entry) => entry.name));
|
|
548
|
+
const frameworks = [];
|
|
549
|
+
const evidence = [];
|
|
550
|
+
|
|
551
|
+
const candidates = [
|
|
552
|
+
{
|
|
553
|
+
name: 'nextjs',
|
|
554
|
+
matches: () => dependencies.has('next') || rootFileSet.has('next.config.js') || rootFileSet.has('next.config.mjs') || rootFileSet.has('next.config.ts') || rootDirSet.has('pages') || rootDirSet.has('app'),
|
|
555
|
+
signals: ['next', 'next.config.*', 'app/pages'],
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
name: 'nuxt',
|
|
559
|
+
matches: () => dependencies.has('nuxt') || rootFileSet.has('nuxt.config.js') || rootFileSet.has('nuxt.config.ts') || rootFileSet.has('app.vue'),
|
|
560
|
+
signals: ['nuxt', 'nuxt.config.*'],
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
name: 'angular',
|
|
564
|
+
matches: () => dependencies.has('@angular/core') || rootFileSet.has('angular.json'),
|
|
565
|
+
signals: ['@angular/core', 'angular.json'],
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
name: 'sveltekit',
|
|
569
|
+
matches: () => dependencies.has('@sveltejs/kit') || rootFileSet.has('svelte.config.js') || rootFileSet.has('svelte.config.ts'),
|
|
570
|
+
signals: ['@sveltejs/kit', 'svelte.config.*'],
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
name: 'vue',
|
|
574
|
+
matches: () => dependencies.has('vue') || rootFileSet.has('vue.config.js') || rootFileSet.has('vite.config.js') || rootFileSet.has('vite.config.ts'),
|
|
575
|
+
signals: ['vue', 'vue.config.*', 'vite.config.*'],
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
name: 'react',
|
|
579
|
+
matches: () => dependencies.has('react'),
|
|
580
|
+
signals: ['react'],
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
name: 'vite',
|
|
584
|
+
matches: () => dependencies.has('vite') || rootFileSet.has('vite.config.js') || rootFileSet.has('vite.config.ts') || rootFileSet.has('vite.config.mjs'),
|
|
585
|
+
signals: ['vite', 'vite.config.*'],
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
name: 'express',
|
|
589
|
+
matches: () => dependencies.has('express'),
|
|
590
|
+
signals: ['express'],
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
name: 'python',
|
|
594
|
+
matches: () => rootFileSet.has('pyproject.toml') || rootFileSet.has('requirements.txt') || rootFileSet.has('Pipfile'),
|
|
595
|
+
signals: ['pyproject.toml', 'requirements.txt', 'Pipfile'],
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
name: 'go',
|
|
599
|
+
matches: () => rootFileSet.has('go.mod'),
|
|
600
|
+
signals: ['go.mod'],
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
name: 'php',
|
|
604
|
+
matches: () => rootFileSet.has('composer.json'),
|
|
605
|
+
signals: ['composer.json'],
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
name: 'ruby',
|
|
609
|
+
matches: () => rootFileSet.has('Gemfile'),
|
|
610
|
+
signals: ['Gemfile'],
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
name: 'rust',
|
|
614
|
+
matches: () => rootFileSet.has('Cargo.toml'),
|
|
615
|
+
signals: ['Cargo.toml'],
|
|
616
|
+
},
|
|
617
|
+
];
|
|
618
|
+
|
|
619
|
+
for (const candidate of candidates) {
|
|
620
|
+
if (candidate.matches()) {
|
|
621
|
+
frameworks.push(candidate.name);
|
|
622
|
+
evidence.push({ framework: candidate.name, signals: candidate.signals });
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const languages = collectLanguageSignals(files);
|
|
627
|
+
|
|
628
|
+
if (frameworks.length === 0 && languages.includes('typescript') && dependencies.has('react')) {
|
|
629
|
+
frameworks.push('react');
|
|
630
|
+
evidence.push({ framework: 'react', signals: ['react', 'typescript files'] });
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const primary = frameworks[0] || 'unknown';
|
|
634
|
+
|
|
635
|
+
return {
|
|
636
|
+
primary,
|
|
637
|
+
frameworks,
|
|
638
|
+
languages,
|
|
639
|
+
evidence,
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function detectConfigFiles(rootEntries) {
|
|
644
|
+
const rootFiles = rootEntries.filter((entry) => entry.type === 'file').map((entry) => entry.name);
|
|
645
|
+
const configNames = new Set([
|
|
646
|
+
'package.json',
|
|
647
|
+
'pnpm-lock.yaml',
|
|
648
|
+
'yarn.lock',
|
|
649
|
+
'package-lock.json',
|
|
650
|
+
'bun.lockb',
|
|
651
|
+
'bun.lock',
|
|
652
|
+
'pyproject.toml',
|
|
653
|
+
'requirements.txt',
|
|
654
|
+
'Pipfile',
|
|
655
|
+
'go.mod',
|
|
656
|
+
'composer.json',
|
|
657
|
+
'Cargo.toml',
|
|
658
|
+
'Gemfile',
|
|
659
|
+
'angular.json',
|
|
660
|
+
'tsconfig.json',
|
|
661
|
+
'tsconfig.app.json',
|
|
662
|
+
'tsconfig.node.json',
|
|
663
|
+
'vite.config.js',
|
|
664
|
+
'vite.config.ts',
|
|
665
|
+
'vite.config.mjs',
|
|
666
|
+
'next.config.js',
|
|
667
|
+
'next.config.mjs',
|
|
668
|
+
'next.config.ts',
|
|
669
|
+
'nuxt.config.js',
|
|
670
|
+
'nuxt.config.ts',
|
|
671
|
+
'svelte.config.js',
|
|
672
|
+
'svelte.config.ts',
|
|
673
|
+
'vue.config.js',
|
|
674
|
+
'eslint.config.js',
|
|
675
|
+
'.eslintrc',
|
|
676
|
+
'.eslintrc.js',
|
|
677
|
+
'.eslintrc.json',
|
|
678
|
+
'.prettierrc',
|
|
679
|
+
'.prettierrc.js',
|
|
680
|
+
'.prettierrc.json',
|
|
681
|
+
]);
|
|
682
|
+
|
|
683
|
+
return rootFiles.filter((name) => configNames.has(name));
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function detectWorkflowFiles(files) {
|
|
687
|
+
return files.filter((file) => file.startsWith('.github/workflows/') && /\.(ya?ml)$/i.test(file));
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function detectDocsFiles(files) {
|
|
691
|
+
return files.filter((file) => file === 'README.md' || file.startsWith('docs/'));
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function detectRisks(projectRoot, scan) {
|
|
695
|
+
const risks = [];
|
|
696
|
+
|
|
697
|
+
if (!scan.project.has_package_json) {
|
|
698
|
+
risks.push('package.json is missing, so command detection is limited');
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if (!scan.docs.has_readme) {
|
|
702
|
+
risks.push('README.md is missing, so onboarding guidance is limited');
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
if (scan.ci.github_actions_workflows.length === 0) {
|
|
706
|
+
risks.push('no GitHub Actions workflows were found');
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
if (scan.stack.primary === 'unknown') {
|
|
710
|
+
risks.push('no primary framework could be inferred from the repository signals');
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
if (scan.structure.source_directories.length === 0) {
|
|
714
|
+
risks.push('no common source directory names were found at the repository root');
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
if (scan.skipped_paths.length === 0) {
|
|
718
|
+
risks.push('no large or secret-like paths were skipped, or the repository is very small');
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
return risks;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function buildProjectScan(projectRoot) {
|
|
725
|
+
const packageJson = readJsonIfExists(path.join(projectRoot, 'package.json'));
|
|
726
|
+
const rootEntries = collectRootEntries(projectRoot);
|
|
727
|
+
const { files, skippedPaths } = collectProjectFiles(projectRoot);
|
|
728
|
+
const topLevelDirectories = rootEntries.filter((entry) => entry.type === 'directory' && !entry.name.startsWith('.')).map((entry) => entry.name);
|
|
729
|
+
const sourceDirectories = detectSourceDirectories(rootEntries);
|
|
730
|
+
const configFiles = detectConfigFiles(rootEntries);
|
|
731
|
+
const workflowFiles = detectWorkflowFiles(files);
|
|
732
|
+
const docsFiles = detectDocsFiles(files);
|
|
733
|
+
const stack = detectFrameworks(projectRoot, files, rootEntries, packageJson);
|
|
734
|
+
const packageManager = collectPackageManagers(projectRoot);
|
|
735
|
+
const workspaces = collectWorkspaces(packageJson);
|
|
736
|
+
const scripts = packageJson?.scripts && typeof packageJson.scripts === 'object' ? packageJson.scripts : {};
|
|
737
|
+
const projectName = packageJson?.name || path.basename(projectRoot) || 'unknown';
|
|
738
|
+
const hasReadme = fs.existsSync(path.join(projectRoot, 'README.md'));
|
|
739
|
+
const generatedDocs = docsFiles.filter((file) => file.startsWith('docs/'));
|
|
740
|
+
|
|
741
|
+
const scan = {
|
|
742
|
+
project: {
|
|
743
|
+
name: projectName,
|
|
744
|
+
root_name: path.basename(projectRoot),
|
|
745
|
+
has_package_json: Boolean(packageJson),
|
|
746
|
+
package_manager: packageManager,
|
|
747
|
+
workspaces,
|
|
748
|
+
scripts,
|
|
749
|
+
top_level_files: rootEntries.filter((entry) => entry.type === 'file').map((entry) => entry.name),
|
|
750
|
+
top_level_directories: topLevelDirectories,
|
|
751
|
+
},
|
|
752
|
+
stack: {
|
|
753
|
+
primary: stack.primary,
|
|
754
|
+
frameworks: stack.frameworks,
|
|
755
|
+
languages: stack.languages,
|
|
756
|
+
evidence: stack.evidence,
|
|
757
|
+
},
|
|
758
|
+
commands: {
|
|
759
|
+
install: packageManager === 'pnpm' ? 'pnpm install' : packageManager === 'yarn' ? 'yarn install' : packageManager === 'bun' ? 'bun install' : 'npm install',
|
|
760
|
+
scripts,
|
|
761
|
+
common: {
|
|
762
|
+
dev: scripts.dev || scripts.start || '',
|
|
763
|
+
build: scripts.build || '',
|
|
764
|
+
test: scripts.test || '',
|
|
765
|
+
lint: scripts.lint || '',
|
|
766
|
+
},
|
|
767
|
+
},
|
|
768
|
+
structure: {
|
|
769
|
+
top_level_directories: topLevelDirectories,
|
|
770
|
+
source_directories: sourceDirectories,
|
|
771
|
+
config_files: configFiles,
|
|
772
|
+
workspace_patterns: workspaces,
|
|
773
|
+
},
|
|
774
|
+
ci: {
|
|
775
|
+
github_actions_workflows: workflowFiles,
|
|
776
|
+
has_ci: workflowFiles.length > 0,
|
|
777
|
+
},
|
|
778
|
+
docs: {
|
|
779
|
+
has_readme: hasReadme,
|
|
780
|
+
files: docsFiles,
|
|
781
|
+
generated_files: generatedDocs,
|
|
782
|
+
},
|
|
783
|
+
risks: [],
|
|
784
|
+
skipped_paths: skippedPaths,
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
scan.risks = detectRisks(projectRoot, scan);
|
|
788
|
+
|
|
789
|
+
return scan;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function renderProjectMap(scan) {
|
|
793
|
+
const lines = [];
|
|
794
|
+
|
|
795
|
+
lines.push('# Project Map');
|
|
796
|
+
lines.push('');
|
|
797
|
+
lines.push('## Project');
|
|
798
|
+
lines.push(`- Name: ${scan.project.name}`);
|
|
799
|
+
lines.push(`- Root folder: ${scan.project.root_name}`);
|
|
800
|
+
lines.push(`- Package manager: ${scan.project.package_manager}`);
|
|
801
|
+
lines.push(`- package.json present: ${scan.project.has_package_json ? 'yes' : 'no'}`);
|
|
802
|
+
if (scan.project.workspaces.length > 0) {
|
|
803
|
+
lines.push(`- Workspaces: ${scan.project.workspaces.join(', ')}`);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
lines.push('');
|
|
807
|
+
lines.push('## Stack');
|
|
808
|
+
lines.push(`- Primary: ${scan.stack.primary}`);
|
|
809
|
+
lines.push(`- Frameworks: ${scan.stack.frameworks.length > 0 ? scan.stack.frameworks.join(', ') : 'none detected'}`);
|
|
810
|
+
lines.push(`- Languages: ${scan.stack.languages.length > 0 ? scan.stack.languages.join(', ') : 'none detected'}`);
|
|
811
|
+
|
|
812
|
+
if (scan.stack.evidence.length > 0) {
|
|
813
|
+
lines.push('');
|
|
814
|
+
lines.push('### Evidence');
|
|
815
|
+
for (const item of scan.stack.evidence) {
|
|
816
|
+
lines.push(`- ${item.framework}: ${item.signals.join(', ')}`);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
lines.push('');
|
|
821
|
+
lines.push('## Commands');
|
|
822
|
+
lines.push('| Command | Value |');
|
|
823
|
+
lines.push('|---------|-------|');
|
|
824
|
+
lines.push(`| Install | ${escapeMarkdownCell(scan.commands.install || 'npm install')} |`);
|
|
825
|
+
lines.push(`| dev | ${escapeMarkdownCell(scan.commands.common.dev || 'not defined')} |`);
|
|
826
|
+
lines.push(`| build | ${escapeMarkdownCell(scan.commands.common.build || 'not defined')} |`);
|
|
827
|
+
lines.push(`| test | ${escapeMarkdownCell(scan.commands.common.test || 'not defined')} |`);
|
|
828
|
+
lines.push(`| lint | ${escapeMarkdownCell(scan.commands.common.lint || 'not defined')} |`);
|
|
829
|
+
|
|
830
|
+
if (Object.keys(scan.commands.scripts).length > 0) {
|
|
831
|
+
lines.push('');
|
|
832
|
+
lines.push('### package.json scripts');
|
|
833
|
+
for (const [name, command] of Object.entries(scan.commands.scripts)) {
|
|
834
|
+
lines.push(`- ${name}: \`${command}\``);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
lines.push('');
|
|
839
|
+
lines.push('## Structure');
|
|
840
|
+
lines.push(`- Top-level directories: ${scan.structure.top_level_directories.length > 0 ? scan.structure.top_level_directories.join(', ') : 'none detected'}`);
|
|
841
|
+
lines.push(`- Source directories: ${scan.structure.source_directories.length > 0 ? scan.structure.source_directories.join(', ') : 'none detected'}`);
|
|
842
|
+
lines.push(`- Config files: ${scan.structure.config_files.length > 0 ? scan.structure.config_files.join(', ') : 'none detected'}`);
|
|
843
|
+
|
|
844
|
+
lines.push('');
|
|
845
|
+
lines.push('## CI');
|
|
846
|
+
lines.push(`- GitHub Actions workflows: ${scan.ci.github_actions_workflows.length > 0 ? scan.ci.github_actions_workflows.join(', ') : 'none detected'}`);
|
|
847
|
+
|
|
848
|
+
lines.push('');
|
|
849
|
+
lines.push('## Docs');
|
|
850
|
+
lines.push(`- README present: ${scan.docs.has_readme ? 'yes' : 'no'}`);
|
|
851
|
+
lines.push(`- Docs files: ${scan.docs.files.length > 0 ? scan.docs.files.join(', ') : 'none detected'}`);
|
|
852
|
+
|
|
853
|
+
lines.push('');
|
|
854
|
+
lines.push('## Risks');
|
|
855
|
+
if (scan.risks.length > 0) {
|
|
856
|
+
for (const risk of scan.risks) {
|
|
857
|
+
lines.push(`- ${risk}`);
|
|
858
|
+
}
|
|
859
|
+
} else {
|
|
860
|
+
lines.push('- No major onboarding risks detected.');
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
lines.push('');
|
|
864
|
+
lines.push('## Skipped Paths');
|
|
865
|
+
if (scan.skipped_paths.length > 0) {
|
|
866
|
+
for (const skippedPath of scan.skipped_paths) {
|
|
867
|
+
lines.push(`- ${skippedPath}`);
|
|
868
|
+
}
|
|
869
|
+
} else {
|
|
870
|
+
lines.push('- None');
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
lines.push('');
|
|
874
|
+
return lines.join('\n');
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function writeProjectScanArtifacts(projectRoot, scan) {
|
|
878
|
+
const docsDir = path.join(projectRoot, 'docs');
|
|
879
|
+
ensureDir(docsDir);
|
|
880
|
+
|
|
881
|
+
const jsonPath = path.join(docsDir, 'PROJECT_SCAN.json');
|
|
882
|
+
const mdPath = path.join(docsDir, 'PROJECT_MAP.md');
|
|
883
|
+
|
|
884
|
+
fs.writeFileSync(jsonPath, `${JSON.stringify(scan, null, 2)}\n`);
|
|
885
|
+
fs.writeFileSync(mdPath, `${renderProjectMap(scan)}\n`);
|
|
886
|
+
|
|
887
|
+
return { jsonPath, mdPath };
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
function runAnalyze(targetDir) {
|
|
891
|
+
const projectRoot = path.resolve(process.cwd(), targetDir);
|
|
892
|
+
|
|
893
|
+
if (!fs.existsSync(projectRoot)) {
|
|
894
|
+
throw new Error(formatError(`target directory does not exist: ${projectRoot}`));
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
const scan = buildProjectScan(projectRoot);
|
|
898
|
+
const artifacts = writeProjectScanArtifacts(projectRoot, scan);
|
|
899
|
+
updateQuiverStateForAnalyze(projectRoot);
|
|
900
|
+
|
|
901
|
+
console.log(`Project analysis completed for ${projectRoot}`);
|
|
902
|
+
console.log(`Wrote ${path.relative(projectRoot, artifacts.jsonPath)}`);
|
|
903
|
+
console.log(`Wrote ${path.relative(projectRoot, artifacts.mdPath)}`);
|
|
904
|
+
console.log(`Detected primary stack: ${scan.stack.primary}`);
|
|
905
|
+
console.log(`Detected package manager: ${scan.project.package_manager}`);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
function runMigrate(targetDir) {
|
|
909
|
+
const projectRoot = path.resolve(process.cwd(), targetDir);
|
|
910
|
+
|
|
911
|
+
if (!fs.existsSync(projectRoot)) {
|
|
912
|
+
throw new Error(formatError(`target directory does not exist: ${projectRoot}`));
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
const packageJson = loadPackageJson(projectRoot);
|
|
916
|
+
const projectName = packageJson.name || path.basename(projectRoot) || 'Quiver Project';
|
|
917
|
+
const packageRoot = path.resolve(__dirname, '../..');
|
|
918
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'quiver-migrate-'));
|
|
919
|
+
|
|
920
|
+
try {
|
|
921
|
+
const templateRoot = packTemplate(packageRoot, tempRoot);
|
|
922
|
+
mergeDirectoryTree(templateRoot, path.join(projectRoot, 'docs-template'));
|
|
923
|
+
const migrationOutput = runInitDocsWithMode(projectRoot, projectName, 'migrate');
|
|
924
|
+
updateQuiverStateForMigrate(projectRoot, projectName);
|
|
925
|
+
|
|
926
|
+
if (migrationOutput.trim().length > 0) {
|
|
927
|
+
process.stdout.write(migrationOutput);
|
|
928
|
+
if (!migrationOutput.endsWith('\n')) {
|
|
929
|
+
process.stdout.write('\n');
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
console.log(`Quiver migration completed for ${projectRoot}`);
|
|
934
|
+
console.log('Missing workflow files were restored without overwriting existing project files.');
|
|
935
|
+
} finally {
|
|
936
|
+
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
218
940
|
function runDoctor(targetDir) {
|
|
219
941
|
const projectRoot = path.resolve(process.cwd(), targetDir);
|
|
220
942
|
|
|
@@ -231,6 +953,8 @@ function runDoctor(targetDir) {
|
|
|
231
953
|
const requiredFiles = [
|
|
232
954
|
'README.md',
|
|
233
955
|
'docs/INDEX.md',
|
|
956
|
+
'docs/AI_CONTEXT.md',
|
|
957
|
+
'docs/AI_ONBOARDING_PROMPT.md',
|
|
234
958
|
'docs/CONTEXTO.md',
|
|
235
959
|
'docs/WORKFLOW.md',
|
|
236
960
|
'docs/SUPPORT_MATRIX.md',
|
|
@@ -260,25 +984,39 @@ function runDoctor(targetDir) {
|
|
|
260
984
|
const missingFiles = assertFilesExist(projectRoot, requiredFiles);
|
|
261
985
|
const nonExecutableScripts = assertExecutablesExist(projectRoot, requiredExecutables);
|
|
262
986
|
const pkg = loadPackageJson(projectRoot);
|
|
263
|
-
const requiredScripts = ['check:slice', 'check:pr', 'start:slice', 'cleanup:slice'];
|
|
987
|
+
const requiredScripts = ['check:slice', 'check:pr', 'start:slice', 'cleanup:slice', 'migrate'];
|
|
264
988
|
const missingScripts = requiredScripts.filter((name) => typeof pkg.scripts?.[name] !== 'string');
|
|
265
|
-
|
|
266
|
-
|
|
989
|
+
const hasScanArtifacts = fs.existsSync(path.join(projectRoot, 'docs', 'PROJECT_SCAN.json'))
|
|
990
|
+
&& fs.existsSync(path.join(projectRoot, 'docs', 'PROJECT_MAP.md'));
|
|
991
|
+
const quiverState = readQuiverState(projectRoot);
|
|
992
|
+
const hasQuiverState = Boolean(quiverState);
|
|
993
|
+
const stateWarnings = hasQuiverState ? [] : ['missing Quiver state metadata: .quiver/state.json'];
|
|
994
|
+
const migrationProblems = [
|
|
267
995
|
...missingFiles.map((file) => `missing file: ${file}`),
|
|
268
996
|
...nonExecutableScripts.map((file) => `missing executable bit: ${file}`),
|
|
269
997
|
...missingScripts.map((name) => `missing package.json script: ${name}`),
|
|
270
998
|
];
|
|
271
999
|
|
|
272
|
-
if (
|
|
273
|
-
throw new Error(formatError(`doctor failed:\n- ${
|
|
1000
|
+
if (migrationProblems.length > 0) {
|
|
1001
|
+
throw new Error(formatError(`doctor failed:\n- ${migrationProblems.join('\n- ')}\n- Run migration first: npx create-quiver migrate --dir .`));
|
|
274
1002
|
}
|
|
275
1003
|
|
|
276
1004
|
console.log(`Quiver doctor passed for ${projectRoot}`);
|
|
277
1005
|
console.log(`Generated project slug: ${projectSlug}`);
|
|
278
1006
|
console.log('Next steps:');
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
1007
|
+
for (const warning of stateWarnings) {
|
|
1008
|
+
console.log(`- Warning: ${warning}`);
|
|
1009
|
+
}
|
|
1010
|
+
if (!hasQuiverState) {
|
|
1011
|
+
console.log('- Run migration first: npx create-quiver migrate --dir .');
|
|
1012
|
+
} else if (!hasScanArtifacts) {
|
|
1013
|
+
console.log('- Analyze the project first: npx create-quiver analyze --dir .');
|
|
1014
|
+
} else {
|
|
1015
|
+
console.log('- Ask your AI agent: Read docs/AI_ONBOARDING_PROMPT.md and execute it.');
|
|
1016
|
+
}
|
|
1017
|
+
console.log(`- Start a slice: bash tools/scripts/start-slice.sh specs/${projectSlug}/slices/slice-template/slice.json`);
|
|
1018
|
+
console.log('- Validate a slice: bash tools/scripts/check-slice-readiness.sh');
|
|
1019
|
+
console.log('- Validate the PR gate: bash tools/scripts/check-pr-readiness.sh');
|
|
282
1020
|
}
|
|
283
1021
|
|
|
284
1022
|
function printInitNextSteps(targetDir, projectName) {
|
|
@@ -300,6 +1038,16 @@ async function run(argv) {
|
|
|
300
1038
|
return;
|
|
301
1039
|
}
|
|
302
1040
|
|
|
1041
|
+
if (args.mode === 'analyze') {
|
|
1042
|
+
runAnalyze(args.targetDir);
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
if (args.mode === 'migrate') {
|
|
1047
|
+
runMigrate(args.targetDir);
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
303
1051
|
if (args.mode === 'doctor') {
|
|
304
1052
|
runDoctor(args.targetDir);
|
|
305
1053
|
return;
|