create-expo-super-stack 0.1.8 → 0.1.10

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/cli.js CHANGED
@@ -1,17 +1,17 @@
1
1
  #!/usr/bin/env node
2
- import { spawn } from 'node:child_process';
3
- import { access, mkdir, readdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
4
- import path from 'node:path';
5
- import { fileURLToPath, pathToFileURL } from 'node:url';
6
- import { cancel, isCancel, log, text } from '@clack/prompts';
7
- import { SUPER_STACK_SUCCESS_MESSAGE, collectOnboardPlan, defaultOnboardPlan, savePersonalOnboardDefaults, } from '@mr.dj2u/cli/onboarding';
8
- import { scaffoldProjectMemory } from '@mr.dj2u/cli/project-memory';
9
- const DEFAULT_PROJECT_NAME = 'my-expo-app';
10
- const CURRENT_EXPO_SDK_MAJOR = 56;
11
- const CURRENT_EXPO_SDK_VERSION = '~56.0.6';
2
+ import { spawn } from "node:child_process";
3
+ import { access, mkdir, readdir, readFile, rename, rm, writeFile, } from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
6
+ import { cancel, isCancel, log, text } from "@clack/prompts";
7
+ import { SUPER_STACK_SUCCESS_MESSAGE, collectOnboardPlan, defaultOnboardPlan, savePersonalOnboardDefaults, } from "@mr.dj2u/cli/onboarding";
8
+ import { scaffoldProjectMemory } from "@mr.dj2u/cli/project-memory";
9
+ import { generateProjectRoadmap, } from "@mr.dj2u/cli/roadmap";
10
+ const DEFAULT_PROJECT_NAME = "my-expo-app";
11
+ export const EXPECTED_EXPO_SDK_MAJOR = 56;
12
12
  const STYLIST_SYNC_API_ROUTES = [
13
- 'app/exposition/stylist-sync+api.ts',
14
- 'src/app/exposition/stylist-sync+api.ts',
13
+ "app/exposition/stylist-sync+api.ts",
14
+ "src/app/exposition/stylist-sync+api.ts",
15
15
  ];
16
16
  export async function main() {
17
17
  const initialParsed = parseArgs(process.argv.slice(2));
@@ -29,7 +29,7 @@ export async function main() {
29
29
  await runCreateExpoStack(createExpoStackArgs, parsed.mds.createExpoStackBin, projectParentDir);
30
30
  }
31
31
  else {
32
- console.log('Skipping create-expo-stack because --mds-skip-create was passed.');
32
+ console.log("Skipping create-expo-stack because --mds-skip-create was passed.");
33
33
  }
34
34
  const projectPath = await resolveGeneratedProjectPath(projectParentDir, projectName);
35
35
  const easSelected = await detectEasSetup(projectPath, parsed.createExpoStackArgs);
@@ -37,13 +37,15 @@ export async function main() {
37
37
  const plan = parsed.mds.yes
38
38
  ? defaultOnboardPlan(onboardArgv, projectPath)
39
39
  : await collectOnboardPlan(onboardArgv, projectPath);
40
- const movedAppDir = !parsed.mds.skipCreate && plan.answers.appDirectory === 'src'
40
+ const movedAppDir = !parsed.mds.skipCreate && plan.answers.appDirectory === "src"
41
41
  ? await moveRootAppIntoSrc(projectPath)
42
42
  : null;
43
- const consolidatedSourceRepairs = !parsed.mds.skipCreate && plan.answers.appDirectory === 'src'
43
+ const consolidatedSourceRepairs = !parsed.mds.skipCreate && plan.answers.appDirectory === "src"
44
44
  ? await consolidateRootSourceFolders(projectPath)
45
45
  : [];
46
- const movedImportRepairs = movedAppDir ? await repairMovedSrcAppImports(projectPath) : [];
46
+ const movedImportRepairs = movedAppDir
47
+ ? await repairMovedSrcAppImports(projectPath)
48
+ : [];
47
49
  const written = await scaffoldProjectMemory(projectPath, plan.answers, {
48
50
  force: parsed.mds.force,
49
51
  guidelinesTemplate: plan.guidelinesTemplate,
@@ -51,21 +53,24 @@ export async function main() {
51
53
  manageUniwind: parsed.mds.skipCreate,
52
54
  richBoilerplate: plan.richBoilerplate,
53
55
  });
54
- const postScaffoldImportRepairs = movedAppDir ? await repairMovedSrcAppImports(projectPath) : [];
56
+ const postScaffoldImportRepairs = movedAppDir
57
+ ? await repairMovedSrcAppImports(projectPath)
58
+ : [];
55
59
  const identifierRepairs = await repairExpoProjectIdentifiers(projectPath, projectName, plan.answers.targetPlatforms);
56
- const stylistWebOutputRepairs = plan.answers.targetPlatforms.includes('web')
60
+ const stylistWebOutputRepairs = plan.answers.targetPlatforms.includes("web")
57
61
  ? await repairExpoWebOutputForStylistLifecycle(projectPath, plan.answers.webOutput)
58
62
  : [];
59
63
  const hasStylistSyncRoute = await hasStylistSyncApiRoute(projectPath);
60
64
  const typeSupportRepairs = await repairGeneratedTypeSupport(projectPath, {
61
- needsNodeTypes: plan.answers.targetPlatforms.includes('web') && hasStylistSyncRoute,
62
- needsUniwindTypes: plan.answers.defaults.includes('uniwind'),
65
+ needsNodeTypes: hasStylistSyncRoute,
66
+ needsUniwindTypes: plan.answers.defaults.includes("uniwind"),
63
67
  });
64
68
  const nativeWindUiPickerRepairs = await repairGeneratedNativeWindUiPicker(projectPath);
69
+ const eslintConfigRepairs = await repairGeneratedEslintConfig(projectPath);
65
70
  console.log();
66
- console.log('MDS onboarding complete.');
71
+ console.log("MDS onboarding complete.");
67
72
  for (const result of written) {
68
- console.log(`${result.wrote ? 'CREATED' : 'KEPT'} ${path.relative(process.cwd(), result.filePath)}`);
73
+ console.log(`${result.wrote ? "CREATED" : "KEPT"} ${path.relative(process.cwd(), result.filePath)}`);
69
74
  }
70
75
  if (movedAppDir) {
71
76
  console.log(`MOVED ${path.relative(process.cwd(), movedAppDir.from)} -> ${path.relative(process.cwd(), movedAppDir.to)}`);
@@ -91,42 +96,61 @@ export async function main() {
91
96
  for (const result of nativeWindUiPickerRepairs) {
92
97
  console.log(`UPDATED ${path.relative(process.cwd(), result)}`);
93
98
  }
99
+ for (const result of eslintConfigRepairs) {
100
+ console.log(`UPDATED ${path.relative(process.cwd(), result)}`);
101
+ }
94
102
  if (plan.saveDefaults) {
95
103
  const defaultsPath = savePersonalOnboardDefaults(plan.answers);
96
104
  if (defaultsPath) {
97
105
  console.log(`Saved personal onboarding defaults: ${defaultsPath}`);
98
106
  }
99
107
  }
108
+ const roadmapStatus = await generateProjectRoadmap(projectPath, {
109
+ write: false,
110
+ preserveStatus: true,
111
+ });
112
+ if (roadmapStatus.blockedByMarkers) {
113
+ console.log();
114
+ console.log("Roadmap note:");
115
+ console.log("Unresolved # TodoForContext(optional): markers are still present in project/info.md, so MDS intentionally left the scaffolded phase template in place.");
116
+ console.log("Resolve those project/info.md markers first, then run `mds roadmap` or let your agent refresh project/todo.md from project/info.md.");
117
+ }
118
+ else if (roadmapStatus.needsClarification) {
119
+ console.log();
120
+ console.log("Roadmap note:");
121
+ console.log("The project docs are still too generic for a high-confidence derived roadmap, so have your agent ask clarifying questions and then rerun `mds roadmap`.");
122
+ }
100
123
  const packageManager = await detectPackageManager(projectPath, parsed.createExpoStackArgs);
101
124
  const noInstallRequested = hasNoInstallFlag(parsed.createExpoStackArgs);
102
125
  if (shouldRunExpoProjectChecks(parsed, noInstallRequested)) {
103
- await runExpoProjectChecks(projectPath, packageManager, {
104
- useLatestExpoSdk: plan.answers.useLatestExpoSdk,
105
- });
126
+ await runExpoProjectChecks(projectPath, packageManager);
106
127
  }
107
128
  else if (noInstallRequested) {
108
129
  console.log();
109
- console.log('Skipped install and Expo dependency repair because create-expo-stack was run with --no-install.');
130
+ console.log("Skipped install and Expo dependency repair because create-expo-stack was run with --no-install.");
110
131
  }
132
+ await assertExpectedExpoSdk(projectPath);
111
133
  console.log();
112
- console.log('Next steps:');
113
- console.log(` cd ${quoteDisplayArg(path.relative(process.cwd(), projectPath) || '.')}`);
134
+ const nextStepsCommands = [
135
+ `cd ${quoteDisplayArg(path.relative(process.cwd(), projectPath) || ".")}`,
136
+ ];
114
137
  if (noInstallRequested || parsed.mds.skipExpoFix || parsed.mds.skipCreate) {
115
- console.log(` ${buildInstallCommand(packageManager).display}`);
116
- if (plan.answers.useLatestExpoSdk && (await shouldRunExpoLatestSdkCommand(projectPath))) {
117
- console.log(` ${buildExpoLatestSdkCommand(packageManager).display}`);
138
+ nextStepsCommands.push(buildInstallCommand(packageManager).display);
139
+ if (await shouldRunExpoLatestSdkCommand(projectPath)) {
140
+ nextStepsCommands.push(buildExpoLatestSdkCommand(packageManager).display);
118
141
  }
119
- console.log(` ${buildExpoInstallFixCommand(packageManager).display}`);
142
+ nextStepsCommands.push(buildExpoInstallFixCommand(packageManager).display);
120
143
  if (await shouldInstallExpoFontPeer(projectPath)) {
121
- console.log(` ${buildExpoFontInstallCommand(packageManager).display}`);
144
+ nextStepsCommands.push(buildExpoFontInstallCommand(packageManager).display);
122
145
  }
123
- console.log(` ${buildExpoDoctorCommand(packageManager).display}`);
146
+ nextStepsCommands.push(buildExpoDoctorCommand(packageManager).display);
124
147
  }
125
- console.log(` ${buildRunScriptCommand(packageManager, 'clear-expo-start')}`);
148
+ nextStepsCommands.push(buildRunScriptCommand(packageManager, "clear-expo-start"));
149
+ printCopyableCommands("Next steps", nextStepsCommands);
126
150
  console.log();
127
151
  console.log(SUPER_STACK_SUCCESS_MESSAGE);
128
152
  console.log();
129
- console.log('For the full dev-suite locally, use the generated scripts or install @mr.dj2u/cli in the app.');
153
+ console.log("For the full dev-suite locally, use the generated scripts or install @mr.dj2u/cli in the app.");
130
154
  }
131
155
  export function prepareCreateExpoStackArgsForWrapper(args, _skipExpoFix = false) {
132
156
  return args;
@@ -146,8 +170,8 @@ export function parseArgs(args) {
146
170
  if (!arg) {
147
171
  continue;
148
172
  }
149
- if (!arg.startsWith('--') && !projectName) {
150
- if (arg === '-h') {
173
+ if (!arg.startsWith("--") && !projectName) {
174
+ if (arg === "-h") {
151
175
  helpRequested = true;
152
176
  continue;
153
177
  }
@@ -155,192 +179,191 @@ export function parseArgs(args) {
155
179
  createExpoStackArgs.push(arg);
156
180
  continue;
157
181
  }
158
- if (arg === '--help' || arg === '-h' || arg === '--mds-help') {
182
+ if (arg === "--help" || arg === "-h" || arg === "--mds-help") {
159
183
  helpRequested = true;
160
184
  continue;
161
185
  }
162
- if (arg === '--mds-force') {
186
+ if (arg === "--mds-force") {
163
187
  mds.force = true;
164
188
  continue;
165
189
  }
166
- if (arg === '--mds-no-rich') {
190
+ if (arg === "--mds-no-rich") {
167
191
  mds.rich = false;
168
192
  continue;
169
193
  }
170
- if (arg === '--mds-rich') {
194
+ if (arg === "--mds-rich") {
171
195
  mds.rich = true;
172
196
  continue;
173
197
  }
174
- if (arg === '--mds-save-defaults') {
198
+ if (arg === "--mds-save-defaults") {
175
199
  mds.saveDefaults = true;
176
200
  continue;
177
201
  }
178
- if (arg === '--mds-no-save-defaults') {
202
+ if (arg === "--mds-no-save-defaults") {
179
203
  mds.saveDefaults = false;
180
204
  continue;
181
205
  }
182
- if (arg === '--mds-yes' || arg === '--mds-non-interactive') {
206
+ if (arg === "--mds-yes" || arg === "--mds-non-interactive") {
183
207
  mds.yes = true;
184
208
  continue;
185
209
  }
186
- if (arg === '--mds-skip-create') {
210
+ if (arg === "--mds-skip-create") {
187
211
  mds.skipCreate = true;
188
212
  continue;
189
213
  }
190
- if (arg === '--mds-skip-expo-fix' || arg === '--mds-no-expo-fix') {
214
+ if (arg === "--mds-skip-expo-fix" || arg === "--mds-no-expo-fix") {
191
215
  mds.skipExpoFix = true;
192
216
  continue;
193
217
  }
194
- if (arg.startsWith('--mds-create-expo-stack-bin=')) {
195
- mds.createExpoStackBin = arg.slice('--mds-create-expo-stack-bin='.length);
218
+ if (arg.startsWith("--mds-create-expo-stack-bin=")) {
219
+ mds.createExpoStackBin = arg.slice("--mds-create-expo-stack-bin=".length);
196
220
  continue;
197
221
  }
198
- if (arg === '--mds-guidelines-template') {
222
+ if (arg === "--mds-guidelines-template") {
199
223
  mds.guidelinesTemplate = true;
200
224
  continue;
201
225
  }
202
- if (arg === '--mds-no-guidelines-template' || arg === '--mds-guidelines-template=false') {
226
+ if (arg === "--mds-no-guidelines-template" ||
227
+ arg === "--mds-guidelines-template=false") {
203
228
  mds.guidelinesTemplate = false;
204
229
  continue;
205
230
  }
206
- if (arg.startsWith('--mds-guidelines-template=')) {
231
+ if (arg.startsWith("--mds-guidelines-template=")) {
207
232
  mds.guidelinesTemplate = true;
208
- mds.guidelinesTemplatePath = arg.slice('--mds-guidelines-template='.length);
233
+ mds.guidelinesTemplatePath = arg.slice("--mds-guidelines-template=".length);
209
234
  continue;
210
235
  }
211
- if (arg.startsWith('--mds-guidelines-template-path=')) {
236
+ if (arg.startsWith("--mds-guidelines-template-path=")) {
212
237
  mds.guidelinesTemplate = true;
213
- mds.guidelinesTemplatePath = arg.slice('--mds-guidelines-template-path='.length);
238
+ mds.guidelinesTemplatePath = arg.slice("--mds-guidelines-template-path=".length);
214
239
  continue;
215
240
  }
216
- if (arg.startsWith('--mds-defaults=')) {
217
- mds.defaults = splitList(arg.slice('--mds-defaults='.length));
241
+ if (arg.startsWith("--mds-defaults=")) {
242
+ mds.defaults = splitList(arg.slice("--mds-defaults=".length));
218
243
  continue;
219
244
  }
220
- if (arg.startsWith('--mds-app-name=')) {
221
- mds.appName = arg.slice('--mds-app-name='.length);
245
+ if (arg.startsWith("--mds-app-name=")) {
246
+ mds.appName = arg.slice("--mds-app-name=".length);
222
247
  continue;
223
248
  }
224
- if (arg.startsWith('--mds-audience=')) {
225
- mds.audience = arg.slice('--mds-audience='.length);
249
+ if (arg.startsWith("--mds-audience=")) {
250
+ mds.audience = arg.slice("--mds-audience=".length);
226
251
  continue;
227
252
  }
228
- if (arg.startsWith('--mds-core-flows=')) {
229
- mds.coreFlows = arg.slice('--mds-core-flows='.length);
253
+ if (arg.startsWith("--mds-core-flows=")) {
254
+ mds.coreFlows = arg.slice("--mds-core-flows=".length);
230
255
  continue;
231
256
  }
232
- if (arg.startsWith('--mds-screens=')) {
233
- mds.screens = arg.slice('--mds-screens='.length);
257
+ if (arg.startsWith("--mds-screens=")) {
258
+ mds.screens = arg.slice("--mds-screens=".length);
234
259
  continue;
235
260
  }
236
- if (arg.startsWith('--mds-data-needs=')) {
237
- mds.dataNeeds = arg.slice('--mds-data-needs='.length);
261
+ if (arg.startsWith("--mds-data-needs=")) {
262
+ mds.dataNeeds = arg.slice("--mds-data-needs=".length);
238
263
  continue;
239
264
  }
240
- if (arg.startsWith('--mds-data-start=')) {
241
- const value = arg.slice('--mds-data-start='.length);
242
- if (value === 'local' || value === 'supabase') {
265
+ if (arg.startsWith("--mds-data-start=")) {
266
+ const value = arg.slice("--mds-data-start=".length);
267
+ if (value === "local" || value === "supabase") {
243
268
  mds.dataStart = value;
244
269
  }
245
270
  continue;
246
271
  }
247
- if (arg.startsWith('--mds-deployment-target=')) {
248
- mds.deploymentTarget = arg.slice('--mds-deployment-target='.length);
272
+ if (arg.startsWith("--mds-deployment-target=")) {
273
+ mds.deploymentTarget = arg.slice("--mds-deployment-target=".length);
249
274
  continue;
250
275
  }
251
- if (arg === '--mds-test-to-main') {
276
+ if (arg === "--mds-test-to-main") {
252
277
  mds.testToMain = true;
253
278
  continue;
254
279
  }
255
- if (arg === '--mds-no-test-to-main') {
280
+ if (arg === "--mds-no-test-to-main") {
256
281
  mds.testToMain = false;
257
282
  continue;
258
283
  }
259
- if (arg.startsWith('--mds-platforms=')) {
260
- mds.platforms = splitList(arg.slice('--mds-platforms='.length));
284
+ if (arg.startsWith("--mds-platforms=")) {
285
+ mds.platforms = splitList(arg.slice("--mds-platforms=".length));
261
286
  continue;
262
287
  }
263
- if (arg.startsWith('--mds-first-platform=')) {
264
- mds.firstPlatform = arg.slice('--mds-first-platform='.length);
288
+ if (arg.startsWith("--mds-first-platform=")) {
289
+ mds.firstPlatform = arg.slice("--mds-first-platform=".length);
265
290
  continue;
266
291
  }
267
- if (arg.startsWith('--mds-platform-strategy=')) {
268
- const value = arg.slice('--mds-platform-strategy='.length);
269
- if (value === 'folders' || value === 'files-only') {
292
+ if (arg.startsWith("--mds-platform-strategy=")) {
293
+ const value = arg.slice("--mds-platform-strategy=".length);
294
+ if (value === "folders" || value === "files-only") {
270
295
  mds.platformStrategy = value;
271
296
  }
272
297
  continue;
273
298
  }
274
- if (arg.startsWith('--mds-app-directory=')) {
275
- const value = arg.slice('--mds-app-directory='.length);
276
- if (value === 'src' || value === 'root') {
299
+ if (arg.startsWith("--mds-app-directory=")) {
300
+ const value = arg.slice("--mds-app-directory=".length);
301
+ if (value === "src" || value === "root") {
277
302
  mds.appDirectory = value;
278
303
  }
279
304
  continue;
280
305
  }
281
- if (arg.startsWith('--mds-platform-layouts=')) {
282
- const value = arg.slice('--mds-platform-layouts='.length);
283
- if (value === 'shared' || value === 'platform-specific') {
306
+ if (arg.startsWith("--mds-platform-layouts=")) {
307
+ const value = arg.slice("--mds-platform-layouts=".length);
308
+ if (value === "shared" || value === "platform-specific") {
284
309
  mds.platformLayouts = value;
285
310
  }
286
311
  continue;
287
312
  }
288
- if (arg.startsWith('--mds-web-output=')) {
289
- const value = arg.slice('--mds-web-output='.length);
290
- if (value === 'static' || value === 'server' || value === 'spa' || value === 'none') {
313
+ if (arg.startsWith("--mds-web-output=")) {
314
+ const value = arg.slice("--mds-web-output=".length);
315
+ if (value === "static" ||
316
+ value === "server" ||
317
+ value === "spa" ||
318
+ value === "none") {
291
319
  mds.webOutput = value;
292
320
  }
293
321
  continue;
294
322
  }
295
- if (arg.startsWith('--mds-deployed-server=')) {
296
- const value = arg.slice('--mds-deployed-server='.length);
297
- if (value === 'standard-expo' || value === 'custom' || value === 'none') {
323
+ if (arg.startsWith("--mds-deployed-server=")) {
324
+ const value = arg.slice("--mds-deployed-server=".length);
325
+ if (value === "standard-expo" || value === "custom" || value === "none") {
298
326
  mds.deployedServer = value;
299
327
  }
300
328
  continue;
301
329
  }
302
- if (arg === '--mds-create-expo-components') {
330
+ if (arg === "--mds-create-expo-components") {
303
331
  mds.createExpoComponents = true;
304
332
  continue;
305
333
  }
306
- if (arg === '--mds-no-create-expo-components') {
334
+ if (arg === "--mds-no-create-expo-components") {
307
335
  mds.createExpoComponents = false;
308
336
  continue;
309
337
  }
310
- if (arg === '--mds-latest-expo-sdk') {
311
- mds.latestExpoSdk = true;
312
- continue;
313
- }
314
- if (arg === '--mds-no-latest-expo-sdk') {
315
- mds.latestExpoSdk = false;
338
+ if (arg === "--mds-latest-expo-sdk" || arg === "--mds-no-latest-expo-sdk") {
316
339
  continue;
317
340
  }
318
- if (arg === '--mds-expo-ui') {
341
+ if (arg === "--mds-expo-ui") {
319
342
  mds.expoUi = true;
320
343
  continue;
321
344
  }
322
- if (arg === '--mds-no-expo-ui') {
345
+ if (arg === "--mds-no-expo-ui") {
323
346
  mds.expoUi = false;
324
347
  continue;
325
348
  }
326
- if (arg === '--mds-expo-ui-universal') {
349
+ if (arg === "--mds-expo-ui-universal") {
327
350
  mds.expoUiUniversal = true;
328
351
  continue;
329
352
  }
330
- if (arg === '--mds-no-expo-ui-universal') {
353
+ if (arg === "--mds-no-expo-ui-universal") {
331
354
  mds.expoUiUniversal = false;
332
355
  continue;
333
356
  }
334
- if (arg === '--mds-expo-native-tabs') {
357
+ if (arg === "--mds-expo-native-tabs") {
335
358
  mds.expoNativeTabs = true;
336
359
  continue;
337
360
  }
338
- if (arg === '--mds-no-expo-native-tabs') {
361
+ if (arg === "--mds-no-expo-native-tabs") {
339
362
  mds.expoNativeTabs = false;
340
363
  continue;
341
364
  }
342
- if (arg.startsWith('--mds-eas-uses=')) {
343
- mds.easUses = splitList(arg.slice('--mds-eas-uses='.length));
365
+ if (arg.startsWith("--mds-eas-uses=")) {
366
+ mds.easUses = splitList(arg.slice("--mds-eas-uses=".length));
344
367
  continue;
345
368
  }
346
369
  createExpoStackArgs.push(arg);
@@ -354,33 +377,33 @@ export function parseArgs(args) {
354
377
  }
355
378
  export function renderHelpText() {
356
379
  return [
357
- 'create-expo-super-stack',
358
- '',
359
- 'Usage:',
360
- ' create-expo-super-stack [project-name] [create-expo-stack options] [mds options]',
361
- '',
362
- 'Examples:',
363
- ' create-expo-super-stack my-app --expo-router --uniwind',
364
- ' create-expo-super-stack ../MyApp --expo-router --mds-yes',
365
- '',
366
- 'Common mds options:',
367
- ' --mds-yes Run non-interactive onboarding defaults',
368
- ' --mds-save-defaults Save onboarding answers as personal defaults',
369
- ' --mds-no-save-defaults Do not save onboarding answers as personal defaults',
370
- ' --mds-skip-create Skip create-expo-stack and only run onboarding in an existing app',
371
- ' --mds-skip-expo-fix Skip dependency install/fix/doctor repair pass',
372
- ' --mds-guidelines-template Use bundled MDS project/guidelines template',
373
- ' --mds-no-guidelines-template Do not use the bundled MDS project/guidelines template',
374
- ' --mds-app-name=<name> Set display app name for project memory',
375
- ' --mds-screens= List must-include screens for project memory',
376
- ' --mds-expo-ui-universal Use Expo UI Universal components when Expo UI is selected',
377
- '',
378
- 'Help:',
379
- ' -h, --help Show this help and exit',
380
- '',
381
- 'Note:',
382
- ' Unknown non-mds flags are forwarded to create-expo-stack.',
383
- ].join('\n');
380
+ "create-expo-super-stack",
381
+ "",
382
+ "Usage:",
383
+ " create-expo-super-stack [project-name] [create-expo-stack options] [mds options]",
384
+ "",
385
+ "Examples:",
386
+ " create-expo-super-stack my-app --expo-router --uniwind",
387
+ " create-expo-super-stack ../MyApp --expo-router --mds-yes",
388
+ "",
389
+ "Common mds options:",
390
+ " --mds-yes Run non-interactive onboarding defaults",
391
+ " --mds-save-defaults Save onboarding answers as personal defaults",
392
+ " --mds-no-save-defaults Do not save onboarding answers as personal defaults",
393
+ " --mds-skip-create Skip create-expo-stack and only run onboarding in an existing app",
394
+ " --mds-skip-expo-fix Skip dependency install/fix/doctor repair pass",
395
+ " --mds-guidelines-template Use bundled MDS project/guidelines template",
396
+ " --mds-no-guidelines-template Do not use the bundled MDS project/guidelines template",
397
+ " --mds-app-name=<name> Set display app name for project memory",
398
+ " --mds-screens= List must-include screens for project memory",
399
+ " --mds-expo-ui-universal Use Expo UI Universal components when Expo UI is selected",
400
+ "",
401
+ "Help:",
402
+ " -h, --help Show this help and exit",
403
+ "",
404
+ "Note:",
405
+ " Unknown non-mds flags are forwarded to create-expo-stack.",
406
+ ].join("\n");
384
407
  }
385
408
  export function withResolvedProjectName(parsed, projectName) {
386
409
  const target = resolveProjectTarget(parsed.projectName ?? projectName);
@@ -419,18 +442,18 @@ async function promptForMissingProjectName(parsed) {
419
442
  return DEFAULT_PROJECT_NAME;
420
443
  }
421
444
  const answer = await text({
422
- message: 'What do you want to name your Expo app?',
445
+ message: "What is the name of your app?",
423
446
  placeholder: DEFAULT_PROJECT_NAME,
424
447
  defaultValue: DEFAULT_PROJECT_NAME,
425
448
  validate: (value) => {
426
449
  if (!value.trim()) {
427
- return 'Please enter an app name, or press Enter to use the visible default.';
450
+ return "Please enter an app name, or press Enter to use the visible default.";
428
451
  }
429
452
  return undefined;
430
453
  },
431
454
  });
432
455
  if (isCancel(answer)) {
433
- cancel('Cancelled. You can rerun create-expo-super-stack whenever you are ready.');
456
+ cancel("Cancelled. You can rerun create-expo-super-stack whenever you are ready.");
434
457
  process.exit(0);
435
458
  }
436
459
  const projectName = answer.trim() || DEFAULT_PROJECT_NAME;
@@ -438,9 +461,9 @@ async function promptForMissingProjectName(parsed) {
438
461
  return projectName;
439
462
  }
440
463
  function printIntro(projectName, createExpoStackArgs, projectParentDir = process.cwd()) {
441
- console.log('create-expo-super-stack');
464
+ console.log("create-expo-super-stack");
442
465
  console.log();
443
- console.log('This uses create-expo-stack under the hood, then applies MDS onboarding.');
466
+ console.log("This uses create-expo-stack under the hood, then applies MDS onboarding.");
444
467
  console.log(`Delegating: create-expo-stack ${formatDisplayArgs(createExpoStackArgs)}`);
445
468
  console.log(`Target app: ${projectName}`);
446
469
  if (projectParentDir !== process.cwd()) {
@@ -454,25 +477,27 @@ async function runCreateExpoStack(args, overrideBin, cwd = process.cwd()) {
454
477
  await new Promise((resolve, reject) => {
455
478
  const child = spawn(command.command, [...command.args, ...args], {
456
479
  cwd,
457
- shell: command.shell ?? process.platform === 'win32',
458
- stdio: 'inherit',
480
+ shell: command.shell ?? process.platform === "win32",
481
+ stdio: "inherit",
459
482
  windowsHide: true,
460
483
  });
461
- child.on('error', reject);
462
- child.on('close', (code) => {
484
+ child.on("error", reject);
485
+ child.on("close", (code) => {
463
486
  if (code === 0) {
464
487
  resolve();
465
488
  }
466
489
  else {
467
- reject(new Error(`create-expo-stack exited with code ${code ?? 'unknown'}.`));
490
+ reject(new Error(`create-expo-stack exited with code ${code ?? "unknown"}.`));
468
491
  }
469
492
  });
470
493
  });
471
494
  }
472
495
  export function resolveProjectTarget(rawProjectName, cwd = process.cwd()) {
473
496
  const trimmed = rawProjectName.trim();
474
- const normalized = trimmed.replace(/[\\/]+$/u, '') || DEFAULT_PROJECT_NAME;
475
- const hasPathSyntax = path.isAbsolute(normalized) || normalized.includes('/') || normalized.includes('\\');
497
+ const normalized = trimmed.replace(/[\\/]+$/u, "") || DEFAULT_PROJECT_NAME;
498
+ const hasPathSyntax = path.isAbsolute(normalized) ||
499
+ normalized.includes("/") ||
500
+ normalized.includes("\\");
476
501
  if (!hasPathSyntax) {
477
502
  return {
478
503
  projectName: normalized,
@@ -488,7 +513,7 @@ export function resolveProjectTarget(rawProjectName, cwd = process.cwd()) {
488
513
  }
489
514
  function replaceProjectArg(args, projectName) {
490
515
  const nextArgs = [...args];
491
- const projectArgIndex = nextArgs.findIndex((arg) => !arg.startsWith('--'));
516
+ const projectArgIndex = nextArgs.findIndex((arg) => !arg.startsWith("--"));
492
517
  if (projectArgIndex >= 0) {
493
518
  nextArgs[projectArgIndex] = projectName;
494
519
  return nextArgs;
@@ -496,27 +521,24 @@ function replaceProjectArg(args, projectName) {
496
521
  return [projectName, ...nextArgs];
497
522
  }
498
523
  export function validateCreateExpoStackArgs(args) {
499
- const authFlags = ['--supabase', '--firebase'].filter((flag) => args.some((arg) => arg === flag || arg.startsWith(`${flag}=`)));
524
+ const authFlags = ["--supabase", "--firebase"].filter((flag) => args.some((arg) => arg === flag || arg.startsWith(`${flag}=`)));
500
525
  if (authFlags.length > 1) {
501
- throw new Error(`Choose one create-expo-stack auth provider, not ${authFlags.join(' and ')}. create-expo-stack scaffolds a single auth slice; Super Stack can still document future data/backend plans in project/.`);
526
+ throw new Error(`Choose one create-expo-stack auth provider, not ${authFlags.join(" and ")}. create-expo-stack scaffolds a single auth slice; Super Stack can still document future data/backend plans in project/.`);
502
527
  }
503
528
  }
504
- export async function runExpoProjectChecks(projectPath, packageManager, options = {}) {
529
+ export async function runExpoProjectChecks(projectPath, packageManager) {
505
530
  console.log();
506
- console.log('Installing MDS-added dependencies, then running Expo dependency repair and doctor.');
531
+ console.log("Installing MDS-added dependencies, then running Expo dependency repair and doctor.");
507
532
  await runProjectCommand(projectPath, buildInstallCommand(packageManager));
508
533
  const missingWindowsOxideBinding = await resolveMissingWindowsTailwindOxideBinding(projectPath);
509
534
  if (missingWindowsOxideBinding) {
510
535
  await runProjectCommand(projectPath, buildAddDevDependencyCommand(packageManager, missingWindowsOxideBinding));
511
536
  }
512
- if (options.useLatestExpoSdk && (await shouldRunExpoLatestSdkCommand(projectPath))) {
537
+ if (await shouldRunExpoLatestSdkCommand(projectPath)) {
513
538
  await runProjectCommand(projectPath, buildExpoLatestSdkCommand(packageManager));
514
539
  }
515
- else if (options.useLatestExpoSdk) {
516
- console.log(` Expo SDK already targets SDK ${CURRENT_EXPO_SDK_MAJOR}; skipping expo@latest.`);
517
- }
518
- if (await shouldRunExpoPinnedSdkCommand(projectPath)) {
519
- await runProjectCommand(projectPath, buildExpoPinnedSdkCommand(packageManager));
540
+ else {
541
+ console.log(` Expo already targets SDK ${EXPECTED_EXPO_SDK_MAJOR}; skipping expo install expo@^${EXPECTED_EXPO_SDK_MAJOR}.`);
520
542
  }
521
543
  await runProjectCommand(projectPath, buildExpoInstallFixCommand(packageManager));
522
544
  if (await shouldInstallExpoFontPeer(projectPath)) {
@@ -530,26 +552,30 @@ async function runProjectCommand(projectPath, spec) {
530
552
  await new Promise((resolve, reject) => {
531
553
  const child = spawn(spec.command, spec.args, {
532
554
  cwd: projectPath,
533
- shell: spec.shell ?? process.platform === 'win32',
534
- stdio: 'inherit',
555
+ shell: spec.shell ?? process.platform === "win32",
556
+ stdio: "inherit",
535
557
  env: spec.env ? { ...process.env, ...spec.env } : process.env,
536
558
  windowsHide: true,
537
559
  });
538
- child.on('error', reject);
539
- child.on('close', (code) => {
560
+ child.on("error", reject);
561
+ child.on("close", (code) => {
540
562
  if (code === 0) {
541
563
  resolve();
542
564
  }
543
565
  else {
544
- reject(new Error(`${spec.display} exited with code ${code ?? 'unknown'}.`));
566
+ reject(new Error(`${spec.display} exited with code ${code ?? "unknown"}.`));
545
567
  }
546
568
  });
547
569
  });
548
570
  }
549
571
  async function resolveCreateExpoStackCommand(overrideBin) {
550
- const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
551
- const executable = process.platform === 'win32' ? 'create-expo-stack.cmd' : 'create-expo-stack';
552
- const override = overrideBin ?? process.env.MRDJ_CREATE_EXPO_STACK_BIN ?? process.env.CREATE_EXPO_STACK_BIN;
572
+ const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
573
+ const executable = process.platform === "win32"
574
+ ? "create-expo-stack.cmd"
575
+ : "create-expo-stack";
576
+ const override = overrideBin ??
577
+ process.env.MRDJ_CREATE_EXPO_STACK_BIN ??
578
+ process.env.CREATE_EXPO_STACK_BIN;
553
579
  if (override) {
554
580
  const overridePath = path.resolve(override);
555
581
  return {
@@ -559,8 +585,8 @@ async function resolveCreateExpoStackCommand(overrideBin) {
559
585
  shell: false,
560
586
  };
561
587
  }
562
- const localForkCliRoot = path.join(packageRoot, '..', '..', '..', 'create-expo-stack', 'cli');
563
- const localForkBin = path.join(localForkCliRoot, 'bin', 'create-expo-stack.js');
588
+ const localForkCliRoot = path.join(packageRoot, "..", "..", "..", "create-expo-stack", "cli");
589
+ const localForkBin = path.join(localForkCliRoot, "bin", "create-expo-stack.js");
564
590
  if (await pathExists(localForkBin)) {
565
591
  await ensureLocalCreateExpoStackBuild(localForkCliRoot);
566
592
  return {
@@ -571,8 +597,8 @@ async function resolveCreateExpoStackCommand(overrideBin) {
571
597
  };
572
598
  }
573
599
  const scopedForkCandidates = [
574
- path.join(packageRoot, 'node_modules', '@mr.dj2u', 'create-expo-stack', 'bin', 'create-expo-stack.js'),
575
- path.join(packageRoot, '..', '..', 'node_modules', '@mr.dj2u', 'create-expo-stack', 'bin', 'create-expo-stack.js'),
600
+ path.join(packageRoot, "node_modules", "@mr.dj2u", "create-expo-stack", "bin", "create-expo-stack.js"),
601
+ path.join(packageRoot, "..", "..", "node_modules", "@mr.dj2u", "create-expo-stack", "bin", "create-expo-stack.js"),
576
602
  ];
577
603
  for (const candidate of scopedForkCandidates) {
578
604
  if (await pathExists(candidate)) {
@@ -585,8 +611,8 @@ async function resolveCreateExpoStackCommand(overrideBin) {
585
611
  }
586
612
  }
587
613
  const candidates = [
588
- path.join(packageRoot, 'node_modules', '.bin', executable),
589
- path.join(packageRoot, '..', '..', 'node_modules', '.bin', executable),
614
+ path.join(packageRoot, "node_modules", ".bin", executable),
615
+ path.join(packageRoot, "..", "..", "node_modules", ".bin", executable),
590
616
  ];
591
617
  for (const candidate of candidates) {
592
618
  if (await pathExists(candidate)) {
@@ -598,58 +624,65 @@ async function resolveCreateExpoStackCommand(overrideBin) {
598
624
  }
599
625
  }
600
626
  return {
601
- command: 'create-expo-stack',
627
+ command: "create-expo-stack",
602
628
  args: [],
603
- display: 'create-expo-stack',
629
+ display: "create-expo-stack",
604
630
  };
605
631
  }
606
632
  async function ensureLocalCreateExpoStackBuild(localForkCliRoot) {
607
- const buildEntry = path.join(localForkCliRoot, 'build', 'cli.js');
633
+ const buildEntry = path.join(localForkCliRoot, "build", "cli.js");
608
634
  if (await pathExists(buildEntry)) {
609
635
  return;
610
636
  }
611
637
  console.log(`Building local create-expo-stack fork: ${localForkCliRoot}`);
612
- if (await commandExists('bun')) {
638
+ if (await commandExists("bun")) {
613
639
  await runProjectCommand(localForkCliRoot, {
614
- command: 'bun',
615
- args: ['run', 'build'],
616
- display: 'bun run build',
640
+ command: "bun",
641
+ args: ["run", "build"],
642
+ display: "bun run build",
617
643
  });
618
644
  return;
619
645
  }
620
- console.log('Bun was not found, so using npm to build the local fork.');
621
- if (!(await pathExists(path.join(localForkCliRoot, 'node_modules')))) {
646
+ console.log("Bun was not found, so using npm to build the local fork.");
647
+ if (!(await pathExists(path.join(localForkCliRoot, "node_modules")))) {
622
648
  await runProjectCommand(localForkCliRoot, {
623
- command: 'npm',
624
- args: ['install'],
625
- display: 'npm install',
649
+ command: "npm",
650
+ args: ["install"],
651
+ display: "npm install",
626
652
  });
627
653
  }
628
654
  await runProjectCommand(localForkCliRoot, {
629
- command: 'npx',
630
- args: ['tsc', '-p', '.'],
631
- display: 'npx tsc -p .',
655
+ command: "npx",
656
+ args: ["tsc", "-p", "."],
657
+ display: "npx tsc -p .",
632
658
  });
633
659
  await runProjectCommand(localForkCliRoot, {
634
- command: 'npx',
635
- args: ['copyfiles', '-u', '2', '-a', './src/templates/**/*', './build/templates'],
660
+ command: "npx",
661
+ args: [
662
+ "copyfiles",
663
+ "-u",
664
+ "2",
665
+ "-a",
666
+ "./src/templates/**/*",
667
+ "./build/templates",
668
+ ],
636
669
  display: 'npx copyfiles -u 2 -a "./src/templates/**/*" ./build/templates',
637
670
  });
638
671
  }
639
672
  async function commandExists(command) {
640
673
  return new Promise((resolve) => {
641
- const child = spawn(command, ['--version'], {
642
- shell: process.platform === 'win32',
643
- stdio: 'ignore',
674
+ const child = spawn(command, ["--version"], {
675
+ shell: process.platform === "win32",
676
+ stdio: "ignore",
644
677
  windowsHide: true,
645
678
  });
646
- child.on('error', () => resolve(false));
647
- child.on('close', (code) => resolve(code === 0));
679
+ child.on("error", () => resolve(false));
680
+ child.on("close", (code) => resolve(code === 0));
648
681
  });
649
682
  }
650
683
  async function resolveGeneratedProjectPath(cwd, projectName) {
651
684
  const directPath = path.resolve(cwd, projectName);
652
- if (await pathExists(path.join(directPath, 'package.json'))) {
685
+ if (await pathExists(path.join(directPath, "package.json"))) {
653
686
  return directPath;
654
687
  }
655
688
  const fromCesConfig = await findProjectFromCesConfig(cwd, projectName);
@@ -659,8 +692,8 @@ async function resolveGeneratedProjectPath(cwd, projectName) {
659
692
  return directPath;
660
693
  }
661
694
  async function moveRootAppIntoSrc(projectPath) {
662
- const rootAppDir = path.join(projectPath, 'app');
663
- const srcAppDir = path.join(projectPath, 'src', 'app');
695
+ const rootAppDir = path.join(projectPath, "app");
696
+ const srcAppDir = path.join(projectPath, "src", "app");
664
697
  if (!(await pathExists(rootAppDir)) || (await pathExists(srcAppDir))) {
665
698
  return null;
666
699
  }
@@ -669,14 +702,14 @@ async function moveRootAppIntoSrc(projectPath) {
669
702
  return { from: rootAppDir, to: srcAppDir };
670
703
  }
671
704
  async function consolidateRootSourceFolders(projectPath) {
672
- const folderNames = ['components', 'theme', 'lib'];
705
+ const folderNames = ["components", "theme", "lib"];
673
706
  const updatedPaths = [];
674
707
  for (const folderName of folderNames) {
675
708
  const rootDir = path.join(projectPath, folderName);
676
709
  if (!(await pathExists(rootDir))) {
677
710
  continue;
678
711
  }
679
- const srcDir = path.join(projectPath, 'src', folderName);
712
+ const srcDir = path.join(projectPath, "src", folderName);
680
713
  if (!(await pathExists(srcDir))) {
681
714
  await mkdir(path.dirname(srcDir), { recursive: true });
682
715
  await rename(rootDir, srcDir);
@@ -710,7 +743,7 @@ async function mergeDirectoryInto(sourceDir, targetDir) {
710
743
  export async function repairMovedSrcAppImports(projectPath) {
711
744
  const layoutPaths = [
712
745
  {
713
- filePath: path.join(projectPath, 'src', 'app', '(tabs)', '_layout.tsx'),
746
+ filePath: path.join(projectPath, "src", "app", "(tabs)", "_layout.tsx"),
714
747
  replacements: [
715
748
  ['"../components/HeaderButton"', '"../../components/HeaderButton"'],
716
749
  ['"../components/TabBarIcon"', '"../../components/TabBarIcon"'],
@@ -719,7 +752,7 @@ export async function repairMovedSrcAppImports(projectPath) {
719
752
  ],
720
753
  },
721
754
  {
722
- filePath: path.join(projectPath, 'src', 'app', '(drawer)', '_layout.tsx'),
755
+ filePath: path.join(projectPath, "src", "app", "(drawer)", "_layout.tsx"),
723
756
  replacements: [
724
757
  ['"../components/HeaderButton"', '"../../components/HeaderButton"'],
725
758
  ['"../components/TabBarIcon"', '"../../components/TabBarIcon"'],
@@ -728,11 +761,17 @@ export async function repairMovedSrcAppImports(projectPath) {
728
761
  ],
729
762
  },
730
763
  {
731
- filePath: path.join(projectPath, 'src', 'app', '(drawer)', '(tabs)', '_layout.tsx'),
764
+ filePath: path.join(projectPath, "src", "app", "(drawer)", "(tabs)", "_layout.tsx"),
732
765
  replacements: [
733
- ['"../../components/HeaderButton"', '"../../../components/HeaderButton"'],
766
+ [
767
+ '"../../components/HeaderButton"',
768
+ '"../../../components/HeaderButton"',
769
+ ],
734
770
  ['"../../components/TabBarIcon"', '"../../../components/TabBarIcon"'],
735
- ["'../../components/HeaderButton'", "'../../../components/HeaderButton'"],
771
+ [
772
+ "'../../components/HeaderButton'",
773
+ "'../../../components/HeaderButton'",
774
+ ],
736
775
  ["'../../components/TabBarIcon'", "'../../../components/TabBarIcon'"],
737
776
  ],
738
777
  },
@@ -750,7 +789,7 @@ export async function repairMovedSrcAppImports(projectPath) {
750
789
  if (updated === raw) {
751
790
  continue;
752
791
  }
753
- await writeFile(layout.filePath, updated, 'utf8');
792
+ await writeFile(layout.filePath, updated, "utf8");
754
793
  updatedPaths.push(layout.filePath);
755
794
  }
756
795
  return updatedPaths;
@@ -759,8 +798,10 @@ export async function repairGeneratedTypeSupport(projectPath, options = {}) {
759
798
  const updatedPaths = new Set();
760
799
  let shouldIncludeUniwindTypes = false;
761
800
  if (options.needsUniwindTypes) {
762
- const packageJson = await readJson(path.join(projectPath, 'package.json'));
763
- const dependencies = isRecord(packageJson.dependencies) ? packageJson.dependencies : {};
801
+ const packageJson = await readJson(path.join(projectPath, "package.json"));
802
+ const dependencies = isRecord(packageJson.dependencies)
803
+ ? packageJson.dependencies
804
+ : {};
764
805
  const devDependencies = isRecord(packageJson.devDependencies)
765
806
  ? packageJson.devDependencies
766
807
  : {};
@@ -768,95 +809,97 @@ export async function repairGeneratedTypeSupport(projectPath, options = {}) {
768
809
  ? packageJson.optionalDependencies
769
810
  : {};
770
811
  shouldIncludeUniwindTypes =
771
- typeof dependencies.uniwind === 'string' ||
772
- typeof devDependencies.uniwind === 'string' ||
773
- typeof optionalDependencies.uniwind === 'string' ||
774
- (await pathExists(path.join(projectPath, 'node_modules', 'uniwind', 'types.d.ts'))) ||
775
- (await pathExists(path.join(projectPath, 'node_modules', 'uniwind', 'types', 'index.d.ts')));
812
+ typeof dependencies.uniwind === "string" ||
813
+ typeof devDependencies.uniwind === "string" ||
814
+ typeof optionalDependencies.uniwind === "string" ||
815
+ (await pathExists(path.join(projectPath, "node_modules", "uniwind", "types.d.ts"))) ||
816
+ (await pathExists(path.join(projectPath, "node_modules", "uniwind", "types", "index.d.ts")));
776
817
  }
777
818
  if (options.needsUniwindTypes) {
778
- const cssEnvPath = path.join(projectPath, 'css-env.d.ts');
779
- const raw = (await readOptionalText(cssEnvPath)) ?? '';
780
- if (shouldIncludeUniwindTypes && !raw.includes('uniwind/types')) {
781
- await writeFile(cssEnvPath, `/// <reference types="uniwind/types" />\n\n${raw}`, 'utf8');
819
+ const cssEnvPath = path.join(projectPath, "css-env.d.ts");
820
+ const raw = (await readOptionalText(cssEnvPath)) ?? "";
821
+ if (shouldIncludeUniwindTypes && !raw.includes("uniwind/types")) {
822
+ await writeFile(cssEnvPath, `/// <reference types="uniwind/types" />\n\n${raw}`, "utf8");
782
823
  updatedPaths.add(cssEnvPath);
783
824
  }
784
- else if (!shouldIncludeUniwindTypes && raw.includes('uniwind/types')) {
825
+ else if (!shouldIncludeUniwindTypes && raw.includes("uniwind/types")) {
785
826
  const updated = raw
786
- .replace(/^\/\/\/ <reference types="uniwind\/types" \/>\r?\n\r?\n?/m, '')
827
+ .replace(/^\/\/\/ <reference types="uniwind\/types" \/>\r?\n\r?\n?/m, "")
787
828
  .trimStart();
788
- await writeFile(cssEnvPath, updated, 'utf8');
829
+ await writeFile(cssEnvPath, updated, "utf8");
789
830
  updatedPaths.add(cssEnvPath);
790
831
  }
791
832
  }
792
833
  if (options.needsNodeTypes || options.needsUniwindTypes) {
793
- const tsconfigPath = path.join(projectPath, 'tsconfig.json');
834
+ const tsconfigPath = path.join(projectPath, "tsconfig.json");
794
835
  const tsconfig = await readJson(tsconfigPath);
795
- const compilerOptions = isRecord(tsconfig.compilerOptions) ? tsconfig.compilerOptions : {};
796
- const pathsConfig = isRecord(compilerOptions.paths) ? compilerOptions.paths : {};
797
- const usingSrcApp = await pathExists(path.join(projectPath, 'src', 'app'));
836
+ const compilerOptions = isRecord(tsconfig.compilerOptions)
837
+ ? tsconfig.compilerOptions
838
+ : {};
839
+ const pathsConfig = isRecord(compilerOptions.paths)
840
+ ? compilerOptions.paths
841
+ : {};
842
+ const usingSrcApp = await pathExists(path.join(projectPath, "src", "app"));
798
843
  const normalizedPathsConfig = usingSrcApp
799
844
  ? await normalizeTsconfigPathsForSrcDirectory(projectPath, pathsConfig)
800
845
  : pathsConfig;
801
- const hasAliasPaths = Object.keys(pathsConfig).some((key) => key.includes('@'));
802
846
  const existingTypes = Array.isArray(compilerOptions.types)
803
- ? compilerOptions.types.filter((item) => typeof item === 'string')
847
+ ? compilerOptions.types.filter((item) => typeof item === "string")
804
848
  : [];
805
849
  const currentTypes = existingTypes
806
- ? existingTypes.filter((item) => shouldIncludeUniwindTypes || item !== 'uniwind/types')
850
+ ? existingTypes.filter((item) => shouldIncludeUniwindTypes || item !== "uniwind/types")
807
851
  : [];
808
852
  const desiredTypes = [
809
853
  ...currentTypes,
810
- ...(options.needsNodeTypes ? ['node'] : []),
811
- ...(shouldIncludeUniwindTypes ? ['uniwind/types'] : []),
854
+ ...(options.needsNodeTypes ? ["node"] : []),
855
+ ...(shouldIncludeUniwindTypes ? ["uniwind/types"] : []),
812
856
  ];
813
857
  const nextTypes = Array.from(new Set(desiredTypes));
814
- const shouldSetBaseUrl = hasAliasPaths && typeof compilerOptions.baseUrl !== 'string';
815
- const shouldSetIgnoreDeprecations = (shouldSetBaseUrl || typeof compilerOptions.baseUrl === 'string') &&
816
- compilerOptions.ignoreDeprecations !== '6.0';
858
+ const shouldRemoveDeprecatedTsconfigOptions = "baseUrl" in compilerOptions || "ignoreDeprecations" in compilerOptions;
817
859
  const shouldUpdatePaths = JSON.stringify(pathsConfig) !== JSON.stringify(normalizedPathsConfig);
818
860
  const shouldUpdateTypes = JSON.stringify(existingTypes) !== JSON.stringify(nextTypes);
819
- if (shouldUpdateTypes || shouldSetBaseUrl || shouldSetIgnoreDeprecations || shouldUpdatePaths) {
861
+ if (shouldUpdateTypes ||
862
+ shouldRemoveDeprecatedTsconfigOptions ||
863
+ shouldUpdatePaths) {
864
+ const { baseUrl: _baseUrl, ignoreDeprecations: _ignoreDeprecations, ...compilerOptionsWithoutDeprecatedOptions } = compilerOptions;
820
865
  tsconfig.compilerOptions = {
821
- ...compilerOptions,
866
+ ...compilerOptionsWithoutDeprecatedOptions,
822
867
  ...(shouldUpdatePaths ? { paths: normalizedPathsConfig } : {}),
823
868
  types: shouldUpdateTypes ? nextTypes : currentTypes,
824
- ...(shouldSetBaseUrl ? { baseUrl: '.' } : {}),
825
- ...(shouldSetIgnoreDeprecations ? { ignoreDeprecations: '6.0' } : {}),
826
869
  };
827
- await writeFile(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}\n`, 'utf8');
870
+ await writeFile(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}\n`, "utf8");
828
871
  updatedPaths.add(tsconfigPath);
829
872
  }
830
873
  }
831
874
  if (options.needsNodeTypes) {
832
- const packageJsonPath = path.join(projectPath, 'package.json');
875
+ const packageJsonPath = path.join(projectPath, "package.json");
833
876
  const packageJson = await readJson(packageJsonPath);
834
877
  const devDependencies = isRecord(packageJson.devDependencies)
835
878
  ? packageJson.devDependencies
836
879
  : {};
837
- if (devDependencies['@types/node'] !== '^25.9.1') {
880
+ if (devDependencies["@types/node"] !== "^25.9.1") {
838
881
  packageJson.devDependencies = {
839
882
  ...devDependencies,
840
- '@types/node': '^25.9.1',
883
+ "@types/node": "^25.9.1",
841
884
  };
842
- await writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, 'utf8');
885
+ await writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, "utf8");
843
886
  updatedPaths.add(packageJsonPath);
844
887
  }
845
888
  }
846
889
  const tabBarIconPaths = [
847
- path.join(projectPath, 'components', 'TabBarIcon.tsx'),
848
- path.join(projectPath, 'src', 'components', 'TabBarIcon.tsx'),
890
+ path.join(projectPath, "components", "TabBarIcon.tsx"),
891
+ path.join(projectPath, "src", "components", "TabBarIcon.tsx"),
849
892
  ];
850
893
  for (const tabBarIconPath of tabBarIconPaths) {
851
894
  const tabBarIconRaw = await readOptionalText(tabBarIconPath);
852
- if (!tabBarIconRaw || !tabBarIconRaw.includes('color: string')) {
895
+ if (!tabBarIconRaw || !tabBarIconRaw.includes("color: string")) {
853
896
  continue;
854
897
  }
855
898
  const updated = tabBarIconRaw
856
899
  .replace("import { StyleSheet } from 'react-native';", "import type { ColorValue } from 'react-native';\nimport { StyleSheet } from 'react-native';")
857
- .replace('color: string;', 'color: ColorValue;');
900
+ .replace("color: string;", "color: ColorValue;");
858
901
  if (updated !== tabBarIconRaw) {
859
- await writeFile(tabBarIconPath, updated, 'utf8');
902
+ await writeFile(tabBarIconPath, updated, "utf8");
860
903
  updatedPaths.add(tabBarIconPath);
861
904
  }
862
905
  }
@@ -871,7 +914,7 @@ async function normalizeTsconfigPathsForSrcDirectory(projectPath, pathsConfig) {
871
914
  }
872
915
  const nextTargets = [];
873
916
  for (const target of targets) {
874
- if (typeof target !== 'string') {
917
+ if (typeof target !== "string") {
875
918
  nextTargets.push(target);
876
919
  continue;
877
920
  }
@@ -883,23 +926,27 @@ async function normalizeTsconfigPathsForSrcDirectory(projectPath, pathsConfig) {
883
926
  return nextPaths;
884
927
  }
885
928
  async function normalizeAliasTargetForSrcDirectory(projectPath, alias, target) {
886
- const normalizedTarget = target.replace(/\\/g, '/').trim();
887
- if (normalizedTarget.startsWith('./src/') || normalizedTarget.startsWith('src/')) {
929
+ const normalizedTarget = target.replace(/\\/g, "/").trim();
930
+ if (normalizedTarget.startsWith("./src/") ||
931
+ normalizedTarget.startsWith("src/")) {
888
932
  return target;
889
933
  }
890
- if (alias === '@/*' && (normalizedTarget === './*' || normalizedTarget === '*')) {
891
- return './src/*';
934
+ if (alias === "@/*" &&
935
+ (normalizedTarget === "./*" || normalizedTarget === "*")) {
936
+ return "./src/*";
892
937
  }
893
- const relativeTarget = normalizedTarget.startsWith('./')
938
+ const relativeTarget = normalizedTarget.startsWith("./")
894
939
  ? normalizedTarget.slice(2)
895
940
  : normalizedTarget;
896
- const wildcardSuffix = relativeTarget.endsWith('/*') ? '/*' : '';
897
- const relativeBase = wildcardSuffix ? relativeTarget.slice(0, -2) : relativeTarget;
898
- if (!relativeBase || relativeBase === '*') {
941
+ const wildcardSuffix = relativeTarget.endsWith("/*") ? "/*" : "";
942
+ const relativeBase = wildcardSuffix
943
+ ? relativeTarget.slice(0, -2)
944
+ : relativeTarget;
945
+ if (!relativeBase || relativeBase === "*") {
899
946
  return target;
900
947
  }
901
948
  const rootCandidate = path.join(projectPath, relativeBase);
902
- const srcCandidate = path.join(projectPath, 'src', relativeBase);
949
+ const srcCandidate = path.join(projectPath, "src", relativeBase);
903
950
  const rootExists = await pathExists(rootCandidate);
904
951
  const srcExists = await pathExists(srcCandidate);
905
952
  if (!rootExists && srcExists) {
@@ -909,28 +956,41 @@ async function normalizeAliasTargetForSrcDirectory(projectPath, alias, target) {
909
956
  }
910
957
  export async function repairGeneratedNativeWindUiPicker(projectPath) {
911
958
  const pickerPaths = [
912
- path.join(projectPath, 'components', 'nativewindui', 'Picker.tsx'),
913
- path.join(projectPath, 'src', 'components', 'nativewindui', 'Picker.tsx'),
959
+ path.join(projectPath, "components", "nativewindui", "Picker.tsx"),
960
+ path.join(projectPath, "src", "components", "nativewindui", "Picker.tsx"),
914
961
  ];
915
962
  const updatedPaths = [];
916
963
  for (const pickerPath of pickerPaths) {
917
964
  const raw = await readOptionalText(pickerPath);
918
- if (!raw || !raw.includes('dropdownIconRippleColor=')) {
965
+ if (!raw || !raw.includes("dropdownIconRippleColor=")) {
919
966
  continue;
920
967
  }
921
968
  let updated = raw;
922
969
  updated = updated.replace("import { View } from 'react-native';", "import { Platform, View } from 'react-native';");
923
- updated = updated.replace(' dropdownIconRippleColor={dropdownIconRippleColor ?? colors.foreground}', " {...(Platform.OS === 'web' ? {} : { dropdownIconRippleColor: dropdownIconRippleColor ?? colors.foreground })}");
970
+ updated = updated.replace(" dropdownIconRippleColor={dropdownIconRippleColor ?? colors.foreground}", " {...(Platform.OS === 'web' ? {} : { dropdownIconRippleColor: dropdownIconRippleColor ?? colors.foreground })}");
924
971
  if (updated === raw) {
925
972
  continue;
926
973
  }
927
- await writeFile(pickerPath, updated, 'utf8');
974
+ await writeFile(pickerPath, updated, "utf8");
928
975
  updatedPaths.push(pickerPath);
929
976
  }
930
977
  return updatedPaths;
931
978
  }
979
+ export async function repairGeneratedEslintConfig(projectPath) {
980
+ const eslintConfigPath = path.join(projectPath, "eslint.config.js");
981
+ const raw = await readOptionalText(eslintConfigPath);
982
+ if (!raw) {
983
+ return [];
984
+ }
985
+ const updated = raw.replace(/^\/\* eslint-env node \*\/\r?\n/, "");
986
+ if (updated === raw) {
987
+ return [];
988
+ }
989
+ await writeFile(eslintConfigPath, updated, "utf8");
990
+ return [eslintConfigPath];
991
+ }
932
992
  export async function repairExpoProjectIdentifiers(projectPath, projectName, targetPlatforms = []) {
933
- const appJsonPath = path.join(projectPath, 'app.json');
993
+ const appJsonPath = path.join(projectPath, "app.json");
934
994
  const raw = await readOptionalText(appJsonPath);
935
995
  if (!raw) {
936
996
  return [];
@@ -949,7 +1009,7 @@ export async function repairExpoProjectIdentifiers(projectPath, projectName, tar
949
1009
  }
950
1010
  const nextSlug = toExpoSlug(readString(expo.slug) ?? projectName);
951
1011
  const currentScheme = expo.scheme;
952
- const hasScheme = Object.prototype.hasOwnProperty.call(expo, 'scheme');
1012
+ const hasScheme = Object.prototype.hasOwnProperty.call(expo, "scheme");
953
1013
  const nextScheme = hasScheme
954
1014
  ? Array.isArray(currentScheme)
955
1015
  ? currentScheme.map((item) => toExpoScheme(readString(item) ?? projectName))
@@ -960,54 +1020,59 @@ export async function repairExpoProjectIdentifiers(projectPath, projectName, tar
960
1020
  expo.slug = nextSlug;
961
1021
  changed = true;
962
1022
  }
963
- if (hasScheme && JSON.stringify(currentScheme) !== JSON.stringify(nextScheme)) {
1023
+ if (hasScheme &&
1024
+ JSON.stringify(currentScheme) !== JSON.stringify(nextScheme)) {
964
1025
  expo.scheme = nextScheme;
965
1026
  changed = true;
966
1027
  }
967
- const shouldIncludeWeb = targetPlatforms.includes('web');
968
1028
  const currentPlatforms = Array.isArray(expo.platforms) ? expo.platforms : [];
969
- if (shouldIncludeWeb && !currentPlatforms.includes('web')) {
970
- expo.platforms = [...currentPlatforms, 'web'];
1029
+ const normalizedTargetPlatforms = Array.from(new Set(targetPlatforms
1030
+ .map((platform) => readString(platform) ?? platform)
1031
+ .filter((platform) => Boolean(platform))));
1032
+ if (normalizedTargetPlatforms.length > 0 &&
1033
+ JSON.stringify(currentPlatforms) !== JSON.stringify(normalizedTargetPlatforms)) {
1034
+ expo.platforms = normalizedTargetPlatforms;
971
1035
  changed = true;
972
1036
  }
973
- const shouldIncludeAndroid = targetPlatforms.includes('android');
1037
+ const shouldIncludeAndroid = targetPlatforms.includes("android");
974
1038
  if (shouldIncludeAndroid) {
975
- if (readString(expo.backgroundColor) !== '#f1f0f8') {
976
- expo.backgroundColor = '#f1f0f8';
1039
+ if (readString(expo.backgroundColor) !== "#f1f0f8") {
1040
+ expo.backgroundColor = "#f1f0f8";
977
1041
  changed = true;
978
1042
  }
979
1043
  const androidConfig = isRecord(expo.android) ? { ...expo.android } : {};
980
- if (readString(androidConfig.backgroundColor) !== '#f1f0f8') {
981
- androidConfig.backgroundColor = '#f1f0f8';
1044
+ if (readString(androidConfig.backgroundColor) !== "#f1f0f8") {
1045
+ androidConfig.backgroundColor = "#f1f0f8";
982
1046
  expo.android = androidConfig;
983
1047
  changed = true;
984
1048
  }
985
1049
  const desiredAndroidStatusBar = {
986
- backgroundColor: '#f1f0f8',
987
- barStyle: 'dark-content',
1050
+ backgroundColor: "#f1f0f8",
1051
+ barStyle: "dark-content",
988
1052
  translucent: false,
989
1053
  };
990
- if (JSON.stringify(expo.androidStatusBar) !== JSON.stringify(desiredAndroidStatusBar)) {
1054
+ if (JSON.stringify(expo.androidStatusBar) !==
1055
+ JSON.stringify(desiredAndroidStatusBar)) {
991
1056
  expo.androidStatusBar = desiredAndroidStatusBar;
992
1057
  changed = true;
993
1058
  }
994
- if (Object.prototype.hasOwnProperty.call(expo, 'androidNavigationBar')) {
1059
+ if (Object.prototype.hasOwnProperty.call(expo, "androidNavigationBar")) {
995
1060
  delete expo.androidNavigationBar;
996
1061
  changed = true;
997
1062
  }
998
1063
  const desiredNavigationBarPlugin = [
999
- 'expo-navigation-bar',
1064
+ "expo-navigation-bar",
1000
1065
  {
1001
- style: 'dark',
1066
+ style: "dark",
1002
1067
  enforceContrast: false,
1003
1068
  },
1004
1069
  ];
1005
1070
  const plugins = Array.isArray(expo.plugins) ? [...expo.plugins] : [];
1006
1071
  const existingNavigationBarPluginIndex = plugins.findIndex((plugin) => {
1007
- if (readString(plugin) === 'expo-navigation-bar') {
1072
+ if (readString(plugin) === "expo-navigation-bar") {
1008
1073
  return true;
1009
1074
  }
1010
- return Array.isArray(plugin) && readString(plugin[0]) === 'expo-navigation-bar';
1075
+ return (Array.isArray(plugin) && readString(plugin[0]) === "expo-navigation-bar");
1011
1076
  });
1012
1077
  if (existingNavigationBarPluginIndex === -1 ||
1013
1078
  JSON.stringify(plugins[existingNavigationBarPluginIndex]) !==
@@ -1021,13 +1086,13 @@ export async function repairExpoProjectIdentifiers(projectPath, projectName, tar
1021
1086
  changed = true;
1022
1087
  }
1023
1088
  const hasSystemUiPlugin = plugins.some((plugin) => {
1024
- if (readString(plugin) === 'expo-system-ui') {
1089
+ if (readString(plugin) === "expo-system-ui") {
1025
1090
  return true;
1026
1091
  }
1027
- return Array.isArray(plugin) && readString(plugin[0]) === 'expo-system-ui';
1092
+ return (Array.isArray(plugin) && readString(plugin[0]) === "expo-system-ui");
1028
1093
  });
1029
1094
  if (!hasSystemUiPlugin) {
1030
- plugins.push('expo-system-ui');
1095
+ plugins.push("expo-system-ui");
1031
1096
  changed = true;
1032
1097
  }
1033
1098
  if (JSON.stringify(expo.plugins) !== JSON.stringify(plugins)) {
@@ -1036,34 +1101,35 @@ export async function repairExpoProjectIdentifiers(projectPath, projectName, tar
1036
1101
  }
1037
1102
  }
1038
1103
  else {
1039
- if (Object.prototype.hasOwnProperty.call(expo, 'androidNavigationBar')) {
1104
+ if (Object.prototype.hasOwnProperty.call(expo, "androidNavigationBar")) {
1040
1105
  delete expo.androidNavigationBar;
1041
1106
  changed = true;
1042
1107
  }
1043
- if (Object.prototype.hasOwnProperty.call(expo, 'androidStatusBar')) {
1108
+ if (Object.prototype.hasOwnProperty.call(expo, "androidStatusBar")) {
1044
1109
  delete expo.androidStatusBar;
1045
1110
  changed = true;
1046
1111
  }
1047
1112
  }
1048
1113
  if (shouldIncludeAndroid) {
1049
1114
  const deprecatedNavigationBar = {
1050
- position: 'absolute',
1051
- backgroundColor: '#00000000',
1052
- style: 'dark',
1115
+ position: "absolute",
1116
+ backgroundColor: "#00000000",
1117
+ style: "dark",
1053
1118
  enforceContrast: false,
1054
1119
  };
1055
- if (JSON.stringify(expo.androidNavigationBar) === JSON.stringify(deprecatedNavigationBar)) {
1120
+ if (JSON.stringify(expo.androidNavigationBar) ===
1121
+ JSON.stringify(deprecatedNavigationBar)) {
1056
1122
  delete expo.androidNavigationBar;
1057
1123
  }
1058
1124
  }
1059
1125
  if (!changed) {
1060
1126
  return [];
1061
1127
  }
1062
- await writeFile(appJsonPath, `${JSON.stringify(parsed, null, 2)}\n`, 'utf8');
1128
+ await writeFile(appJsonPath, `${JSON.stringify(parsed, null, 2)}\n`, "utf8");
1063
1129
  return [appJsonPath];
1064
1130
  }
1065
- export async function repairExpoWebOutputForStylistLifecycle(projectPath, preferredWebOutput = 'static') {
1066
- const appJsonPath = path.join(projectPath, 'app.json');
1131
+ export async function repairExpoWebOutputForStylistLifecycle(projectPath, preferredWebOutput = "static") {
1132
+ const appJsonPath = path.join(projectPath, "app.json");
1067
1133
  const raw = await readOptionalText(appJsonPath);
1068
1134
  if (!raw) {
1069
1135
  return [];
@@ -1081,13 +1147,15 @@ export async function repairExpoWebOutputForStylistLifecycle(projectPath, prefer
1081
1147
  return [];
1082
1148
  }
1083
1149
  const hasWebPlatform = !Array.isArray(expo.platforms) ||
1084
- expo.platforms.some((platform) => readString(platform) === 'web');
1150
+ expo.platforms.some((platform) => readString(platform) === "web");
1085
1151
  if (!hasWebPlatform) {
1086
1152
  return [];
1087
1153
  }
1088
1154
  const hasStylistSyncRoute = await hasStylistSyncApiRoute(projectPath);
1089
1155
  const preferred = normalizeExpoWebOutput(preferredWebOutput);
1090
- const desiredOutput = hasStylistSyncRoute ? 'server' : preferred;
1156
+ const desiredOutput = hasStylistSyncRoute
1157
+ ? "server"
1158
+ : preferred;
1091
1159
  const webConfig = isRecord(expo.web) ? expo.web : {};
1092
1160
  const currentOutput = readString(webConfig.output);
1093
1161
  if (currentOutput === desiredOutput) {
@@ -1097,7 +1165,7 @@ export async function repairExpoWebOutputForStylistLifecycle(projectPath, prefer
1097
1165
  ...webConfig,
1098
1166
  output: desiredOutput,
1099
1167
  };
1100
- await writeFile(appJsonPath, `${JSON.stringify(parsed, null, 2)}\n`, 'utf8');
1168
+ await writeFile(appJsonPath, `${JSON.stringify(parsed, null, 2)}\n`, "utf8");
1101
1169
  return [appJsonPath];
1102
1170
  }
1103
1171
  async function hasStylistSyncApiRoute(projectPath) {
@@ -1110,15 +1178,15 @@ async function hasStylistSyncApiRoute(projectPath) {
1110
1178
  }
1111
1179
  function normalizeExpoWebOutput(value) {
1112
1180
  switch (value) {
1113
- case 'server':
1114
- return 'server';
1115
- case 'spa':
1116
- return 'single';
1117
- case 'none':
1118
- return 'static';
1119
- case 'static':
1181
+ case "server":
1182
+ return "server";
1183
+ case "spa":
1184
+ return "single";
1185
+ case "none":
1186
+ return "static";
1187
+ case "static":
1120
1188
  default:
1121
- return 'static';
1189
+ return "static";
1122
1190
  }
1123
1191
  }
1124
1192
  async function findProjectFromCesConfig(cwd, projectName) {
@@ -1129,7 +1197,7 @@ async function findProjectFromCesConfig(cwd, projectName) {
1129
1197
  continue;
1130
1198
  }
1131
1199
  const projectPath = path.join(cwd, entry.name);
1132
- const raw = await readOptionalText(path.join(projectPath, 'cesconfig.jsonc'));
1200
+ const raw = await readOptionalText(path.join(projectPath, "cesconfig.jsonc"));
1133
1201
  if (!raw) {
1134
1202
  continue;
1135
1203
  }
@@ -1145,348 +1213,303 @@ async function findProjectFromCesConfig(cwd, projectName) {
1145
1213
  return null;
1146
1214
  }
1147
1215
  async function detectPackageManager(projectPath, args) {
1148
- if (args.includes('--pnpm'))
1149
- return 'pnpm';
1150
- if (args.includes('--yarn'))
1151
- return 'yarn';
1152
- if (args.includes('--bun'))
1153
- return 'bun';
1154
- if (args.includes('--npm'))
1155
- return 'npm';
1156
- const packageJson = await readJson(path.join(projectPath, 'package.json'));
1216
+ if (args.includes("--pnpm"))
1217
+ return "pnpm";
1218
+ if (args.includes("--yarn"))
1219
+ return "yarn";
1220
+ if (args.includes("--bun"))
1221
+ return "bun";
1222
+ if (args.includes("--npm"))
1223
+ return "npm";
1224
+ const packageJson = await readJson(path.join(projectPath, "package.json"));
1157
1225
  const declared = readString(packageJson.packageManager);
1158
- if (declared?.startsWith('pnpm@'))
1159
- return 'pnpm';
1160
- if (declared?.startsWith('yarn@'))
1161
- return 'yarn';
1162
- if (declared?.startsWith('bun@'))
1163
- return 'bun';
1164
- if (declared?.startsWith('npm@'))
1165
- return 'npm';
1166
- if (await pathExists(path.join(projectPath, 'pnpm-lock.yaml')))
1167
- return 'pnpm';
1168
- if (await pathExists(path.join(projectPath, 'yarn.lock')))
1169
- return 'yarn';
1170
- if ((await pathExists(path.join(projectPath, 'bun.lock'))) ||
1171
- (await pathExists(path.join(projectPath, 'bun.lockb')))) {
1172
- return 'bun';
1173
- }
1174
- return 'npm';
1226
+ if (declared?.startsWith("pnpm@"))
1227
+ return "pnpm";
1228
+ if (declared?.startsWith("yarn@"))
1229
+ return "yarn";
1230
+ if (declared?.startsWith("bun@"))
1231
+ return "bun";
1232
+ if (declared?.startsWith("npm@"))
1233
+ return "npm";
1234
+ if (await pathExists(path.join(projectPath, "pnpm-lock.yaml")))
1235
+ return "pnpm";
1236
+ if (await pathExists(path.join(projectPath, "yarn.lock")))
1237
+ return "yarn";
1238
+ if ((await pathExists(path.join(projectPath, "bun.lock"))) ||
1239
+ (await pathExists(path.join(projectPath, "bun.lockb")))) {
1240
+ return "bun";
1241
+ }
1242
+ return "npm";
1175
1243
  }
1176
1244
  function shouldRunExpoProjectChecks(parsed, noInstallRequested) {
1177
- return !parsed.mds.skipCreate && !parsed.mds.skipExpoFix && !noInstallRequested;
1245
+ return (!parsed.mds.skipCreate && !parsed.mds.skipExpoFix && !noInstallRequested);
1178
1246
  }
1179
1247
  function hasNoInstallFlag(args) {
1180
1248
  return args.some((arg) => {
1181
1249
  const normalized = arg.trim().toLowerCase();
1182
- return (normalized === '--no-install' ||
1183
- normalized === '--noinstall' ||
1184
- normalized === '--no-install=true' ||
1185
- normalized === '--install=false');
1250
+ return (normalized === "--no-install" ||
1251
+ normalized === "--noinstall" ||
1252
+ normalized === "--no-install=true" ||
1253
+ normalized === "--install=false");
1186
1254
  });
1187
1255
  }
1188
1256
  export function buildInstallCommand(packageManager) {
1189
1257
  switch (packageManager) {
1190
- case 'pnpm':
1258
+ case "pnpm":
1191
1259
  return {
1192
- command: 'pnpm',
1193
- args: ['install', '--config.strict-dep-builds=false', '--ignore-workspace'],
1194
- display: 'pnpm install --config.strict-dep-builds=false --ignore-workspace',
1195
- env: { PNPM_CONFIG_STRICT_DEP_BUILDS: 'false' },
1260
+ command: "pnpm",
1261
+ args: [
1262
+ "install",
1263
+ "--config.strict-dep-builds=false",
1264
+ "--ignore-workspace",
1265
+ ],
1266
+ display: "pnpm install --config.strict-dep-builds=false --ignore-workspace",
1267
+ env: { PNPM_CONFIG_STRICT_DEP_BUILDS: "false" },
1196
1268
  };
1197
- case 'yarn':
1269
+ case "yarn":
1198
1270
  return {
1199
- command: 'yarn',
1200
- args: ['install'],
1201
- display: 'yarn install',
1271
+ command: "yarn",
1272
+ args: ["install"],
1273
+ display: "yarn install",
1202
1274
  };
1203
- case 'bun':
1275
+ case "bun":
1204
1276
  return {
1205
- command: 'bun',
1206
- args: ['install'],
1207
- display: 'bun install',
1277
+ command: "bun",
1278
+ args: ["install"],
1279
+ display: "bun install",
1208
1280
  };
1209
- case 'npm':
1281
+ case "npm":
1210
1282
  return {
1211
- command: 'npm',
1212
- args: ['install'],
1213
- display: 'npm install',
1283
+ command: "npm",
1284
+ args: ["install"],
1285
+ display: "npm install",
1214
1286
  };
1215
1287
  }
1216
1288
  }
1217
1289
  export function buildExpoInstallFixCommand(packageManager) {
1218
1290
  switch (packageManager) {
1219
- case 'pnpm':
1291
+ case "pnpm":
1220
1292
  return {
1221
- command: 'pnpm',
1222
- args: ['--ignore-workspace', 'exec', 'expo', 'install', '--fix'],
1223
- display: 'pnpm --ignore-workspace exec expo install --fix',
1224
- env: { PNPM_CONFIG_STRICT_DEP_BUILDS: 'false' },
1293
+ command: "pnpm",
1294
+ args: ["--ignore-workspace", "exec", "expo", "install", "--fix"],
1295
+ display: "pnpm --ignore-workspace exec expo install --fix",
1296
+ env: { PNPM_CONFIG_STRICT_DEP_BUILDS: "false" },
1225
1297
  };
1226
- case 'yarn':
1298
+ case "yarn":
1227
1299
  return {
1228
- command: 'yarn',
1229
- args: ['expo', 'install', '--fix'],
1230
- display: 'yarn expo install --fix',
1300
+ command: "yarn",
1301
+ args: ["expo", "install", "--fix"],
1302
+ display: "yarn expo install --fix",
1231
1303
  };
1232
- case 'bun':
1304
+ case "bun":
1233
1305
  return {
1234
- command: 'bunx',
1235
- args: ['expo', 'install', '--fix'],
1236
- display: 'bunx expo install --fix',
1306
+ command: "bunx",
1307
+ args: ["expo", "install", "--fix"],
1308
+ display: "bunx expo install --fix",
1237
1309
  };
1238
- case 'npm':
1310
+ case "npm":
1239
1311
  return {
1240
- command: 'npx',
1241
- args: ['expo', 'install', '--fix'],
1242
- display: 'npx expo install --fix',
1312
+ command: "npx",
1313
+ args: ["expo", "install", "--fix"],
1314
+ display: "npx expo install --fix",
1243
1315
  };
1244
1316
  }
1245
1317
  }
1246
1318
  export function buildExpoLatestSdkCommand(packageManager) {
1247
1319
  switch (packageManager) {
1248
- case 'pnpm':
1320
+ case "pnpm":
1249
1321
  return {
1250
- command: 'pnpm',
1251
- args: ['--ignore-workspace', 'exec', 'expo', 'install', 'expo@latest'],
1252
- display: 'pnpm --ignore-workspace exec expo install expo@latest',
1253
- env: { PNPM_CONFIG_STRICT_DEP_BUILDS: 'false' },
1322
+ command: "pnpm",
1323
+ args: ["--ignore-workspace", "exec", "expo", "install", `expo@^${EXPECTED_EXPO_SDK_MAJOR}`],
1324
+ display: `pnpm --ignore-workspace exec expo install expo@^${EXPECTED_EXPO_SDK_MAJOR}`,
1325
+ env: { PNPM_CONFIG_STRICT_DEP_BUILDS: "false" },
1254
1326
  };
1255
- case 'yarn':
1327
+ case "yarn":
1256
1328
  return {
1257
- command: 'yarn',
1258
- args: ['expo', 'install', 'expo@latest'],
1259
- display: 'yarn expo install expo@latest',
1329
+ command: "yarn",
1330
+ args: ["expo", "install", `expo@^${EXPECTED_EXPO_SDK_MAJOR}`],
1331
+ display: `yarn expo install expo@^${EXPECTED_EXPO_SDK_MAJOR}`,
1260
1332
  };
1261
- case 'bun':
1333
+ case "bun":
1262
1334
  return {
1263
- command: 'bunx',
1264
- args: ['expo', 'install', 'expo@latest'],
1265
- display: 'bunx expo install expo@latest',
1335
+ command: "bunx",
1336
+ args: ["expo", "install", `expo@^${EXPECTED_EXPO_SDK_MAJOR}`],
1337
+ display: `bunx expo install expo@^${EXPECTED_EXPO_SDK_MAJOR}`,
1266
1338
  };
1267
- case 'npm':
1339
+ case "npm":
1268
1340
  return {
1269
- command: 'npx',
1270
- args: ['expo', 'install', 'expo@latest'],
1271
- display: 'npx expo install expo@latest',
1341
+ command: "npx",
1342
+ args: ["expo", "install", `expo@^${EXPECTED_EXPO_SDK_MAJOR}`],
1343
+ display: `npx expo install expo@^${EXPECTED_EXPO_SDK_MAJOR}`,
1272
1344
  };
1273
1345
  }
1274
1346
  }
1275
- export function buildExpoPinnedSdkCommand(packageManager) {
1276
- const target = `expo@${CURRENT_EXPO_SDK_VERSION}`;
1277
- switch (packageManager) {
1278
- case 'pnpm':
1279
- return {
1280
- command: 'pnpm',
1281
- args: ['--ignore-workspace', 'exec', 'expo', 'install', target],
1282
- display: `pnpm --ignore-workspace exec expo install ${target}`,
1283
- env: { PNPM_CONFIG_STRICT_DEP_BUILDS: 'false' },
1284
- };
1285
- case 'yarn':
1286
- return {
1287
- command: 'yarn',
1288
- args: ['expo', 'install', target],
1289
- display: `yarn expo install ${target}`,
1290
- };
1291
- case 'bun':
1292
- return {
1293
- command: 'bunx',
1294
- args: ['expo', 'install', target],
1295
- display: `bunx expo install ${target}`,
1296
- };
1297
- case 'npm':
1298
- return {
1299
- command: 'npx',
1300
- args: ['expo', 'install', target],
1301
- display: `npx expo install ${target}`,
1302
- };
1303
- }
1304
- }
1305
- export async function shouldRunExpoPinnedSdkCommand(projectPath) {
1306
- const packageJson = await readJson(path.join(projectPath, 'package.json'));
1307
- return shouldRunExpoPinnedSdkCommandFromPackageJson(packageJson);
1308
- }
1309
- export function shouldRunExpoPinnedSdkCommandFromPackageJson(packageJson) {
1310
- const dependencies = isRecord(packageJson.dependencies) ? packageJson.dependencies : {};
1311
- const devDependencies = isRecord(packageJson.devDependencies) ? packageJson.devDependencies : {};
1312
- const version = readString(dependencies.expo) ?? readString(devDependencies.expo);
1313
- const current = version ? readSemverTriplet(version) : undefined;
1314
- const target = readSemverTriplet(CURRENT_EXPO_SDK_VERSION);
1315
- if (!current || !target) {
1316
- return false;
1317
- }
1318
- if (current.major !== target.major || current.minor !== target.minor) {
1319
- return false;
1320
- }
1321
- return current.patch < target.patch;
1322
- }
1323
1347
  export async function shouldRunExpoLatestSdkCommand(projectPath) {
1324
- const packageJson = await readJson(path.join(projectPath, 'package.json'));
1348
+ const packageJson = await readJson(path.join(projectPath, "package.json"));
1325
1349
  return shouldRunExpoLatestSdkCommandFromPackageJson(packageJson);
1326
1350
  }
1327
1351
  export function shouldRunExpoLatestSdkCommandFromPackageJson(packageJson) {
1328
- const dependencies = isRecord(packageJson.dependencies) ? packageJson.dependencies : {};
1329
- const devDependencies = isRecord(packageJson.devDependencies) ? packageJson.devDependencies : {};
1352
+ const dependencies = isRecord(packageJson.dependencies)
1353
+ ? packageJson.dependencies
1354
+ : {};
1355
+ const devDependencies = isRecord(packageJson.devDependencies)
1356
+ ? packageJson.devDependencies
1357
+ : {};
1330
1358
  const version = readString(dependencies.expo) ?? readString(devDependencies.expo);
1331
- if (!version || version === 'latest') {
1332
- return !version;
1333
- }
1334
- const major = readSemverMajor(version);
1335
- return major === undefined || major < CURRENT_EXPO_SDK_MAJOR;
1336
- }
1337
- function readSemverMajor(version) {
1338
- const match = version.match(/\d+/u);
1339
- if (!match) {
1340
- return undefined;
1341
- }
1342
- return Number.parseInt(match[0], 10);
1359
+ return parseExpoSdkMajor(version) !== EXPECTED_EXPO_SDK_MAJOR;
1343
1360
  }
1344
- function readSemverTriplet(version) {
1345
- const match = version.match(/(\d+)\.(\d+)\.(\d+)/u);
1346
- if (!match) {
1347
- return undefined;
1361
+ export function parseExpoSdkMajor(version) {
1362
+ if (!version) {
1363
+ return null;
1348
1364
  }
1349
- const major = match[1];
1350
- const minor = match[2];
1351
- const patch = match[3];
1352
- if (!major || !minor || !patch) {
1353
- return undefined;
1365
+ const match = /(\d+)/u.exec(version);
1366
+ return match ? Number.parseInt(match[1] ?? "", 10) : null;
1367
+ }
1368
+ export async function assertExpectedExpoSdk(projectPath) {
1369
+ const packageJson = await readJson(path.join(projectPath, "package.json"));
1370
+ const dependencies = isRecord(packageJson.dependencies)
1371
+ ? packageJson.dependencies
1372
+ : {};
1373
+ const devDependencies = isRecord(packageJson.devDependencies)
1374
+ ? packageJson.devDependencies
1375
+ : {};
1376
+ const version = readString(dependencies.expo) ?? readString(devDependencies.expo);
1377
+ const resolvedMajor = parseExpoSdkMajor(version);
1378
+ if (resolvedMajor !== EXPECTED_EXPO_SDK_MAJOR) {
1379
+ throw new Error(`Generated project did not resolve to Expo SDK ${EXPECTED_EXPO_SDK_MAJOR}. Found expo dependency ${version ?? "missing"} in package.json.`);
1354
1380
  }
1355
- return {
1356
- major: Number.parseInt(major, 10),
1357
- minor: Number.parseInt(minor, 10),
1358
- patch: Number.parseInt(patch, 10),
1359
- };
1360
1381
  }
1361
1382
  export function buildExpoFontInstallCommand(packageManager) {
1362
1383
  switch (packageManager) {
1363
- case 'pnpm':
1384
+ case "pnpm":
1364
1385
  return {
1365
- command: 'pnpm',
1366
- args: ['--ignore-workspace', 'exec', 'expo', 'install', 'expo-font'],
1367
- display: 'pnpm --ignore-workspace exec expo install expo-font',
1368
- env: { PNPM_CONFIG_STRICT_DEP_BUILDS: 'false' },
1386
+ command: "pnpm",
1387
+ args: ["--ignore-workspace", "exec", "expo", "install", "expo-font"],
1388
+ display: "pnpm --ignore-workspace exec expo install expo-font",
1389
+ env: { PNPM_CONFIG_STRICT_DEP_BUILDS: "false" },
1369
1390
  };
1370
- case 'yarn':
1391
+ case "yarn":
1371
1392
  return {
1372
- command: 'yarn',
1373
- args: ['expo', 'install', 'expo-font'],
1374
- display: 'yarn expo install expo-font',
1393
+ command: "yarn",
1394
+ args: ["expo", "install", "expo-font"],
1395
+ display: "yarn expo install expo-font",
1375
1396
  };
1376
- case 'bun':
1397
+ case "bun":
1377
1398
  return {
1378
- command: 'bunx',
1379
- args: ['expo', 'install', 'expo-font'],
1380
- display: 'bunx expo install expo-font',
1399
+ command: "bunx",
1400
+ args: ["expo", "install", "expo-font"],
1401
+ display: "bunx expo install expo-font",
1381
1402
  };
1382
- case 'npm':
1403
+ case "npm":
1383
1404
  return {
1384
- command: 'npx',
1385
- args: ['expo', 'install', 'expo-font'],
1386
- display: 'npx expo install expo-font',
1405
+ command: "npx",
1406
+ args: ["expo", "install", "expo-font"],
1407
+ display: "npx expo install expo-font",
1387
1408
  };
1388
1409
  }
1389
1410
  }
1390
1411
  export function buildExpoDoctorCommand(packageManager) {
1391
1412
  switch (packageManager) {
1392
- case 'pnpm':
1413
+ case "pnpm":
1393
1414
  return {
1394
- command: 'pnpm',
1395
- args: ['--ignore-workspace', 'dlx', 'expo-doctor'],
1396
- display: 'pnpm --ignore-workspace dlx expo-doctor',
1415
+ command: "pnpm",
1416
+ args: ["--ignore-workspace", "dlx", "expo-doctor"],
1417
+ display: "pnpm --ignore-workspace dlx expo-doctor",
1397
1418
  };
1398
- case 'yarn':
1419
+ case "yarn":
1399
1420
  return {
1400
- command: 'yarn',
1401
- args: ['dlx', 'expo-doctor'],
1402
- display: 'yarn dlx expo-doctor',
1421
+ command: "yarn",
1422
+ args: ["dlx", "expo-doctor"],
1423
+ display: "yarn dlx expo-doctor",
1403
1424
  };
1404
- case 'bun':
1425
+ case "bun":
1405
1426
  return {
1406
- command: 'bunx',
1407
- args: ['expo-doctor'],
1408
- display: 'bunx expo-doctor',
1427
+ command: "bunx",
1428
+ args: ["expo-doctor"],
1429
+ display: "bunx expo-doctor",
1409
1430
  };
1410
- case 'npm':
1431
+ case "npm":
1411
1432
  return {
1412
- command: 'npx',
1413
- args: ['expo-doctor'],
1414
- display: 'npx expo-doctor',
1433
+ command: "npx",
1434
+ args: ["expo-doctor"],
1435
+ display: "npx expo-doctor",
1415
1436
  };
1416
1437
  }
1417
1438
  }
1418
1439
  export function buildPrettierWriteCommand(packageManager) {
1419
- const glob = '**/*.{js,jsx,ts,tsx,json}';
1440
+ const glob = "**/*.{js,jsx,ts,tsx,json}";
1420
1441
  switch (packageManager) {
1421
- case 'pnpm':
1442
+ case "pnpm":
1422
1443
  return {
1423
- command: 'pnpm',
1424
- args: ['exec', 'prettier', '--write', glob],
1444
+ command: "pnpm",
1445
+ args: ["exec", "prettier", "--write", glob],
1425
1446
  display: `pnpm exec prettier --write "${glob}"`,
1426
1447
  };
1427
- case 'yarn':
1448
+ case "yarn":
1428
1449
  return {
1429
- command: 'yarn',
1430
- args: ['prettier', '--write', glob],
1450
+ command: "yarn",
1451
+ args: ["prettier", "--write", glob],
1431
1452
  display: `yarn prettier --write "${glob}"`,
1432
1453
  };
1433
- case 'bun':
1454
+ case "bun":
1434
1455
  return {
1435
- command: 'bunx',
1436
- args: ['prettier', '--write', glob],
1456
+ command: "bunx",
1457
+ args: ["prettier", "--write", glob],
1437
1458
  display: `bunx prettier --write "${glob}"`,
1438
1459
  };
1439
- case 'npm':
1460
+ case "npm":
1440
1461
  default:
1441
1462
  return {
1442
- command: 'npx',
1443
- args: ['prettier', '--write', glob],
1463
+ command: "npx",
1464
+ args: ["prettier", "--write", glob],
1444
1465
  display: `npx prettier --write "${glob}"`,
1445
1466
  };
1446
1467
  }
1447
1468
  }
1448
1469
  export function buildAddDevDependencyCommand(packageManager, dependency) {
1449
1470
  switch (packageManager) {
1450
- case 'pnpm':
1471
+ case "pnpm":
1451
1472
  return {
1452
- command: 'pnpm',
1453
- args: ['--ignore-workspace', 'add', '-D', dependency],
1473
+ command: "pnpm",
1474
+ args: ["--ignore-workspace", "add", "-D", dependency],
1454
1475
  display: `pnpm --ignore-workspace add -D ${dependency}`,
1455
- env: { PNPM_CONFIG_STRICT_DEP_BUILDS: 'false' },
1476
+ env: { PNPM_CONFIG_STRICT_DEP_BUILDS: "false" },
1456
1477
  };
1457
- case 'yarn':
1478
+ case "yarn":
1458
1479
  return {
1459
- command: 'yarn',
1460
- args: ['add', '--dev', dependency],
1480
+ command: "yarn",
1481
+ args: ["add", "--dev", dependency],
1461
1482
  display: `yarn add --dev ${dependency}`,
1462
1483
  };
1463
- case 'bun':
1484
+ case "bun":
1464
1485
  return {
1465
- command: 'bun',
1466
- args: ['add', '--dev', dependency],
1486
+ command: "bun",
1487
+ args: ["add", "--dev", dependency],
1467
1488
  display: `bun add --dev ${dependency}`,
1468
1489
  };
1469
- case 'npm':
1490
+ case "npm":
1470
1491
  return {
1471
- command: 'npm',
1472
- args: ['install', '--save-dev', dependency],
1492
+ command: "npm",
1493
+ args: ["install", "--save-dev", dependency],
1473
1494
  display: `npm install --save-dev ${dependency}`,
1474
1495
  };
1475
1496
  }
1476
1497
  }
1477
1498
  export function resolveWindowsTailwindOxidePackage({ platform = process.platform, arch = process.arch, nodeTargetType = process.config?.variables?.node_target_type, shlibSuffix = process.config?.variables?.shlib_suffix, } = {}) {
1478
- if (platform !== 'win32') {
1499
+ if (platform !== "win32") {
1479
1500
  return undefined;
1480
1501
  }
1481
- if (arch === 'x64') {
1482
- const usesGnu = shlibSuffix === 'dll.a' || nodeTargetType === 'shared_library';
1483
- return usesGnu ? '@tailwindcss/oxide-win32-x64-gnu' : '@tailwindcss/oxide-win32-x64-msvc';
1502
+ if (arch === "x64") {
1503
+ const usesGnu = shlibSuffix === "dll.a" || nodeTargetType === "shared_library";
1504
+ return usesGnu
1505
+ ? "@tailwindcss/oxide-win32-x64-gnu"
1506
+ : "@tailwindcss/oxide-win32-x64-msvc";
1484
1507
  }
1485
- if (arch === 'ia32') {
1486
- return '@tailwindcss/oxide-win32-ia32-msvc';
1508
+ if (arch === "ia32") {
1509
+ return "@tailwindcss/oxide-win32-ia32-msvc";
1487
1510
  }
1488
- if (arch === 'arm64') {
1489
- return '@tailwindcss/oxide-win32-arm64-msvc';
1511
+ if (arch === "arm64") {
1512
+ return "@tailwindcss/oxide-win32-arm64-msvc";
1490
1513
  }
1491
1514
  return undefined;
1492
1515
  }
@@ -1495,46 +1518,55 @@ export async function resolveMissingWindowsTailwindOxideBinding(projectPath) {
1495
1518
  if (!packageName) {
1496
1519
  return undefined;
1497
1520
  }
1498
- const packageJson = await readJson(path.join(projectPath, 'package.json'));
1499
- const dependencies = isRecord(packageJson.dependencies) ? packageJson.dependencies : {};
1500
- const devDependencies = isRecord(packageJson.devDependencies) ? packageJson.devDependencies : {};
1501
- const hasUniwind = typeof dependencies.uniwind === 'string' || typeof devDependencies.uniwind === 'string';
1521
+ const packageJson = await readJson(path.join(projectPath, "package.json"));
1522
+ const dependencies = isRecord(packageJson.dependencies)
1523
+ ? packageJson.dependencies
1524
+ : {};
1525
+ const devDependencies = isRecord(packageJson.devDependencies)
1526
+ ? packageJson.devDependencies
1527
+ : {};
1528
+ const hasUniwind = typeof dependencies.uniwind === "string" ||
1529
+ typeof devDependencies.uniwind === "string";
1502
1530
  if (!hasUniwind) {
1503
1531
  return undefined;
1504
1532
  }
1505
- if (await pathExists(path.join(projectPath, 'node_modules', packageName))) {
1533
+ if (await pathExists(path.join(projectPath, "node_modules", packageName))) {
1506
1534
  return undefined;
1507
1535
  }
1508
- const oxidePackageJsonPath = path.join(projectPath, 'node_modules', '@tailwindcss', 'oxide', 'package.json');
1536
+ const oxidePackageJsonPath = path.join(projectPath, "node_modules", "@tailwindcss", "oxide", "package.json");
1509
1537
  const oxidePackage = await readJson(oxidePackageJsonPath);
1510
1538
  const oxideVersion = readString(oxidePackage.version);
1511
1539
  return oxideVersion ? `${packageName}@${oxideVersion}` : packageName;
1512
1540
  }
1513
1541
  export async function shouldInstallExpoFontPeer(projectPath) {
1514
- const packageJson = await readJson(path.join(projectPath, 'package.json'));
1542
+ const packageJson = await readJson(path.join(projectPath, "package.json"));
1515
1543
  return shouldInstallExpoFontPeerFromPackageJson(packageJson);
1516
1544
  }
1517
1545
  export function shouldInstallExpoFontPeerFromPackageJson(packageJson) {
1518
- const dependencies = isRecord(packageJson.dependencies) ? packageJson.dependencies : {};
1519
- const devDependencies = isRecord(packageJson.devDependencies) ? packageJson.devDependencies : {};
1520
- const hasVectorIcons = typeof dependencies['@expo/vector-icons'] === 'string' ||
1521
- typeof devDependencies['@expo/vector-icons'] === 'string';
1522
- const hasExpoFont = typeof dependencies['expo-font'] === 'string' ||
1523
- typeof devDependencies['expo-font'] === 'string';
1546
+ const dependencies = isRecord(packageJson.dependencies)
1547
+ ? packageJson.dependencies
1548
+ : {};
1549
+ const devDependencies = isRecord(packageJson.devDependencies)
1550
+ ? packageJson.devDependencies
1551
+ : {};
1552
+ const hasVectorIcons = typeof dependencies["@expo/vector-icons"] === "string" ||
1553
+ typeof devDependencies["@expo/vector-icons"] === "string";
1554
+ const hasExpoFont = typeof dependencies["expo-font"] === "string" ||
1555
+ typeof devDependencies["expo-font"] === "string";
1524
1556
  return hasVectorIcons && !hasExpoFont;
1525
1557
  }
1526
1558
  export async function detectEasSetup(projectPath, createExpoStackArgs) {
1527
- if (hasFlag(createExpoStackArgs, '--eas')) {
1559
+ if (hasFlag(createExpoStackArgs, "--eas")) {
1528
1560
  return true;
1529
1561
  }
1530
- if (await pathExists(path.join(projectPath, 'eas.json'))) {
1562
+ if (await pathExists(path.join(projectPath, "eas.json"))) {
1531
1563
  return true;
1532
1564
  }
1533
- const appJson = await readJson(path.join(projectPath, 'app.json'));
1565
+ const appJson = await readJson(path.join(projectPath, "app.json"));
1534
1566
  if (hasExpoEasProjectId(appJson)) {
1535
1567
  return true;
1536
1568
  }
1537
- const appConfigJson = await readJson(path.join(projectPath, 'app.config.json'));
1569
+ const appConfigJson = await readJson(path.join(projectPath, "app.config.json"));
1538
1570
  if (hasExpoEasProjectId(appConfigJson)) {
1539
1571
  return true;
1540
1572
  }
@@ -1547,22 +1579,22 @@ function hasExpoEasProjectId(config) {
1547
1579
  const expo = isRecord(config.expo) ? config.expo : config;
1548
1580
  const extra = isRecord(expo.extra) ? expo.extra : undefined;
1549
1581
  const eas = extra && isRecord(extra.eas) ? extra.eas : undefined;
1550
- return typeof eas?.projectId === 'string' && eas.projectId.trim().length > 0;
1582
+ return typeof eas?.projectId === "string" && eas.projectId.trim().length > 0;
1551
1583
  }
1552
1584
  function buildRunScriptCommand(packageManager, script) {
1553
1585
  switch (packageManager) {
1554
- case 'pnpm':
1586
+ case "pnpm":
1555
1587
  return `pnpm ${script}`;
1556
- case 'yarn':
1588
+ case "yarn":
1557
1589
  return `yarn ${script}`;
1558
- case 'bun':
1590
+ case "bun":
1559
1591
  return `bun run ${script}`;
1560
- case 'npm':
1592
+ case "npm":
1561
1593
  return `npm run ${script}`;
1562
1594
  }
1563
1595
  }
1564
1596
  function formatDisplayArgs(args) {
1565
- return args.map(quoteDisplayArg).join(' ');
1597
+ return args.map(quoteDisplayArg).join(" ");
1566
1598
  }
1567
1599
  function quoteDisplayArg(value) {
1568
1600
  if (!value || /[\s"]/u.test(value)) {
@@ -1570,7 +1602,29 @@ function quoteDisplayArg(value) {
1570
1602
  }
1571
1603
  return value;
1572
1604
  }
1605
+ function printCopyableCommands(title, commands) {
1606
+ console.log(`${title} (copy this):`);
1607
+ console.log(renderCommandBox(commands));
1608
+ }
1609
+ function renderCommandBox(commands) {
1610
+ const visibleCommands = commands.filter((command) => command.trim().length > 0);
1611
+ if (visibleCommands.length === 0) {
1612
+ return "";
1613
+ }
1614
+ const formattedCommands = visibleCommands.map((command) => colorizeCommand(`> ${command}`));
1615
+ return ["┌", ...formattedCommands.map((command) => `│ ${command}`), "└"].join("\n");
1616
+ }
1617
+ function colorizeCommand(command) {
1618
+ if (!supportsAnsiColor()) {
1619
+ return command;
1620
+ }
1621
+ return `\x1b[36m${command}\x1b[0m`;
1622
+ }
1623
+ function supportsAnsiColor() {
1624
+ return process.stdout.isTTY === true && process.env.TERM !== "dumb";
1625
+ }
1573
1626
  function buildOnboardArgv(projectPath, parsed, easSelected) {
1627
+ const generatorChoices = inferGeneratorChoices(parsed.createExpoStackArgs, easSelected);
1574
1628
  return {
1575
1629
  project: projectPath,
1576
1630
  yes: parsed.mds.yes,
@@ -1580,6 +1634,14 @@ function buildOnboardArgv(projectPath, parsed, easSelected) {
1580
1634
  guidelinesTemplatePath: parsed.mds.guidelinesTemplatePath,
1581
1635
  easSelected,
1582
1636
  appName: parsed.mds.appName,
1637
+ generatorScriptLanguage: generatorChoices.scriptLanguage,
1638
+ generatorPackageManager: generatorChoices.packageManager,
1639
+ generatorNavigationLibrary: generatorChoices.navigationLibrary,
1640
+ generatorReactNavigationLayout: generatorChoices.reactNavigationLayout,
1641
+ generatorStylingSystem: generatorChoices.stylingSystem,
1642
+ generatorStateManagement: generatorChoices.stateManagement,
1643
+ generatorAuthBackend: generatorChoices.authBackend,
1644
+ generatorEasSetup: generatorChoices.easSetup,
1583
1645
  audience: parsed.mds.audience,
1584
1646
  coreFlows: parsed.mds.coreFlows,
1585
1647
  screens: parsed.mds.screens,
@@ -1592,21 +1654,55 @@ function buildOnboardArgv(projectPath, parsed, easSelected) {
1592
1654
  platforms: parsed.mds.platforms,
1593
1655
  firstPlatform: parsed.mds.firstPlatform,
1594
1656
  platformStrategy: parsed.mds.platformStrategy,
1595
- appDirectory: parsed.mds.appDirectory ?? 'src',
1657
+ appDirectory: parsed.mds.appDirectory ?? "src",
1596
1658
  platformLayouts: parsed.mds.platformLayouts,
1597
1659
  webOutput: parsed.mds.webOutput,
1598
1660
  deployedServer: parsed.mds.deployedServer,
1599
1661
  createExpoComponents: parsed.mds.createExpoComponents,
1600
- latestExpoSdk: parsed.mds.latestExpoSdk,
1601
1662
  expoUi: parsed.mds.expoUi,
1602
1663
  expoUiUniversal: parsed.mds.expoUiUniversal,
1603
1664
  expoNativeTabs: parsed.mds.expoNativeTabs,
1604
1665
  easUses: parsed.mds.easUses,
1605
1666
  };
1606
1667
  }
1668
+ function inferGeneratorChoices(args, easSelected) {
1669
+ const hasFlag = (flag) => args.includes(flag);
1670
+ return {
1671
+ scriptLanguage: hasFlag("--javascript") ? "javascript" : "typescript",
1672
+ packageManager: hasFlag("--pnpm")
1673
+ ? "pnpm"
1674
+ : hasFlag("--yarn")
1675
+ ? "yarn"
1676
+ : hasFlag("--bun")
1677
+ ? "bun"
1678
+ : "npm",
1679
+ navigationLibrary: hasFlag("--react-navigation") ? "react-navigation" : "expo-router",
1680
+ reactNavigationLayout: hasFlag("--tabs")
1681
+ ? "tabs"
1682
+ : hasFlag("--drawer+tabs")
1683
+ ? "drawer"
1684
+ : "stack",
1685
+ stylingSystem: hasFlag("--nativewind")
1686
+ ? "nativewind"
1687
+ : hasFlag("--tamagui")
1688
+ ? "tamagui"
1689
+ : hasFlag("--restyle")
1690
+ ? "restyle"
1691
+ : hasFlag("--uniwind")
1692
+ ? "uniwind"
1693
+ : "stylesheet",
1694
+ stateManagement: hasFlag("--zustand") ? "zustand" : "none",
1695
+ authBackend: hasFlag("--supabase")
1696
+ ? "supabase"
1697
+ : hasFlag("--firebase")
1698
+ ? "firebase"
1699
+ : "none",
1700
+ easSetup: easSelected,
1701
+ };
1702
+ }
1607
1703
  function splitList(value) {
1608
1704
  return value
1609
- .split(',')
1705
+ .split(",")
1610
1706
  .map((item) => item.trim())
1611
1707
  .filter(Boolean);
1612
1708
  }
@@ -1621,7 +1717,7 @@ async function pathExists(filePath) {
1621
1717
  }
1622
1718
  async function readOptionalText(filePath) {
1623
1719
  try {
1624
- return await readFile(filePath, 'utf8');
1720
+ return await readFile(filePath, "utf8");
1625
1721
  }
1626
1722
  catch {
1627
1723
  return null;
@@ -1641,32 +1737,32 @@ async function readJson(filePath) {
1641
1737
  }
1642
1738
  }
1643
1739
  function parseJsonc(value) {
1644
- const cleaned = value.replace(/^\s*\/\/.*$/gm, '');
1740
+ const cleaned = value.replace(/^\s*\/\/.*$/gm, "");
1645
1741
  const parsed = JSON.parse(cleaned);
1646
1742
  return isRecord(parsed) ? parsed : {};
1647
1743
  }
1648
1744
  function readString(value) {
1649
- return typeof value === 'string' ? value : undefined;
1745
+ return typeof value === "string" ? value : undefined;
1650
1746
  }
1651
1747
  export function toExpoSlug(value) {
1652
1748
  const slug = value
1653
1749
  .trim()
1654
1750
  .toLowerCase()
1655
- .replace(/[^a-z0-9_-]+/g, '-')
1656
- .replace(/^-+|-+$/g, '');
1657
- return slug || 'expo-app';
1751
+ .replace(/[^a-z0-9_-]+/g, "-")
1752
+ .replace(/^-+|-+$/g, "");
1753
+ return slug || "expo-app";
1658
1754
  }
1659
1755
  export function toExpoScheme(value) {
1660
1756
  const scheme = value
1661
1757
  .trim()
1662
1758
  .toLowerCase()
1663
- .replace(/[^a-z0-9+.-]+/g, '-')
1664
- .replace(/^[^a-z]+/, '')
1665
- .replace(/-+$/g, '');
1666
- return scheme || 'expo-app';
1759
+ .replace(/[^a-z0-9+.-]+/g, "-")
1760
+ .replace(/^[^a-z]+/, "")
1761
+ .replace(/-+$/g, "");
1762
+ return scheme || "expo-app";
1667
1763
  }
1668
1764
  function isRecord(value) {
1669
- return typeof value === 'object' && value !== null && !Array.isArray(value);
1765
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1670
1766
  }
1671
1767
  export function isCliEntryPoint(argv = process.argv) {
1672
1768
  const entry = argv[1];