Skip to content

Commit 552c047

Browse files
committed
Added Zustand and state management option
1 parent 2404f6a commit 552c047

File tree

9 files changed

+635
-373
lines changed

9 files changed

+635
-373
lines changed

.changeset/twenty-chefs-burn.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'create-expo-stack': minor
3+
---
4+
5+
Added a StateManagement Question with zustand to start and potentially more to follow
6+
7+
- setup with a question prompt just before the internationalization prompt
8+
- --zustand flag to skip the prompt
9+
- Adds a StateManagement folder to the project with a zustandStore.ts file to start with

cli/src/commands/create-expo-stack.ts

Lines changed: 114 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,20 @@ import {
1111
runIgnite,
1212
showHelp
1313
} from '../utilities';
14-
import { DEFAULT_APP_NAME, defaultOptions } from '../constants';
14+
import {
15+
bunInstallationError,
16+
DEFAULT_APP_NAME,
17+
defaultOptions,
18+
nativeWindUIOptions,
19+
navigationValidationError,
20+
projectNameValidationError
21+
} from '../constants';
1522
import { CliResults, availablePackages } from '../types';
1623
import clearStylingPackages from '../utilities/clearStylingPackages';
1724
import { validateProjectName } from '../utilities/validateProjectName';
1825
import { cancel, intro, isCancel, text } from '@clack/prompts';
1926
import clearNavigationPackages from '../utilities/clearNavigationPackages';
2027

