project-auto-wizard 0.13.3 → 0.14.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 +1 -1
- 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 +296 -257
- package/payload/scripts/issue_helper.py +54 -50
- package/payload/scripts/messages.py +2279 -0
- package/payload/scripts/truncate_release_notes.py +7 -6
- package/payload/scripts/version_manager.py +64 -61
- package/payload/version.yml.template +2 -1
- package/payload/workflows/common/PROJECT-COMMON-AI-PR-SUMMARY.yaml +17 -12
- package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +59 -34
- package/payload/workflows/common/PROJECT-COMMON-ISSUE-HELPER.yaml +17 -15
- package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +17 -10
- package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +54 -44
- package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +24 -17
- 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 +85 -89
- package/src/commands/dry-run.js +63 -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 +45 -35
- 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 +187 -0
- package/src/i18n/catalog/en/copy.js +19 -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 +187 -0
- package/src/i18n/catalog/ko/copy.js +19 -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 +128 -94
- 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 +89 -88
package/src/ui/env-plan.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// @wizard env
|
|
2
|
-
// io
|
|
1
|
+
// @wizard env plan question UI - collects ask keys, prints field cards, lets the user keep all defaults or change some.
|
|
2
|
+
// io is injected - tests pass {select, multiselect, text} stubs. The default is the real readline-engine.
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { readFileSync } from "node:fs";
|
|
5
5
|
import { stdin, stderr } from "node:process";
|
|
@@ -10,14 +10,15 @@ import { loadWizardPrompts, wfField, workflowDisplayName } from "../core/wizard-
|
|
|
10
10
|
import { deployFilter, payloadWorkflowNames, NO_DEPLOY_STYLE } from "../core/deploy-style.js";
|
|
11
11
|
import { buildTypeRootFilter, readSavedDeployValues } from "../core/copy/workflows.js";
|
|
12
12
|
import * as engine from "./readline-engine.js";
|
|
13
|
+
import { t } from "../i18n/index.js";
|
|
13
14
|
|
|
14
15
|
const CANCEL = engine.CANCEL;
|
|
15
16
|
|
|
16
|
-
//
|
|
17
|
+
// Progress output goes to stderr (keeps stdout pipes clean).
|
|
17
18
|
const defaultLog = (s = "") => stderr.write(s + "\n");
|
|
18
19
|
|
|
19
|
-
//
|
|
20
|
-
// usages: [{type, workflowName}]
|
|
20
|
+
// Build the usage string.
|
|
21
|
+
// usages: [{type, workflowName}] - with several types only "t1·t2", with one type "type name1·name2".
|
|
21
22
|
export function scopeString(usages = []) {
|
|
22
23
|
const types = []; const names = [];
|
|
23
24
|
for (const { type, workflowName } of usages) {
|
|
@@ -28,19 +29,19 @@ export function scopeString(usages = []) {
|
|
|
28
29
|
return `${types.join("·")} ${names.join("·")}`.trim();
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
// ask
|
|
32
|
-
// payloadRoot:
|
|
32
|
+
// Collect ask KEYs - scans the same sources as the workflows that actually get installed.
|
|
33
|
+
// payloadRoot: package payload/ root. types: target types to install.
|
|
33
34
|
// opts:
|
|
34
|
-
// resolvers -
|
|
35
|
-
// flutterStore - Flutter
|
|
36
|
-
// prompts - wizard-labels
|
|
37
|
-
// saved - Map<type, Map<key,value>>: version.yml deploy
|
|
38
|
-
// (
|
|
39
|
-
//
|
|
35
|
+
// resolvers - resolves @-prefixed defaults (@repo etc.) at collection time
|
|
36
|
+
// flutterStore - Flutter store targets (string[]|null). An array excludes workflows of deselected stores from the scan (null = current behaviour)
|
|
37
|
+
// prompts - parsed wizard-labels object (for workflow display names; falls back to the extension-stripped name when null)
|
|
38
|
+
// saved - Map<type, Map<key,value>>: values saved in the version.yml deploy block. When present they are shown as defaults
|
|
39
|
+
// (choosing "keep defaults" on an update must preserve the values answered at install time)
|
|
40
|
+
// Returns: { keys:[], defaults:Map<key,default>, typeDefaults:Map<"type|key",default>,
|
|
40
41
|
// usages:Map<key,[{type,workflowName}]> }
|
|
41
42
|
export function collectAsks(payloadRoot, types = [], opts = {}) {
|
|
42
43
|
const { resolvers = {}, deployStyle = "", flutterStore = null, prompts = null, saved = new Map() } = opts;
|
|
43
|
-
//
|
|
44
|
+
// Do not ask about deploy workflows that will not be installed - the number of questions follows the install scope.
|
|
44
45
|
const available = payloadWorkflowNames(payloadRoot);
|
|
45
46
|
const keepDeploy = deployFilter(deployStyle, available);
|
|
46
47
|
const baseDir = join(payloadRoot, PAYLOAD.workflowsDir);
|
|
@@ -49,18 +50,18 @@ export function collectAsks(payloadRoot, types = [], opts = {}) {
|
|
|
49
50
|
const typeDefaults = new Map();
|
|
50
51
|
const usages = new Map();
|
|
51
52
|
|
|
52
|
-
//
|
|
53
|
-
//
|
|
53
|
+
// Scan units: [type, folder]. The top level of common/ is always installed regardless of the chosen types
|
|
54
|
+
// (same rule as the copy engine), so it is always scanned.
|
|
54
55
|
const units = [];
|
|
55
56
|
const commonDir = join(baseDir, "common");
|
|
56
57
|
if (exists(commonDir)) units.push(["common", commonDir, null]);
|
|
57
58
|
for (const type of types) {
|
|
58
59
|
const [typeDir, serverDeployDir] = typeWorkflowDirs(payloadRoot, type);
|
|
59
60
|
if (!exists(typeDir)) continue;
|
|
60
|
-
//
|
|
61
|
-
// go/python
|
|
62
|
-
//
|
|
63
|
-
// Flutter
|
|
61
|
+
// Same folder layout as the copy engine: type root + server-deploy (only when deploy is not "none").
|
|
62
|
+
// Types such as go/python and react/next keep server deploy workflows directly in the type root; they go
|
|
63
|
+
// through the same deploy-style filter - with "no deploy", CD/PR-preview ask keys (DEPLOY_PORT, SSH_AUTH_METHOD, ...) are not asked.
|
|
64
|
+
// Flutter also filters out deselected store workflows (PLAYSTORE, TESTFLIGHT) so the question scope equals the install scope.
|
|
64
65
|
units.push([type, typeDir, buildTypeRootFilter(type, deployStyle, flutterStore, available)]);
|
|
65
66
|
if (deployStyle !== NO_DEPLOY_STYLE) {
|
|
66
67
|
units.push([type, serverDeployDir, keepDeploy]);
|
|
@@ -77,13 +78,13 @@ export function collectAsks(payloadRoot, types = [], opts = {}) {
|
|
|
77
78
|
for (const line of content.split(/\r?\n/)) {
|
|
78
79
|
const p = parseWizardLine(line);
|
|
79
80
|
if (!p || p.action !== "ask") continue;
|
|
80
|
-
//
|
|
81
|
+
// Per-type default: resolved via resolver when @-prefixed, otherwise a literal
|
|
81
82
|
const rawDefault = p.arg.startsWith("@")
|
|
82
83
|
? resolveToken(p.arg.slice(1), type, resolvers)
|
|
83
84
|
: p.arg;
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
85
|
+
// If a literal default embeds __PROJECT_NAME__ etc., expand it to the real repoName -
|
|
86
|
+
// it must be the same substitution substituteEnv() applies to installed files, otherwise the
|
|
87
|
+
// wizard display and the actual install result would diverge.
|
|
87
88
|
const savedValue = saved.get(type)?.get(p.key);
|
|
88
89
|
const typeDefault = savedValue != null && savedValue !== ""
|
|
89
90
|
? savedValue
|
|
@@ -99,14 +100,14 @@ export function collectAsks(payloadRoot, types = [], opts = {}) {
|
|
|
99
100
|
return { keys, defaults, typeDefaults, usages };
|
|
100
101
|
}
|
|
101
102
|
|
|
102
|
-
// KEY
|
|
103
|
+
// The type where KEY first appears - used for type-override priority when looking up labels
|
|
103
104
|
function firstTypeFor(usages, key) {
|
|
104
105
|
return usages.get(key)?.[0]?.type ?? "";
|
|
105
106
|
}
|
|
106
107
|
|
|
107
|
-
//
|
|
108
|
-
// isDefault
|
|
109
|
-
//
|
|
108
|
+
// Final answer list - built here so the completion summary and the install log share the same data.
|
|
109
|
+
// isDefault means "left at the default". It is the first thing checked when a deploy later fails,
|
|
110
|
+
// so keeping only the value is not enough.
|
|
110
111
|
function buildAnswers(prompts, asks, values, useDefaults) {
|
|
111
112
|
return asks.keys.map((key) => {
|
|
112
113
|
const def = asks.defaults.get(key) ?? "";
|
|
@@ -121,54 +122,54 @@ function buildAnswers(prompts, asks, values, useDefaults) {
|
|
|
121
122
|
});
|
|
122
123
|
}
|
|
123
124
|
|
|
124
|
-
// KEY
|
|
125
|
-
// info: { default, usages }
|
|
125
|
+
// Print one KEY as a 'label, usage, description, example, default' card.
|
|
126
|
+
// info: { default, usages } - with idx/tot a "(i/t)" progress marker is shown. log is injectable (silences tests).
|
|
126
127
|
export function printFieldCard(prompts, key, info, idx = null, tot = null, log = defaultLog) {
|
|
127
|
-
const
|
|
128
|
-
const label = wfField(prompts,
|
|
129
|
-
const help = wfField(prompts,
|
|
130
|
-
const ex = wfField(prompts,
|
|
128
|
+
const type = info.usages?.[0]?.type ?? "";
|
|
129
|
+
const label = wfField(prompts, type, key, "label");
|
|
130
|
+
const help = wfField(prompts, type, key, "help");
|
|
131
|
+
const ex = wfField(prompts, type, key, "example");
|
|
131
132
|
const scope = scopeString(info.usages || []);
|
|
132
133
|
const head = idx != null && tot != null
|
|
133
134
|
? ` ▸ (${idx}/${tot}) ${label} [${scope}]`
|
|
134
135
|
: ` ▸ ${label} [${scope}]`;
|
|
135
136
|
log(head);
|
|
136
137
|
if (help) log(` ${help}`);
|
|
137
|
-
if (ex) log(
|
|
138
|
-
log(
|
|
138
|
+
if (ex) log(t("ui.env-plan.card.example", { example: ex }));
|
|
139
|
+
log(t("ui.env-plan.card.default", { value: info.default ?? "" }));
|
|
139
140
|
log("");
|
|
140
141
|
}
|
|
141
142
|
|
|
142
|
-
// ask
|
|
143
|
-
//
|
|
143
|
+
// An ask field whose default is exactly "true"/"false" is treated as a boolean field.
|
|
144
|
+
// Decided from the literal value alone without changing the marker syntax (@wizard ask:...) - no separate type annotation needed.
|
|
144
145
|
function isBooleanDefault(value) {
|
|
145
146
|
return value === "true" || value === "false";
|
|
146
147
|
}
|
|
147
148
|
|
|
148
|
-
//
|
|
149
|
-
//
|
|
149
|
+
// Validate ask values with a fixed format - a bad value passes install and only fails at deploy time, so re-ask at input time.
|
|
150
|
+
// Returns: the error message ("" when fine).
|
|
150
151
|
export function validateAskValue(key, value) {
|
|
151
152
|
const v = String(value);
|
|
152
153
|
if (/_PORT$/.test(key)) {
|
|
153
154
|
const n = Number(v);
|
|
154
|
-
return /^\d+$/.test(v) && n >= 1 && n <= 65535 ? "" : "
|
|
155
|
+
return /^\d+$/.test(v) && n >= 1 && n <= 65535 ? "" : t("ui.env-plan.validate.port");
|
|
155
156
|
}
|
|
156
157
|
if (key === "SSH_AUTH_METHOD") {
|
|
157
|
-
return v === "password" || v === "key" ? "" : "
|
|
158
|
+
return v === "password" || v === "key" ? "" : t("ui.env-plan.validate.sshAuth");
|
|
158
159
|
}
|
|
159
160
|
if (key === "JAVA_VERSION") {
|
|
160
|
-
return /^\d+(\.\d+)*$/.test(v) ? "" : "
|
|
161
|
+
return /^\d+(\.\d+)*$/.test(v) ? "" : t("ui.env-plan.validate.javaVersion");
|
|
161
162
|
}
|
|
162
163
|
return "";
|
|
163
164
|
}
|
|
164
165
|
|
|
165
|
-
//
|
|
166
|
-
//
|
|
166
|
+
// Prompt for the given KEYs one by one and record them in values.
|
|
167
|
+
// Empty input (Enter) / ESC keeps the KEY's shared default.
|
|
167
168
|
async function promptEach(io, prompts, asks, todoKeys, values, log) {
|
|
168
169
|
const tot = todoKeys.length;
|
|
169
170
|
if (tot === 0) return;
|
|
170
171
|
log("");
|
|
171
|
-
log("
|
|
172
|
+
log(t("ui.env-plan.each.intro"));
|
|
172
173
|
log("");
|
|
173
174
|
let i = 0;
|
|
174
175
|
for (const key of todoKeys) {
|
|
@@ -178,36 +179,36 @@ async function promptEach(io, prompts, asks, todoKeys, values, log) {
|
|
|
178
179
|
const label = wfField(prompts, firstTypeFor(asks.usages, key), key, "label");
|
|
179
180
|
let input;
|
|
180
181
|
if (isBooleanDefault(def)) {
|
|
181
|
-
const answer = await io.confirm({ message:
|
|
182
|
+
const answer = await io.confirm({ message: t("ui.env-plan.each.enable", { label }), initialValue: def === "true" });
|
|
182
183
|
input = answer === CANCEL ? def : (answer ? "true" : "false");
|
|
183
184
|
} else {
|
|
184
185
|
for (;;) {
|
|
185
|
-
input = await io.text({ message:
|
|
186
|
+
input = await io.text({ message: t("ui.env-plan.each.input", { def }), defaultValue: def });
|
|
186
187
|
if (input === CANCEL || input == null) { input = def; break; }
|
|
187
188
|
input = String(input).trim();
|
|
188
189
|
if (input === "") { input = def; break; }
|
|
189
190
|
const problem = validateAskValue(key, input);
|
|
190
191
|
if (!problem) break;
|
|
191
|
-
log(
|
|
192
|
+
log(t("ui.env-plan.each.invalid", { input, problem }));
|
|
192
193
|
}
|
|
193
194
|
}
|
|
194
195
|
values.set(key, input);
|
|
195
|
-
log(
|
|
196
|
+
log(t("ui.env-plan.each.result", { label, input }));
|
|
196
197
|
log("");
|
|
197
198
|
}
|
|
198
199
|
}
|
|
199
200
|
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
// - useDefaults=true
|
|
203
|
-
// - useDefaults=false
|
|
204
|
-
// (
|
|
205
|
-
//
|
|
206
|
-
// payloadRoot/types/resolvers/flutterStore
|
|
207
|
-
// targetRoot
|
|
208
|
-
// force
|
|
209
|
-
// io
|
|
210
|
-
// log
|
|
201
|
+
// Deploy env setup plan.
|
|
202
|
+
// Returns: { values: Map<key,value>, useDefaults: boolean, answers: [{key,label,value,isDefault,scope}] }
|
|
203
|
+
// - useDefaults=true -> the caller passes it straight to substituteEnv, taking the per-type default path
|
|
204
|
+
// - useDefaults=false -> only keys in values are replaced with user-confirmed values, the rest use defaults
|
|
205
|
+
// (WARNING: substituteEnv reads values only when useDefaults=false, so always pass this flag along)
|
|
206
|
+
// Arguments:
|
|
207
|
+
// payloadRoot/types/resolvers/flutterStore - same meaning as collectAsks
|
|
208
|
+
// targetRoot - first place to look for wizard-prompts.yml (default ".")
|
|
209
|
+
// force - when true, take all defaults without asking
|
|
210
|
+
// io - injected {select, multiselect, text} (default readline-engine). Test stub point.
|
|
211
|
+
// log - injected card/notice output function (default stderr)
|
|
211
212
|
export async function promptEnvPlan({
|
|
212
213
|
payloadRoot, types = [], io = null, force = false, resolvers = {},
|
|
213
214
|
deployStyle = "", flutterStore = null, targetRoot = ".", repoName = "", log = defaultLog,
|
|
@@ -217,11 +218,11 @@ export async function promptEnvPlan({
|
|
|
217
218
|
const asks = collectAsks(payloadRoot, types, { resolvers, deployStyle, flutterStore, prompts, saved });
|
|
218
219
|
const defaults = asks.defaults;
|
|
219
220
|
|
|
220
|
-
//
|
|
221
|
+
// No collected keys -> nothing to ask
|
|
221
222
|
if (asks.keys.length === 0) return { values: new Map(), useDefaults: true, answers: [] };
|
|
222
223
|
|
|
223
|
-
//
|
|
224
|
-
// io
|
|
224
|
+
// Non-interactive: force or (no io injected && non-TTY) -> all defaults
|
|
225
|
+
// With io injected (tests / parent wizard) it runs interactively regardless of TTY.
|
|
225
226
|
const interactive = !force && (io != null || stdin.isTTY);
|
|
226
227
|
if (!interactive) {
|
|
227
228
|
const values = new Map(defaults);
|
|
@@ -230,57 +231,57 @@ export async function promptEnvPlan({
|
|
|
230
231
|
|
|
231
232
|
const ui = io ?? engine;
|
|
232
233
|
|
|
233
|
-
//
|
|
234
|
+
// Print the full default-preview cards
|
|
234
235
|
log("");
|
|
235
|
-
log("
|
|
236
|
+
log(t("ui.env-plan.intro.title"));
|
|
236
237
|
log("");
|
|
237
|
-
log("
|
|
238
|
-
log("
|
|
238
|
+
log(t("ui.env-plan.intro.line1"));
|
|
239
|
+
log(t("ui.env-plan.intro.line2"));
|
|
239
240
|
log("");
|
|
240
241
|
const tot = asks.keys.length;
|
|
241
242
|
asks.keys.forEach((key, i) => {
|
|
242
243
|
printFieldCard(prompts, key, { default: defaults.get(key), usages: asks.usages.get(key) || [] }, i + 1, tot, log);
|
|
243
244
|
});
|
|
244
|
-
log("
|
|
245
|
+
log(t("ui.env-plan.intro.rule"));
|
|
245
246
|
|
|
246
247
|
const choice = await ui.select({
|
|
247
|
-
message: "
|
|
248
|
+
message: t("ui.env-plan.choice.message"),
|
|
248
249
|
options: [
|
|
249
|
-
{ value: "all", label: "
|
|
250
|
-
{ value: "each", label: "
|
|
251
|
-
{ value: "some", label: "
|
|
250
|
+
{ value: "all", label: t("ui.env-plan.choice.all") },
|
|
251
|
+
{ value: "each", label: t("ui.env-plan.choice.each") },
|
|
252
|
+
{ value: "some", label: t("ui.env-plan.choice.some") },
|
|
252
253
|
],
|
|
253
254
|
});
|
|
254
|
-
// ESC
|
|
255
|
+
// ESC/cancel -> all defaults
|
|
255
256
|
if (choice === CANCEL || choice == null || choice === "all") {
|
|
256
257
|
const values = new Map(defaults);
|
|
257
258
|
return { values, useDefaults: true, answers: buildAnswers(prompts, asks, values, true) };
|
|
258
259
|
}
|
|
259
260
|
|
|
260
|
-
//
|
|
261
|
-
// values
|
|
261
|
+
// Keep only user-confirmed keys in values - substituteEnv(useDefaults:false) fills keys
|
|
262
|
+
// missing from values with the per-type defaults (same as overriding defaults with just the answered values).
|
|
262
263
|
const values = new Map();
|
|
263
264
|
if (choice === "each") {
|
|
264
265
|
await promptEach(ui, prompts, asks, asks.keys, values, log);
|
|
265
266
|
return { values, useDefaults: false, answers: buildAnswers(prompts, asks, values, false) };
|
|
266
267
|
}
|
|
267
268
|
|
|
268
|
-
// some:
|
|
269
|
+
// some: multi-select the items to change -> prompt only for those
|
|
269
270
|
const options = asks.keys.map((key) => ({
|
|
270
271
|
value: key,
|
|
271
|
-
label:
|
|
272
|
+
label: t("ui.env-plan.some.option", { label: wfField(prompts, firstTypeFor(asks.usages, key), key, "label"), def: defaults.get(key) }),
|
|
272
273
|
}));
|
|
273
274
|
const selected = await ui.multiselect({
|
|
274
|
-
message: "
|
|
275
|
+
message: t("ui.env-plan.some.message"),
|
|
275
276
|
options,
|
|
276
277
|
initialValues: [],
|
|
277
278
|
});
|
|
278
|
-
// ESC
|
|
279
|
+
// ESC/empty selection -> all defaults
|
|
279
280
|
if (selected === CANCEL || !Array.isArray(selected) || selected.length === 0) {
|
|
280
281
|
const values = new Map(defaults);
|
|
281
282
|
return { values, useDefaults: true, answers: buildAnswers(prompts, asks, values, true) };
|
|
282
283
|
}
|
|
283
|
-
//
|
|
284
|
+
// Keep collection order + accept only collected keys
|
|
284
285
|
const todo = asks.keys.filter((k) => selected.includes(k));
|
|
285
286
|
await promptEach(ui, prompts, asks, todo, values, log);
|
|
286
287
|
return { values, useDefaults: false, answers: buildAnswers(prompts, asks, values, false) };
|