project-auto-wizard 0.14.0 → 0.15.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.
@@ -179,6 +179,11 @@ export function runDoctor(cwd = process.cwd(), { exec = defaultExec } = {}) {
179
179
  return results;
180
180
  }
181
181
 
182
+ // Exit code for the CLI: 0 when every item is OK/INFO, 1 when any WARN or FAIL item was found.
183
+ // Warnings and errors are not split: both mean "something the workflows rely on needs attention",
184
+ // and a script that wants to tolerate warnings can ignore the code (`|| true`).
185
+ export const doctorExitCode = (results) => (results.some((r) => r.status === "WARN" || r.status === "FAIL") ? 1 : 0);
186
+
182
187
  const asLines = (v) => (Array.isArray(v) ? v : v ? [String(v)] : []);
183
188
  const headOf = (r) => `${r.label || r.name}${r.purpose ? ` — ${r.purpose}` : ""}`;
184
189
 
@@ -144,6 +144,7 @@ export function printDryRun(plan) {
144
144
  "skip-no-readme": t("cmd.dryRun.readme.skipNoReadme"),
145
145
  "skip-marker": t("cmd.dryRun.readme.skipMarker"),
146
146
  "skip-version-line": t("cmd.dryRun.readme.skipVersionLine"),
147
+ "heading-updated": t("cmd.dryRun.readme.headingUpdated"),
147
148
  }[plan.readme] || t("cmd.dryRun.readme.other", { status: plan.readme }));
148
149
  }
