openuispec 0.2.19 → 0.2.20
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/dist/check/audit.js +392 -0
- package/dist/check/index.js +216 -0
- package/dist/cli/configure-target.js +391 -0
- package/dist/cli/index.js +510 -0
- package/dist/cli/init.js +1047 -0
- package/dist/drift/index.js +903 -0
- package/dist/mcp-server/index.js +886 -0
- package/dist/mcp-server/preview-render.js +1761 -0
- package/dist/mcp-server/preview.js +233 -0
- package/dist/mcp-server/screenshot-android.js +458 -0
- package/dist/mcp-server/screenshot-ios.js +639 -0
- package/dist/mcp-server/screenshot-shared.js +180 -0
- package/dist/mcp-server/screenshot.js +459 -0
- package/dist/prepare/index.js +1216 -0
- package/dist/runtime/package-paths.js +33 -0
- package/dist/schema/semantic-lint.js +564 -0
- package/dist/schema/validate.js +689 -0
- package/dist/status/index.js +194 -0
- package/package.json +12 -13
- package/check/audit.ts +0 -426
- package/check/index.ts +0 -320
- package/cli/configure-target.ts +0 -523
- package/cli/index.ts +0 -537
- package/cli/init.ts +0 -1253
- package/drift/index.ts +0 -1165
- package/mcp-server/index.ts +0 -1041
- package/mcp-server/preview-render.ts +0 -1922
- package/mcp-server/preview.ts +0 -292
- package/mcp-server/screenshot-android.ts +0 -621
- package/mcp-server/screenshot-ios.ts +0 -753
- package/mcp-server/screenshot-shared.ts +0 -237
- package/mcp-server/screenshot.ts +0 -563
- package/prepare/index.ts +0 -1530
- package/schema/semantic-lint.ts +0 -692
- package/schema/validate.ts +0 -870
- package/scripts/regenerate-previews.ts +0 -136
- package/scripts/take-all-screenshots.ts +0 -507
- package/status/index.ts +0 -275
package/cli/configure-target.ts
DELETED
|
@@ -1,523 +0,0 @@
|
|
|
1
|
-
import { createInterface } from "node:readline/promises";
|
|
2
|
-
import { stdin, stdout } from "node:process";
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { dirname, join, relative, resolve } from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
import YAML from "yaml";
|
|
7
|
-
import { findProjectDir, isSupportedTarget, readManifest, type SupportedTarget } from "../drift/index.js";
|
|
8
|
-
import { ask, askChoice } from "./init.js";
|
|
9
|
-
|
|
10
|
-
type WizardOptionPreset = {
|
|
11
|
-
value: string;
|
|
12
|
-
generation_value?: string;
|
|
13
|
-
framework_filter?: string[];
|
|
14
|
-
dependencies?: string[];
|
|
15
|
-
extra_generation?: Record<string, any>;
|
|
16
|
-
refs?: {
|
|
17
|
-
plugins?: string[];
|
|
18
|
-
libraries?: string[];
|
|
19
|
-
packages?: string[];
|
|
20
|
-
docs?: string[];
|
|
21
|
-
};
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
type ChoiceQuestionPreset = {
|
|
25
|
-
key: string;
|
|
26
|
-
prompt: string;
|
|
27
|
-
recommended: string;
|
|
28
|
-
options: WizardOptionPreset[];
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
type TargetWizardPreset = {
|
|
32
|
-
framework: string;
|
|
33
|
-
framework_prompt?: string;
|
|
34
|
-
framework_options?: string[];
|
|
35
|
-
language?: string;
|
|
36
|
-
min_version?: string;
|
|
37
|
-
min_sdk?: number;
|
|
38
|
-
target_sdk?: number;
|
|
39
|
-
generation_defaults?: Record<string, any>;
|
|
40
|
-
base_dependencies?: string[];
|
|
41
|
-
framework_dependencies?: Record<string, string[]>;
|
|
42
|
-
questions: ChoiceQuestionPreset[];
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export type TargetWizardOptionsResponse = {
|
|
46
|
-
target: SupportedTarget;
|
|
47
|
-
defaults_are_unconfirmed: true;
|
|
48
|
-
confirmation_required_before_implementation: true;
|
|
49
|
-
interactive_command: string;
|
|
50
|
-
defaults_command: string;
|
|
51
|
-
framework: {
|
|
52
|
-
prompt: string;
|
|
53
|
-
recommended: string;
|
|
54
|
-
options: string[];
|
|
55
|
-
custom_allowed: true;
|
|
56
|
-
};
|
|
57
|
-
questions: Array<{
|
|
58
|
-
key: string;
|
|
59
|
-
prompt: string;
|
|
60
|
-
recommended: string;
|
|
61
|
-
custom_allowed: true;
|
|
62
|
-
options: WizardOptionPreset[];
|
|
63
|
-
}>;
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
function readWizardPresets(): Record<SupportedTarget, TargetWizardPreset> {
|
|
67
|
-
const presetsPath = join(dirname(fileURLToPath(import.meta.url)), "target-presets.json");
|
|
68
|
-
return JSON.parse(readFileSync(presetsPath, "utf-8")) as Record<SupportedTarget, TargetWizardPreset>;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const TARGET_WIZARDS = readWizardPresets();
|
|
72
|
-
|
|
73
|
-
function listFrameworkOptions(wizard: TargetWizardPreset): string[] {
|
|
74
|
-
return [...new Set([...(wizard.framework_options ?? [wizard.framework]), "other"])];
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function listTargetWizardOptions(target: SupportedTarget): TargetWizardOptionsResponse {
|
|
78
|
-
const wizard = TARGET_WIZARDS[target];
|
|
79
|
-
return {
|
|
80
|
-
target,
|
|
81
|
-
defaults_are_unconfirmed: true,
|
|
82
|
-
confirmation_required_before_implementation: true,
|
|
83
|
-
interactive_command: `openuispec configure-target ${target}`,
|
|
84
|
-
defaults_command: `openuispec configure-target ${target} --defaults`,
|
|
85
|
-
framework: {
|
|
86
|
-
prompt: wizard.framework_prompt ?? `${target} framework`,
|
|
87
|
-
recommended: wizard.framework,
|
|
88
|
-
options: listFrameworkOptions(wizard),
|
|
89
|
-
custom_allowed: true,
|
|
90
|
-
},
|
|
91
|
-
questions: wizard.questions.map((question) => ({
|
|
92
|
-
key: question.key,
|
|
93
|
-
prompt: question.prompt,
|
|
94
|
-
recommended: question.recommended,
|
|
95
|
-
custom_allowed: true,
|
|
96
|
-
options: question.options,
|
|
97
|
-
})),
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function filterOptionsForFramework(
|
|
102
|
-
question: ChoiceQuestionPreset,
|
|
103
|
-
framework: string
|
|
104
|
-
): WizardOptionPreset[] {
|
|
105
|
-
return question.options.filter((option) => {
|
|
106
|
-
if (!option.framework_filter) return true;
|
|
107
|
-
return option.framework_filter.includes(framework);
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function effectiveDefault(
|
|
112
|
-
question: ChoiceQuestionPreset,
|
|
113
|
-
framework: string,
|
|
114
|
-
inferred: string | undefined
|
|
115
|
-
): string {
|
|
116
|
-
if (inferred) return inferred;
|
|
117
|
-
const filtered = filterOptionsForFramework(question, framework);
|
|
118
|
-
if (filtered.some((o) => o.value === question.recommended)) return question.recommended;
|
|
119
|
-
return filtered[0]?.value ?? question.recommended;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function mergeDependencies(
|
|
123
|
-
derived: string[],
|
|
124
|
-
existing: unknown,
|
|
125
|
-
managedDependencies: Set<string>
|
|
126
|
-
): string[] {
|
|
127
|
-
const extras = Array.isArray(existing)
|
|
128
|
-
? existing.filter((dep): dep is string => typeof dep === "string" && !managedDependencies.has(dep))
|
|
129
|
-
: [];
|
|
130
|
-
return Array.from(new Set([...derived, ...extras]));
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function findOption(question: ChoiceQuestionPreset, value: string): WizardOptionPreset | null {
|
|
134
|
-
return question.options.find((option) => option.value === value) ?? null;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function collectManagedDependencies(wizard: TargetWizardPreset): Set<string> {
|
|
138
|
-
const managed = new Set<string>(wizard.base_dependencies ?? []);
|
|
139
|
-
if (wizard.framework_dependencies) {
|
|
140
|
-
for (const deps of Object.values(wizard.framework_dependencies)) {
|
|
141
|
-
for (const dep of deps) managed.add(dep);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
for (const question of wizard.questions) {
|
|
145
|
-
for (const option of question.options) {
|
|
146
|
-
for (const dependency of option.dependencies ?? []) {
|
|
147
|
-
managed.add(dependency);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
return managed;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function normalizeAndroid(existingPlatform: Record<string, any>): Record<string, string> {
|
|
155
|
-
const generation = existingPlatform.generation ?? {};
|
|
156
|
-
const deps = new Set(Array.isArray(generation.dependencies) ? generation.dependencies : []);
|
|
157
|
-
const architectureValue = typeof generation.architecture === "string" ? generation.architecture.toLowerCase() : "";
|
|
158
|
-
const stateValue = typeof generation.state === "string" ? generation.state.toLowerCase() : "";
|
|
159
|
-
const persistenceValue = typeof generation.persistence === "string" ? generation.persistence.toLowerCase() : "";
|
|
160
|
-
return {
|
|
161
|
-
architecture:
|
|
162
|
-
typeof generation.architecture === "string" &&
|
|
163
|
-
!architectureValue.includes("decompose") &&
|
|
164
|
-
!architectureValue.includes("compose") &&
|
|
165
|
-
!deps.has("decompose") &&
|
|
166
|
-
!deps.has("navigation-compose")
|
|
167
|
-
? generation.architecture
|
|
168
|
-
: architectureValue.includes("decompose") || deps.has("decompose")
|
|
169
|
-
? "decompose"
|
|
170
|
-
: architectureValue.includes("compose") || deps.has("navigation-compose")
|
|
171
|
-
? "plain_compose"
|
|
172
|
-
: "decompose",
|
|
173
|
-
state:
|
|
174
|
-
typeof generation.state === "string" &&
|
|
175
|
-
!stateValue.includes("mvikotlin") &&
|
|
176
|
-
!stateValue.includes("viewmodel") &&
|
|
177
|
-
!deps.has("mvikotlin") &&
|
|
178
|
-
!deps.has("lifecycle-viewmodel-compose")
|
|
179
|
-
? generation.state
|
|
180
|
-
: stateValue.includes("mvikotlin") || deps.has("mvikotlin")
|
|
181
|
-
? "mvikotlin"
|
|
182
|
-
: stateValue.includes("viewmodel") || deps.has("lifecycle-viewmodel-compose")
|
|
183
|
-
? "viewmodel"
|
|
184
|
-
: "mvikotlin",
|
|
185
|
-
preferences:
|
|
186
|
-
typeof generation.preferences === "string"
|
|
187
|
-
? generation.preferences
|
|
188
|
-
: persistenceValue === "datastore" || deps.has("datastore-preferences")
|
|
189
|
-
? "datastore"
|
|
190
|
-
: "datastore",
|
|
191
|
-
database:
|
|
192
|
-
typeof generation.database === "string"
|
|
193
|
-
? generation.database
|
|
194
|
-
: persistenceValue === "sqldelight" || deps.has("sqldelight")
|
|
195
|
-
? "sqldelight"
|
|
196
|
-
: persistenceValue === "room" || deps.has("room-runtime")
|
|
197
|
-
? "room"
|
|
198
|
-
: "none",
|
|
199
|
-
di:
|
|
200
|
-
typeof generation.di === "string"
|
|
201
|
-
? ["metro", "koin", "hilt", "none"].includes(generation.di)
|
|
202
|
-
? generation.di
|
|
203
|
-
: generation.di
|
|
204
|
-
: deps.has("metro")
|
|
205
|
-
? "metro"
|
|
206
|
-
: deps.has("koin-android")
|
|
207
|
-
? "koin"
|
|
208
|
-
: deps.has("hilt-android")
|
|
209
|
-
? "hilt"
|
|
210
|
-
: "metro",
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function normalizeWeb(existingPlatform: Record<string, any>): Record<string, string> {
|
|
215
|
-
const generation = existingPlatform.generation ?? {};
|
|
216
|
-
const result: Record<string, string> = {
|
|
217
|
-
runtime:
|
|
218
|
-
typeof generation.runtime === "string"
|
|
219
|
-
? generation.runtime
|
|
220
|
-
: "frontend_only",
|
|
221
|
-
css:
|
|
222
|
-
typeof generation.css === "string"
|
|
223
|
-
? generation.css
|
|
224
|
-
: "tailwind",
|
|
225
|
-
storage_backend:
|
|
226
|
-
typeof generation.storage_backend === "string"
|
|
227
|
-
? generation.storage_backend
|
|
228
|
-
: "none",
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
if (typeof generation.routing === "string") {
|
|
232
|
-
result.routing = generation.routing.includes("tanstack")
|
|
233
|
-
? "tanstack_router"
|
|
234
|
-
: generation.routing.includes("react-router") || generation.routing === "react_router"
|
|
235
|
-
? "react_router"
|
|
236
|
-
: generation.routing;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
if (typeof generation.state === "string") {
|
|
240
|
-
result.state = generation.state === "redux-toolkit"
|
|
241
|
-
? "redux"
|
|
242
|
-
: generation.state === "tanstack-query"
|
|
243
|
-
? "query_only"
|
|
244
|
-
: generation.state;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return result;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function normalizeIos(existingPlatform: Record<string, any>): Record<string, string> {
|
|
251
|
-
const generation = existingPlatform.generation ?? {};
|
|
252
|
-
const deps = new Set(Array.isArray(generation.dependencies) ? generation.dependencies : []);
|
|
253
|
-
return {
|
|
254
|
-
architecture:
|
|
255
|
-
typeof generation.architecture === "string" &&
|
|
256
|
-
!generation.architecture.toLowerCase().includes("tca") &&
|
|
257
|
-
generation.architecture.toLowerCase() !== "native swiftui"
|
|
258
|
-
? generation.architecture
|
|
259
|
-
: typeof generation.architecture === "string" && generation.architecture.toLowerCase().includes("tca")
|
|
260
|
-
? "tca_style"
|
|
261
|
-
: deps.has("swift-composable-architecture")
|
|
262
|
-
? "tca_style"
|
|
263
|
-
: "native",
|
|
264
|
-
persistence:
|
|
265
|
-
typeof generation.persistence === "string"
|
|
266
|
-
? generation.persistence
|
|
267
|
-
: deps.has("sqlite")
|
|
268
|
-
? "sqlite"
|
|
269
|
-
: deps.has("swiftdata")
|
|
270
|
-
? "swiftdata"
|
|
271
|
-
: "swiftdata",
|
|
272
|
-
di:
|
|
273
|
-
typeof generation.di === "string"
|
|
274
|
-
? generation.di
|
|
275
|
-
: deps.has("factory")
|
|
276
|
-
? "factory"
|
|
277
|
-
: deps.has("custom-di")
|
|
278
|
-
? "custom"
|
|
279
|
-
: "none",
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function normalizeExisting(target: SupportedTarget, existingPlatform: Record<string, any>): Record<string, string> {
|
|
284
|
-
switch (target) {
|
|
285
|
-
case "android":
|
|
286
|
-
return normalizeAndroid(existingPlatform);
|
|
287
|
-
case "web":
|
|
288
|
-
return normalizeWeb(existingPlatform);
|
|
289
|
-
case "ios":
|
|
290
|
-
return normalizeIos(existingPlatform);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
function buildGeneration(
|
|
295
|
-
wizard: TargetWizardPreset,
|
|
296
|
-
answers: Record<string, string>,
|
|
297
|
-
existingGeneration: Record<string, any>,
|
|
298
|
-
framework: string
|
|
299
|
-
): Record<string, any> {
|
|
300
|
-
const generation = {
|
|
301
|
-
...(wizard.generation_defaults ?? {}),
|
|
302
|
-
...existingGeneration,
|
|
303
|
-
};
|
|
304
|
-
const managedDependencies = collectManagedDependencies(wizard);
|
|
305
|
-
const frameworkDeps = wizard.framework_dependencies?.[framework] ?? [];
|
|
306
|
-
const derivedDependencies = [...(wizard.base_dependencies ?? []), ...frameworkDeps];
|
|
307
|
-
|
|
308
|
-
for (const question of wizard.questions) {
|
|
309
|
-
const answer = answers[question.key];
|
|
310
|
-
const selected = answer ? findOption(question, answer) : null;
|
|
311
|
-
if (!selected) {
|
|
312
|
-
generation[question.key] = answer || question.recommended;
|
|
313
|
-
continue;
|
|
314
|
-
}
|
|
315
|
-
generation[question.key] = selected.generation_value ?? selected.value;
|
|
316
|
-
Object.assign(generation, selected.extra_generation ?? {});
|
|
317
|
-
derivedDependencies.push(...(selected.dependencies ?? []));
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
generation.dependencies = mergeDependencies(
|
|
321
|
-
derivedDependencies,
|
|
322
|
-
existingGeneration.dependencies,
|
|
323
|
-
managedDependencies
|
|
324
|
-
);
|
|
325
|
-
|
|
326
|
-
return generation;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function stackConfirmation(useDefaults: boolean): Record<string, string> {
|
|
330
|
-
const now = new Date().toISOString();
|
|
331
|
-
return useDefaults
|
|
332
|
-
? {
|
|
333
|
-
status: "pending_user_confirmation",
|
|
334
|
-
source: "defaults",
|
|
335
|
-
updated_at: now,
|
|
336
|
-
}
|
|
337
|
-
: {
|
|
338
|
-
status: "confirmed",
|
|
339
|
-
source: "user",
|
|
340
|
-
confirmed_at: now,
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
function parseTarget(argv: string[]): SupportedTarget | null {
|
|
345
|
-
const direct = argv[0];
|
|
346
|
-
if (direct && isSupportedTarget(direct)) {
|
|
347
|
-
return direct;
|
|
348
|
-
}
|
|
349
|
-
const targetIdx = argv.indexOf("--target");
|
|
350
|
-
if (targetIdx !== -1 && argv[targetIdx + 1] && isSupportedTarget(argv[targetIdx + 1])) {
|
|
351
|
-
return argv[targetIdx + 1];
|
|
352
|
-
}
|
|
353
|
-
return null;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
function parseSetPairs(argv: string[]): Record<string, string> {
|
|
357
|
-
const pairs: Record<string, string> = {};
|
|
358
|
-
for (let i = 0; i < argv.length; i++) {
|
|
359
|
-
if (argv[i] === "--set" && argv[i + 1]) {
|
|
360
|
-
const raw = argv[i + 1];
|
|
361
|
-
const eqIdx = raw.indexOf("=");
|
|
362
|
-
if (eqIdx > 0) {
|
|
363
|
-
pairs[raw.slice(0, eqIdx)] = raw.slice(eqIdx + 1);
|
|
364
|
-
}
|
|
365
|
-
i++;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
return pairs;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
export async function runConfigureTarget(argv: string[]): Promise<void> {
|
|
372
|
-
const target = parseTarget(argv);
|
|
373
|
-
const listOptions = argv.includes("--list-options");
|
|
374
|
-
const useDefaults = argv.includes("--defaults");
|
|
375
|
-
const quiet = argv.includes("--quiet");
|
|
376
|
-
const setPairs = parseSetPairs(argv);
|
|
377
|
-
const hasSetPairs = Object.keys(setPairs).length > 0;
|
|
378
|
-
const interactive = stdin.isTTY && stdout.isTTY && !useDefaults && !hasSetPairs;
|
|
379
|
-
if (!target) {
|
|
380
|
-
console.error("Error: target is required for configure-target");
|
|
381
|
-
console.error("Usage: openuispec configure-target <ios|android|web> [--defaults] [--list-options] [--set key=value]");
|
|
382
|
-
process.exit(1);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
if (listOptions) {
|
|
386
|
-
console.log(JSON.stringify(listTargetWizardOptions(target), null, 2));
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
if (!interactive && !useDefaults && !hasSetPairs) {
|
|
391
|
-
console.error(
|
|
392
|
-
"Error: `openuispec configure-target` needs a TTY for prompts.\n" +
|
|
393
|
-
"Preferred: ask the user to confirm the target stack, then run `openuispec configure-target <target>` in an interactive terminal.\n" +
|
|
394
|
-
"Fallback: run with `--defaults` only for unattended setup; those values remain unconfirmed and `prepare` will block implementation until the user confirms them."
|
|
395
|
-
);
|
|
396
|
-
process.exit(1);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const projectDir = findProjectDir(process.cwd());
|
|
400
|
-
const manifest = readManifest(projectDir);
|
|
401
|
-
const configuredTargets: string[] = manifest.generation?.targets ?? [];
|
|
402
|
-
if (configuredTargets.length > 0 && !configuredTargets.includes(target)) {
|
|
403
|
-
console.error(
|
|
404
|
-
`Error: target "${target}" is not listed in generation.targets.\n` +
|
|
405
|
-
`Configured targets: ${configuredTargets.join(", ")}`
|
|
406
|
-
);
|
|
407
|
-
process.exit(1);
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
const platformDir = resolve(projectDir, manifest.includes?.platform ?? "./platform/");
|
|
411
|
-
mkdirSync(platformDir, { recursive: true });
|
|
412
|
-
|
|
413
|
-
const platformPath = join(platformDir, `${target}.yaml`);
|
|
414
|
-
const existingDoc = existsSync(platformPath)
|
|
415
|
-
? (YAML.parse(readFileSync(platformPath, "utf-8")) as Record<string, any>)
|
|
416
|
-
: {};
|
|
417
|
-
const existingPlatform = existingDoc[target] ?? {};
|
|
418
|
-
const existingGeneration = existingPlatform.generation ?? {};
|
|
419
|
-
const wizard = TARGET_WIZARDS[target];
|
|
420
|
-
const defaultFramework =
|
|
421
|
-
typeof existingPlatform.framework === "string" && existingPlatform.framework.trim().length > 0
|
|
422
|
-
? existingPlatform.framework
|
|
423
|
-
: wizard.framework;
|
|
424
|
-
const inferredDefaults = {
|
|
425
|
-
...normalizeExisting(target, existingPlatform),
|
|
426
|
-
};
|
|
427
|
-
|
|
428
|
-
let framework = defaultFramework;
|
|
429
|
-
|
|
430
|
-
function computeDefaultAnswers(fw: string): Record<string, string> {
|
|
431
|
-
return Object.fromEntries(
|
|
432
|
-
wizard.questions.map((question) => {
|
|
433
|
-
const defaultValue = effectiveDefault(question, fw, inferredDefaults[question.key]);
|
|
434
|
-
return [question.key, defaultValue];
|
|
435
|
-
})
|
|
436
|
-
);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
let answers = computeDefaultAnswers(framework);
|
|
440
|
-
|
|
441
|
-
if (hasSetPairs) {
|
|
442
|
-
// Non-interactive --set path: merge provided values with existing/defaults
|
|
443
|
-
const knownKeys = new Set(wizard.questions.map((q) => q.key));
|
|
444
|
-
for (const [key, value] of Object.entries(setPairs)) {
|
|
445
|
-
if (!knownKeys.has(key)) {
|
|
446
|
-
console.error(`Warning: "${key}" does not match any wizard question; setting as custom value.`);
|
|
447
|
-
}
|
|
448
|
-
answers[key] = value;
|
|
449
|
-
}
|
|
450
|
-
} else if (interactive) {
|
|
451
|
-
const rl = createInterface({ input: stdin, output: stdout });
|
|
452
|
-
|
|
453
|
-
try {
|
|
454
|
-
console.log(`\nOpenUISpec — Configure ${target}\n`);
|
|
455
|
-
console.log(`Writing target stack choices to ${relative(process.cwd(), platformPath)}\n`);
|
|
456
|
-
|
|
457
|
-
const frameworkOptions = [
|
|
458
|
-
...(wizard.framework_options ?? [wizard.framework]),
|
|
459
|
-
"other",
|
|
460
|
-
];
|
|
461
|
-
const chosenFramework = await askChoice(
|
|
462
|
-
rl,
|
|
463
|
-
wizard.framework_prompt ?? `${target} framework`,
|
|
464
|
-
frameworkOptions,
|
|
465
|
-
framework
|
|
466
|
-
);
|
|
467
|
-
framework =
|
|
468
|
-
chosenFramework === "other"
|
|
469
|
-
? await ask(rl, `Custom ${target} framework`, framework)
|
|
470
|
-
: chosenFramework;
|
|
471
|
-
|
|
472
|
-
const defaultAnswers = computeDefaultAnswers(framework);
|
|
473
|
-
answers = {};
|
|
474
|
-
for (const question of wizard.questions) {
|
|
475
|
-
const filtered = filterOptionsForFramework(question, framework);
|
|
476
|
-
const chosen = await askChoice(
|
|
477
|
-
rl,
|
|
478
|
-
question.prompt,
|
|
479
|
-
[...filtered.map((option) => option.value), "other"],
|
|
480
|
-
defaultAnswers[question.key]
|
|
481
|
-
);
|
|
482
|
-
answers[question.key] =
|
|
483
|
-
chosen === "other"
|
|
484
|
-
? await ask(rl, `Custom value for ${question.key}`, defaultAnswers[question.key])
|
|
485
|
-
: chosen;
|
|
486
|
-
}
|
|
487
|
-
} finally {
|
|
488
|
-
rl.close();
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
// --set implies confirmed (user explicitly chose values); --defaults without --set is pending
|
|
493
|
-
const updatedPlatform: Record<string, any> = {
|
|
494
|
-
...existingPlatform,
|
|
495
|
-
framework,
|
|
496
|
-
generation: {
|
|
497
|
-
...buildGeneration(wizard, answers, existingGeneration, framework),
|
|
498
|
-
stack_confirmation: stackConfirmation(useDefaults && !hasSetPairs),
|
|
499
|
-
},
|
|
500
|
-
};
|
|
501
|
-
|
|
502
|
-
if (wizard.language) updatedPlatform.language = wizard.language;
|
|
503
|
-
if (wizard.min_version) updatedPlatform.min_version = existingPlatform.min_version ?? wizard.min_version;
|
|
504
|
-
if (typeof wizard.min_sdk === "number") updatedPlatform.min_sdk = existingPlatform.min_sdk ?? wizard.min_sdk;
|
|
505
|
-
if (typeof wizard.target_sdk === "number") {
|
|
506
|
-
updatedPlatform.target_sdk = existingPlatform.target_sdk ?? wizard.target_sdk;
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
writeFileSync(platformPath, YAML.stringify({ [target]: updatedPlatform }));
|
|
510
|
-
|
|
511
|
-
const savedPath = relative(process.cwd(), platformPath);
|
|
512
|
-
if (argv.includes("--silent")) {
|
|
513
|
-
// Called as subroutine (e.g. from init --quiet) — no output at all
|
|
514
|
-
} else if (quiet) {
|
|
515
|
-
console.log(savedPath);
|
|
516
|
-
} else {
|
|
517
|
-
console.log(`\nSaved ${savedPath}`);
|
|
518
|
-
console.log("Configured values:");
|
|
519
|
-
for (const [key, value] of Object.entries(answers)) {
|
|
520
|
-
console.log(` - ${key}: ${value}`);
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
}
|