project-auto-wizard 0.3.3 → 0.4.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 +2 -2
- package/package.json +1 -1
- package/payload/config/wizard-prompts.yml +6 -0
- package/payload/scripts/__pycache__/changelog_manager.cpython-314.pyc +0 -0
- package/payload/scripts/__pycache__/issue_helper.cpython-314.pyc +0 -0
- package/payload/scripts/__pycache__/version_manager.cpython-314.pyc +0 -0
- package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +53 -0
- package/payload/workflows/common/PROJECT-COMMON-ISSUE-HELPER.yaml +1 -1
- package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +7 -0
- package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +18 -0
- package/src/commands/interactive.js +10 -2
- package/src/core/copy/workflows.js +12 -1
- package/src/ui/env-plan.js +23 -8
- package/src/ui/prompts.js +19 -0
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ npx project-auto-wizard
|
|
|
17
17
|
[](package.json)
|
|
18
18
|
|
|
19
19
|
<!-- AUTO-VERSION-SECTION: DO NOT EDIT MANUALLY -->
|
|
20
|
-
## 최신 버전 : v0.3.
|
|
20
|
+
## 최신 버전 : v0.3.3 (2026-08-18)
|
|
21
21
|
|
|
22
22
|
[전체 버전 기록 보기](CHANGELOG.md)
|
|
23
23
|
|
|
@@ -130,7 +130,7 @@ flowchart LR
|
|
|
130
130
|
|
|
131
131
|
- **pr-flow** (기본): `VERSION-CONTROL`(main 직접 push 안전망) + `AUTO-CHANGELOG-CONTROL`(릴리스 PR) + `RELEASE-PUBLISH`(tag+Release) 3종 설치
|
|
132
132
|
- **trunk-based** (릴리스 브랜치 = 개발 브랜치): `RELEASE-PUBLISH` 하나가 main push마다 버전확정 → 체인지로그 → tag → Release를 순차 처리
|
|
133
|
-
- 마법사가
|
|
133
|
+
- 마법사가 브랜치 전략(pr-flow/trunk-based)을 먼저 묻고, trunk-based를 고르면 개발 브랜치 질문 없이 릴리스 브랜치 하나만 사용합니다. 비대화형은 `--main-branch`/`--develop-branch` 플래그로 지정하며, 없으면 **생성 + push**까지. 선택은 `version.yml`에 저장되어 업데이트 시 재질문 없음
|
|
134
134
|
|
|
135
135
|
## 설치 옵션
|
|
136
136
|
|
package/package.json
CHANGED
|
@@ -45,6 +45,11 @@ SSH_AUTH_METHOD:
|
|
|
45
45
|
help: "배포 서버 접속 방식입니다. 둘 중 하나를 그대로 입력하세요."
|
|
46
46
|
example: "password 또는 key"
|
|
47
47
|
|
|
48
|
+
ISSUE_HELPER_CREATE_BRANCH:
|
|
49
|
+
label: "이슈 생성 시 브랜치 자동 생성"
|
|
50
|
+
help: "이슈가 열리면 base 브랜치 기준으로 브랜치를 자동으로 만듭니다. 원치 않으면 기본값(아니오)을 그대로 두세요."
|
|
51
|
+
example: "true"
|
|
52
|
+
|
|
48
53
|
# workflow filename (partial match) -> human readable short name
|
|
49
54
|
# wizard uses this to build "[usage]" in env questions.
|
|
50
55
|
_workflow_names:
|
|
@@ -60,3 +65,4 @@ _workflow_names:
|
|
|
60
65
|
FLUTTER-ANDROID-SELFHOSTED: "안드로이드 자체배포"
|
|
61
66
|
FLUTTER-ANDROID-PLAYSTORE: "플레이스토어 배포"
|
|
62
67
|
FLUTTER-ANDROID-FIREBASE: "Firebase 배포"
|
|
68
|
+
ISSUE-HELPER: "이슈 헬퍼"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -36,6 +36,11 @@
|
|
|
36
36
|
# {{MAIN_BRANCH}}-push workflows (e.g. RELEASE-PUBLISH) for that merge.
|
|
37
37
|
# - secrets.AI_API_KEY (+ vars.AI_API_BASE_URL / vars.AI_MODEL): preferred
|
|
38
38
|
# summary engine. Falls back to GitHub Models, then to rule-based.
|
|
39
|
+
#
|
|
40
|
+
# Issue #90: without WORKFLOW_PAT, a wait-for-merge-and-trigger-release job
|
|
41
|
+
# polls until the automerge actually lands, then wakes RELEASE-PUBLISH via
|
|
42
|
+
# workflow_dispatch — GITHUB_TOKEN can always trigger that event, unlike
|
|
43
|
+
# the push/release events blocked by GitHub's anti-recursion policy.
|
|
39
44
|
# ===================================================================
|
|
40
45
|
|
|
41
46
|
name: PROJECT-AUTO-CHANGELOG-CONTROL
|
|
@@ -268,3 +273,51 @@ jobs:
|
|
|
268
273
|
fi
|
|
269
274
|
exit 1
|
|
270
275
|
fi
|
|
276
|
+
|
|
277
|
+
wait-for-merge-and-trigger-release:
|
|
278
|
+
name: Wait for merge and trigger RELEASE-PUBLISH (WORKFLOW_PAT 없이도 동작)
|
|
279
|
+
needs: changelog-and-merge
|
|
280
|
+
runs-on: ubuntu-latest
|
|
281
|
+
timeout-minutes: 35
|
|
282
|
+
permissions:
|
|
283
|
+
pull-requests: read
|
|
284
|
+
actions: write
|
|
285
|
+
steps:
|
|
286
|
+
- name: Poll for merge completion
|
|
287
|
+
id: wait
|
|
288
|
+
env:
|
|
289
|
+
GH_TOKEN: ${{ github.token }}
|
|
290
|
+
GH_REPO: ${{ github.repository }}
|
|
291
|
+
run: |
|
|
292
|
+
PR_NUMBER=${{ github.event.pull_request.number }}
|
|
293
|
+
MAX_WAIT=1800
|
|
294
|
+
INTERVAL=20
|
|
295
|
+
ELAPSED=0
|
|
296
|
+
MERGED=false
|
|
297
|
+
|
|
298
|
+
while [ $ELAPSED -lt $MAX_WAIT ]; do
|
|
299
|
+
PR_STATE=$(gh pr view "$PR_NUMBER" --json state -q .state 2>/dev/null || echo "")
|
|
300
|
+
if [ "$PR_STATE" = "MERGED" ]; then
|
|
301
|
+
MERGED=true
|
|
302
|
+
echo "PR #$PR_NUMBER 병합 확인 (경과 ${ELAPSED}s)"
|
|
303
|
+
break
|
|
304
|
+
fi
|
|
305
|
+
sleep $INTERVAL
|
|
306
|
+
ELAPSED=$((ELAPSED + INTERVAL))
|
|
307
|
+
done
|
|
308
|
+
|
|
309
|
+
if [ "$MERGED" != "true" ]; then
|
|
310
|
+
echo "::warning::PR #$PR_NUMBER 병합을 ${MAX_WAIT}s 내에 확인하지 못했습니다 — RELEASE-PUBLISH 자동 트리거를 건너뜁니다. WORKFLOW_PAT이 등록되어 있다면 기존 경로로 정상 진행되며, 없다면 필요 시 RELEASE-PUBLISH를 수동으로 workflow_dispatch 하세요."
|
|
311
|
+
fi
|
|
312
|
+
|
|
313
|
+
echo "merged=$MERGED" >> "$GITHUB_OUTPUT"
|
|
314
|
+
|
|
315
|
+
- name: Trigger RELEASE-PUBLISH
|
|
316
|
+
if: steps.wait.outputs.merged == 'true'
|
|
317
|
+
continue-on-error: true
|
|
318
|
+
env:
|
|
319
|
+
GH_TOKEN: ${{ github.token }}
|
|
320
|
+
GH_REPO: ${{ github.repository }}
|
|
321
|
+
run: |
|
|
322
|
+
gh workflow run PROJECT-COMMON-RELEASE-PUBLISH.yaml --ref {{MAIN_BRANCH}}
|
|
323
|
+
echo "RELEASE-PUBLISH workflow_dispatch 트리거 요청 완료 (WORKFLOW_PAT 없이도 동작)"
|
|
@@ -36,7 +36,7 @@ jobs:
|
|
|
36
36
|
ISSUE_HELPER_BRANCH_PREFIX: ""
|
|
37
37
|
ISSUE_HELPER_MAX_BRANCH_LENGTH: "100"
|
|
38
38
|
ISSUE_HELPER_COMMIT_TEMPLATE: "${issueTitle} : feat : {변경 사항에 대한 설명} ${issueUrl}"
|
|
39
|
-
ISSUE_HELPER_CREATE_BRANCH: "false"
|
|
39
|
+
ISSUE_HELPER_CREATE_BRANCH: "false" # @wizard ask:false
|
|
40
40
|
ISSUE_HELPER_BASE_BRANCH: "{{MAIN_BRANCH}}"
|
|
41
41
|
steps:
|
|
42
42
|
- name: Checkout repository
|
|
@@ -37,6 +37,13 @@
|
|
|
37
37
|
# workflow_dispatch republishes the current version (recovery path when a
|
|
38
38
|
# previous run failed after the version was confirmed but before the
|
|
39
39
|
# release was created). The idempotency guard keeps it safe to re-run.
|
|
40
|
+
#
|
|
41
|
+
# Issue #90: this file and its self-copy (.github/workflows/) intentionally
|
|
42
|
+
# differ by one step — the self-copy also triggers NPM-PUBLISH via
|
|
43
|
+
# workflow_dispatch right after the release is created. NPM-PUBLISH.yaml is
|
|
44
|
+
# a repo-only workflow (not shipped in payload), so wiring that dispatch
|
|
45
|
+
# here would make every wizard-installed repo call a workflow that doesn't
|
|
46
|
+
# exist for them on every release.
|
|
40
47
|
# ===================================================================
|
|
41
48
|
|
|
42
49
|
name: PROJECT-RELEASE-PUBLISH
|
|
@@ -19,6 +19,11 @@
|
|
|
19
19
|
#
|
|
20
20
|
# Release-confirm pattern: "chore(release):" — keep in sync with
|
|
21
21
|
# PROJECT-COMMON-AUTO-CHANGELOG-CONTROL and PROJECT-COMMON-RELEASE-PUBLISH.
|
|
22
|
+
#
|
|
23
|
+
# Issue #90 / #61: after pushing the safety-net bump, this workflow also
|
|
24
|
+
# triggers RELEASE-PUBLISH via workflow_dispatch so the drift it used to
|
|
25
|
+
# only detect (issue #61) is now auto-recovered instead of requiring a
|
|
26
|
+
# manual re-run.
|
|
22
27
|
# ===================================================================
|
|
23
28
|
|
|
24
29
|
name: PROJECT-VERSION-CONTROL
|
|
@@ -39,6 +44,7 @@ on:
|
|
|
39
44
|
|
|
40
45
|
permissions:
|
|
41
46
|
contents: write
|
|
47
|
+
actions: write
|
|
42
48
|
|
|
43
49
|
jobs:
|
|
44
50
|
version-bump:
|
|
@@ -129,6 +135,7 @@ jobs:
|
|
|
129
135
|
fi
|
|
130
136
|
|
|
131
137
|
- name: Commit and push changes
|
|
138
|
+
id: commit_push
|
|
132
139
|
if: steps.release_guard.outputs.skip != 'true'
|
|
133
140
|
run: |
|
|
134
141
|
git config --local user.email "action@github.com"
|
|
@@ -138,6 +145,7 @@ jobs:
|
|
|
138
145
|
|
|
139
146
|
if git diff --staged --quiet; then
|
|
140
147
|
echo "no changes to commit (already in sync)"
|
|
148
|
+
echo "pushed=false" >> "$GITHUB_OUTPUT"
|
|
141
149
|
else
|
|
142
150
|
NEW_VERSION="${{ steps.version.outputs.new_version }}"
|
|
143
151
|
COMMIT_MSG="chore(version): bump to v${NEW_VERSION} [skip ci]"
|
|
@@ -172,12 +180,22 @@ jobs:
|
|
|
172
180
|
echo "push failed after $MAX_RETRIES attempts"
|
|
173
181
|
exit 1
|
|
174
182
|
fi
|
|
183
|
+
echo "pushed=true" >> "$GITHUB_OUTPUT"
|
|
175
184
|
else
|
|
176
185
|
echo "commit failed"
|
|
177
186
|
exit 1
|
|
178
187
|
fi
|
|
179
188
|
fi
|
|
180
189
|
|
|
190
|
+
- name: Trigger RELEASE-PUBLISH (safety-net bump 발행)
|
|
191
|
+
if: steps.release_guard.outputs.skip != 'true' && steps.commit_push.outputs.pushed == 'true'
|
|
192
|
+
continue-on-error: true
|
|
193
|
+
env:
|
|
194
|
+
GH_TOKEN: ${{ github.token }}
|
|
195
|
+
run: |
|
|
196
|
+
gh workflow run PROJECT-COMMON-RELEASE-PUBLISH.yaml --ref {{MAIN_BRANCH}}
|
|
197
|
+
echo "RELEASE-PUBLISH workflow_dispatch 트리거 요청 완료 (WORKFLOW_PAT 없이도 동작, 이슈 #61 드리프트 자동 복구)"
|
|
198
|
+
|
|
181
199
|
- name: Summary
|
|
182
200
|
if: always()
|
|
183
201
|
run: |
|
|
@@ -192,11 +192,19 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), payloadRoot
|
|
|
192
192
|
: resolveBranchConfig({ defaultBranch: branch });
|
|
193
193
|
if (showOptional && !existing?.branches) {
|
|
194
194
|
const remoteBranches = await detectRemoteBranches(cwd);
|
|
195
|
+
// 이슈 #93 — 두 질문에 같은 이름을 입력해야만 trunk-based가 되는 암묵적 규칙 대신,
|
|
196
|
+
// 전략을 먼저 명시적으로 고르게 한다. 취소/그 외 값은 기존 기본 동작과 같은 pr-flow로 폴백
|
|
197
|
+
// (selectDeployStyle의 "ESC = 기본값" 패턴과 동일).
|
|
198
|
+
const strategyPick = await io.selectBranchStrategy();
|
|
199
|
+
const strategy = strategyPick === "trunk-based" ? "trunk-based" : "pr-flow";
|
|
195
200
|
const mainB = await pickBranch(io, `릴리스 브랜치를 선택하세요 (기본: ${branch})`, branch, remoteBranches, isCancel);
|
|
196
|
-
|
|
201
|
+
// trunk-based면 개발 브랜치 질문 자체를 생략 — 유일한 브랜치(main)를 그대로 develop으로 쓴다.
|
|
202
|
+
const devB = strategy === "trunk-based"
|
|
203
|
+
? mainB
|
|
204
|
+
: await pickBranch(io, "개발 브랜치를 선택하세요 (기본: develop)", "develop", remoteBranches, isCancel);
|
|
197
205
|
branches = resolveBranchConfig({ mainBranch: mainB, developBranch: devB, defaultBranch: branch });
|
|
198
206
|
if (branches.mode === "trunk-based") {
|
|
199
|
-
io.note?.(
|
|
207
|
+
io.note?.(`릴리스 브랜치(${branches.main}) 하나만 사용하는 trunk-based 모드로 설치합니다 (RELEASE-PUBLISH 단독).`, "브랜치 모드");
|
|
200
208
|
} else if (remoteBranches.length && !remoteBranches.includes(branches.develop)) {
|
|
201
209
|
await ensureDevelopBranch({
|
|
202
210
|
develop: branches.develop, remoteBranches, cwd,
|
|
@@ -159,7 +159,18 @@ export function copyWorkflows(context, payloadRoot, targetRoot = ".", hooks = {}
|
|
|
159
159
|
const commonDir = join(projectTypesDir, "common");
|
|
160
160
|
if (exists(commonDir)) {
|
|
161
161
|
const notExcluded = (filename) => !(branchMode === "trunk-based" && TRUNK_BASED_EXCLUDED.has(filename));
|
|
162
|
-
processDir(commonDir, workflowsDir, envOptsFor("common"), dirCtx, counters, notExcluded);
|
|
162
|
+
const c = processDir(commonDir, workflowsDir, envOptsFor("common"), dirCtx, counters, notExcluded);
|
|
163
|
+
// env 치환 — 타입별 폴더(copyWorkflowsForType)와 동일하게, 손대지 않기로 한 파일(unchanged/localOnly)은
|
|
164
|
+
// 건너뛴다. common 최상위는 지금까지 @wizard 마커가 없어 이 루프가 없어도 드러나지 않았지만,
|
|
165
|
+
// ISSUE_HELPER_CREATE_BRANCH(issue #94)부터는 실제로 값이 반영돼야 한다.
|
|
166
|
+
const untouched = [...c.unchanged, ...c.localOnly];
|
|
167
|
+
for (const filename of listYamlFiles(commonDir)) {
|
|
168
|
+
if (!notExcluded(filename)) continue;
|
|
169
|
+
const target = join(workflowsDir, filename);
|
|
170
|
+
if (!existsSync(target)) continue;
|
|
171
|
+
if (untouched.includes(filename)) continue;
|
|
172
|
+
configureEnv(target, envOptsFor("common"));
|
|
173
|
+
}
|
|
163
174
|
}
|
|
164
175
|
|
|
165
176
|
// (2~4) 타입별
|
package/src/ui/env-plan.js
CHANGED
|
@@ -48,10 +48,13 @@ export function collectAsks(payloadRoot, types = [], opts = {}) {
|
|
|
48
48
|
const typeDefaults = new Map();
|
|
49
49
|
const usages = new Map();
|
|
50
50
|
|
|
51
|
-
// 스캔 단위: [타입, 폴더].
|
|
52
|
-
//
|
|
53
|
-
//
|
|
51
|
+
// 스캔 단위: [타입, 폴더]. common/ 최상위는 타입 선택과 무관하게 항상 설치되므로(복사 엔진과
|
|
52
|
+
// 동일 규칙 — issue #94) 무조건 스캔한다. secret-backup은 common 최상위가 아니라 그 하위
|
|
53
|
+
// 폴더고, 파일 전체가 조건부로 설치되므로 포함하기로 한 경우에만 별도로 스캔한다 (이슈 #82) —
|
|
54
|
+
// 종전에는 스캔 대상이 아니어서 my-project 같은 예시값이 질문 없이 그대로 설치됐다.
|
|
54
55
|
const units = [];
|
|
56
|
+
const commonDir = join(baseDir, "common");
|
|
57
|
+
if (exists(commonDir)) units.push(["common", commonDir, null]);
|
|
55
58
|
for (const type of types) {
|
|
56
59
|
const typeDir = join(baseDir, type);
|
|
57
60
|
if (!exists(typeDir)) continue;
|
|
@@ -126,6 +129,12 @@ export function printFieldCard(prompts, key, info, idx = null, tot = null, log =
|
|
|
126
129
|
log("");
|
|
127
130
|
}
|
|
128
131
|
|
|
132
|
+
// ask 필드의 기본값이 정확히 "true"/"false"면 boolean 필드로 간주한다 (이슈 #94).
|
|
133
|
+
// 마커 문법(@wizard ask:...)을 바꾸지 않고 리터럴 값 형태만으로 판단 — 별도 타입 표기가 필요 없다.
|
|
134
|
+
function isBooleanDefault(value) {
|
|
135
|
+
return value === "true" || value === "false";
|
|
136
|
+
}
|
|
137
|
+
|
|
129
138
|
// 지정 KEY들을 하나씩 입력받아 values에 기록 (.sh _wf_prefill_interactive 등가).
|
|
130
139
|
// 빈 입력(Enter)/ESC → KEY 공통 기본값 유지 (.sh safe_read || _in="" 등가).
|
|
131
140
|
async function promptEach(io, prompts, asks, todoKeys, values, log) {
|
|
@@ -139,10 +148,16 @@ async function promptEach(io, prompts, asks, todoKeys, values, log) {
|
|
|
139
148
|
i++;
|
|
140
149
|
const def = asks.defaults.get(key) ?? "";
|
|
141
150
|
printFieldCard(prompts, key, { default: def, usages: asks.usages.get(key) || [] }, i, tot, log);
|
|
142
|
-
let input = await io.text({ message: `↳ 값 입력 (Enter=기본값 «${def}» 유지):`, defaultValue: def });
|
|
143
|
-
if (input === CANCEL || input == null || input === "") input = def;
|
|
144
|
-
values.set(key, input);
|
|
145
151
|
const label = wfField(prompts, firstTypeFor(asks.usages, key), key, "label");
|
|
152
|
+
let input;
|
|
153
|
+
if (isBooleanDefault(def)) {
|
|
154
|
+
const answer = await io.confirm({ message: `↳ ${label} — 활성화할까요?`, initialValue: def === "true" });
|
|
155
|
+
input = answer === CANCEL ? def : (answer ? "true" : "false");
|
|
156
|
+
} else {
|
|
157
|
+
input = await io.text({ message: `↳ 값 입력 (Enter=기본값 «${def}» 유지):`, defaultValue: def });
|
|
158
|
+
if (input === CANCEL || input == null || input === "") input = def;
|
|
159
|
+
}
|
|
160
|
+
values.set(key, input);
|
|
146
161
|
log(` → ${label} = ${input}`);
|
|
147
162
|
log("");
|
|
148
163
|
}
|
|
@@ -182,9 +197,9 @@ export async function promptEnvPlan({
|
|
|
182
197
|
|
|
183
198
|
// 기본값 미리보기 카드 전체 출력 (.sh 3237~3251)
|
|
184
199
|
log("");
|
|
185
|
-
log("▶
|
|
200
|
+
log("▶ 워크플로우 환경설정을 채웁니다");
|
|
186
201
|
log("");
|
|
187
|
-
log(" 설치되는
|
|
202
|
+
log(" 설치되는 워크플로우가 사용할 값입니다. 항목마다 '무엇에 쓰이는지·설명·예시'와");
|
|
188
203
|
log(" 기본값을 함께 보여드립니다. 그대로 둬도 되고, 원하는 것만 바꿀 수 있습니다.");
|
|
189
204
|
log("");
|
|
190
205
|
const tot = asks.keys.length;
|
package/src/ui/prompts.js
CHANGED
|
@@ -97,6 +97,25 @@ export async function selectDeployStyle() {
|
|
|
97
97
|
});
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
// 브랜치 전략 선택 (이슈 #93). 종전에는 "릴리스 브랜치"/"개발 브랜치" 두 질문에
|
|
101
|
+
// 같은 이름을 입력해야만 trunk-based가 됐는데, 그 규칙이 사전에 안내되지 않아
|
|
102
|
+
// 의도치 않게 pr-flow로 흘러갔다. 전략을 먼저 명시적으로 고르게 해 이를 없앤다.
|
|
103
|
+
// 옵션 순서(pr-flow 먼저)는 비-TTY 환경의 기본값과 직결되므로 바꾸지 않는다.
|
|
104
|
+
export async function selectBranchStrategy() {
|
|
105
|
+
engine.note(
|
|
106
|
+
"pr-flow는 develop에서 작업해 main으로 PR을 올리는 팀 협업 흐름입니다.\n" +
|
|
107
|
+
"trunk-based는 브랜치를 하나만 두고 그 브랜치에서 바로 작업하는 단순한 흐름입니다.",
|
|
108
|
+
"브랜치 전략",
|
|
109
|
+
);
|
|
110
|
+
return engine.select({
|
|
111
|
+
message: "브랜치 전략을 선택하세요",
|
|
112
|
+
options: [
|
|
113
|
+
{ value: "pr-flow", label: "develop → main PR 흐름 (pr-flow) — 팀 협업/리뷰 프로세스가 필요할 때" },
|
|
114
|
+
{ value: "trunk-based", label: "main 단일 브랜치 (trunk-based) — 개인/소규모 프로젝트로 브랜치 없이 단순하게 쓸 때" },
|
|
115
|
+
],
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
100
119
|
// 텍스트 입력 (빈 입력=기본값 유지).
|
|
101
120
|
export async function askText(message, defaultValue = "") {
|
|
102
121
|
const v = await engine.text({ message, defaultValue });
|