21-
const navigationValidationError = `You must pass in either --react-navigation or --expo-router if you want to use the --tabs or --drawer+tabs options`;
22-
const projectNameValidationError = `A project with the name`;
23-
const bunInstallationError = 'User cancelled to install recommended version of Bun.';
24-
2528
const command: GluegunCommand = {
2629
name: 'create-expo-stack',
2730
description: 'Create a new Expo project',
@@ -36,7 +39,7 @@ const command: GluegunCommand = {
3639
const printSomethingWentWrong = () => {
3740
info(`\nOops, something went wrong while creating your project 😢`);
3841
info(
39-
`\nIf this was unexpected, please open an issue: https://github.com/danstepanov/create-expo-stack#reporting-bugs--feedback`
42+
`\nIf this was unexpected, please open an issue: https://github.com/roninoss/create-expo-stack#reporting-bugs--feedback`
4043
);
4144
info('');
4245
};
@@ -69,7 +72,9 @@ const command: GluegunCommand = {
6972
!options.reactnavigation &&
7073
!options.expoRouter &&
7174
!options['expo-router'] &&
72-
!options.exporouter
75+
!options.exporouter &&
76+
// nativewindui applies the expo router option by default
77+
!options.nativewindui
7378
) {
7479
// throw an error without a stack trace
7580
throw navigationValidationError;
@@ -107,6 +112,10 @@ const command: GluegunCommand = {
107112
cliResults.flags.overwrite = true;
108113
}
109114

115+
if (options.eas) {
116+
cliResults.flags.eas = true;
117+
}
118+
110119
await validateProjectName(
111120
exists,
112121
removeAsync,
@@ -115,6 +124,10 @@ const command: GluegunCommand = {
115124
cliResults.flags.overwrite
116125
);
117126
} catch (err: string | any) {
127+
if (process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'development') {
128+
error(`error: ${err}`);
129+
}
130+
118131
if (err === '') {
119132
// user cancelled/exited the interactive CLI
120133
return void success(`\nCancelled... 👋 \n`);
@@ -127,8 +140,11 @@ const command: GluegunCommand = {
127140
return void success(`\nCancelled... 👋 \n`);
128141
}
129142

130-
// Delete all files with projectName
131-
await removeAsync(cliResults.projectName);
143+
// we keep this around so we can check what went wrong
144+
if (process.env.NODE_ENV !== 'test') {
145+
// Delete all files with projectName
146+
await removeAsync(cliResults.projectName);
147+
}
132148

133149
printSomethingWentWrong();
134150
throw err;
@@ -196,38 +212,30 @@ const command: GluegunCommand = {
196212
} else if (options.nativewindui) {
197213
cliResults = clearStylingPackages(cliResults);
198214
cliResults = clearNavigationPackages(cliResults);
215+
216+
const parsedComponents =
217+
options?.selectedComponents
218+
?.split(',')
219+
?.map((c: string) => c.trim())
220+
?.filter((item) => nativeWindUIOptions.includes(item)) ?? [];
221+
222+
const selectedComponents = parsedComponents.length
223+
? Array.from(new Set([...parsedComponents, 'text']))
224+
: nativeWindUIOptions;
225+
199226
cliResults.packages.push({
200227
name: 'nativewindui',
201228
type: 'styling',
202229
options: {
203-
selectedComponents: options.blank
204-
? []
205-
: [
206-
'action-sheet',
207-
'activity-indicator',
208-
'activity-view',
209-
'alert',
210-
'avatar',
211-
'bottom-sheet',
212-
'context-menu',
213-
'date-picker',
214-
'dropdown-menu',
215-
'picker',
216-
'progress-indicator',
217-
'ratings-indicator',
218-
'segmented-control',
219-
'selectable-text',
220-
'slider',
221-
'text',
222-
'toggle'
223-
]
230+
selectedComponents: options.blank ? ['text'] : selectedComponents
224231
}
225232
});
233+
226234
cliResults.packages.push({
227235
name: 'expo-router',
228236
type: 'navigation',
229237
options: {
230-
type: 'drawer + tabs'
238+
type: options.tabs ? 'tabs' : options['drawer+tabs'] ? 'drawer + tabs' : 'stack'
231239
}
232240
});
233241
} else if (options.tamagui) {
@@ -288,6 +296,15 @@ const command: GluegunCommand = {
288296
});
289297
}
290298

299+
// State Management packages
300+
if (options.zustand) {
301+
// Add zustand package
302+
cliResults.packages.push({
303+
name: 'zustand',
304+
type: 'state-management'
305+
});
306+
}
307+
291308
// Internalization packages
292309
if (options.i18next) {
293310
cliResults.packages.push({
@@ -296,6 +313,11 @@ const command: GluegunCommand = {
296313
});
297314
}
298315

316+
// Analytics packages
317+
if (options.vexoAnalytics || options['vexo-analytics'] || options.vexoanalytics) {
318+
cliResults.packages.push({ name: 'vexo-analytics', type: 'analytics' });
319+
}
320+
299321
// By this point, all cliResults should be set
300322
info('');
301323
highlight('Your project configuration:');
@@ -306,12 +328,47 @@ const command: GluegunCommand = {
306328

307329
// Function that outputs a string given the CLI results and the packageManager. The outputted string should be a command that can be run to recreate the project
308330
const generateRerunScript = (cliResults: CliResults) => {
309-
let script = `npx create-expo-stack ${cliResults.projectName} `;
331+
let script = `npx create-expo-stack@latest ${cliResults.projectName} `;
332+
333+
const isNativeWindUISelected = cliResults.packages.some((p) => p.name === 'nativewindui');
310334

311-
if (cliResults.packages.length > 0 && cliResults.packages[0].name === 'nativewindui') {
335+
if (isNativeWindUISelected) {
312336
script += '--nativewindui ';
313-
if (cliResults.packages[0].options.selectedComponents.length === 0) {
337+
338+
const nativeWindUIComponents =
339+
cliResults.packages.find((p) => p.name === 'nativewindui')?.options.selectedComponents ?? [];
340+
341+
// we do this to account for older stored config e.g that has selectable text in it
342+
const onlyValidComponents = nativeWindUIComponents.filter((c) => nativeWindUIOptions.includes(c));
343+
344+
if (onlyValidComponents.length === 0) {
314345
script += '--blank ';
346+
} else if (onlyValidComponents.length !== nativeWindUIOptions.length) {
347+
script += `--selected-components=${onlyValidComponents.join(',')} `;
348+
}
349+
350+
// this should always be expo router for nwui
351+
const chosenNavigationOption = cliResults.packages.find((p) => p.type === 'navigation');
352+
353+
const hasNavigationPackage = chosenNavigationOption !== undefined;
354+
355+
const navigationType = chosenNavigationOption.options.type;
356+
357+
if (hasNavigationPackage) {
358+
// NOTE we don't need to add expo-router since its currently getting automatically added with nativewindui
359+
// NOTE stack is default so doesn't need to applied.
360+
if (navigationType === 'tabs') {
361+
script += '--tabs ';
362+
} else if (navigationType === 'drawer + tabs') {
363+
script += '--drawer+tabs ';
364+
}
365+
}
366+
367+
const stateManagementPackage = cliResults.packages.find((p) => p.type === 'state-management');
368+
369+
if (stateManagementPackage) {
370+
// currently only redux is supported
371+
script += `--${stateManagementPackage.name} `;
315372
}
316373
} else {
317374
// Add the packages
@@ -347,6 +404,10 @@ const command: GluegunCommand = {
347404
script += `--${cliResults.flags.packageManager}`;
348405
}
349406

407+
if (cliResults.flags.eas) {
408+
script += ` --eas`;
409+
}
410+
350411
return script;
351412
};
352413

@@ -361,6 +422,10 @@ const command: GluegunCommand = {
361422
// if there is no styling package, add the stylesheet package
362423
const stylingPackage = packages.find((p) => p.type === 'styling');
363424
const internalizationPackage = packages.find((p) => p.type === 'internationalization');
425+
const analyticsPackage = packages.find((p) => p.type === 'analytics');
426+
427+
//add the state management package if it is selected
428+
const stateManagementPackage = packages.find((p) => p.type === 'state-management') || undefined;
364429

365430
let files: string[] = [];
366431

@@ -369,40 +434,50 @@ const command: GluegunCommand = {
369434
files,
370435
navigationPackage,
371436
stylingPackage,
437+
analyticsPackage,
372438
toolbox,
373439
cliResults,
374-
internalizationPackage
440+
internalizationPackage,
441+
stateManagementPackage
375442
);
376443

377444
// Once all the files are defined, format and generate them
378445
let formattedFiles: any[] = [];
379446

380447
formattedFiles = generateProjectFiles(
381448
authenticationPackage,
449+
analyticsPackage,
382450
cliResults,
383451
files,
384452
formattedFiles,
385453
navigationPackage,
386454
packageManager,
387455
stylingPackage,
388456
toolbox,
389-
internalizationPackage
457+
internalizationPackage,
458+
stateManagementPackage
390459
);
391460

392461
await printOutput(cliResults, formattedFiles, toolbox, stylingPackage);
393462
}
394463
} catch (err) {
464+
if (process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'development') {
465+
error(`error: ${err}`);
466+
}
467+
395468
if (err === '') {
396469
// user cancelled/exited the interactive CLI
397470
return void success(`\nCancelled... 👋 \n`);
398471
}
399472

400473
if (err.message.includes(bunInstallationError)) {
401-
return void success(`\nUser cancelled to install recommended version of Bun... 👋 \n`);
474+
return void success(`\nCancelled to install recommended version of Bun.... 👋 \n`);
402475
}
403476

404-
// Delete all files with projectName
405-
await removeAsync(cliResults.projectName);
477+
if (process.env.NODE_ENV !== 'test') {
478+
// Delete all files with projectName
479+
await removeAsync(cliResults.projectName);
480+
}
406481

407482
printSomethingWentWrong();
408483
throw err;

0 commit comments

Comments
 (0)