project-auto-wizard 0.13.3 → 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.
- package/README.ja.md +2 -0
- package/README.ko.md +2 -0
- package/README.md +3 -1
- package/README.zh-CN.md +2 -0
- package/bin/project-auto-wizard.js +18 -4
- package/package.json +2 -2
- package/payload/config/breaking-changes.json +28 -14
- package/payload/config/wizard-prompts.yml +102 -46
- package/payload/flutter-app/android/fastlane/Fastfile.playstore +44 -32
- package/payload/flutter-app/ios/ExportOptions.plist +8 -8
- package/payload/flutter-app/ios/fastlane/Fastfile +57 -43
- package/payload/scripts/changelog_manager.py +304 -265
- package/payload/scripts/issue_helper.py +54 -50
- package/payload/scripts/messages.py +2585 -0
- package/payload/scripts/truncate_release_notes.py +7 -6
- package/payload/scripts/version_manager.py +110 -107
- package/payload/version.yml.template +2 -1
- package/payload/workflows/common/PROJECT-COMMON-AI-PR-SUMMARY.yaml +23 -16
- package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +106 -63
- package/payload/workflows/common/PROJECT-COMMON-ISSUE-HELPER.yaml +17 -15
- package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +55 -45
- package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +90 -72
- package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +59 -45
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +194 -184
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +226 -213
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +46 -42
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +334 -241
- package/payload/workflows/flutter/PROJECT-FLUTTER-APP-BUILD-TRIGGER.yaml +152 -128
- package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +246 -188
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +353 -238
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +96 -85
- package/payload/workflows/go/PROJECT-GO-CI.yaml +47 -36
- package/payload/workflows/go/PROJECT-GO-PR-PREVIEW.yaml +945 -508
- package/payload/workflows/go/PROJECT-GO-SIMPLE-CICD.yaml +155 -133
- package/payload/workflows/next/PROJECT-NEXT-CI.yaml +76 -60
- package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +97 -77
- package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +62 -47
- package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +942 -505
- package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +148 -126
- package/payload/workflows/react/PROJECT-REACT-CI.yaml +86 -70
- package/payload/workflows/react/PROJECT-REACT-CICD.yaml +112 -92
- package/payload/workflows/spring/PROJECT-SPRING-CI.yml +112 -82
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +180 -150
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +161 -131
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +970 -549
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +193 -162
- package/src/cli/args.js +98 -52
- package/src/cli/help.js +8 -41
- package/src/commands/doctor.js +90 -89
- package/src/commands/dry-run.js +64 -43
- package/src/commands/full.js +79 -77
- package/src/commands/install-settings.js +16 -15
- package/src/commands/interactive-flutter.js +20 -19
- package/src/commands/interactive.js +135 -132
- package/src/commands/purge.js +47 -42
- package/src/commands/status.js +36 -36
- package/src/commands/uninstall.js +43 -41
- package/src/context.js +12 -11
- package/src/core/assets.js +12 -11
- package/src/core/baseline.js +27 -27
- package/src/core/branches.js +29 -27
- package/src/core/branding.js +7 -6
- package/src/core/breaking-check.js +22 -21
- package/src/core/breaking.js +15 -8
- package/src/core/copy/app-files.js +5 -4
- package/src/core/copy/flutter-app.js +8 -8
- package/src/core/copy/gitignore.js +58 -50
- package/src/core/copy/readme.js +102 -36
- package/src/core/copy/simple.js +16 -15
- package/src/core/copy/workflows.js +131 -124
- package/src/core/deploy-style.js +51 -48
- package/src/core/detect-fs.js +57 -57
- package/src/core/detect.js +67 -65
- package/src/core/errors.js +2 -2
- package/src/core/flutter-doctor.js +17 -16
- package/src/core/flutter-hooks.js +33 -32
- package/src/core/flutter-options.js +34 -33
- package/src/core/fsutil.js +12 -12
- package/src/core/installed-stores.js +4 -4
- package/src/core/logger.js +42 -40
- package/src/core/paths-resolve.js +88 -86
- package/src/core/paths.js +10 -10
- package/src/core/release-options.js +8 -8
- package/src/core/removal-exec.js +14 -13
- package/src/core/removal-plan.js +37 -37
- package/src/core/types.js +47 -47
- package/src/core/verify.js +44 -38
- package/src/core/version-yml.js +76 -68
- package/src/core/wizard-env.js +46 -46
- package/src/core/wizard-labels.js +63 -41
- package/src/i18n/catalog/en/cli.js +50 -0
- package/src/i18n/catalog/en/commands.js +188 -0
- package/src/i18n/catalog/en/copy.js +20 -0
- package/src/i18n/catalog/en/core.js +74 -0
- package/src/i18n/catalog/en/core2.js +15 -0
- package/src/i18n/catalog/en/ui.js +221 -0
- package/src/i18n/catalog/en.js +12 -0
- package/src/i18n/catalog/index.js +5 -0
- package/src/i18n/catalog/ko/cli.js +50 -0
- package/src/i18n/catalog/ko/commands.js +188 -0
- package/src/i18n/catalog/ko/copy.js +20 -0
- package/src/i18n/catalog/ko/core.js +74 -0
- package/src/i18n/catalog/ko/core2.js +15 -0
- package/src/i18n/catalog/ko/ui.js +221 -0
- package/src/i18n/catalog/ko.js +10 -0
- package/src/i18n/index.js +44 -0
- package/src/i18n/languages.js +9 -0
- package/src/index.js +132 -97
- package/src/ui/ansi.js +7 -7
- package/src/ui/banner.js +7 -6
- package/src/ui/env-plan.js +82 -81
- package/src/ui/prompts.js +97 -122
- package/src/ui/readline-engine.js +59 -58
- package/src/ui/status-cards.js +37 -31
- package/src/ui/summary.js +90 -89
|
@@ -1,33 +1,34 @@
|
|
|
1
|
-
//
|
|
2
|
-
// WHY: @clack/prompts 1.7.0
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// Interactive prompt engine built on node:readline (replaces @clack/prompts).
|
|
2
|
+
// WHY: @clack/prompts 1.7.0 has a bug on Windows TTY consoles where the Enter (return) key is not
|
|
3
|
+
// handled and the prompt hangs. node:readline keypress events work correctly on Windows.
|
|
4
|
+
// Implementing the menus ourselves means zero external dependencies -> safe on internal networks too.
|
|
5
5
|
//
|
|
6
|
-
//
|
|
7
|
-
// Ctrl+C
|
|
6
|
+
// Contract: ESC returns the CANCEL symbol (callers interpret it as "default/stay"). Every function is async.
|
|
7
|
+
// Ctrl+C, Ctrl+D and stdin close abort - rejected as PromptAbortError so any question can be left immediately.
|
|
8
8
|
import { emitKeypressEvents } from "node:readline";
|
|
9
9
|
import { stdin, stdout } from "node:process";
|
|
10
10
|
import { visualWidth } from "./ansi.js";
|
|
11
|
+
import { t } from "../i18n/index.js";
|
|
11
12
|
|
|
12
13
|
export const CANCEL = Symbol("cancel");
|
|
13
14
|
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
// signal:
|
|
15
|
+
// User abort (Ctrl+C etc.). Unlike ESC it must not continue with defaults, so it propagates as an exception, not a return value -
|
|
16
|
+
// callers interpreted CANCEL differently, and some forced the install with defaults or redrew the same screen forever.
|
|
17
|
+
// signal: name of the external signal ("SIGINT"/"SIGTERM") when aborted by one - aligns the exit code with the shell convention (128+number).
|
|
17
18
|
export class PromptAbortError extends Error {
|
|
18
19
|
constructor(signal = null) {
|
|
19
|
-
super("
|
|
20
|
+
super(t("ui.readline-engine.aborted"));
|
|
20
21
|
this.name = "PromptAbortError";
|
|
21
22
|
this.signal = signal;
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
export const isPromptAbort = (e) => e instanceof PromptAbortError;
|
|
25
26
|
|
|
26
|
-
// raw mode
|
|
27
|
+
// In raw mode Ctrl+C arrives as a keypress, not SIGINT. Ctrl+D is not EOF in raw mode either.
|
|
27
28
|
const isAbortKey = (key) => key.ctrl && (key.name === "c" || key.name === "d");
|
|
28
29
|
|
|
29
|
-
//
|
|
30
|
-
//
|
|
30
|
+
// External signals (kill -INT / kill -TERM) arrive as process signals, not keypresses. Without a listener Node exits
|
|
31
|
+
// immediately and the hidden cursor and raw mode are never restored - while waiting for input, route them through the same abort path as keyboard Ctrl+C.
|
|
31
32
|
const TERM_SIGNALS = ["SIGINT", "SIGTERM"];
|
|
32
33
|
function onTermSignal(fn) {
|
|
33
34
|
const handlers = TERM_SIGNALS.map((sig) => [sig, () => fn(sig)]);
|
|
@@ -35,19 +36,19 @@ function onTermSignal(fn) {
|
|
|
35
36
|
return () => { for (const [sig, h] of handlers) process.removeListener(sig, h); };
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
// ── ANSI
|
|
39
|
+
// ── ANSI helpers (replaces picocolors - zero dependencies) ───────────
|
|
39
40
|
const ESC = "\x1b[";
|
|
40
41
|
const c = {
|
|
41
42
|
reset: `${ESC}0m`, dim: `${ESC}2m`, bold: `${ESC}1m`,
|
|
42
43
|
cyan: `${ESC}36m`, green: `${ESC}32m`, gray: `${ESC}90m`, yellow: `${ESC}33m`,
|
|
43
44
|
};
|
|
44
|
-
// NO_COLOR(https://no-color.org)
|
|
45
|
-
//
|
|
46
|
-
// TERM=dumb
|
|
45
|
+
// NO_COLOR (https://no-color.org) / non-TTY guard - same rule as ansi.js (presence only, value ignored) but
|
|
46
|
+
// implemented locally to stay dependency-free.
|
|
47
|
+
// TERM=dumb cannot interpret cursor-movement/erase sequences either, not just colors - then output continues instead of redrawing.
|
|
47
48
|
const isDumb = () => process.env.TERM === "dumb";
|
|
48
49
|
const colorEnabled = () => process.env.NO_COLOR === undefined && !!stdout.isTTY && !isDumb();
|
|
49
50
|
const paint = (s, color, enabled = colorEnabled()) => (enabled ? `${color}${s}${c.reset}` : String(s));
|
|
50
|
-
//
|
|
51
|
+
// If the process ends with the cursor hidden (exception, process.exit, ...) the terminal cursor stays invisible - restore it once more on exit.
|
|
51
52
|
let cursorHidden = false;
|
|
52
53
|
let exitGuard = false;
|
|
53
54
|
const hideCursor = () => {
|
|
@@ -65,7 +66,7 @@ const showCursor = () => {
|
|
|
65
66
|
cursorHidden = false;
|
|
66
67
|
};
|
|
67
68
|
|
|
68
|
-
//
|
|
69
|
+
// Symbols (keeps the clack look)
|
|
69
70
|
const S_ACTIVE = paint("●", c.green);
|
|
70
71
|
const S_INACTIVE = paint("○", c.dim);
|
|
71
72
|
const S_CHECK_ON = paint("◼", c.green);
|
|
@@ -74,22 +75,22 @@ const S_BAR = paint("│", c.gray);
|
|
|
74
75
|
const S_Q = paint("◆", c.cyan);
|
|
75
76
|
const S_DONE = paint("◇", c.green);
|
|
76
77
|
|
|
77
|
-
//
|
|
78
|
-
//
|
|
78
|
+
// Number of rows a line actually occupies in the terminal - lines wider than the terminal wrap into several rows.
|
|
79
|
+
// Moving up only by the logical line count would leave the wrapped top part uncleared and stack copies on screen.
|
|
79
80
|
export function physicalRows(line, columns = stdout.columns) {
|
|
80
81
|
if (!columns) return 1;
|
|
81
82
|
return Math.max(1, Math.ceil(visualWidth(line) / columns));
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
//
|
|
85
|
-
//
|
|
85
|
+
// Renderer that erases several lines and returns the cursor to the start of the block.
|
|
86
|
+
// Moves up by the number of physical rows drawn last time, erases, and redraws.
|
|
86
87
|
function makeRenderer() {
|
|
87
88
|
let prevLines = 0;
|
|
88
89
|
return {
|
|
89
90
|
render(lines) {
|
|
90
91
|
if (isDumb()) { stdout.write(lines.join("\n") + "\n"); return; }
|
|
91
|
-
if (prevLines > 0) stdout.write(`${ESC}${prevLines}A`); //
|
|
92
|
-
stdout.write(`${ESC}0J`); //
|
|
92
|
+
if (prevLines > 0) stdout.write(`${ESC}${prevLines}A`); // up
|
|
93
|
+
stdout.write(`${ESC}0J`); // erase everything below the cursor
|
|
93
94
|
stdout.write(lines.join("\n") + "\n");
|
|
94
95
|
prevLines = lines.reduce((n, l) => n + physicalRows(l), 0);
|
|
95
96
|
},
|
|
@@ -97,8 +98,8 @@ function makeRenderer() {
|
|
|
97
98
|
};
|
|
98
99
|
}
|
|
99
100
|
|
|
100
|
-
// raw keypress
|
|
101
|
-
//
|
|
101
|
+
// Common wrapper for raw keypress sessions. Ends when onKey(str,key) returns a value.
|
|
102
|
+
// The result is produced by the caller's onKey; CANCEL on cancel.
|
|
102
103
|
function keySession(renderFn, onKey) {
|
|
103
104
|
return new Promise((resolve, reject) => {
|
|
104
105
|
const wasRaw = stdin.isTTY ? stdin.isRaw : false;
|
|
@@ -121,8 +122,8 @@ function keySession(renderFn, onKey) {
|
|
|
121
122
|
reject(new PromptAbortError(sig));
|
|
122
123
|
});
|
|
123
124
|
|
|
124
|
-
// stdin
|
|
125
|
-
// CANCEL
|
|
125
|
+
// stdin closed (EOF, dropped SSH connection, ...) - no more input can arrive, so abort.
|
|
126
|
+
// Returning CANCEL would make screens that read it as "stay" ask again and wait forever.
|
|
126
127
|
const onEnd = () => {
|
|
127
128
|
cleanup();
|
|
128
129
|
reject(new PromptAbortError());
|
|
@@ -151,15 +152,15 @@ function keySession(renderFn, onKey) {
|
|
|
151
152
|
};
|
|
152
153
|
stdin.on("keypress", handler);
|
|
153
154
|
stdin.on("end", onEnd);
|
|
154
|
-
renderFn(); //
|
|
155
|
+
renderFn(); // initial render
|
|
155
156
|
});
|
|
156
157
|
}
|
|
157
158
|
|
|
158
|
-
// ──
|
|
159
|
-
// options: [{value,label,hint?}].
|
|
159
|
+
// ── Single select (arrow keys + Enter) ───────────────────────────────
|
|
160
|
+
// options: [{value,label,hint?}]. Returns: the selected value or CANCEL.
|
|
160
161
|
export async function select({ message, options, initialIndex = 0 }) {
|
|
161
162
|
if (!stdin.isTTY) {
|
|
162
|
-
//
|
|
163
|
+
// Non-TTY: return the default (first item) - guards piped environments
|
|
163
164
|
return options[initialIndex]?.value;
|
|
164
165
|
}
|
|
165
166
|
const r = makeRenderer();
|
|
@@ -174,24 +175,24 @@ export async function select({ message, options, initialIndex = 0 }) {
|
|
|
174
175
|
const hint = o.hint && sel ? paint(` (${o.hint})`, c.dim) : "";
|
|
175
176
|
lines.push(`${S_BAR} ${marker} ${label}${hint}`);
|
|
176
177
|
});
|
|
177
|
-
lines.push(paint(
|
|
178
|
+
lines.push(paint(t("ui.readline-engine.footer.select"), c.gray));
|
|
178
179
|
r.render(lines);
|
|
179
180
|
};
|
|
180
181
|
|
|
181
182
|
const result = await keySession(draw, (str, key) => {
|
|
182
183
|
if (key.name === "up" || key.name === "k") { idx = (idx - 1 + options.length) % options.length; return; }
|
|
183
184
|
if (key.name === "down" || key.name === "j") { idx = (idx + 1) % options.length; return; }
|
|
184
|
-
//
|
|
185
|
+
// Number jump (1-9)
|
|
185
186
|
if (/^[1-9]$/.test(str || "")) {
|
|
186
187
|
const n = Number(str) - 1;
|
|
187
188
|
if (n < options.length) { idx = n; return; }
|
|
188
189
|
return;
|
|
189
190
|
}
|
|
190
191
|
if (key.name === "return" || key.name === "enter") return options[idx].value;
|
|
191
|
-
return; //
|
|
192
|
+
return; // any other key: ignore and continue
|
|
192
193
|
});
|
|
193
194
|
|
|
194
|
-
//
|
|
195
|
+
// Redraw the confirmed screen (◇ done symbol + chosen value)
|
|
195
196
|
if (result !== CANCEL) {
|
|
196
197
|
const chosen = options.find((o) => o.value === result);
|
|
197
198
|
r.render([S_BAR, `${S_DONE} ${paint(message, c.dim)}`, `${S_BAR} ${paint(chosen?.label ?? "", c.dim)}`]);
|
|
@@ -199,8 +200,8 @@ export async function select({ message, options, initialIndex = 0 }) {
|
|
|
199
200
|
return result;
|
|
200
201
|
}
|
|
201
202
|
|
|
202
|
-
// ──
|
|
203
|
-
// options: [{value,label,hint?,disabled?}].
|
|
203
|
+
// ── Multi select (Space toggle + Enter) ──────────────────────────────
|
|
204
|
+
// options: [{value,label,hint?,disabled?}]. Returns: array of selected values or CANCEL.
|
|
204
205
|
export async function multiselect({ message, options, initialValues = [], required = false }) {
|
|
205
206
|
if (!stdin.isTTY) {
|
|
206
207
|
return initialValues.length ? [...initialValues] : (required ? [options[0]?.value].filter(Boolean) : []);
|
|
@@ -221,7 +222,7 @@ export async function multiselect({ message, options, initialValues = [], requir
|
|
|
221
222
|
lines.push(`${S_BAR} ${pointer} ${box} ${label}${hint}`);
|
|
222
223
|
});
|
|
223
224
|
if (warn) lines.push(paint(` ${warn}`, c.yellow));
|
|
224
|
-
lines.push(paint(
|
|
225
|
+
lines.push(paint(t("ui.readline-engine.footer.multiselect"), c.gray));
|
|
225
226
|
r.render(lines);
|
|
226
227
|
};
|
|
227
228
|
|
|
@@ -231,26 +232,26 @@ export async function multiselect({ message, options, initialValues = [], requir
|
|
|
231
232
|
if (key.name === "down" || key.name === "j") { idx = (idx + 1) % options.length; return; }
|
|
232
233
|
if (key.name === "space" || str === " ") {
|
|
233
234
|
const o = options[idx];
|
|
234
|
-
if (o.disabled) { warn = "
|
|
235
|
+
if (o.disabled) { warn = t("ui.readline-engine.warn.disabled"); return; }
|
|
235
236
|
if (chosen.has(o.value)) chosen.delete(o.value); else chosen.add(o.value);
|
|
236
237
|
return;
|
|
237
238
|
}
|
|
238
239
|
if (key.name === "return" || key.name === "enter") {
|
|
239
|
-
if (required && chosen.size === 0) { warn = "
|
|
240
|
+
if (required && chosen.size === 0) { warn = t("ui.readline-engine.warn.required"); return; }
|
|
240
241
|
return [...chosen];
|
|
241
242
|
}
|
|
242
243
|
return;
|
|
243
244
|
});
|
|
244
245
|
|
|
245
246
|
if (result !== CANCEL) {
|
|
246
|
-
const labels = options.filter((o) => chosen.has(o.value)).map((o) => o.label).join(", ") || "
|
|
247
|
+
const labels = options.filter((o) => chosen.has(o.value)).map((o) => o.label).join(", ") || t("ui.readline-engine.none");
|
|
247
248
|
r.render([S_BAR, `${S_DONE} ${paint(message, c.dim)}`, `${S_BAR} ${paint(labels, c.dim)}`]);
|
|
248
249
|
}
|
|
249
250
|
return result;
|
|
250
251
|
}
|
|
251
252
|
|
|
252
|
-
// ──
|
|
253
|
-
//
|
|
253
|
+
// ── Text input (Enter confirms, empty input = default) ────────────────
|
|
254
|
+
// Returns: the typed string (defaultValue on empty input) or CANCEL.
|
|
254
255
|
export async function text({ message, defaultValue = "" }) {
|
|
255
256
|
if (!stdin.isTTY) return defaultValue;
|
|
256
257
|
return new Promise((resolve, reject) => {
|
|
@@ -260,15 +261,15 @@ export async function text({ message, defaultValue = "" }) {
|
|
|
260
261
|
stdin.resume();
|
|
261
262
|
let buf = "";
|
|
262
263
|
|
|
263
|
-
// dumb
|
|
264
|
+
// A dumb terminal cannot erase lines, so write the prompt once and just append typed characters.
|
|
264
265
|
const dumb = isDumb();
|
|
265
|
-
let prevRows = 0; //
|
|
266
|
+
let prevRows = 0; // physical rows the previous prompt occupied - long prompts wrap into several rows
|
|
266
267
|
const prompt = () => {
|
|
267
268
|
if (dumb) {
|
|
268
269
|
stdout.write(`${S_Q} ${message} ${defaultValue ? `[${defaultValue}] ` : ""}`);
|
|
269
270
|
return;
|
|
270
271
|
}
|
|
271
|
-
//
|
|
272
|
+
// The cursor is on the last wrapped row, so move up to the first row and erase everything below
|
|
272
273
|
stdout.write(prevRows > 1 ? `\r${ESC}${prevRows - 1}A${ESC}0J` : `\r${ESC}0J`);
|
|
273
274
|
const shown = buf.length ? buf : paint(defaultValue || "", c.dim);
|
|
274
275
|
const line = `${S_Q} ${paint(message, c.bold)} ${shown}`;
|
|
@@ -289,7 +290,7 @@ export async function text({ message, defaultValue = "" }) {
|
|
|
289
290
|
reject(new PromptAbortError(sig));
|
|
290
291
|
});
|
|
291
292
|
|
|
292
|
-
// stdin
|
|
293
|
+
// stdin closed (EOF) - no more input can arrive, so abort.
|
|
293
294
|
const onEnd = () => {
|
|
294
295
|
cleanup();
|
|
295
296
|
reject(new PromptAbortError());
|
|
@@ -310,7 +311,7 @@ export async function text({ message, defaultValue = "" }) {
|
|
|
310
311
|
if (!dumb) prompt();
|
|
311
312
|
return;
|
|
312
313
|
}
|
|
313
|
-
//
|
|
314
|
+
// Regular character (control characters excluded)
|
|
314
315
|
if (str && !key.ctrl && !key.meta && str.length === 1 && str >= " ") {
|
|
315
316
|
buf += str;
|
|
316
317
|
if (dumb) stdout.write(str); else prompt();
|
|
@@ -323,18 +324,18 @@ export async function text({ message, defaultValue = "" }) {
|
|
|
323
324
|
});
|
|
324
325
|
}
|
|
325
326
|
|
|
326
|
-
// ── Y/N
|
|
327
|
-
//
|
|
327
|
+
// ── Y/N confirm (left/right or y/n, Enter confirms) ──────────────────
|
|
328
|
+
// Returns: true/false or CANCEL.
|
|
328
329
|
export async function confirm({ message, initialValue = true }) {
|
|
329
330
|
if (!stdin.isTTY) return initialValue;
|
|
330
331
|
const r = makeRenderer();
|
|
331
332
|
let val = initialValue;
|
|
332
333
|
|
|
333
334
|
const draw = () => {
|
|
334
|
-
const yes = val ? paint(
|
|
335
|
-
const no = !val ? paint(
|
|
335
|
+
const yes = val ? paint(`● ${t("ui.readline-engine.yes")}`, c.green) : paint(`○ ${t("ui.readline-engine.yes")}`, c.dim);
|
|
336
|
+
const no = !val ? paint(`● ${t("ui.readline-engine.no")}`, c.green) : paint(`○ ${t("ui.readline-engine.no")}`, c.dim);
|
|
336
337
|
r.render([S_BAR, `${S_Q} ${paint(message, c.bold)}`, `${S_BAR} ${yes} ${no}`,
|
|
337
|
-
paint(
|
|
338
|
+
paint(t("ui.readline-engine.footer.confirm"), c.gray)]);
|
|
338
339
|
};
|
|
339
340
|
|
|
340
341
|
const result = await keySession(draw, (str, key) => {
|
|
@@ -346,15 +347,15 @@ export async function confirm({ message, initialValue = true }) {
|
|
|
346
347
|
});
|
|
347
348
|
|
|
348
349
|
if (result !== CANCEL) {
|
|
349
|
-
r.render([S_BAR, `${S_DONE} ${paint(message, c.dim)}`, `${S_BAR} ${paint(result ? "
|
|
350
|
+
r.render([S_BAR, `${S_DONE} ${paint(message, c.dim)}`, `${S_BAR} ${paint(result ? t("ui.readline-engine.yes") : t("ui.readline-engine.no"), c.dim)}`]);
|
|
350
351
|
}
|
|
351
352
|
return result;
|
|
352
353
|
}
|
|
353
354
|
|
|
354
|
-
// ──
|
|
355
|
+
// ── Output helpers (replace clack intro/outro/note/cancel) ────────────
|
|
355
356
|
export function intro(text) { stdout.write(`\n${paint("┌", c.gray)} ${paint(text, c.bold)}\n`); }
|
|
356
357
|
export function outro(text) { stdout.write(`${paint("└", c.gray)} ${paint(text, c.green)}\n\n`); }
|
|
357
|
-
export function cancelMessage(text = "
|
|
358
|
+
export function cancelMessage(text = t("ui.readline-engine.cancelled")) { stdout.write(`${paint("■", c.yellow)} ${paint(text, c.yellow)}\n`); }
|
|
358
359
|
export function note(text, title = "") {
|
|
359
360
|
const lines = String(text).split("\n");
|
|
360
361
|
stdout.write(`${paint("○", c.cyan)} ${paint(title, c.bold)}\n`);
|
package/src/ui/status-cards.js
CHANGED
|
@@ -1,75 +1,81 @@
|
|
|
1
|
-
//
|
|
2
|
-
// (Breaking Changes
|
|
1
|
+
// First-screen status layer - detection log, analysis card, new-install/update detection
|
|
2
|
+
// (the Breaking Changes box is handled by core/breaking-check.js)
|
|
3
3
|
import { A, paint } from "./ansi.js";
|
|
4
|
+
import { t } from "../i18n/index.js";
|
|
4
5
|
import { DEFAULT_DEPLOY_MODE } from "../core/flutter-options.js";
|
|
5
6
|
|
|
6
7
|
const GUT = paint("│", A.gray);
|
|
7
8
|
const HEAD = paint("◆", A.cyan);
|
|
8
9
|
const OK = paint("✓", A.green);
|
|
9
10
|
|
|
10
|
-
//
|
|
11
|
-
// markers: Map<type,
|
|
12
|
-
// warnings:
|
|
13
|
-
//
|
|
11
|
+
// Detection log - which type was detected and on what evidence
|
|
12
|
+
// markers: Map<type, file actually found>.
|
|
13
|
+
// warnings: warnings raised during detection. Detection runs before the box is drawn, so warnings
|
|
14
|
+
// used to leak above the box and look like they belonged to the previous question - print them inside the box.
|
|
14
15
|
export function printDetectionLog({ types = [], version = "", branch = "", markers = new Map(), warnings = [] },
|
|
15
16
|
out = (s) => process.stderr.write(s)) {
|
|
16
|
-
out(`${paint("┌", A.gray)}
|
|
17
|
+
out(`${paint("┌", A.gray)} ${t("ui.status-cards.detect.title")}\n`);
|
|
17
18
|
if (types.length && !(types.length === 1 && types[0] === "basic")) {
|
|
18
|
-
for (const
|
|
19
|
-
const marker = markers.get(
|
|
20
|
-
|
|
19
|
+
for (const type of types) {
|
|
20
|
+
const marker = markers.get(type);
|
|
21
|
+
const found = marker
|
|
22
|
+
? t("ui.status-cards.detect.foundWithMarker", { marker, type: paint(type, A.bold) })
|
|
23
|
+
: t("ui.status-cards.detect.found", { type: paint(type, A.bold) });
|
|
24
|
+
out(`${GUT} ${OK} ${found}\n`);
|
|
21
25
|
}
|
|
22
26
|
} else {
|
|
23
|
-
out(`${GUT} ${paint("─", A.dim)}
|
|
27
|
+
out(`${GUT} ${paint("─", A.dim)} ${t("ui.status-cards.detect.none", { type: paint("basic", A.bold) })}\n`);
|
|
24
28
|
}
|
|
25
|
-
out(`${GUT} ${OK}
|
|
29
|
+
out(`${GUT} ${OK} ${t("ui.status-cards.detect.versionBranch", { version: paint(`v${version}`, A.green), branch: paint(branch, A.green) })}\n`);
|
|
26
30
|
for (const w of warnings) out(`${GUT} ${paint(w, A.yellow)}\n`);
|
|
27
31
|
out(`${GUT}\n`);
|
|
28
32
|
}
|
|
29
33
|
|
|
30
|
-
//
|
|
34
|
+
// Project analysis overview card
|
|
31
35
|
export function printAnalysisCard({ mode = "", modeLabel = "", types = [], version = "", branch = "",
|
|
32
36
|
paths = new Map(), showOptional = false,
|
|
33
37
|
flutter = null, envModeDefault = "", options = null },
|
|
34
38
|
out = (s) => process.stderr.write(s)) {
|
|
35
|
-
out(`${HEAD} ${paint("
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
row(
|
|
39
|
-
row("
|
|
40
|
-
|
|
39
|
+
out(`${HEAD} ${paint(t("ui.status-cards.card.title"), A.bold)}\n`);
|
|
40
|
+
// Label column width differs per language (English labels are longer than the Korean ones)
|
|
41
|
+
const labelWidth = Number(t("ui.status-cards.card.labelWidth"));
|
|
42
|
+
const row = (icon, label, value) => out(`${GUT} ${icon} ${label.padEnd(labelWidth)} ${value}\n`);
|
|
43
|
+
row("📂", types.length > 1 ? t("ui.status-cards.card.typeMulti") : t("ui.status-cards.card.type"), paint(types.join(", ") || "basic", A.bold));
|
|
44
|
+
row("🌙", t("ui.status-cards.card.version"), paint(`v${version}`, A.green));
|
|
45
|
+
row("🌿", t("ui.status-cards.card.branch"), branch);
|
|
46
|
+
if (modeLabel || mode) row("💫", t("ui.status-cards.card.mode"), modeLabel || mode);
|
|
41
47
|
if (showOptional) {
|
|
42
|
-
// Flutter
|
|
48
|
+
// Flutter options - show the chosen values on the pre-confirmation screen too.
|
|
43
49
|
if (flutter && types.includes("flutter")) {
|
|
44
50
|
const stores = flutter.stores ?? [];
|
|
45
51
|
const modeParts = stores.map(
|
|
46
52
|
(p) => `${p}=${(p === "android" ? flutter.androidDeployMode : flutter.iosDeployMode) || DEFAULT_DEPLOY_MODE}`,
|
|
47
53
|
);
|
|
48
|
-
row("⚙️", "
|
|
49
|
-
row("🏬", "
|
|
50
|
-
row("🚀", "
|
|
54
|
+
row("⚙️", t("ui.status-cards.card.envMode"), flutter.envMode || envModeDefault);
|
|
55
|
+
row("🏬", t("ui.status-cards.card.stores"), stores.length ? stores.join(", ") : t("ui.status-cards.card.none"));
|
|
56
|
+
row("🚀", t("ui.status-cards.card.deployMode"), modeParts.length ? modeParts.join(" ") : t("ui.status-cards.card.none"));
|
|
51
57
|
}
|
|
52
58
|
}
|
|
53
|
-
//
|
|
59
|
+
// Optional workflows - a saved value is used without asking, so show the current value before confirmation.
|
|
54
60
|
if (options) {
|
|
55
|
-
const onOff = (v) => (v ? paint("
|
|
56
|
-
row("🔢", "
|
|
61
|
+
const onOff = (v) => (v ? paint(t("ui.status-cards.card.on"), A.green) : paint(t("ui.status-cards.card.off"), A.dim));
|
|
62
|
+
row("🔢", t("ui.status-cards.card.autoBump"), onOff(options.semverAuto));
|
|
57
63
|
row("🤖", "Copilot", onOff(options.copilotAi));
|
|
58
64
|
}
|
|
59
|
-
//
|
|
65
|
+
// Monorepo paths - shown when at least one entry is not the root
|
|
60
66
|
const nonRoot = [...paths.entries()].filter(([, p]) => p && p !== ".");
|
|
61
67
|
if (nonRoot.length) {
|
|
62
|
-
row("📁", "
|
|
68
|
+
row("📁", t("ui.status-cards.card.paths"), [...paths.entries()].map(([type, p]) => `${type}→${p}`).join(", "));
|
|
63
69
|
}
|
|
64
70
|
out(`${GUT}\n`);
|
|
65
71
|
}
|
|
66
72
|
|
|
67
|
-
//
|
|
73
|
+
// New-install vs update line (the Breaking box is in breaking-check.js)
|
|
68
74
|
export function printInstallKind({ currentTemplateVersion = "", templateVersion = "" }, out = (s) => process.stderr.write(s)) {
|
|
69
75
|
if (currentTemplateVersion) {
|
|
70
|
-
out(`${GUT} ♻️ ${paint("
|
|
76
|
+
out(`${GUT} ♻️ ${t("ui.status-cards.kind.update", { title: paint(t("ui.status-cards.kind.updateTitle"), A.bold), from: paint(`v${currentTemplateVersion}`, A.dim), to: paint(`v${templateVersion}`, A.green) })}\n`);
|
|
71
77
|
} else {
|
|
72
|
-
out(`${GUT} 🆕 ${
|
|
78
|
+
out(`${GUT} 🆕 ${t("ui.status-cards.kind.fresh", { title: paint(t("ui.status-cards.kind.freshTitle"), A.bold), version: paint(`v${templateVersion}`, A.green) })}\n`);
|
|
73
79
|
}
|
|
74
80
|
out(`${GUT}\n`);
|
|
75
81
|
}
|