@dawitworku/projectcli 0.2.2 → 3.0.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/CHANGELOG.md +29 -0
- package/README.md +98 -0
- package/ROADMAP.md +23 -0
- package/docs/demo.cast +445 -0
- package/docs/demo.svg +254637 -0
- package/package.json +13 -3
- package/scripts/release-notes.js +34 -0
- package/src/add.js +164 -1
- package/src/config.js +66 -0
- package/src/core/doctor/index.js +504 -0
- package/src/index.js +184 -37
- package/src/plugin.js +140 -0
- package/src/plugins/contributions.js +101 -0
- package/src/plugins/manager.js +38 -0
- package/src/preflight.js +26 -0
- package/src/preset.js +59 -0
- package/src/presets/index.js +124 -0
- package/src/registry/index.js +91 -0
- package/src/registry.js +3 -909
- package/src/registry_legacy.js +911 -0
- package/src/settings.js +11 -0
- package/src/upgrade.js +228 -0
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
const fs = require("node:fs");
|
|
2
|
+
const path = require("node:path");
|
|
3
|
+
const chalk = require("chalk");
|
|
4
|
+
|
|
5
|
+
const { detectLanguage, detectPackageManager } = require("../../detect");
|
|
6
|
+
const { generateCI, generateDocker } = require("../../cicd");
|
|
7
|
+
const { generateLicense, licenseTypes } = require("../../license");
|
|
8
|
+
const { pmAddCommand } = require("../../pm");
|
|
9
|
+
const { runSteps } = require("../../run");
|
|
10
|
+
const { getPreset } = require("../../presets");
|
|
11
|
+
const { getPluginDoctorChecks } = require("../../plugins/contributions");
|
|
12
|
+
|
|
13
|
+
function exists(projectRoot, relPath) {
|
|
14
|
+
return fs.existsSync(path.join(projectRoot, relPath));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function listFilesSafe(dir) {
|
|
18
|
+
try {
|
|
19
|
+
return fs.readdirSync(dir);
|
|
20
|
+
} catch {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function detectGitHubActions(projectRoot) {
|
|
26
|
+
const workflowsDir = path.join(projectRoot, ".github", "workflows");
|
|
27
|
+
const files = listFilesSafe(workflowsDir);
|
|
28
|
+
return files.some((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readJsonSafe(filePath) {
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function detectNodeTooling(projectRoot) {
|
|
40
|
+
const pkgPath = path.join(projectRoot, "package.json");
|
|
41
|
+
const pkg = readJsonSafe(pkgPath) || {};
|
|
42
|
+
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
43
|
+
|
|
44
|
+
const hasPrettier =
|
|
45
|
+
Boolean(deps.prettier) ||
|
|
46
|
+
exists(projectRoot, ".prettierrc") ||
|
|
47
|
+
exists(projectRoot, ".prettierrc.json") ||
|
|
48
|
+
exists(projectRoot, ".prettierrc.yml") ||
|
|
49
|
+
exists(projectRoot, ".prettierrc.yaml") ||
|
|
50
|
+
exists(projectRoot, ".prettierrc.js") ||
|
|
51
|
+
exists(projectRoot, "prettier.config.js") ||
|
|
52
|
+
Boolean(pkg.prettier);
|
|
53
|
+
|
|
54
|
+
const hasEslint =
|
|
55
|
+
Boolean(deps.eslint) ||
|
|
56
|
+
exists(projectRoot, "eslint.config.js") ||
|
|
57
|
+
exists(projectRoot, ".eslintrc") ||
|
|
58
|
+
exists(projectRoot, ".eslintrc.json") ||
|
|
59
|
+
exists(projectRoot, ".eslintrc.js") ||
|
|
60
|
+
exists(projectRoot, ".eslintrc.cjs") ||
|
|
61
|
+
Boolean(pkg.eslintConfig);
|
|
62
|
+
|
|
63
|
+
const scripts = pkg.scripts || {};
|
|
64
|
+
const hasTests =
|
|
65
|
+
Boolean(scripts.test) ||
|
|
66
|
+
exists(projectRoot, "test") ||
|
|
67
|
+
exists(projectRoot, "tests") ||
|
|
68
|
+
Boolean(deps.vitest) ||
|
|
69
|
+
Boolean(deps.jest) ||
|
|
70
|
+
Boolean(deps.mocha);
|
|
71
|
+
|
|
72
|
+
return { hasPrettier, hasEslint, hasTests };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function filterExistingWriteFiles(steps, projectRoot) {
|
|
76
|
+
const kept = [];
|
|
77
|
+
const skipped = [];
|
|
78
|
+
for (const step of steps || []) {
|
|
79
|
+
if (step && step.type === "writeFile" && typeof step.path === "string") {
|
|
80
|
+
const target = path.resolve(projectRoot, step.path);
|
|
81
|
+
if (fs.existsSync(target)) {
|
|
82
|
+
skipped.push(step.path);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
kept.push(step);
|
|
87
|
+
}
|
|
88
|
+
return { kept, skipped };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function printStepsPreview(steps) {
|
|
92
|
+
console.log(chalk.bold.cyan("\nPlanned actions:"));
|
|
93
|
+
for (const step of steps) {
|
|
94
|
+
const type = step.type || "command";
|
|
95
|
+
if (type === "command") {
|
|
96
|
+
const where = step.cwdFromProjectRoot ? "(in project)" : "(here)";
|
|
97
|
+
console.log(
|
|
98
|
+
chalk.gray("- ") +
|
|
99
|
+
chalk.green(`${step.program} ${(step.args || []).join(" ")}`) +
|
|
100
|
+
chalk.dim(` ${where}`)
|
|
101
|
+
);
|
|
102
|
+
} else if (type === "mkdir") {
|
|
103
|
+
console.log(chalk.gray("- ") + chalk.yellow(`mkdir -p ${step.path}`));
|
|
104
|
+
} else if (type === "writeFile") {
|
|
105
|
+
console.log(chalk.gray("- ") + chalk.yellow(`write ${step.path}`));
|
|
106
|
+
} else {
|
|
107
|
+
console.log(chalk.gray(`- ${type}`));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
console.log("");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function parseDoctorArgs(argv) {
|
|
114
|
+
const out = {
|
|
115
|
+
fix: false,
|
|
116
|
+
json: false,
|
|
117
|
+
ciOnly: false,
|
|
118
|
+
yes: false,
|
|
119
|
+
dryRun: false,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const args = Array.isArray(argv) ? argv : [];
|
|
123
|
+
for (const a of args) {
|
|
124
|
+
if (a === "--fix") out.fix = true;
|
|
125
|
+
else if (a === "--json") out.json = true;
|
|
126
|
+
else if (a === "--ci-only") out.ciOnly = true;
|
|
127
|
+
else if (a === "--yes" || a === "-y") out.yes = true;
|
|
128
|
+
else if (a === "--dry-run") out.dryRun = true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function formatCheckLine(check) {
|
|
135
|
+
const symbol = check.ok ? chalk.green("✓") : chalk.red("✗");
|
|
136
|
+
return `${symbol} ${check.title}`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function runDoctor({ prompt, argv, effectiveConfig }) {
|
|
140
|
+
const flags = parseDoctorArgs(argv);
|
|
141
|
+
const projectRoot = process.cwd();
|
|
142
|
+
const language = detectLanguage(projectRoot);
|
|
143
|
+
const pm = detectPackageManager(projectRoot);
|
|
144
|
+
|
|
145
|
+
const presetId =
|
|
146
|
+
typeof effectiveConfig?.preset === "string" && effectiveConfig.preset.trim()
|
|
147
|
+
? effectiveConfig.preset.trim()
|
|
148
|
+
: "startup";
|
|
149
|
+
const preset = getPreset(presetId, effectiveConfig);
|
|
150
|
+
const presetDefaults = preset?.defaults || {};
|
|
151
|
+
|
|
152
|
+
const checks = [];
|
|
153
|
+
|
|
154
|
+
const hasCI = detectGitHubActions(projectRoot);
|
|
155
|
+
const hasDocker = exists(projectRoot, "Dockerfile");
|
|
156
|
+
const hasLicense =
|
|
157
|
+
exists(projectRoot, "LICENSE") || exists(projectRoot, "LICENSE.md");
|
|
158
|
+
|
|
159
|
+
const langForTemplates =
|
|
160
|
+
language === "JavaScript/TypeScript" ? "JavaScript" : language;
|
|
161
|
+
|
|
162
|
+
if (!flags.ciOnly) {
|
|
163
|
+
checks.push({
|
|
164
|
+
id: "missing-ci",
|
|
165
|
+
title: `GitHub Actions CI (${langForTemplates})`,
|
|
166
|
+
ok: Boolean(hasCI),
|
|
167
|
+
fixable: langForTemplates !== "Unknown",
|
|
168
|
+
fixId: "add-ci",
|
|
169
|
+
});
|
|
170
|
+
} else {
|
|
171
|
+
checks.push({
|
|
172
|
+
id: "missing-ci",
|
|
173
|
+
title: `GitHub Actions CI (${langForTemplates})`,
|
|
174
|
+
ok: Boolean(hasCI),
|
|
175
|
+
fixable: langForTemplates !== "Unknown",
|
|
176
|
+
fixId: "add-ci",
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (!flags.ciOnly) {
|
|
181
|
+
checks.push({
|
|
182
|
+
id: "missing-docker",
|
|
183
|
+
title: "Dockerfile",
|
|
184
|
+
ok: Boolean(hasDocker),
|
|
185
|
+
fixable: langForTemplates !== "Unknown",
|
|
186
|
+
fixId: "add-docker",
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
checks.push({
|
|
190
|
+
id: "missing-license",
|
|
191
|
+
title: "LICENSE",
|
|
192
|
+
ok: Boolean(hasLicense),
|
|
193
|
+
fixable: true,
|
|
194
|
+
fixId: "add-license",
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (language === "JavaScript/TypeScript") {
|
|
198
|
+
const node = detectNodeTooling(projectRoot);
|
|
199
|
+
|
|
200
|
+
checks.push({
|
|
201
|
+
id: "missing-formatter",
|
|
202
|
+
title: "Formatter (Prettier)",
|
|
203
|
+
ok: Boolean(node.hasPrettier),
|
|
204
|
+
fixable: pm === "npm" || pm === "pnpm" || pm === "yarn" || pm === "bun",
|
|
205
|
+
fixId: "add-prettier",
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
checks.push({
|
|
209
|
+
id: "missing-linter",
|
|
210
|
+
title: "Linter (ESLint)",
|
|
211
|
+
ok: Boolean(node.hasEslint),
|
|
212
|
+
fixable: pm === "npm" || pm === "pnpm" || pm === "yarn" || pm === "bun",
|
|
213
|
+
fixId: "add-eslint",
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
checks.push({
|
|
217
|
+
id: "missing-tests",
|
|
218
|
+
title: "Tests",
|
|
219
|
+
ok: Boolean(node.hasTests),
|
|
220
|
+
fixable: pm === "npm" || pm === "pnpm" || pm === "yarn" || pm === "bun",
|
|
221
|
+
fixId: "add-vitest",
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Plugin checks
|
|
227
|
+
const pluginChecks = getPluginDoctorChecks(effectiveConfig);
|
|
228
|
+
if (!flags.ciOnly && pluginChecks.length > 0) {
|
|
229
|
+
for (const pc of pluginChecks) {
|
|
230
|
+
const title =
|
|
231
|
+
typeof pc.title === "string" && pc.title.trim()
|
|
232
|
+
? pc.title.trim()
|
|
233
|
+
: `${pc.pluginId}:${pc.id}`;
|
|
234
|
+
|
|
235
|
+
let ok = false;
|
|
236
|
+
let detectError = null;
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
const res =
|
|
240
|
+
typeof pc.detect === "function"
|
|
241
|
+
? await pc.detect({
|
|
242
|
+
projectRoot,
|
|
243
|
+
language,
|
|
244
|
+
packageManager: pm,
|
|
245
|
+
effectiveConfig,
|
|
246
|
+
preset,
|
|
247
|
+
})
|
|
248
|
+
: false;
|
|
249
|
+
|
|
250
|
+
if (typeof res === "boolean") ok = res;
|
|
251
|
+
else if (res && typeof res.ok === "boolean") ok = res.ok;
|
|
252
|
+
else ok = Boolean(res);
|
|
253
|
+
} catch (err) {
|
|
254
|
+
ok = false;
|
|
255
|
+
detectError = err && err.message ? err.message : String(err);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const fixable = typeof pc.fix === "function";
|
|
259
|
+
|
|
260
|
+
checks.push({
|
|
261
|
+
id: pc.id,
|
|
262
|
+
title,
|
|
263
|
+
ok,
|
|
264
|
+
fixable,
|
|
265
|
+
fixId: `plugin:${pc.pluginId}:${pc.id}`,
|
|
266
|
+
pluginId: pc.pluginId,
|
|
267
|
+
error: detectError || undefined,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const ok = checks.every((c) => c.ok);
|
|
273
|
+
|
|
274
|
+
if (flags.json) {
|
|
275
|
+
console.log(
|
|
276
|
+
JSON.stringify(
|
|
277
|
+
{
|
|
278
|
+
ok,
|
|
279
|
+
projectRoot,
|
|
280
|
+
language,
|
|
281
|
+
packageManager: pm,
|
|
282
|
+
checks,
|
|
283
|
+
},
|
|
284
|
+
null,
|
|
285
|
+
2
|
|
286
|
+
)
|
|
287
|
+
);
|
|
288
|
+
process.exitCode = ok ? 0 : 1;
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
console.log(chalk.bold(`\nProjectCLI Doctor`));
|
|
293
|
+
console.log(chalk.dim(`Project: ${projectRoot}`));
|
|
294
|
+
console.log(chalk.dim(`Detected: ${language} (${pm})\n`));
|
|
295
|
+
|
|
296
|
+
for (const check of checks) {
|
|
297
|
+
console.log(formatCheckLine(check));
|
|
298
|
+
if (!check.ok && check.error) {
|
|
299
|
+
console.log(chalk.dim(` → ${check.error}`));
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const missingFixable = checks.filter((c) => !c.ok && c.fixable);
|
|
304
|
+
if (missingFixable.length === 0) {
|
|
305
|
+
if (!ok) {
|
|
306
|
+
console.log(
|
|
307
|
+
chalk.yellow(
|
|
308
|
+
"\nSome checks failed, but there are no safe automatic fixes for this project yet."
|
|
309
|
+
)
|
|
310
|
+
);
|
|
311
|
+
} else {
|
|
312
|
+
console.log(chalk.green("\nAll good."));
|
|
313
|
+
}
|
|
314
|
+
process.exitCode = ok ? 0 : 1;
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const shouldFix = flags.fix
|
|
319
|
+
? true
|
|
320
|
+
: await (async () => {
|
|
321
|
+
const { go } = await prompt([
|
|
322
|
+
{
|
|
323
|
+
type: "confirm",
|
|
324
|
+
name: "go",
|
|
325
|
+
message: "\nFix now?",
|
|
326
|
+
default: true,
|
|
327
|
+
},
|
|
328
|
+
]);
|
|
329
|
+
return Boolean(go);
|
|
330
|
+
})();
|
|
331
|
+
|
|
332
|
+
if (!shouldFix) {
|
|
333
|
+
process.exitCode = ok ? 0 : 1;
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const defaultFixEnabled = (fixId) => {
|
|
338
|
+
if (!fixId) return true;
|
|
339
|
+
if (fixId === "add-ci") return presetDefaults?.extras?.ci !== false;
|
|
340
|
+
if (fixId === "add-docker") return presetDefaults?.extras?.docker === true;
|
|
341
|
+
if (fixId === "add-license")
|
|
342
|
+
return presetDefaults?.extras?.license !== false;
|
|
343
|
+
|
|
344
|
+
if (fixId === "add-prettier") return presetDefaults?.js?.formatter === true;
|
|
345
|
+
if (fixId === "add-eslint") return presetDefaults?.js?.linter === true;
|
|
346
|
+
if (fixId === "add-vitest") return presetDefaults?.js?.tests === true;
|
|
347
|
+
return true;
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
const selectable = missingFixable.map((c) => ({
|
|
351
|
+
name: c.title,
|
|
352
|
+
value: c.fixId,
|
|
353
|
+
checked: defaultFixEnabled(c.fixId),
|
|
354
|
+
}));
|
|
355
|
+
|
|
356
|
+
const selectedFixIds = flags.yes
|
|
357
|
+
? selectable.filter((s) => s.checked).map((s) => s.value)
|
|
358
|
+
: (
|
|
359
|
+
await prompt([
|
|
360
|
+
{
|
|
361
|
+
type: "checkbox",
|
|
362
|
+
name: "picked",
|
|
363
|
+
message: "Select fixes to apply:",
|
|
364
|
+
choices: selectable,
|
|
365
|
+
},
|
|
366
|
+
])
|
|
367
|
+
).picked;
|
|
368
|
+
|
|
369
|
+
const fixIds = Array.isArray(selectedFixIds) ? selectedFixIds : [];
|
|
370
|
+
if (fixIds.length === 0) {
|
|
371
|
+
console.log(chalk.dim("\nNo fixes selected."));
|
|
372
|
+
process.exitCode = ok ? 0 : 1;
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const steps = [];
|
|
377
|
+
|
|
378
|
+
if (fixIds.includes("add-ci")) {
|
|
379
|
+
steps.push(...generateCI(projectRoot, langForTemplates, pm));
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (fixIds.includes("add-docker")) {
|
|
383
|
+
steps.push(...generateDocker(projectRoot, langForTemplates));
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (fixIds.includes("add-license")) {
|
|
387
|
+
const type =
|
|
388
|
+
typeof effectiveConfig?.license === "string" &&
|
|
389
|
+
licenseTypes.includes(effectiveConfig.license)
|
|
390
|
+
? effectiveConfig.license
|
|
391
|
+
: "MIT";
|
|
392
|
+
const author =
|
|
393
|
+
typeof effectiveConfig?.author === "string" &&
|
|
394
|
+
effectiveConfig.author.trim()
|
|
395
|
+
? effectiveConfig.author.trim()
|
|
396
|
+
: "The Authors";
|
|
397
|
+
|
|
398
|
+
steps.push(...generateLicense(projectRoot, type, author));
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (fixIds.includes("add-prettier")) {
|
|
402
|
+
const cmd = pmAddCommand(pm, ["prettier"], { dev: true });
|
|
403
|
+
steps.push({ type: "command", ...cmd, cwdFromProjectRoot: true });
|
|
404
|
+
steps.push({
|
|
405
|
+
type: "writeFile",
|
|
406
|
+
path: ".prettierrc.json",
|
|
407
|
+
content:
|
|
408
|
+
JSON.stringify({ semi: true, singleQuote: false }, null, 2) + "\n",
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (fixIds.includes("add-eslint")) {
|
|
413
|
+
const cmd = pmAddCommand(pm, ["eslint"], { dev: true });
|
|
414
|
+
steps.push({ type: "command", ...cmd, cwdFromProjectRoot: true });
|
|
415
|
+
steps.push({
|
|
416
|
+
type: "writeFile",
|
|
417
|
+
path: "eslint.config.js",
|
|
418
|
+
content:
|
|
419
|
+
"export default [\n" +
|
|
420
|
+
" {\n" +
|
|
421
|
+
' files: ["**/*.js", "**/*.cjs", "**/*.mjs"],\n' +
|
|
422
|
+
" languageOptions: { ecmaVersion: 2022 },\n" +
|
|
423
|
+
" rules: {\n" +
|
|
424
|
+
' "no-unused-vars": "warn",\n' +
|
|
425
|
+
' "no-undef": "error",\n' +
|
|
426
|
+
" },\n" +
|
|
427
|
+
" },\n" +
|
|
428
|
+
"];\n",
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (fixIds.includes("add-vitest")) {
|
|
433
|
+
const cmd = pmAddCommand(pm, ["vitest"], { dev: true });
|
|
434
|
+
steps.push({ type: "command", ...cmd, cwdFromProjectRoot: true });
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Plugin fixes
|
|
438
|
+
const pluginFixes = fixIds.filter((id) => String(id).startsWith("plugin:"));
|
|
439
|
+
if (pluginFixes.length > 0) {
|
|
440
|
+
const allPluginChecks = getPluginDoctorChecks(effectiveConfig);
|
|
441
|
+
for (const fixId of pluginFixes) {
|
|
442
|
+
const parts = String(fixId).split(":");
|
|
443
|
+
if (parts.length < 3) continue;
|
|
444
|
+
const pluginId = parts[1];
|
|
445
|
+
const checkId = parts.slice(2).join(":");
|
|
446
|
+
const pc = allPluginChecks.find(
|
|
447
|
+
(c) => c.pluginId === pluginId && c.id === checkId
|
|
448
|
+
);
|
|
449
|
+
if (!pc || typeof pc.fix !== "function") continue;
|
|
450
|
+
|
|
451
|
+
const produced = await pc.fix({
|
|
452
|
+
projectRoot,
|
|
453
|
+
language,
|
|
454
|
+
packageManager: pm,
|
|
455
|
+
effectiveConfig,
|
|
456
|
+
preset,
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
if (Array.isArray(produced)) {
|
|
460
|
+
steps.push(...produced);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const { kept, skipped } = filterExistingWriteFiles(steps, projectRoot);
|
|
466
|
+
|
|
467
|
+
if (flags.dryRun) {
|
|
468
|
+
printStepsPreview(kept);
|
|
469
|
+
if (skipped.length > 0) {
|
|
470
|
+
console.log(chalk.dim(`Skipped existing files: ${skipped.join(", ")}`));
|
|
471
|
+
}
|
|
472
|
+
console.log("Dry run: nothing executed.");
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
printStepsPreview(kept);
|
|
477
|
+
|
|
478
|
+
if (!flags.yes) {
|
|
479
|
+
const { ok: proceed } = await prompt([
|
|
480
|
+
{
|
|
481
|
+
type: "confirm",
|
|
482
|
+
name: "ok",
|
|
483
|
+
message: "Proceed?",
|
|
484
|
+
default: true,
|
|
485
|
+
},
|
|
486
|
+
]);
|
|
487
|
+
if (!proceed) return;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (kept.length > 0) {
|
|
491
|
+
await runSteps(kept, { projectRoot });
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
if (skipped.length > 0) {
|
|
495
|
+
console.log(chalk.dim(`Skipped existing files: ${skipped.join(", ")}`));
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
console.log(chalk.green("\nDone."));
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
module.exports = {
|
|
502
|
+
runDoctor,
|
|
503
|
+
parseDoctorArgs,
|
|
504
|
+
};
|