@precisa-saude/cli 1.0.1

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.
Files changed (34) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +131 -0
  3. package/dist/bin.d.ts +1 -0
  4. package/dist/bin.js +640 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/index.d.ts +86 -0
  7. package/dist/index.js +630 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/templates/README.md +51 -0
  10. package/dist/templates/dotfiles/editorconfig +12 -0
  11. package/dist/templates/dotfiles/gitignore +10 -0
  12. package/dist/templates/dotfiles/npmrc +1 -0
  13. package/dist/templates/dotfiles/nvmrc +1 -0
  14. package/dist/templates/dotfiles/prettierignore +7 -0
  15. package/dist/templates/dotfiles/prettierrc +1 -0
  16. package/dist/templates/dotfiles/renovate.json +29 -0
  17. package/dist/templates/github/ISSUE_TEMPLATE/bug.md +33 -0
  18. package/dist/templates/github/ISSUE_TEMPLATE/config.yml +8 -0
  19. package/dist/templates/github/ISSUE_TEMPLATE/feature.md +18 -0
  20. package/dist/templates/github/PULL_REQUEST_TEMPLATE.md +18 -0
  21. package/dist/templates/github/workflows/ci.yml +44 -0
  22. package/dist/templates/github/workflows/release.yml +62 -0
  23. package/dist/templates/github/workflows/review.yml +435 -0
  24. package/dist/templates/governance/CITATION.cff +8 -0
  25. package/dist/templates/governance/CODE_OF_CONDUCT.md +43 -0
  26. package/dist/templates/governance/CONTRIBUTING.md +32 -0
  27. package/dist/templates/governance/CONVENTIONS.md +37 -0
  28. package/dist/templates/governance/SECURITY.md +22 -0
  29. package/dist/templates/governance/SUPPORT.md +14 -0
  30. package/dist/templates/husky/commit-msg +8 -0
  31. package/dist/templates/husky/pre-commit +1 -0
  32. package/dist/templates/husky/pre-push +21 -0
  33. package/dist/templates/templates.manifest.yml +125 -0
  34. package/package.json +62 -0
