-
-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathcreateApp.ts
More file actions
165 lines (144 loc) · 4.37 KB
/
createApp.ts
File metadata and controls
165 lines (144 loc) · 4.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import { confirm } from '@inquirer/prompts';
import chalk from 'chalk';
import fs from 'fs-extra';
import _ from 'lodash';
import ora from 'ora';
import path from 'path';
import { PACKAGE_ROOT, globals } from '../constants';
import commit from '../util/commit';
import copyTemplateDirectory from '../util/copyTemplateDirectory';
import exec from '../util/exec';
import { lockFileNames } from '../util/getPackageManager';
import getUserPackageManager from '../util/getUserPackageManager';
import print from '../util/print';
import validateAndSanitizeAppName from '../util/validateAndSanitizeAppName';
type PackageManagerOptions = {
bun?: boolean;
npm?: boolean;
yarn?: boolean;
pnpm?: boolean;
};
type Options = {
interactive?: boolean;
} & PackageManagerOptions;
export async function createApp(
name: string | undefined,
options: Options = {},
) {
const { interactive = true } = options;
globals.interactive = interactive;
const appName = await validateAndSanitizeAppName(name);
await ensureDirectoryDoesNotExist(appName);
await printIntro(appName);
const spinner = ora('Creating app with Belt').start();
try {
await exec(`mkdir ${appName}`);
await copyTemplateDirectory({
templateDir: 'boilerplate',
destinationDir: appName,
gitignore: await boilerplateIgnoreFiles(),
stringSubstitutions: {
'app.json': {
BELT_APP_NAME: appName,
},
'package.json': {
belt_app_name: _.kebabCase(appName),
},
},
});
spinner.succeed('Created new Belt app with Expo');
process.chdir(`./${appName}`);
const packageManager = await getPackageManager(options);
spinner.start('Installing dependencies');
await exec(`${packageManager} install`);
await exec('git init');
await commit('Initial commit');
spinner.succeed('Installed dependencies');
print(chalk.green(`\n\n👖 ${appName} successfully configured!`));
print(`
Your pants are now secure! To get started with your new app:
cd ${appName}
${packageManager} run ios
${packageManager} run android
For more information about Belt, visit https://github.com/thoughtbot/belt.
`);
} catch (e) {
spinner.fail('An error occurred creating the app\n');
if (e instanceof Error) {
print(chalk.red(e.message));
}
process.exit(1);
}
}
/**
* Commander requires this signature to be ...args: unknown[]
* Actual args are:
* ([<appName>, <Options hash>, <Command>])
* or ([<Options hash>, <Command>]) if <appName> not passed)
*/
export default function createAppAction(...args: unknown[]) {
// if argument ommitted, args[0] is options
const appNameArg = (args[0] as string[])[0];
const options = (args[0] as unknown[])[1] as Options;
return createApp(appNameArg, options);
}
async function printIntro(appName: string) {
print('Let’s get started!');
print(`\nWe will now create a new app in ./${chalk.bold(
appName,
)} for you with all of the following goodies:
- Expo
- TypeScript
- Prettier
- ESLint
- Jest, React Native Testing Library
- React Navigation
- TanStack Query (formerly known as React Query)
`);
if (!globals.interactive) {
return;
}
const proceed = await confirm({ message: 'Ready to proceed?' });
if (!proceed) {
process.exit(0);
}
print(''); // add new line
}
function getPackageManager(options: Options) {
return options.bun
? 'bun'
: options.yarn
? 'yarn'
: options.pnpm
? 'pnpm'
: options.npm
? 'npm'
: getUserPackageManager();
}
async function ensureDirectoryDoesNotExist(appName: string) {
if (await fs.exists(appName)) {
print(
chalk.yellow(
`Whoopsy. The directory ${process.cwd()}/${appName} already exists. Please choose a different name or delete the existing directory.\n`,
),
);
process.exit(0);
}
}
/**
* Don't copy any files over that are in the boilerplate gitignore.
* Additionally, don't copy any package manager lockfiles over. This is
* primarily helpful for development in the case that the developer has run the
* app directly from the `boilerplate` directory and might have a node_modules
* directory and lockfile
*/
async function boilerplateIgnoreFiles() {
const gitignorePath = path.join(
PACKAGE_ROOT,
'templates/boilerplate/.gitignore.eta',
);
return `
${(await fs.readFile(gitignorePath, 'utf8')).toString()}
${lockFileNames.join('\n')}
`;
}