149
150
  if (plan.baselineExists !== undefined) {
@@ -15,15 +15,66 @@ export const README_STATUS_LABEL = {
15
15
  get "skip-no-readme"() { return t("copy.readme.status.skipNoReadme"); },
16
16
  get "skip-marker"() { return t("copy.readme.status.skipMarker"); },
17
17
  get "skip-version-line"() { return t("copy.readme.status.skipVersionLine"); },
18
+ get "heading-updated"() { return t("copy.readme.status.headingUpdated"); },
18
19
  };
19
20
 
21
+ // Bundled default heading text ("Latest Version", ...) without the "## " prefix and version part, per language.
22
+ const defaultHeading = (lang) => t("copy.readme.versionHeading", { version: "0" }, lang).replace(/^##\s*/, "").replace(/\s*:\s*v0$/, "");
23
+ const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
24
+
25
+ // Text of the history link line in a given language (without the trailing newline).
26
+ const historyLink = (lang) => t("copy.readme.historyLink", {}, lang);
27
+
28
+ // Rewrites the wizard-made block (marker line, heading line, blank line, history link line) that was written
29
+ // in another language so it matches the current one. Only text that exactly equals another language's bundled
30
+ // default is replaced: a heading or link the user edited is left alone, and so is anything not directly
31
+ // under the marker (code-block examples, a README without the marker).
32
+ // Returns the new content, or null when nothing needs to change.
33
+ function refreshBlockLanguage(content) {
34
+ const currentHeading = defaultHeading();
35
+ const currentLink = historyLink();
36
+ const markerRe = new RegExp("^" + escapeRe(MARKER_LINE.trimEnd()) + "[ \\t]*\\r?\\n", "gm");
37
+ let out = "";
38
+ let last = 0;
39
+ let changed = false;
40
+ for (let m = markerRe.exec(content); m; m = markerRe.exec(content)) {
41
+ // An odd number of fence lines before the marker means it sits inside a code block (an example).
42
+ if ((content.slice(0, m.index).match(/^[ \t]*(```|~~~)/gm) || []).length % 2 === 1) continue;
43
+ const start = m.index + m[0].length;
44
+ let rest = content.slice(start);
45
+ let head = "";
46
+ // Heading line directly under the marker.
47
+ for (const lang of SUPPORTED_LANGUAGES) {
48
+ const heading = defaultHeading(lang);
49
+ if (heading === currentHeading) continue;
50
+ const hm = new RegExp("^## " + escapeRe(heading) + " : (?=v[0-9])").exec(rest);
51
+ if (hm) { head = "## " + currentHeading + " : "; rest = rest.slice(hm[0].length); changed = true; break; }
52
+ }
53
+ out += content.slice(last, start) + head;
54
+ last = start + (content.slice(start).length - rest.length);
55
+ // History link line: heading line, one blank line, then the link.
56
+ const lm = /^([^\r\n]*\r?\n\r?\n)([^\r\n]*)/.exec(rest);
57
+ if (lm) {
58
+ const linkStart = last + lm[1].length;
59
+ const isStale = SUPPORTED_LANGUAGES.some((lang) => historyLink(lang) !== currentLink && lm[2] === historyLink(lang));
60
+ if (isStale) {
61
+ out += content.slice(last, linkStart) + currentLink;
62
+ last = linkStart + lm[2].length;
63
+ changed = true;
64
+ }
65
+ }
66
+ }
67
+ return changed ? out + content.slice(last) : null;
68
+ }
69
+
20
70
  // Skip when README.md is missing, or when a marker or version line exists. Otherwise append to the end.
21
- // Returns: 'skip-no-readme' | 'skip-marker' | 'skip-version-line' | 'added'
71
+ // Returns: 'skip-no-readme' | 'heading-updated' | 'skip-marker' | 'skip-version-line' | 'added'
22
72
  // Only decides without writing, so the real append and the --dry-run preview share one decision.
23
73
  export function planVersionSection(targetRoot = ".") {
24
74
  const p = join(targetRoot, "README.md");
25
75
  if (!existsSync(p)) return "skip-no-readme";
26
76
  const content = readFileSync(p, "utf8");
77
+ if (refreshBlockLanguage(content) !== null) return "heading-updated";
27
78
  if (content.includes(MARKER)) return "skip-marker";
28
79
  if (VERSION_LINE_RE.test(content)) return "skip-version-line";
29
80
  return "added";
@@ -31,8 +82,13 @@ export function planVersionSection(targetRoot = ".") {
31
82
 
32
83
  export function addVersionSectionToReadme(version, targetRoot = ".") {
33
84
  const status = planVersionSection(targetRoot);
34
- if (status !== "added") return status;
35
85
  const p = join(targetRoot, "README.md");
86
+ if (status === "heading-updated") {
87
+ // Swap only the bundled default wording; version text after the colon stays as the workflow wrote it.
88
+ writeFileSync(p, refreshBlockLanguage(readFileSync(p, "utf8")));
89
+ return status;
90
+ }
91
+ if (status !== "added") return status;
36
92
  const content = readFileSync(p, "utf8");
37
93
 
38
94
  // The appended body starts with "\n---\n..." to leave a blank line between the existing text and the rule.
@@ -115,6 +115,7 @@ export default {
115
115
  "cmd.dryRun.readme.skipNoReadme": "README.md: no file, so no version section is added",
116
116
  "cmd.dryRun.readme.skipMarker": "README.md: a version section already exists, no change",
117
117
  "cmd.dryRun.readme.skipVersionLine": "README.md: a version line already exists, no change",
118
+ "cmd.dryRun.readme.headingUpdated": "README.md: the version heading will switch to the current language (it is the bundled default text)",
118
119
  "cmd.dryRun.readme.other": "README.md: {status}",
119
120
  "cmd.dryRun.baseline.update": "{path}: will be updated (reference for the next update)",
120
121
  "cmd.dryRun.baseline.create": "{path}: will be created (reference for the next update)",
@@ -4,6 +4,7 @@ export default {
4
4
  "copy.readme.status.skipNoReadme": "README.md not found; version section not added",
5
5
  "copy.readme.status.skipMarker": "Version section already present; left as is",
6
6
  "copy.readme.status.skipVersionLine": "Version line already present; left as is",
7
+ "copy.readme.status.headingUpdated": "Switched the version heading to the current language",
7
8
  "copy.readme.versionHeading": "## Latest Version : v{version}",
8
9
  "copy.readme.historyLink": "[View full version history](CHANGELOG.md)",
9
10
  "copy.gitignore.newFileHeader": "# project-auto-wizard: backup files created during conflict handling (safe to ignore)",
@@ -115,6 +115,7 @@ export default {
115
115
  "cmd.dryRun.readme.skipNoReadme": "README.md: 파일이 없어 버전 섹션을 추가하지 않음",
116
116
  "cmd.dryRun.readme.skipMarker": "README.md: 이미 버전 섹션이 있어 변경 없음",
117
117
  "cmd.dryRun.readme.skipVersionLine": "README.md: 이미 버전 줄이 있어 변경 없음",
118
+ "cmd.dryRun.readme.headingUpdated": "README.md: 버전 제목이 현재 언어로 교체될 예정 (기본 제목 그대로인 경우)",
118
119
  "cmd.dryRun.readme.other": "README.md: {status}",
119
120
  "cmd.dryRun.baseline.update": "{path}: 갱신될 예정 (다음 업데이트의 비교 기준)",
120
121
  "cmd.dryRun.baseline.create": "{path}: 새로 생성될 예정 (다음 업데이트의 비교 기준)",
@@ -4,6 +4,7 @@ export default {
4
4
  "copy.readme.status.skipNoReadme": "README.md가 없어 버전 섹션을 추가하지 않음",
5
5
  "copy.readme.status.skipMarker": "이미 버전 섹션이 있어 그대로 둠",
6
6
  "copy.readme.status.skipVersionLine": "이미 버전 줄이 있어 그대로 둠",
7
+ "copy.readme.status.headingUpdated": "버전 제목을 현재 언어로 교체",
7
8
  "copy.readme.versionHeading": "## 최신 버전 : v{version}",
8
9
  "copy.readme.historyLink": "[전체 버전 기록 보기](CHANGELOG.md)",
9
10
  "copy.gitignore.newFileHeader": "# project-auto-wizard: 충돌 처리 시 생성되는 백업 파일 (안전하게 무시해도 됩니다)",
package/src/index.js CHANGED
@@ -31,7 +31,7 @@ import { runInteractive } from "./commands/interactive.js";
31
31
  import { resolveDeployStyle, resolveVersion, resolveVersionCode, buildInstallContext } from "./commands/install-settings.js";
32
32
  import { initLogger, closeLogger, currentLogPath, hasLegacyMdLogs, log } from "./core/logger.js";
33
33
  import { runStatus, printStatus } from "./commands/status.js";
34
- import { runDoctor, printDoctorReport } from "./commands/doctor.js";
34
+ import { runDoctor, printDoctorReport, doctorExitCode } from "./commands/doctor.js";
35
35
  import { planDryRun, printDryRun } from "./commands/dry-run.js";
36
36
  import { planPurge, executePurge, printPurgePlan, printPurgeResult } from "./commands/purge.js";
37
37
 
@@ -271,8 +271,9 @@ async function runInner(argv, {
271
271
  }
272
272
  // doctor mode: read-only, always works regardless of TTY/--force
273
273
  if (opts.mode === "doctor") {
274
- printDoctorReport(runDoctor(cwd));
275
- return 0;
274
+ const results = runDoctor(cwd);
275
+ printDoctorReport(results);
276
+ return doctorExitCode(results);
276
277
  }
277
278
  // An explicit mode (full) without --force is rejected immediately, TTY or not
278
279
  // (fixes a defect where a TTY run installed at once without confirmation).
package/src/ui/summary.js CHANGED
@@ -34,7 +34,7 @@ export function printSummary(ctx) {
34
34
  err(t("ui.summary.features.title"));
35
35
 
36
36
  // Mention the automatic update only when the README version section really exists (added now or already there).
37
- const readmeTracked = readme === "added" || readme === "skip-marker" || readme === "skip-version-line";
37
+ const readmeTracked = readme === "added" || readme === "skip-marker" || readme === "skip-version-line" || readme === "heading-updated";
38
38
  if (mode === "full") {
39
39
  err(t("ui.summary.features.versionSystem"));
40
40
  if (readmeTracked) err(t("ui.summary.features.readmeAuto"));