package/dist/index.js ADDED
@@ -0,0 +1,630 @@
1
+ // src/commands/doctor.ts
2
+ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
3
+ import { resolve as resolve4 } from "path";
4
+ import chalk from "chalk";
5
+
6
+ // src/lib/templates.ts
7
+ import { readFileSync } from "fs";
8
+ import { resolve as resolve2 } from "path";
9
+ import { parse as parseYaml } from "yaml";
10
+
11
+ // src/lib/paths.ts
12
+ import { existsSync } from "fs";
13
+ import { dirname, resolve } from "path";
14
+ import { fileURLToPath } from "url";
15
+ function resolveTemplatesDir() {
16
+ const here = dirname(fileURLToPath(import.meta.url));
17
+ const bundled = resolve(here, "templates");
18
+ if (existsSync(bundled)) return bundled;
19
+ const repoRoot = resolve(here, "..", "..", "..", "..");
20
+ const repoTemplates = resolve(repoRoot, "templates");
21
+ if (existsSync(repoTemplates)) return repoTemplates;
22
+ const packageParent = resolve(here, "..", "..", "templates");
23
+ if (existsSync(packageParent)) return packageParent;
24
+ throw new Error(
25
+ `Could not locate templates/ directory. Searched:
26
+ ${bundled}
27
+ ${repoTemplates}
28
+ ${packageParent}`
29
+ );
30
+ }
31
+
32
+ // src/lib/templates.ts
33
+ function loadTemplateManifest() {
34
+ const dir = resolveTemplatesDir();
35
+ const manifestPath = resolve2(dir, "templates.manifest.yml");
36
+ const raw = readFileSync(manifestPath, "utf-8");
37
+ const parsed = parseYaml(raw);
38
+ if (!parsed || !Array.isArray(parsed.templates)) {
39
+ throw new Error(`${manifestPath}: expected { templates: [...] } at root`);
40
+ }
41
+ for (const [i, t] of parsed.templates.entries()) {
42
+ if (!t.source || !t.target) {
43
+ throw new Error(`${manifestPath}: entry ${i} is missing source or target`);
44
+ }
45
+ }
46
+ return parsed.templates;
47
+ }
48
+ function readTemplateSource(entry) {
49
+ const dir = resolveTemplatesDir();
50
+ const path = resolve2(dir, entry.source);
51
+ return readFileSync(path, "utf-8");
52
+ }
53
+ function renderTokens(source, context) {
54
+ return source.replace(/\{\{([A-Z_]+)\}\}/g, (match, token) => {
55
+ return Object.prototype.hasOwnProperty.call(context, token) ? context[token] : match;
56
+ });
57
+ }
58
+
59
+ // src/manifest.ts
60
+ import { readFileSync as readFileSync2, writeFileSync } from "fs";
61
+ import { resolve as resolve3 } from "path";
62
+ var MANIFEST_FILENAME = ".precisa.json";
63
+ var DEFAULT_MANIFEST_FIELDS = {
64
+ hasPackages: true,
65
+ hasSite: false,
66
+ nodeVersion: "22",
67
+ pnpmVersion: "9.15.9",
68
+ publishesToNpm: true,
69
+ schemaVersion: 1,
70
+ visibility: "oss"
71
+ };
72
+ function isRequired(when, manifest) {
73
+ switch (when) {
74
+ case "always":
75
+ return true;
76
+ case "oss":
77
+ return manifest.visibility === "oss";
78
+ case "private":
79
+ return manifest.visibility === "private";
80
+ case "has_site":
81
+ return manifest.hasSite;
82
+ case "has_packages":
83
+ return manifest.hasPackages;
84
+ case "publishes_to_npm":
85
+ return manifest.publishesToNpm;
86
+ default:
87
+ return true;
88
+ }
89
+ }
90
+ function tokenContext(manifest) {
91
+ return {
92
+ COMMIT_SCOPES: manifest.commitScopes.join(","),
93
+ CONDUCT_EMAIL: manifest.contactEmails.conduct,
94
+ HAS_PACKAGES: String(manifest.hasPackages),
95
+ HAS_SITE: String(manifest.hasSite),
96
+ NODE_VERSION: manifest.nodeVersion ?? "22",
97
+ OWNER_ORG: manifest.owner,
98
+ PNPM_VERSION: manifest.pnpmVersion ?? "9.15.9",
99
+ PUBLISHES_TO_NPM: String(manifest.publishesToNpm),
100
+ REPO_NAME: manifest.name,
101
+ REPO_SLUG: `${manifest.owner}/${manifest.name}`,
102
+ SECURITY_EMAIL: manifest.contactEmails.security,
103
+ VISIBILITY: manifest.visibility
104
+ };
105
+ }
106
+ function validateManifest(raw) {
107
+ const errors = [];
108
+ const m = raw;
109
+ if (!m || typeof m !== "object") {
110
+ return [{ message: "manifest must be a JSON object", path: "$" }];
111
+ }
112
+ if (m.schemaVersion !== 1) {
113
+ errors.push({ message: "must be 1", path: "schemaVersion" });
114
+ }
115
+ if (typeof m.name !== "string" || !m.name) {
116
+ errors.push({ message: "must be a non-empty string", path: "name" });
117
+ }
118
+ if (typeof m.owner !== "string" || !m.owner) {
119
+ errors.push({ message: "must be a non-empty string", path: "owner" });
120
+ }
121
+ if (m.visibility !== "oss" && m.visibility !== "private") {
122
+ errors.push({ message: "must be 'oss' or 'private'", path: "visibility" });
123
+ }
124
+ for (const key of ["hasSite", "hasPackages", "publishesToNpm"]) {
125
+ if (typeof m[key] !== "boolean") {
126
+ errors.push({ message: "must be a boolean", path: key });
127
+ }
128
+ }
129
+ if (!Array.isArray(m.commitScopes)) {
130
+ errors.push({ message: "must be an array of strings", path: "commitScopes" });
131
+ }
132
+ if (!m.contactEmails || typeof m.contactEmails !== "object") {
133
+ errors.push({ message: "must be an object", path: "contactEmails" });
134
+ } else {
135
+ for (const key of ["security", "conduct"]) {
136
+ if (typeof m.contactEmails[key] !== "string" || !m.contactEmails[key]) {
137
+ errors.push({
138
+ message: "must be a non-empty string",
139
+ path: `contactEmails.${key}`
140
+ });
141
+ }
142
+ }
143
+ }
144
+ return errors;
145
+ }
146
+ function loadManifest(cwd) {
147
+ const path = resolve3(cwd, MANIFEST_FILENAME);
148
+ let raw;
149
+ try {
150
+ raw = JSON.parse(readFileSync2(path, "utf-8"));
151
+ } catch (err) {
152
+ throw new Error(`Failed to read ${MANIFEST_FILENAME} at ${path}: ${err.message}`);
153
+ }
154
+ const errors = validateManifest(raw);
155
+ if (errors.length > 0) {
156
+ const detail = errors.map((e) => ` - ${e.path}: ${e.message}`).join("\n");
157
+ throw new Error(`Invalid ${MANIFEST_FILENAME}:
158
+ ${detail}`);
159
+ }
160
+ return raw;
161
+ }
162
+ function writeManifest(cwd, manifest) {
163
+ const path = resolve3(cwd, MANIFEST_FILENAME);
164
+ writeFileSync(path, `${JSON.stringify(manifest, null, 2)}
165
+ `);
166
+ }
167
+
168
+ // src/commands/doctor.ts
169
+ async function runDoctor() {
170
+ const cwd = process.cwd();
171
+ console.log(chalk.bold.cyan("\nprecisa doctor"));
172
+ let manifest;
173
+ try {
174
+ manifest = loadManifest(cwd);
175
+ } catch (err) {
176
+ console.error(chalk.red(`
177
+ ${err.message}`));
178
+ process.exit(1);
179
+ }
180
+ const entries = loadTemplateManifest();
181
+ const context = tokenContext(manifest);
182
+ const reports = [];
183
+ for (const entry of entries) {
184
+ const required = isRequired(entry.required_when, manifest);
185
+ const targetPath = resolve4(cwd, entry.target);
186
+ const exists = existsSync2(targetPath);
187
+ if (!required && !exists) continue;
188
+ if (!required && exists) {
189
+ reports.push({
190
+ message: `Present but not required by the manifest profile (ok)`,
191
+ severity: "info",
192
+ target: entry.target
193
+ });
194
+ continue;
195
+ }
196
+ if (!exists) {
197
+ reports.push({
198
+ message: "Missing",
199
+ severity: "error",
200
+ target: entry.target
201
+ });
202
+ continue;
203
+ }
204
+ const source = readTemplateSource(entry);
205
+ const rendered = renderTokens(source, context);
206
+ const current = readFileSync3(targetPath, "utf-8");
207
+ reports.push(compareContent(entry, current, rendered));
208
+ }
209
+ console.log("");
210
+ let errors = 0;
211
+ let warnings = 0;
212
+ let infos = 0;
213
+ let oks = 0;
214
+ for (const r of reports) {
215
+ const icon = iconFor(r.severity);
216
+ const line = `${icon} ${r.target}${r.message ? chalk.dim(` \u2014 ${r.message}`) : ""}`;
217
+ console.log(line);
218
+ if (r.severity === "error") errors += 1;
219
+ else if (r.severity === "warning") warnings += 1;
220
+ else if (r.severity === "info") infos += 1;
221
+ else oks += 1;
222
+ }
223
+ console.log("");
224
+ console.log(chalk.dim(`${oks} ok, ${warnings} warning, ${errors} error, ${infos} info`));
225
+ if (errors > 0) process.exit(1);
226
+ }
227
+ function compareContent(entry, current, rendered) {
228
+ if (entry.merge_strategy === "preserve") {
229
+ return {
230
+ message: current === rendered ? "" : "Differs from template (preserve strategy \u2014 suggestion only)",
231
+ severity: current === rendered ? "ok" : "info",
232
+ target: entry.target
233
+ };
234
+ }
235
+ if (current === rendered) {
236
+ return { message: "", severity: "ok", target: entry.target };
237
+ }
238
+ return {
239
+ message: "Differs from template (run `precisa sync` to update)",
240
+ severity: "warning",
241
+ target: entry.target
242
+ };
243
+ }
244
+ function iconFor(severity) {
245
+ switch (severity) {
246
+ case "ok":
247
+ return chalk.green("\u2713");
248
+ case "info":
249
+ return chalk.blue("i");
250
+ case "warning":
251
+ return chalk.yellow("!");
252
+ case "error":
253
+ return chalk.red("\u2717");
254
+ }
255
+ }
256
+
257
+ // src/commands/new.ts
258
+ import { execSync } from "child_process";
259
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync } from "fs";
260
+ import { resolve as resolve6 } from "path";
261
+ import chalk2 from "chalk";
262
+ import ora from "ora";
263
+ import prompts from "prompts";
264
+
265
+ // src/lib/merge.ts
266
+ import { chmodSync, existsSync as existsSync3, mkdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
267
+ import { dirname as dirname2, resolve as resolve5 } from "path";
268
+ function applyTemplate({ cwd, dryRun, entry, rendered }) {
269
+ const targetPath = resolve5(cwd, entry.target);
270
+ const exists = existsSync3(targetPath);
271
+ if (!exists) {
272
+ if (!dryRun) writeFile(targetPath, rendered, entry.executable === true);
273
+ return { kind: "create", rendered, target: entry.target };
274
+ }
275
+ const current = readFileSync4(targetPath, "utf-8");
276
+ switch (entry.merge_strategy) {
277
+ case "overwrite": {
278
+ if (current === rendered) {
279
+ return { kind: "skip-identical", target: entry.target };
280
+ }
281
+ if (!dryRun) writeFile(targetPath, rendered, entry.executable === true);
282
+ return { kind: "update", previous: current, rendered, target: entry.target };
283
+ }
284
+ case "skip_if_exists": {
285
+ return { kind: "skip-exists", target: entry.target };
286
+ }
287
+ case "preserve": {
288
+ return { current, kind: "preserve", rendered, target: entry.target };
289
+ }
290
+ case "merge_json": {
291
+ let merged;
292
+ try {
293
+ merged = mergeJson(current, rendered);
294
+ } catch (err) {
295
+ return {
296
+ kind: "error",
297
+ message: `JSON merge failed: ${err.message}`,
298
+ target: entry.target
299
+ };
300
+ }
301
+ if (current === merged) {
302
+ return { kind: "skip-identical", target: entry.target };
303
+ }
304
+ if (!dryRun) writeFile(targetPath, merged, entry.executable === true);
305
+ return { kind: "merge-json", previous: current, rendered: merged, target: entry.target };
306
+ }
307
+ default: {
308
+ return {
309
+ kind: "error",
310
+ message: `Unknown merge_strategy '${entry.merge_strategy}'`,
311
+ target: entry.target
312
+ };
313
+ }
314
+ }
315
+ }
316
+ function writeFile(path, contents, executable) {
317
+ mkdirSync(dirname2(path), { recursive: true });
318
+ writeFileSync2(path, contents);
319
+ if (executable) chmodSync(path, 493);
320
+ }
321
+ function mergeJson(currentRaw, templateRaw) {
322
+ const current = JSON.parse(currentRaw);
323
+ const template = JSON.parse(templateRaw);
324
+ const merged = mergeValue(template, current);
325
+ return `${JSON.stringify(merged, null, 2)}
326
+ `;
327
+ }
328
+ function mergeValue(template, current) {
329
+ if (current === void 0) return template;
330
+ if (typeof template !== "object" || template === null || Array.isArray(template) || typeof current !== "object" || current === null || Array.isArray(current)) {
331
+ return current;
332
+ }
333
+ const out = {};
334
+ const keys = /* @__PURE__ */ new Set([
335
+ ...Object.keys(template),
336
+ ...Object.keys(current)
337
+ ]);
338
+ for (const k of keys) {
339
+ out[k] = mergeValue(
340
+ template[k],
341
+ current[k]
342
+ );
343
+ }
344
+ return out;
345
+ }
346
+
347
+ // src/commands/new.ts
348
+ var PROFILES = {
349
+ "oss-library": {
350
+ hasPackages: true,
351
+ hasSite: false,
352
+ publishesToNpm: true,
353
+ visibility: "oss"
354
+ },
355
+ "oss-site": {
356
+ hasPackages: false,
357
+ hasSite: true,
358
+ publishesToNpm: false,
359
+ visibility: "oss"
360
+ },
361
+ "private-app": {
362
+ hasPackages: true,
363
+ hasSite: true,
364
+ publishesToNpm: false,
365
+ visibility: "private"
366
+ }
367
+ };
368
+ async function runNew(repoName, opts) {
369
+ const targetDir = resolve6(process.cwd(), repoName);
370
+ if (existsSync4(targetDir) && readdirSync(targetDir).length > 0) {
371
+ console.error(chalk2.red(`
372
+ Error: ${targetDir} already exists and is not empty.`));
373
+ process.exit(1);
374
+ }
375
+ const profile = PROFILES[opts.profile];
376
+ if (!profile) {
377
+ console.error(
378
+ chalk2.red(`
379
+ Error: unknown profile '${opts.profile}'. Choose one of:`),
380
+ Object.keys(PROFILES).join(", ")
381
+ );
382
+ process.exit(1);
383
+ }
384
+ console.log(chalk2.bold.cyan(`
385
+ precisa new ${repoName}`));
386
+ console.log(chalk2.dim(`profile: ${opts.profile}${opts.dryRun ? " (dry-run)" : ""}`));
387
+ console.log(chalk2.dim(`target: ${targetDir}
388
+ `));
389
+ const answers = await prompts(
390
+ [
391
+ {
392
+ initial: "Precisa-Saude",
393
+ message: "GitHub owner (org or user):",
394
+ name: "owner",
395
+ type: "text"
396
+ },
397
+ {
398
+ initial: "security@precisa-saude.com.br",
399
+ message: "Security contact email:",
400
+ name: "securityEmail",
401
+ type: "text"
402
+ },
403
+ {
404
+ initial: "conduct@precisa-saude.com.br",
405
+ message: "Code-of-conduct contact email:",
406
+ name: "conductEmail",
407
+ type: "text"
408
+ },
409
+ {
410
+ initial: "docs,ci,deps",
411
+ message: "Commit scopes (comma-separated):",
412
+ name: "commitScopes",
413
+ separator: ",",
414
+ type: "list"
415
+ }
416
+ ],
417
+ { onCancel: () => process.exit(1) }
418
+ );
419
+ const manifest = {
420
+ ...DEFAULT_MANIFEST_FIELDS,
421
+ ...profile,
422
+ commitScopes: answers.commitScopes,
423
+ contactEmails: {
424
+ conduct: answers.conductEmail,
425
+ security: answers.securityEmail
426
+ },
427
+ name: repoName,
428
+ owner: answers.owner
429
+ };
430
+ const spinner = ora("Rendering templates").start();
431
+ try {
432
+ if (!opts.dryRun) mkdirSync2(targetDir, { recursive: true });
433
+ const entries = loadTemplateManifest().filter((e) => isRequired(e.required_when, manifest));
434
+ const context = tokenContext(manifest);
435
+ let created = 0;
436
+ let skipped = 0;
437
+ for (const entry of entries) {
438
+ const source = readTemplateSource(entry);
439
+ const rendered = renderTokens(source, context);
440
+ const outcome = applyTemplate({
441
+ cwd: targetDir,
442
+ dryRun: opts.dryRun,
443
+ entry,
444
+ rendered
445
+ });
446
+ if (outcome.kind === "create") created += 1;
447
+ else if (outcome.kind === "skip-exists") skipped += 1;
448
+ else if (outcome.kind === "error") {
449
+ spinner.fail(`Failed on ${entry.target}`);
450
+ console.error(chalk2.red(outcome.message));
451
+ process.exit(1);
452
+ }
453
+ }
454
+ spinner.succeed(
455
+ opts.dryRun ? `Would render ${created} file${created === 1 ? "" : "s"} (${skipped} skipped)` : `Rendered ${created} file${created === 1 ? "" : "s"} (${skipped} skipped)`
456
+ );
457
+ if (opts.dryRun) return;
458
+ writeManifest(targetDir, manifest);
459
+ console.log(chalk2.dim(` wrote .precisa.json`));
460
+ const gitStep = ora("git init + pnpm install").start();
461
+ try {
462
+ execSync("git init --initial-branch=main", { cwd: targetDir, stdio: "pipe" });
463
+ execSync("pnpm install", { cwd: targetDir, stdio: "pipe" });
464
+ gitStep.succeed("git init + pnpm install complete");
465
+ } catch (err) {
466
+ gitStep.warn(
467
+ `git/pnpm setup skipped: ${err.message}. Run manually in ${targetDir}.`
468
+ );
469
+ }
470
+ console.log(chalk2.bold.green(`
471
+ \u2713 ${repoName} scaffolded at ${targetDir}`));
472
+ console.log(chalk2.dim("\nNext:"));
473
+ console.log(chalk2.dim(` cd ${repoName}`));
474
+ console.log(chalk2.dim(` # start adding your code`));
475
+ } catch (err) {
476
+ spinner.fail(err.message);
477
+ process.exit(1);
478
+ }
479
+ }
480
+
481
+ // src/commands/sync.ts
482
+ import chalk4 from "chalk";
483
+
484
+ // src/lib/diff.ts
485
+ import chalk3 from "chalk";
486
+ import { createPatch } from "diff";
487
+ function colorDiff(filename, previous, next) {
488
+ const patch = createPatch(filename, previous, next, "", "");
489
+ const lines = patch.split("\n");
490
+ const out = [];
491
+ for (const line of lines) {
492
+ if (line.startsWith("---") || line.startsWith("+++")) continue;
493
+ if (line.startsWith("@@")) {
494
+ out.push(chalk3.cyan(line));
495
+ continue;
496
+ }
497
+ if (line.startsWith("+")) {
498
+ out.push(chalk3.green(line));
499
+ continue;
500
+ }
501
+ if (line.startsWith("-")) {
502
+ out.push(chalk3.red(line));
503
+ continue;
504
+ }
505
+ out.push(line);
506
+ }
507
+ return out.join("\n");
508
+ }
509
+
510
+ // src/commands/sync.ts
511
+ async function runSync(opts) {
512
+ const cwd = process.cwd();
513
+ console.log(chalk4.bold.cyan(`
514
+ precisa sync${opts.dryRun ? " --dry-run" : ""}`));
515
+ let manifest;
516
+ try {
517
+ manifest = loadManifest(cwd);
518
+ } catch (err) {
519
+ console.error(chalk4.red(`
520
+ ${err.message}`));
521
+ console.error(
522
+ chalk4.dim("\n Run `precisa new <repo>` in an empty directory to scaffold a new repo,")
523
+ );
524
+ console.error(chalk4.dim(" or create a `.precisa.json` manifest manually."));
525
+ process.exit(1);
526
+ }
527
+ const entries = loadTemplateManifest().filter((e) => isRequired(e.required_when, manifest));
528
+ const context = tokenContext(manifest);
529
+ const outcomes = [];
530
+ for (const entry of entries) {
531
+ const source = readTemplateSource(entry);
532
+ const rendered = renderTokens(source, context);
533
+ outcomes.push(
534
+ applyTemplate({
535
+ cwd,
536
+ dryRun: opts.dryRun,
537
+ entry,
538
+ rendered
539
+ })
540
+ );
541
+ }
542
+ console.log("");
543
+ for (const o of outcomes) {
544
+ printOutcome(o, opts.dryRun);
545
+ }
546
+ const summary = summarize(outcomes);
547
+ console.log("");
548
+ console.log(
549
+ chalk4.dim(
550
+ `${summary.create} create, ${summary.update} update, ${summary.mergeJson} merge-json, ${summary.preserve} preserve, ${summary.skip} skip, ${summary.error} error`
551
+ )
552
+ );
553
+ if (summary.error > 0) process.exit(1);
554
+ }
555
+ function summarize(outcomes) {
556
+ const s = { create: 0, error: 0, mergeJson: 0, preserve: 0, skip: 0, update: 0 };
557
+ for (const o of outcomes) {
558
+ switch (o.kind) {
559
+ case "create":
560
+ s.create += 1;
561
+ break;
562
+ case "update":
563
+ s.update += 1;
564
+ break;
565
+ case "merge-json":
566
+ s.mergeJson += 1;
567
+ break;
568
+ case "preserve":
569
+ s.preserve += 1;
570
+ break;
571
+ case "skip-identical":
572
+ case "skip-exists":
573
+ s.skip += 1;
574
+ break;
575
+ case "error":
576
+ s.error += 1;
577
+ break;
578
+ }
579
+ }
580
+ return s;
581
+ }
582
+ function printOutcome(o, dryRun) {
583
+ const prefix = dryRun ? "(dry-run) " : "";
584
+ switch (o.kind) {
585
+ case "create":
586
+ console.log(`${chalk4.green("+")} ${prefix}create ${o.target}`);
587
+ return;
588
+ case "update":
589
+ console.log(`${chalk4.yellow("~")} ${prefix}update ${o.target}`);
590
+ if (dryRun) console.log(indent(colorDiff(o.target, o.previous, o.rendered)));
591
+ return;
592
+ case "merge-json":
593
+ console.log(`${chalk4.yellow("~")} ${prefix}merge ${o.target}`);
594
+ if (dryRun) console.log(indent(colorDiff(o.target, o.previous, o.rendered)));
595
+ return;
596
+ case "preserve":
597
+ console.log(
598
+ `${chalk4.blue("i")} ${prefix}preserve ${o.target} (template differs; not written)`
599
+ );
600
+ if (dryRun && o.current !== o.rendered) {
601
+ console.log(indent(colorDiff(o.target, o.current, o.rendered)));
602
+ }
603
+ return;
604
+ case "skip-identical":
605
+ console.log(`${chalk4.dim("=")} ${prefix}unchanged ${o.target}`);
606
+ return;
607
+ case "skip-exists":
608
+ console.log(`${chalk4.dim("s")} ${prefix}skip ${o.target} (already exists)`);
609
+ return;
610
+ case "error":
611
+ console.log(`${chalk4.red("!")} ${prefix}error ${o.target}: ${o.message}`);
612
+ return;
613
+ }
614
+ }
615
+ function indent(text) {
616
+ return text.split("\n").map((l) => ` ${l}`).join("\n");
617
+ }
618
+ export {
619
+ DEFAULT_MANIFEST_FIELDS,
620
+ MANIFEST_FILENAME,
621
+ isRequired,
622
+ loadManifest,
623
+ runDoctor,
624
+ runNew,
625
+ runSync,
626
+ tokenContext,
627
+ validateManifest,
628
+ writeManifest
629
+ };
630
+ //# sourceMappingURL=index.js.map