create-expo-module 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,12 +4,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const spawn_async_1 = __importDefault(require("@expo/spawn-async"));
7
+ const chalk_1 = __importDefault(require("chalk"));
7
8
  const commander_1 = require("commander");
8
9
  const download_tarball_1 = __importDefault(require("download-tarball"));
9
10
  const ejs_1 = __importDefault(require("ejs"));
10
11
  const fs_extra_1 = __importDefault(require("fs-extra"));
11
12
  const path_1 = __importDefault(require("path"));
12
13
  const prompts_1 = __importDefault(require("prompts"));
14
+ const validate_npm_package_name_1 = __importDefault(require("validate-npm-package-name"));
15
+ const createExampleApp_1 = require("./createExampleApp");
16
+ const packageManager_1 = require("./packageManager");
17
+ const resolvePackageManager_1 = require("./resolvePackageManager");
18
+ const utils_1 = require("./utils");
13
19
  const packageJson = require('../package.json');
14
20
  // `yarn run` may change the current working dir, then we should use `INIT_CWD` env.
15
21
  const CWD = process.env.INIT_CWD || process.cwd();
@@ -24,28 +30,29 @@ const IGNORES_PATHS = ['.DS_Store', 'build', 'node_modules', 'package.json'];
24
30
  */
25
31
  async function main(target, options) {
26
32
  const targetDir = target ? path_1.default.join(CWD, target) : CWD;
27
- options.target = targetDir;
28
33
  await fs_extra_1.default.ensureDir(targetDir);
34
+ await confirmTargetDirAsync(targetDir);
35
+ options.target = targetDir;
29
36
  const data = await askForSubstitutionDataAsync(targetDir, options);
30
- const packageManager = await selectPackageManagerAsync();
37
+ const packageManager = await (0, resolvePackageManager_1.resolvePackageManager)();
31
38
  const packagePath = options.source
32
39
  ? path_1.default.join(CWD, options.source)
33
40
  : await downloadPackageAsync(targetDir);
34
- const files = await getFilesAsync(packagePath);
35
- console.log('🎨 Creating Expo module from the template files...');
36
- // Iterate through all template files.
37
- for (const file of files) {
38
- const renderedRelativePath = ejs_1.default.render(file.replace(/^\$/, ''), data, {
39
- openDelimiter: '{',
40
- closeDelimiter: '}',
41
- escape: (value) => value.replace('.', path_1.default.sep),
41
+ await (0, utils_1.newStep)('Creating the module from template files', async (step) => {
42
+ await createModuleFromTemplate(packagePath, targetDir, data);
43
+ step.succeed('Created the module from template files');
44
+ });
45
+ await (0, utils_1.newStep)('Installing module dependencies', async (step) => {
46
+ await (0, packageManager_1.installDependencies)(packageManager, targetDir);
47
+ step.succeed('Installed module dependencies');
48
+ });
49
+ await (0, utils_1.newStep)('Compiling TypeScript files', async (step) => {
50
+ await (0, spawn_async_1.default)(packageManager, ['run', 'build'], {
51
+ cwd: targetDir,
52
+ stdio: 'ignore',
42
53
  });
43
- const fromPath = path_1.default.join(packagePath, file);
44
- const toPath = path_1.default.join(targetDir, renderedRelativePath);
45
- const template = await fs_extra_1.default.readFile(fromPath, { encoding: 'utf8' });
46
- const renderedContent = ejs_1.default.render(template, data);
47
- await fs_extra_1.default.outputFile(toPath, renderedContent, { encoding: 'utf8' });
48
- }
54
+ step.succeed('Compiled TypeScript files');
55
+ });
49
56
  if (!options.source) {
50
57
  // Files in the downloaded tarball are wrapped in `package` dir.
51
58
  // We should remove it after all.
@@ -57,8 +64,11 @@ async function main(target, options) {
57
64
  if (!options.withChangelog) {
58
65
  await fs_extra_1.default.remove(path_1.default.join(targetDir, 'CHANGELOG.md'));
59
66
  }
60
- // Install dependencies and build
61
- await postActionsAsync(packageManager, targetDir);
67
+ if (options.example) {
68
+ // Create "example" folder
69
+ await (0, createExampleApp_1.createExampleApp)(data, targetDir, packageManager);
70
+ }
71
+ console.log();
62
72
  console.log('✅ Successfully created Expo module');
63
73
  }
64
74
  /**
@@ -106,43 +116,34 @@ async function npmWhoamiAsync(targetDir) {
106
116
  * Downloads the template from NPM registry.
107
117
  */
108
118
  async function downloadPackageAsync(targetDir) {
109
- const tarballUrl = await getNpmTarballUrl('expo-module-template');
110
- console.log('⬇️ Downloading module template from npm...');
111
- await (0, download_tarball_1.default)({
112
- url: tarballUrl,
113
- dir: targetDir,
114
- });
115
- return path_1.default.join(targetDir, 'package');
116
- }
117
- /**
118
- * Asks whether to use Yarn or npm as a dependency package manager.
119
- */
120
- async function selectPackageManagerAsync() {
121
- const { packageManager } = await (0, prompts_1.default)({
122
- type: 'select',
123
- name: 'packageManager',
124
- message: 'Which package manager do you want to use to install dependencies?',
125
- choices: [
126
- { title: 'yarn', value: 'yarn' },
127
- { title: 'npm', value: 'npm' },
128
- ],
119
+ return await (0, utils_1.newStep)('Downloading module template from npm', async (step) => {
120
+ const tarballUrl = await getNpmTarballUrl('expo-module-template');
121
+ await (0, download_tarball_1.default)({
122
+ url: tarballUrl,
123
+ dir: targetDir,
124
+ });
125
+ step.succeed('Downloaded module template from npm');
126
+ return path_1.default.join(targetDir, 'package');
129
127
  });
130
- return packageManager;
131
128
  }
132
129
  /**
133
- * Installs dependencies and builds TypeScript files.
130
+ * Creates the module based on the `ejs` template (e.g. `expo-module-template` package).
134
131
  */
135
- async function postActionsAsync(packageManager, targetDir) {
136
- async function run(...args) {
137
- await (0, spawn_async_1.default)(packageManager, args, {
138
- cwd: targetDir,
139
- stdio: 'ignore',
132
+ async function createModuleFromTemplate(templatePath, targetPath, data) {
133
+ const files = await getFilesAsync(templatePath);
134
+ // Iterate through all template files.
135
+ for (const file of files) {
136
+ const renderedRelativePath = ejs_1.default.render(file.replace(/^\$/, ''), data, {
137
+ openDelimiter: '{',
138
+ closeDelimiter: '}',
139
+ escape: (value) => value.replace('.', path_1.default.sep),
140
140
  });
141
+ const fromPath = path_1.default.join(templatePath, file);
142
+ const toPath = path_1.default.join(targetPath, renderedRelativePath);
143
+ const template = await fs_extra_1.default.readFile(fromPath, { encoding: 'utf8' });
144
+ const renderedContent = ejs_1.default.render(template, data);
145
+ await fs_extra_1.default.outputFile(toPath, renderedContent, { encoding: 'utf8' });
141
146
  }
142
- console.log('📦 Installing dependencies...');
143
- await run('install');
144
- console.log('🛠 Compiling TypeScript files...');
145
- await run('run', 'build');
146
147
  }
147
148
  /**
148
149
  * Asks the user for some data necessary to render the template.
@@ -151,6 +152,7 @@ async function postActionsAsync(packageManager, targetDir) {
151
152
  async function askForSubstitutionDataAsync(targetDir, options) {
152
153
  var _a, _b;
153
154
  const defaultPackageSlug = path_1.default.basename(targetDir);
155
+ const useDefaultSlug = options.target && (0, validate_npm_package_name_1.default)(defaultPackageSlug);
154
156
  const defaultProjectName = defaultPackageSlug
155
157
  .replace(/^./, (match) => match.toUpperCase())
156
158
  .replace(/\W+(\w)/g, (_, p1) => p1.toUpperCase());
@@ -160,7 +162,8 @@ async function askForSubstitutionDataAsync(targetDir, options) {
160
162
  name: 'slug',
161
163
  message: 'What is the package slug?',
162
164
  initial: defaultPackageSlug,
163
- resolvedValue: options.target ? defaultPackageSlug : null,
165
+ resolvedValue: useDefaultSlug ? defaultPackageSlug : null,
166
+ validate: (input) => (0, validate_npm_package_name_1.default)(input).validForNewPackages || 'Must be a valid npm package name',
164
167
  },
165
168
  {
166
169
  type: 'text',
@@ -172,6 +175,7 @@ async function askForSubstitutionDataAsync(targetDir, options) {
172
175
  type: 'text',
173
176
  name: 'description',
174
177
  message: 'How would you describe the module?',
178
+ validate: (input) => !!input || 'Cannot be empty',
175
179
  },
176
180
  {
177
181
  type: 'text',
@@ -195,12 +199,17 @@ async function askForSubstitutionDataAsync(targetDir, options) {
195
199
  type: 'text',
196
200
  name: 'repo',
197
201
  message: 'What is the repository URL?',
202
+ validate: (input) => /^https?:\/\//.test(input) || 'Must be a valid URL',
198
203
  },
199
204
  ];
205
+ // Stop the process when the user cancels/exits the prompt.
206
+ const onCancel = () => {
207
+ process.exit(0);
208
+ };
200
209
  const answers = {};
201
210
  for (const query of promptQueries) {
202
211
  const { name, resolvedValue } = query;
203
- answers[name] = (_b = resolvedValue !== null && resolvedValue !== void 0 ? resolvedValue : options[name]) !== null && _b !== void 0 ? _b : (await (0, prompts_1.default)(query))[name];
212
+ answers[name] = (_b = resolvedValue !== null && resolvedValue !== void 0 ? resolvedValue : options[name]) !== null && _b !== void 0 ? _b : (await (0, prompts_1.default)(query, { onCancel }))[name];
204
213
  }
205
214
  const { slug, name, description, package: projectPackage, author, license, repo } = answers;
206
215
  return {
@@ -216,6 +225,26 @@ async function askForSubstitutionDataAsync(targetDir, options) {
216
225
  repo,
217
226
  };
218
227
  }
228
+ /**
229
+ * Checks whether the target directory is empty and if not, asks the user to confirm if he wants to continue.
230
+ */
231
+ async function confirmTargetDirAsync(targetDir) {
232
+ const files = await fs_extra_1.default.readdir(targetDir);
233
+ if (files.length === 0) {
234
+ return;
235
+ }
236
+ const { shouldContinue } = await (0, prompts_1.default)({
237
+ type: 'confirm',
238
+ name: 'shouldContinue',
239
+ message: `The target directory ${chalk_1.default.magenta(targetDir)} is not empty.\nDo you want to continue anyway?`,
240
+ initial: true,
241
+ }, {
242
+ onCancel: () => false,
243
+ });
244
+ if (!shouldContinue) {
245
+ process.exit(0);
246
+ }
247
+ }
219
248
  const program = new commander_1.Command();
220
249
  program
221
250
  .name(packageJson.name)
@@ -231,6 +260,7 @@ program
231
260
  .option('-r, --repo <repo_url>', 'The URL to the repository.')
232
261
  .option('--with-readme', 'Whether to include README.md file.', false)
233
262
  .option('--with-changelog', 'Whether to include CHANGELOG.md file.', false)
263
+ .option('--no-example', 'Whether to skip creating the example app.', false)
234
264
  .action(main);
235
265
  program.parse(process.argv);
236
266
  //# sourceMappingURL=create-expo-module.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-expo-module.js","sourceRoot":"","sources":["../src/create-expo-module.ts"],"names":[],"mappings":"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","sourcesContent":["import spawnAsync from '@expo/spawn-async';\nimport { Command } from 'commander';\nimport downloadTarball from 'download-tarball';\nimport ejs from 'ejs';\nimport fs from 'fs-extra';\nimport path from 'path';\nimport prompts, { PromptObject } from 'prompts';\n\nconst packageJson = require('../package.json');\n\n// `yarn run` may change the current working dir, then we should use `INIT_CWD` env.\nconst CWD = process.env.INIT_CWD || process.cwd();\n\n// Ignore some paths. Especially `package.json` as it is rendered\n// from `$package.json` file instead of the original one.\nconst IGNORES_PATHS = ['.DS_Store', 'build', 'node_modules', 'package.json'];\n\n/**\n * Possible command options.\n */\ntype CommandOptions = {\n target: string;\n source?: string;\n name?: string;\n description?: string;\n package?: string;\n author?: string;\n license?: string;\n repo?: string;\n withReadme: boolean;\n withChangelog: boolean;\n};\n\n/**\n * Represents an object that is passed to `ejs` when rendering the template.\n */\ntype SubstitutionData = {\n project: {\n slug: string;\n name: string;\n version: string;\n description: string;\n package: string;\n };\n author: string;\n license: string;\n repo: string;\n};\n\ntype CustomPromptObject = PromptObject & {\n name: string;\n resolvedValue?: string | null;\n};\n\ntype PackageManager = 'npm' | 'yarn';\n\n/**\n * The main function of the command.\n *\n * @param target Path to the directory where to create the module. Defaults to current working dir.\n * @param command An object from `commander`.\n */\nasync function main(target: string | undefined, options: CommandOptions) {\n const targetDir = target ? path.join(CWD, target) : CWD;\n\n options.target = targetDir;\n await fs.ensureDir(targetDir);\n\n const data = await askForSubstitutionDataAsync(targetDir, options);\n const packageManager = await selectPackageManagerAsync();\n const packagePath = options.source\n ? path.join(CWD, options.source)\n : await downloadPackageAsync(targetDir);\n const files = await getFilesAsync(packagePath);\n\n console.log('🎨 Creating Expo module from the template files...');\n\n // Iterate through all template files.\n for (const file of files) {\n const renderedRelativePath = ejs.render(file.replace(/^\\$/, ''), data, {\n openDelimiter: '{',\n closeDelimiter: '}',\n escape: (value: string) => value.replace('.', path.sep),\n });\n const fromPath = path.join(packagePath, file);\n const toPath = path.join(targetDir, renderedRelativePath);\n const template = await fs.readFile(fromPath, { encoding: 'utf8' });\n const renderedContent = ejs.render(template, data);\n\n await fs.outputFile(toPath, renderedContent, { encoding: 'utf8' });\n }\n\n if (!options.source) {\n // Files in the downloaded tarball are wrapped in `package` dir.\n // We should remove it after all.\n await fs.remove(packagePath);\n }\n if (!options.withReadme) {\n await fs.remove(path.join(targetDir, 'README.md'));\n }\n if (!options.withChangelog) {\n await fs.remove(path.join(targetDir, 'CHANGELOG.md'));\n }\n\n // Install dependencies and build\n await postActionsAsync(packageManager, targetDir);\n\n console.log('✅ Successfully created Expo module');\n}\n\n/**\n * Recursively scans for the files within the directory. Returned paths are relative to the `root` path.\n */\nasync function getFilesAsync(root: string, dir: string | null = null): Promise<string[]> {\n const files: string[] = [];\n const baseDir = dir ? path.join(root, dir) : root;\n\n for (const file of await fs.readdir(baseDir)) {\n const relativePath = dir ? path.join(dir, file) : file;\n\n if (IGNORES_PATHS.includes(relativePath) || IGNORES_PATHS.includes(file)) {\n continue;\n }\n\n const fullPath = path.join(baseDir, file);\n const stat = await fs.lstat(fullPath);\n\n if (stat.isDirectory()) {\n files.push(...(await getFilesAsync(root, relativePath)));\n } else {\n files.push(relativePath);\n }\n }\n return files;\n}\n\n/**\n * Asks NPM registry for the url to the tarball.\n */\nasync function getNpmTarballUrl(packageName: string, version: string = 'latest'): Promise<string> {\n const { stdout } = await spawnAsync('npm', ['view', `${packageName}@${version}`, 'dist.tarball']);\n return stdout.trim();\n}\n\n/**\n * Gets the username of currently logged in user. Used as a default in the prompt asking for the module author.\n */\nasync function npmWhoamiAsync(targetDir: string): Promise<string | null> {\n try {\n const { stdout } = await spawnAsync('npm', ['whoami'], { cwd: targetDir });\n return stdout.trim();\n } catch {\n return null;\n }\n}\n\n/**\n * Downloads the template from NPM registry.\n */\nasync function downloadPackageAsync(targetDir: string): Promise<string> {\n const tarballUrl = await getNpmTarballUrl('expo-module-template');\n\n console.log('⬇️ Downloading module template from npm...');\n\n await downloadTarball({\n url: tarballUrl,\n dir: targetDir,\n });\n return path.join(targetDir, 'package');\n}\n\n/**\n * Asks whether to use Yarn or npm as a dependency package manager.\n */\nasync function selectPackageManagerAsync(): Promise<PackageManager> {\n const { packageManager } = await prompts({\n type: 'select',\n name: 'packageManager',\n message: 'Which package manager do you want to use to install dependencies?',\n choices: [\n { title: 'yarn', value: 'yarn' },\n { title: 'npm', value: 'npm' },\n ],\n });\n return packageManager;\n}\n\n/**\n * Installs dependencies and builds TypeScript files.\n */\nasync function postActionsAsync(packageManager: PackageManager, targetDir: string) {\n async function run(...args: string[]) {\n await spawnAsync(packageManager, args, {\n cwd: targetDir,\n stdio: 'ignore',\n });\n }\n\n console.log('📦 Installing dependencies...');\n await run('install');\n\n console.log('🛠 Compiling TypeScript files...');\n await run('run', 'build');\n}\n\n/**\n * Asks the user for some data necessary to render the template.\n * Some values may already be provided by command options, the prompt is skipped in that case.\n */\nasync function askForSubstitutionDataAsync(\n targetDir: string,\n options: CommandOptions\n): Promise<SubstitutionData> {\n const defaultPackageSlug = path.basename(targetDir);\n const defaultProjectName = defaultPackageSlug\n .replace(/^./, (match) => match.toUpperCase())\n .replace(/\\W+(\\w)/g, (_, p1) => p1.toUpperCase());\n\n const promptQueries: CustomPromptObject[] = [\n {\n type: 'text',\n name: 'slug',\n message: 'What is the package slug?',\n initial: defaultPackageSlug,\n resolvedValue: options.target ? defaultPackageSlug : null,\n },\n {\n type: 'text',\n name: 'name',\n message: 'What is the project name?',\n initial: defaultProjectName,\n },\n {\n type: 'text',\n name: 'description',\n message: 'How would you describe the module?',\n },\n {\n type: 'text',\n name: 'package',\n message: 'What is the Android package name?',\n initial: `expo.modules.${defaultPackageSlug.replace(/\\W/g, '').toLowerCase()}`,\n },\n {\n type: 'text',\n name: 'author',\n message: 'Who is the author?',\n initial: (await npmWhoamiAsync(targetDir)) ?? '',\n },\n {\n type: 'text',\n name: 'license',\n message: 'What is the license?',\n initial: 'MIT',\n },\n {\n type: 'text',\n name: 'repo',\n message: 'What is the repository URL?',\n },\n ];\n\n const answers: Record<string, string> = {};\n for (const query of promptQueries) {\n const { name, resolvedValue } = query;\n answers[name] = resolvedValue ?? options[name] ?? (await prompts(query))[name];\n }\n\n const { slug, name, description, package: projectPackage, author, license, repo } = answers;\n\n return {\n project: {\n slug,\n name,\n version: '0.1.0',\n description,\n package: projectPackage,\n },\n author,\n license,\n repo,\n };\n}\n\nconst program = new Command();\n\nprogram\n .name(packageJson.name)\n .version(packageJson.version)\n .description(packageJson.description)\n .arguments('[target_dir]')\n .option(\n '-s, --source <source_dir>',\n 'Local path to the template. By default it downloads `expo-module-template` from NPM.'\n )\n .option('-n, --name <module_name>', 'Name of the native module.')\n .option('-d, --description <description>', 'Description of the module.')\n .option('-p, --package <package>', 'The Android package name.')\n .option('-a, --author <author>', 'The author name.')\n .option('-l, --license <license>', 'The license that the module is distributed with.')\n .option('-r, --repo <repo_url>', 'The URL to the repository.')\n .option('--with-readme', 'Whether to include README.md file.', false)\n .option('--with-changelog', 'Whether to include CHANGELOG.md file.', false)\n .action(main);\n\nprogram.parse(process.argv);\n"]}
1
+ {"version":3,"file":"create-expo-module.js","sourceRoot":"","sources":["../src/create-expo-module.ts"],"names":[],"mappings":"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","sourcesContent":["import spawnAsync from '@expo/spawn-async';\nimport chalk from 'chalk';\nimport { Command } from 'commander';\nimport downloadTarball from 'download-tarball';\nimport ejs from 'ejs';\nimport fs from 'fs-extra';\nimport path from 'path';\nimport prompts from 'prompts';\nimport validateNpmPackage from 'validate-npm-package-name';\n\nimport { createExampleApp } from './createExampleApp';\nimport { installDependencies } from './packageManager';\nimport { resolvePackageManager } from './resolvePackageManager';\nimport { CommandOptions, CustomPromptObject, SubstitutionData } from './types';\nimport { newStep } from './utils';\n\nconst packageJson = require('../package.json');\n\n// `yarn run` may change the current working dir, then we should use `INIT_CWD` env.\nconst CWD = process.env.INIT_CWD || process.cwd();\n\n// Ignore some paths. Especially `package.json` as it is rendered\n// from `$package.json` file instead of the original one.\nconst IGNORES_PATHS = ['.DS_Store', 'build', 'node_modules', 'package.json'];\n\n/**\n * The main function of the command.\n *\n * @param target Path to the directory where to create the module. Defaults to current working dir.\n * @param command An object from `commander`.\n */\nasync function main(target: string | undefined, options: CommandOptions) {\n const targetDir = target ? path.join(CWD, target) : CWD;\n\n await fs.ensureDir(targetDir);\n await confirmTargetDirAsync(targetDir);\n\n options.target = targetDir;\n\n const data = await askForSubstitutionDataAsync(targetDir, options);\n const packageManager = await resolvePackageManager();\n const packagePath = options.source\n ? path.join(CWD, options.source)\n : await downloadPackageAsync(targetDir);\n\n await newStep('Creating the module from template files', async (step) => {\n await createModuleFromTemplate(packagePath, targetDir, data);\n step.succeed('Created the module from template files');\n });\n\n await newStep('Installing module dependencies', async (step) => {\n await installDependencies(packageManager, targetDir);\n step.succeed('Installed module dependencies');\n });\n\n await newStep('Compiling TypeScript files', async (step) => {\n await spawnAsync(packageManager, ['run', 'build'], {\n cwd: targetDir,\n stdio: 'ignore',\n });\n step.succeed('Compiled TypeScript files');\n });\n\n if (!options.source) {\n // Files in the downloaded tarball are wrapped in `package` dir.\n // We should remove it after all.\n await fs.remove(packagePath);\n }\n if (!options.withReadme) {\n await fs.remove(path.join(targetDir, 'README.md'));\n }\n if (!options.withChangelog) {\n await fs.remove(path.join(targetDir, 'CHANGELOG.md'));\n }\n if (options.example) {\n // Create \"example\" folder\n await createExampleApp(data, targetDir, packageManager);\n }\n\n console.log();\n console.log('✅ Successfully created Expo module');\n}\n\n/**\n * Recursively scans for the files within the directory. Returned paths are relative to the `root` path.\n */\nasync function getFilesAsync(root: string, dir: string | null = null): Promise<string[]> {\n const files: string[] = [];\n const baseDir = dir ? path.join(root, dir) : root;\n\n for (const file of await fs.readdir(baseDir)) {\n const relativePath = dir ? path.join(dir, file) : file;\n\n if (IGNORES_PATHS.includes(relativePath) || IGNORES_PATHS.includes(file)) {\n continue;\n }\n\n const fullPath = path.join(baseDir, file);\n const stat = await fs.lstat(fullPath);\n\n if (stat.isDirectory()) {\n files.push(...(await getFilesAsync(root, relativePath)));\n } else {\n files.push(relativePath);\n }\n }\n return files;\n}\n\n/**\n * Asks NPM registry for the url to the tarball.\n */\nasync function getNpmTarballUrl(packageName: string, version: string = 'latest'): Promise<string> {\n const { stdout } = await spawnAsync('npm', ['view', `${packageName}@${version}`, 'dist.tarball']);\n return stdout.trim();\n}\n\n/**\n * Gets the username of currently logged in user. Used as a default in the prompt asking for the module author.\n */\nasync function npmWhoamiAsync(targetDir: string): Promise<string | null> {\n try {\n const { stdout } = await spawnAsync('npm', ['whoami'], { cwd: targetDir });\n return stdout.trim();\n } catch {\n return null;\n }\n}\n\n/**\n * Downloads the template from NPM registry.\n */\nasync function downloadPackageAsync(targetDir: string): Promise<string> {\n return await newStep('Downloading module template from npm', async (step) => {\n const tarballUrl = await getNpmTarballUrl('expo-module-template');\n\n await downloadTarball({\n url: tarballUrl,\n dir: targetDir,\n });\n\n step.succeed('Downloaded module template from npm');\n\n return path.join(targetDir, 'package');\n });\n}\n\n/**\n * Creates the module based on the `ejs` template (e.g. `expo-module-template` package).\n */\nasync function createModuleFromTemplate(\n templatePath: string,\n targetPath: string,\n data: SubstitutionData\n) {\n const files = await getFilesAsync(templatePath);\n\n // Iterate through all template files.\n for (const file of files) {\n const renderedRelativePath = ejs.render(file.replace(/^\\$/, ''), data, {\n openDelimiter: '{',\n closeDelimiter: '}',\n escape: (value: string) => value.replace('.', path.sep),\n });\n const fromPath = path.join(templatePath, file);\n const toPath = path.join(targetPath, renderedRelativePath);\n const template = await fs.readFile(fromPath, { encoding: 'utf8' });\n const renderedContent = ejs.render(template, data);\n\n await fs.outputFile(toPath, renderedContent, { encoding: 'utf8' });\n }\n}\n\n/**\n * Asks the user for some data necessary to render the template.\n * Some values may already be provided by command options, the prompt is skipped in that case.\n */\nasync function askForSubstitutionDataAsync(\n targetDir: string,\n options: CommandOptions\n): Promise<SubstitutionData> {\n const defaultPackageSlug = path.basename(targetDir);\n const useDefaultSlug = options.target && validateNpmPackage(defaultPackageSlug);\n const defaultProjectName = defaultPackageSlug\n .replace(/^./, (match) => match.toUpperCase())\n .replace(/\\W+(\\w)/g, (_, p1) => p1.toUpperCase());\n\n const promptQueries: CustomPromptObject[] = [\n {\n type: 'text',\n name: 'slug',\n message: 'What is the package slug?',\n initial: defaultPackageSlug,\n resolvedValue: useDefaultSlug ? defaultPackageSlug : null,\n validate: (input) =>\n validateNpmPackage(input).validForNewPackages || 'Must be a valid npm package name',\n },\n {\n type: 'text',\n name: 'name',\n message: 'What is the project name?',\n initial: defaultProjectName,\n },\n {\n type: 'text',\n name: 'description',\n message: 'How would you describe the module?',\n validate: (input) => !!input || 'Cannot be empty',\n },\n {\n type: 'text',\n name: 'package',\n message: 'What is the Android package name?',\n initial: `expo.modules.${defaultPackageSlug.replace(/\\W/g, '').toLowerCase()}`,\n },\n {\n type: 'text',\n name: 'author',\n message: 'Who is the author?',\n initial: (await npmWhoamiAsync(targetDir)) ?? '',\n },\n {\n type: 'text',\n name: 'license',\n message: 'What is the license?',\n initial: 'MIT',\n },\n {\n type: 'text',\n name: 'repo',\n message: 'What is the repository URL?',\n validate: (input) => /^https?:\\/\\//.test(input) || 'Must be a valid URL',\n },\n ];\n\n // Stop the process when the user cancels/exits the prompt.\n const onCancel = () => {\n process.exit(0);\n };\n\n const answers: Record<string, string> = {};\n for (const query of promptQueries) {\n const { name, resolvedValue } = query;\n answers[name] = resolvedValue ?? options[name] ?? (await prompts(query, { onCancel }))[name];\n }\n\n const { slug, name, description, package: projectPackage, author, license, repo } = answers;\n\n return {\n project: {\n slug,\n name,\n version: '0.1.0',\n description,\n package: projectPackage,\n },\n author,\n license,\n repo,\n };\n}\n\n/**\n * Checks whether the target directory is empty and if not, asks the user to confirm if he wants to continue.\n */\nasync function confirmTargetDirAsync(targetDir: string): Promise<void> {\n const files = await fs.readdir(targetDir);\n\n if (files.length === 0) {\n return;\n }\n const { shouldContinue } = await prompts(\n {\n type: 'confirm',\n name: 'shouldContinue',\n message: `The target directory ${chalk.magenta(\n targetDir\n )} is not empty.\\nDo you want to continue anyway?`,\n initial: true,\n },\n {\n onCancel: () => false,\n }\n );\n if (!shouldContinue) {\n process.exit(0);\n }\n}\n\nconst program = new Command();\n\nprogram\n .name(packageJson.name)\n .version(packageJson.version)\n .description(packageJson.description)\n .arguments('[target_dir]')\n .option(\n '-s, --source <source_dir>',\n 'Local path to the template. By default it downloads `expo-module-template` from NPM.'\n )\n .option('-n, --name <module_name>', 'Name of the native module.')\n .option('-d, --description <description>', 'Description of the module.')\n .option('-p, --package <package>', 'The Android package name.')\n .option('-a, --author <author>', 'The author name.')\n .option('-l, --license <license>', 'The license that the module is distributed with.')\n .option('-r, --repo <repo_url>', 'The URL to the repository.')\n .option('--with-readme', 'Whether to include README.md file.', false)\n .option('--with-changelog', 'Whether to include CHANGELOG.md file.', false)\n .option('--no-example', 'Whether to skip creating the example app.', false)\n .action(main);\n\nprogram.parse(process.argv);\n"]}
@@ -0,0 +1,6 @@
1
+ import { PackageManagerName } from './resolvePackageManager';
2
+ import { SubstitutionData } from './types';
3
+ /**
4
+ * Initializes a new Expo project as an example app.
5
+ */
6
+ export declare function createExampleApp(data: SubstitutionData, targetDir: string, packageManager: PackageManagerName): Promise<void>;
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createExampleApp = void 0;
7
+ const spawn_async_1 = __importDefault(require("@expo/spawn-async"));
8
+ const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const packageManager_1 = require("./packageManager");
11
+ const utils_1 = require("./utils");
12
+ // These dependencies will be removed from the example app (`expo init` adds them)
13
+ const DEPENDENCIES_TO_REMOVE = ['expo-status-bar', 'expo-splash-screen'];
14
+ /**
15
+ * Initializes a new Expo project as an example app.
16
+ */
17
+ async function createExampleApp(data, targetDir, packageManager) {
18
+ const exampleProjectSlug = `${data.project.slug}-example`;
19
+ await (0, utils_1.newStep)('Initializing the example app', async (step) => {
20
+ await (0, spawn_async_1.default)('expo', ['init', exampleProjectSlug, '--template', 'expo-template-blank-typescript'], {
21
+ cwd: targetDir,
22
+ stdio: ['ignore', 'ignore', 'inherit'],
23
+ });
24
+ step.succeed('Initialized the example app');
25
+ });
26
+ // `expo init` creates a new folder with the same name as the project slug
27
+ const appTmpPath = path_1.default.join(targetDir, exampleProjectSlug);
28
+ // Path to the target example dir
29
+ const appTargetPath = path_1.default.join(targetDir, 'example');
30
+ await (0, utils_1.newStep)('Configuring the example app', async (step) => {
31
+ // "example" folder already exists and contains template files,
32
+ // that should replace these created by `expo init`.
33
+ await moveFiles(appTargetPath, appTmpPath);
34
+ // Cleanup the "example" dir
35
+ await fs_extra_1.default.rmdir(appTargetPath);
36
+ // Move the temporary example app to "example" dir
37
+ await fs_extra_1.default.rename(appTmpPath, appTargetPath);
38
+ await addMissingAppConfigFields(appTargetPath, data);
39
+ step.succeed('Configured the example app');
40
+ });
41
+ await prebuildExampleApp(appTargetPath);
42
+ await modifyPackageJson(appTargetPath);
43
+ await (0, utils_1.newStep)('Installing dependencies in the example app', async (step) => {
44
+ await (0, packageManager_1.installDependencies)(packageManager, appTargetPath);
45
+ await podInstall(appTargetPath);
46
+ step.succeed('Installed dependencies in the example app');
47
+ });
48
+ }
49
+ exports.createExampleApp = createExampleApp;
50
+ /**
51
+ * Copies files from one directory to another.
52
+ */
53
+ async function moveFiles(fromPath, toPath) {
54
+ for (const file of await fs_extra_1.default.readdir(fromPath)) {
55
+ await fs_extra_1.default.move(path_1.default.join(fromPath, file), path_1.default.join(toPath, file), {
56
+ overwrite: true,
57
+ });
58
+ }
59
+ }
60
+ /**
61
+ * Adds missing configuration that are required to run `expo prebuild`.
62
+ */
63
+ async function addMissingAppConfigFields(appPath, data) {
64
+ const appConfigPath = path_1.default.join(appPath, 'app.json');
65
+ const appConfig = await fs_extra_1.default.readJson(appConfigPath);
66
+ const appId = `${data.project.package}.example`;
67
+ // Android package name needs to be added to app.json
68
+ if (!appConfig.expo.android) {
69
+ appConfig.expo.android = {};
70
+ }
71
+ appConfig.expo.android.package = appId;
72
+ // Specify iOS bundle identifier
73
+ if (!appConfig.expo.ios) {
74
+ appConfig.expo.ios = {};
75
+ }
76
+ appConfig.expo.ios.bundleIdentifier = appId;
77
+ await fs_extra_1.default.writeJson(appConfigPath, appConfig, {
78
+ spaces: 2,
79
+ });
80
+ }
81
+ /**
82
+ * Applies necessary changes to **package.json** of the example app.
83
+ * It means setting the autolinking config and removing unnecessary dependencies.
84
+ */
85
+ async function modifyPackageJson(appPath) {
86
+ const packageJsonPath = path_1.default.join(appPath, 'package.json');
87
+ const packageJson = await fs_extra_1.default.readJson(packageJsonPath);
88
+ if (!packageJson.expo) {
89
+ packageJson.expo = {};
90
+ }
91
+ // Set the native modules dir to the root folder,
92
+ // so that the autolinking can detect and link the module.
93
+ packageJson.expo.autolinking = {
94
+ nativeModulesDir: '..',
95
+ };
96
+ // Remove unnecessary dependencies
97
+ for (const dependencyToRemove of DEPENDENCIES_TO_REMOVE) {
98
+ delete packageJson.dependencies[dependencyToRemove];
99
+ }
100
+ await fs_extra_1.default.writeJson(packageJsonPath, packageJson, {
101
+ spaces: 2,
102
+ });
103
+ }
104
+ /**
105
+ * Runs `expo prebuild` in the example app.
106
+ */
107
+ async function prebuildExampleApp(exampleAppPath) {
108
+ await (0, utils_1.newStep)('Prebuilding the example app', async (step) => {
109
+ await (0, spawn_async_1.default)('expo', ['prebuild', '--no-install'], {
110
+ cwd: exampleAppPath,
111
+ stdio: ['ignore', 'ignore', 'pipe'],
112
+ });
113
+ step.succeed('Prebuilt the example app');
114
+ });
115
+ }
116
+ /**
117
+ * Runs `pod install` in the iOS project at the given path.
118
+ */
119
+ async function podInstall(appPath) {
120
+ await (0, spawn_async_1.default)('pod', ['install'], {
121
+ cwd: path_1.default.join(appPath, 'ios'),
122
+ stdio: ['ignore', 'ignore', 'pipe'],
123
+ });
124
+ }
125
+ //# sourceMappingURL=createExampleApp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createExampleApp.js","sourceRoot":"","sources":["../src/createExampleApp.ts"],"names":[],"mappings":"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","sourcesContent":["import spawnAsync from '@expo/spawn-async';\nimport fs from 'fs-extra';\nimport path from 'path';\n\nimport { installDependencies } from './packageManager';\nimport { PackageManagerName } from './resolvePackageManager';\nimport { SubstitutionData } from './types';\nimport { newStep } from './utils';\n\n// These dependencies will be removed from the example app (`expo init` adds them)\nconst DEPENDENCIES_TO_REMOVE = ['expo-status-bar', 'expo-splash-screen'];\n\n/**\n * Initializes a new Expo project as an example app.\n */\nexport async function createExampleApp(\n data: SubstitutionData,\n targetDir: string,\n packageManager: PackageManagerName\n): Promise<void> {\n const exampleProjectSlug = `${data.project.slug}-example`;\n\n await newStep('Initializing the example app', async (step) => {\n await spawnAsync(\n 'expo',\n ['init', exampleProjectSlug, '--template', 'expo-template-blank-typescript'],\n {\n cwd: targetDir,\n stdio: ['ignore', 'ignore', 'inherit'],\n }\n );\n step.succeed('Initialized the example app');\n });\n\n // `expo init` creates a new folder with the same name as the project slug\n const appTmpPath = path.join(targetDir, exampleProjectSlug);\n\n // Path to the target example dir\n const appTargetPath = path.join(targetDir, 'example');\n\n await newStep('Configuring the example app', async (step) => {\n // \"example\" folder already exists and contains template files,\n // that should replace these created by `expo init`.\n await moveFiles(appTargetPath, appTmpPath);\n\n // Cleanup the \"example\" dir\n await fs.rmdir(appTargetPath);\n\n // Move the temporary example app to \"example\" dir\n await fs.rename(appTmpPath, appTargetPath);\n\n await addMissingAppConfigFields(appTargetPath, data);\n\n step.succeed('Configured the example app');\n });\n\n await prebuildExampleApp(appTargetPath);\n\n await modifyPackageJson(appTargetPath);\n\n await newStep('Installing dependencies in the example app', async (step) => {\n await installDependencies(packageManager, appTargetPath);\n await podInstall(appTargetPath);\n step.succeed('Installed dependencies in the example app');\n });\n}\n\n/**\n * Copies files from one directory to another.\n */\nasync function moveFiles(fromPath: string, toPath: string): Promise<void> {\n for (const file of await fs.readdir(fromPath)) {\n await fs.move(path.join(fromPath, file), path.join(toPath, file), {\n overwrite: true,\n });\n }\n}\n\n/**\n * Adds missing configuration that are required to run `expo prebuild`.\n */\nasync function addMissingAppConfigFields(appPath: string, data: SubstitutionData): Promise<void> {\n const appConfigPath = path.join(appPath, 'app.json');\n const appConfig = await fs.readJson(appConfigPath);\n const appId = `${data.project.package}.example`;\n\n // Android package name needs to be added to app.json\n if (!appConfig.expo.android) {\n appConfig.expo.android = {};\n }\n appConfig.expo.android.package = appId;\n\n // Specify iOS bundle identifier\n if (!appConfig.expo.ios) {\n appConfig.expo.ios = {};\n }\n appConfig.expo.ios.bundleIdentifier = appId;\n\n await fs.writeJson(appConfigPath, appConfig, {\n spaces: 2,\n });\n}\n\n/**\n * Applies necessary changes to **package.json** of the example app.\n * It means setting the autolinking config and removing unnecessary dependencies.\n */\nasync function modifyPackageJson(appPath: string): Promise<void> {\n const packageJsonPath = path.join(appPath, 'package.json');\n const packageJson = await fs.readJson(packageJsonPath);\n\n if (!packageJson.expo) {\n packageJson.expo = {};\n }\n\n // Set the native modules dir to the root folder,\n // so that the autolinking can detect and link the module.\n packageJson.expo.autolinking = {\n nativeModulesDir: '..',\n };\n\n // Remove unnecessary dependencies\n for (const dependencyToRemove of DEPENDENCIES_TO_REMOVE) {\n delete packageJson.dependencies[dependencyToRemove];\n }\n\n await fs.writeJson(packageJsonPath, packageJson, {\n spaces: 2,\n });\n}\n\n/**\n * Runs `expo prebuild` in the example app.\n */\nasync function prebuildExampleApp(exampleAppPath: string): Promise<void> {\n await newStep('Prebuilding the example app', async (step) => {\n await spawnAsync('expo', ['prebuild', '--no-install'], {\n cwd: exampleAppPath,\n stdio: ['ignore', 'ignore', 'pipe'],\n });\n step.succeed('Prebuilt the example app');\n });\n}\n\n/**\n * Runs `pod install` in the iOS project at the given path.\n */\nasync function podInstall(appPath: string): Promise<void> {\n await spawnAsync('pod', ['install'], {\n cwd: path.join(appPath, 'ios'),\n stdio: ['ignore', 'ignore', 'pipe'],\n });\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import { PackageManagerName } from './resolvePackageManager';
2
+ export declare function installDependencies(packageManager: PackageManagerName, appPath: string, ...args: string[]): Promise<void>;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.installDependencies = void 0;
7
+ const spawn_async_1 = __importDefault(require("@expo/spawn-async"));
8
+ async function installDependencies(packageManager, appPath, ...args) {
9
+ await (0, spawn_async_1.default)(packageManager, ['install', ...args], {
10
+ cwd: appPath,
11
+ stdio: 'ignore',
12
+ });
13
+ }
14
+ exports.installDependencies = installDependencies;
15
+ //# sourceMappingURL=packageManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"packageManager.js","sourceRoot":"","sources":["../src/packageManager.ts"],"names":[],"mappings":";;;;;;AAAA,oEAA2C;AAIpC,KAAK,UAAU,mBAAmB,CACvC,cAAkC,EAClC,OAAe,EACf,GAAG,IAAc;IAEjB,MAAM,IAAA,qBAAU,EAAC,cAAc,EAAE,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,EAAE;QACrD,GAAG,EAAE,OAAO;QACZ,KAAK,EAAE,QAAQ;KAChB,CAAC,CAAC;AACL,CAAC;AATD,kDASC","sourcesContent":["import spawnAsync from '@expo/spawn-async';\n\nimport { PackageManagerName } from './resolvePackageManager';\n\nexport async function installDependencies(\n packageManager: PackageManagerName,\n appPath: string,\n ...args: string[]\n) {\n await spawnAsync(packageManager, ['install', ...args], {\n cwd: appPath,\n stdio: 'ignore',\n });\n}\n"]}
@@ -0,0 +1,3 @@
1
+ export declare type PackageManagerName = 'npm' | 'pnpm' | 'yarn';
2
+ /** Determine which package manager to use for installing dependencies based on how the process was started. */
3
+ export declare function resolvePackageManager(): PackageManagerName;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolvePackageManager = void 0;
4
+ const child_process_1 = require("child_process");
5
+ /** Determine which package manager to use for installing dependencies based on how the process was started. */
6
+ function resolvePackageManager() {
7
+ // Attempt to detect if the user started the command using `yarn` or `pnpm`
8
+ const userAgent = process.env.npm_config_user_agent;
9
+ if (userAgent === null || userAgent === void 0 ? void 0 : userAgent.startsWith('yarn')) {
10
+ return 'yarn';
11
+ }
12
+ else if (userAgent === null || userAgent === void 0 ? void 0 : userAgent.startsWith('pnpm')) {
13
+ return 'pnpm';
14
+ }
15
+ else if (userAgent === null || userAgent === void 0 ? void 0 : userAgent.startsWith('npm')) {
16
+ return 'npm';
17
+ }
18
+ // Try availability
19
+ if (isPackageManagerAvailable('yarn')) {
20
+ return 'yarn';
21
+ }
22
+ else if (isPackageManagerAvailable('pnpm')) {
23
+ return 'pnpm';
24
+ }
25
+ return 'npm';
26
+ }
27
+ exports.resolvePackageManager = resolvePackageManager;
28
+ function isPackageManagerAvailable(manager) {
29
+ try {
30
+ (0, child_process_1.execSync)(`${manager} --version`, { stdio: 'ignore' });
31
+ return true;
32
+ }
33
+ catch { }
34
+ return false;
35
+ }
36
+ //# sourceMappingURL=resolvePackageManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolvePackageManager.js","sourceRoot":"","sources":["../src/resolvePackageManager.ts"],"names":[],"mappings":";;;AAAA,iDAAyC;AAIzC,+GAA+G;AAC/G,SAAgB,qBAAqB;IACnC,2EAA2E;IAC3E,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC;IAEpD,IAAI,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,UAAU,CAAC,MAAM,CAAC,EAAE;QACjC,OAAO,MAAM,CAAC;KACf;SAAM,IAAI,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,UAAU,CAAC,MAAM,CAAC,EAAE;QACxC,OAAO,MAAM,CAAC;KACf;SAAM,IAAI,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,UAAU,CAAC,KAAK,CAAC,EAAE;QACvC,OAAO,KAAK,CAAC;KACd;IAED,mBAAmB;IACnB,IAAI,yBAAyB,CAAC,MAAM,CAAC,EAAE;QACrC,OAAO,MAAM,CAAC;KACf;SAAM,IAAI,yBAAyB,CAAC,MAAM,CAAC,EAAE;QAC5C,OAAO,MAAM,CAAC;KACf;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AApBD,sDAoBC;AAED,SAAS,yBAAyB,CAAC,OAA2B;IAC5D,IAAI;QACF,IAAA,wBAAQ,EAAC,GAAG,OAAO,YAAY,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QACtD,OAAO,IAAI,CAAC;KACb;IAAC,MAAM,GAAE;IACV,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["import { execSync } from 'child_process';\n\nexport type PackageManagerName = 'npm' | 'pnpm' | 'yarn';\n\n/** Determine which package manager to use for installing dependencies based on how the process was started. */\nexport function resolvePackageManager(): PackageManagerName {\n // Attempt to detect if the user started the command using `yarn` or `pnpm`\n const userAgent = process.env.npm_config_user_agent;\n\n if (userAgent?.startsWith('yarn')) {\n return 'yarn';\n } else if (userAgent?.startsWith('pnpm')) {\n return 'pnpm';\n } else if (userAgent?.startsWith('npm')) {\n return 'npm';\n }\n\n // Try availability\n if (isPackageManagerAvailable('yarn')) {\n return 'yarn';\n } else if (isPackageManagerAvailable('pnpm')) {\n return 'pnpm';\n }\n\n return 'npm';\n}\n\nfunction isPackageManagerAvailable(manager: PackageManagerName): boolean {\n try {\n execSync(`${manager} --version`, { stdio: 'ignore' });\n return true;\n } catch {}\n return false;\n}\n"]}
@@ -0,0 +1,36 @@
1
+ import { PromptObject } from 'prompts';
2
+ /**
3
+ * Possible command options.
4
+ */
5
+ export declare type CommandOptions = {
6
+ target: string;
7
+ source?: string;
8
+ name?: string;
9
+ description?: string;
10
+ package?: string;
11
+ author?: string;
12
+ license?: string;
13
+ repo?: string;
14
+ withReadme: boolean;
15
+ withChangelog: boolean;
16
+ example: boolean;
17
+ };
18
+ /**
19
+ * Represents an object that is passed to `ejs` when rendering the template.
20
+ */
21
+ export declare type SubstitutionData = {
22
+ project: {
23
+ slug: string;
24
+ name: string;
25
+ version: string;
26
+ description: string;
27
+ package: string;
28
+ };
29
+ author: string;
30
+ license: string;
31
+ repo: string;
32
+ };
33
+ export declare type CustomPromptObject = PromptObject & {
34
+ name: string;
35
+ resolvedValue?: string | null;
36
+ };
package/build/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import { PromptObject } from 'prompts';\n\n/**\n * Possible command options.\n */\nexport type CommandOptions = {\n target: string;\n source?: string;\n name?: string;\n description?: string;\n package?: string;\n author?: string;\n license?: string;\n repo?: string;\n withReadme: boolean;\n withChangelog: boolean;\n example: boolean;\n};\n\n/**\n * Represents an object that is passed to `ejs` when rendering the template.\n */\nexport type SubstitutionData = {\n project: {\n slug: string;\n name: string;\n version: string;\n description: string;\n package: string;\n };\n author: string;\n license: string;\n repo: string;\n};\n\nexport type CustomPromptObject = PromptObject & {\n name: string;\n resolvedValue?: string | null;\n};\n"]}
@@ -0,0 +1,3 @@
1
+ import ora from 'ora';
2
+ export declare type StepOptions = ora.Options;
3
+ export declare function newStep<Result>(title: string, action: (step: ora.Ora) => Promise<Result> | Result, options?: StepOptions): Promise<Result>;
package/build/utils.js ADDED
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.newStep = void 0;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const ora_1 = __importDefault(require("ora"));
9
+ async function newStep(title, action, options = {}) {
10
+ const disabled = process.env.CI || process.env.EXPO_DEBUG;
11
+ const step = (0, ora_1.default)({
12
+ text: chalk_1.default.bold(title),
13
+ isEnabled: !disabled,
14
+ stream: disabled ? process.stdout : process.stderr,
15
+ ...options,
16
+ });
17
+ step.start();
18
+ try {
19
+ return await action(step);
20
+ }
21
+ catch (error) {
22
+ step.fail();
23
+ console.error(error);
24
+ process.exit(1);
25
+ }
26
+ }
27
+ exports.newStep = newStep;
28
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;;;;AAAA,kDAA0B;AAC1B,8CAAsB;AAIf,KAAK,UAAU,OAAO,CAC3B,KAAa,EACb,MAAmD,EACnD,UAAuB,EAAE;IAEzB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,EAAE,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC;IAC1D,MAAM,IAAI,GAAG,IAAA,aAAG,EAAC;QACf,IAAI,EAAE,eAAK,CAAC,IAAI,CAAC,KAAK,CAAC;QACvB,SAAS,EAAE,CAAC,QAAQ;QACpB,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM;QAClD,GAAG,OAAO;KACX,CAAC,CAAC;IAEH,IAAI,CAAC,KAAK,EAAE,CAAC;IAEb,IAAI;QACF,OAAO,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;KAC3B;IAAC,OAAO,KAAK,EAAE;QACd,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;KACjB;AACH,CAAC;AAtBD,0BAsBC","sourcesContent":["import chalk from 'chalk';\nimport ora from 'ora';\n\nexport type StepOptions = ora.Options;\n\nexport async function newStep<Result>(\n title: string,\n action: (step: ora.Ora) => Promise<Result> | Result,\n options: StepOptions = {}\n): Promise<Result> {\n const disabled = process.env.CI || process.env.EXPO_DEBUG;\n const step = ora({\n text: chalk.bold(title),\n isEnabled: !disabled,\n stream: disabled ? process.stdout : process.stderr,\n ...options,\n });\n\n step.start();\n\n try {\n return await action(step);\n } catch (error) {\n step.fail();\n console.error(error);\n process.exit(1);\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-expo-module",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "The script to create the Expo module",
5
5
  "main": "build/create-expo-module.js",
6
6
  "types": "build/create-expo-module.d.ts",
@@ -34,16 +34,19 @@
34
34
  "homepage": "https://github.com/expo/expo/tree/main/packages/expo",
35
35
  "dependencies": {
36
36
  "@expo/spawn-async": "^1.5.0",
37
+ "chalk": "^4.1.2",
37
38
  "commander": "^8.3.0",
38
39
  "download-tarball": "^2.0.0",
39
- "ejs": "^3.1.6",
40
+ "ejs": "^3.1.7",
40
41
  "fs-extra": "^10.0.0",
41
- "prompts": "^2.4.2"
42
+ "ora": "^5.4.1",
43
+ "prompts": "^2.4.2",
44
+ "validate-npm-package-name": "^4.0.0"
42
45
  },
43
46
  "devDependencies": {
44
47
  "@types/ejs": "^3.1.0",
45
48
  "@types/prompts": "^2.0.14",
46
49
  "expo-module-scripts": "^2.0.0"
47
50
  },
48
- "gitHead": "89a27c0ca0ca8becd7546697298e874a15e94faf"
51
+ "gitHead": "c6678c65b68e45062d49a2deea8e822f69388278"
49
52
  }
@@ -1,10 +1,18 @@
1
1
  import spawnAsync from '@expo/spawn-async';
2
+ import chalk from 'chalk';
2
3
  import { Command } from 'commander';
3
4
  import downloadTarball from 'download-tarball';
4
5
  import ejs from 'ejs';
5
6
  import fs from 'fs-extra';
6
7
  import path from 'path';
7
- import prompts, { PromptObject } from 'prompts';
8
+ import prompts from 'prompts';
9
+ import validateNpmPackage from 'validate-npm-package-name';
10
+
11
+ import { createExampleApp } from './createExampleApp';
12
+ import { installDependencies } from './packageManager';
13
+ import { resolvePackageManager } from './resolvePackageManager';
14
+ import { CommandOptions, CustomPromptObject, SubstitutionData } from './types';
15
+ import { newStep } from './utils';
8
16
 
9
17
  const packageJson = require('../package.json');
10
18
 
@@ -15,45 +23,6 @@ const CWD = process.env.INIT_CWD || process.cwd();
15
23
  // from `$package.json` file instead of the original one.
16
24
  const IGNORES_PATHS = ['.DS_Store', 'build', 'node_modules', 'package.json'];
17
25
 
18
- /**
19
- * Possible command options.
20
- */
21
- type CommandOptions = {
22
- target: string;
23
- source?: string;
24
- name?: string;
25
- description?: string;
26
- package?: string;
27
- author?: string;
28
- license?: string;
29
- repo?: string;
30
- withReadme: boolean;
31
- withChangelog: boolean;
32
- };
33
-
34
- /**
35
- * Represents an object that is passed to `ejs` when rendering the template.
36
- */
37
- type SubstitutionData = {
38
- project: {
39
- slug: string;
40
- name: string;
41
- version: string;
42
- description: string;
43
- package: string;
44
- };
45
- author: string;
46
- license: string;
47
- repo: string;
48
- };
49
-
50
- type CustomPromptObject = PromptObject & {
51
- name: string;
52
- resolvedValue?: string | null;
53
- };
54
-
55
- type PackageManager = 'npm' | 'yarn';
56
-
57
26
  /**
58
27
  * The main function of the command.
59
28
  *
@@ -63,32 +32,34 @@ type PackageManager = 'npm' | 'yarn';
63
32
  async function main(target: string | undefined, options: CommandOptions) {
64
33
  const targetDir = target ? path.join(CWD, target) : CWD;
65
34
 
66
- options.target = targetDir;
67
35
  await fs.ensureDir(targetDir);
36
+ await confirmTargetDirAsync(targetDir);
37
+
38
+ options.target = targetDir;
68
39
 
69
40
  const data = await askForSubstitutionDataAsync(targetDir, options);
70
- const packageManager = await selectPackageManagerAsync();
41
+ const packageManager = await resolvePackageManager();
71
42
  const packagePath = options.source
72
43
  ? path.join(CWD, options.source)
73
44
  : await downloadPackageAsync(targetDir);
74
- const files = await getFilesAsync(packagePath);
75
45
 
76
- console.log('🎨 Creating Expo module from the template files...');
46
+ await newStep('Creating the module from template files', async (step) => {
47
+ await createModuleFromTemplate(packagePath, targetDir, data);
48
+ step.succeed('Created the module from template files');
49
+ });
77
50
 
78
- // Iterate through all template files.
79
- for (const file of files) {
80
- const renderedRelativePath = ejs.render(file.replace(/^\$/, ''), data, {
81
- openDelimiter: '{',
82
- closeDelimiter: '}',
83
- escape: (value: string) => value.replace('.', path.sep),
84
- });
85
- const fromPath = path.join(packagePath, file);
86
- const toPath = path.join(targetDir, renderedRelativePath);
87
- const template = await fs.readFile(fromPath, { encoding: 'utf8' });
88
- const renderedContent = ejs.render(template, data);
51
+ await newStep('Installing module dependencies', async (step) => {
52
+ await installDependencies(packageManager, targetDir);
53
+ step.succeed('Installed module dependencies');
54
+ });
89
55
 
90
- await fs.outputFile(toPath, renderedContent, { encoding: 'utf8' });
91
- }
56
+ await newStep('Compiling TypeScript files', async (step) => {
57
+ await spawnAsync(packageManager, ['run', 'build'], {
58
+ cwd: targetDir,
59
+ stdio: 'ignore',
60
+ });
61
+ step.succeed('Compiled TypeScript files');
62
+ });
92
63
 
93
64
  if (!options.source) {
94
65
  // Files in the downloaded tarball are wrapped in `package` dir.
@@ -101,10 +72,12 @@ async function main(target: string | undefined, options: CommandOptions) {
101
72
  if (!options.withChangelog) {
102
73
  await fs.remove(path.join(targetDir, 'CHANGELOG.md'));
103
74
  }
75
+ if (options.example) {
76
+ // Create "example" folder
77
+ await createExampleApp(data, targetDir, packageManager);
78
+ }
104
79
 
105
- // Install dependencies and build
106
- await postActionsAsync(packageManager, targetDir);
107
-
80
+ console.log();
108
81
  console.log('✅ Successfully created Expo module');
109
82
  }
110
83
 
@@ -158,49 +131,44 @@ async function npmWhoamiAsync(targetDir: string): Promise<string | null> {
158
131
  * Downloads the template from NPM registry.
159
132
  */
160
133
  async function downloadPackageAsync(targetDir: string): Promise<string> {
161
- const tarballUrl = await getNpmTarballUrl('expo-module-template');
134
+ return await newStep('Downloading module template from npm', async (step) => {
135
+ const tarballUrl = await getNpmTarballUrl('expo-module-template');
162
136
 
163
- console.log('⬇️ Downloading module template from npm...');
137
+ await downloadTarball({
138
+ url: tarballUrl,
139
+ dir: targetDir,
140
+ });
164
141
 
165
- await downloadTarball({
166
- url: tarballUrl,
167
- dir: targetDir,
168
- });
169
- return path.join(targetDir, 'package');
170
- }
142
+ step.succeed('Downloaded module template from npm');
171
143
 
172
- /**
173
- * Asks whether to use Yarn or npm as a dependency package manager.
174
- */
175
- async function selectPackageManagerAsync(): Promise<PackageManager> {
176
- const { packageManager } = await prompts({
177
- type: 'select',
178
- name: 'packageManager',
179
- message: 'Which package manager do you want to use to install dependencies?',
180
- choices: [
181
- { title: 'yarn', value: 'yarn' },
182
- { title: 'npm', value: 'npm' },
183
- ],
144
+ return path.join(targetDir, 'package');
184
145
  });
185
- return packageManager;
186
146
  }
187
147
 
188
148
  /**
189
- * Installs dependencies and builds TypeScript files.
149
+ * Creates the module based on the `ejs` template (e.g. `expo-module-template` package).
190
150
  */
191
- async function postActionsAsync(packageManager: PackageManager, targetDir: string) {
192
- async function run(...args: string[]) {
193
- await spawnAsync(packageManager, args, {
194
- cwd: targetDir,
195
- stdio: 'ignore',
196
- });
197
- }
151
+ async function createModuleFromTemplate(
152
+ templatePath: string,
153
+ targetPath: string,
154
+ data: SubstitutionData
155
+ ) {
156
+ const files = await getFilesAsync(templatePath);
198
157
 
199
- console.log('📦 Installing dependencies...');
200
- await run('install');
158
+ // Iterate through all template files.
159
+ for (const file of files) {
160
+ const renderedRelativePath = ejs.render(file.replace(/^\$/, ''), data, {
161
+ openDelimiter: '{',
162
+ closeDelimiter: '}',
163
+ escape: (value: string) => value.replace('.', path.sep),
164
+ });
165
+ const fromPath = path.join(templatePath, file);
166
+ const toPath = path.join(targetPath, renderedRelativePath);
167
+ const template = await fs.readFile(fromPath, { encoding: 'utf8' });
168
+ const renderedContent = ejs.render(template, data);
201
169
 
202
- console.log('🛠 Compiling TypeScript files...');
203
- await run('run', 'build');
170
+ await fs.outputFile(toPath, renderedContent, { encoding: 'utf8' });
171
+ }
204
172
  }
205
173
 
206
174
  /**
@@ -212,6 +180,7 @@ async function askForSubstitutionDataAsync(
212
180
  options: CommandOptions
213
181
  ): Promise<SubstitutionData> {
214
182
  const defaultPackageSlug = path.basename(targetDir);
183
+ const useDefaultSlug = options.target && validateNpmPackage(defaultPackageSlug);
215
184
  const defaultProjectName = defaultPackageSlug
216
185
  .replace(/^./, (match) => match.toUpperCase())
217
186
  .replace(/\W+(\w)/g, (_, p1) => p1.toUpperCase());
@@ -222,7 +191,9 @@ async function askForSubstitutionDataAsync(
222
191
  name: 'slug',
223
192
  message: 'What is the package slug?',
224
193
  initial: defaultPackageSlug,
225
- resolvedValue: options.target ? defaultPackageSlug : null,
194
+ resolvedValue: useDefaultSlug ? defaultPackageSlug : null,
195
+ validate: (input) =>
196
+ validateNpmPackage(input).validForNewPackages || 'Must be a valid npm package name',
226
197
  },
227
198
  {
228
199
  type: 'text',
@@ -234,6 +205,7 @@ async function askForSubstitutionDataAsync(
234
205
  type: 'text',
235
206
  name: 'description',
236
207
  message: 'How would you describe the module?',
208
+ validate: (input) => !!input || 'Cannot be empty',
237
209
  },
238
210
  {
239
211
  type: 'text',
@@ -257,13 +229,19 @@ async function askForSubstitutionDataAsync(
257
229
  type: 'text',
258
230
  name: 'repo',
259
231
  message: 'What is the repository URL?',
232
+ validate: (input) => /^https?:\/\//.test(input) || 'Must be a valid URL',
260
233
  },
261
234
  ];
262
235
 
236
+ // Stop the process when the user cancels/exits the prompt.
237
+ const onCancel = () => {
238
+ process.exit(0);
239
+ };
240
+
263
241
  const answers: Record<string, string> = {};
264
242
  for (const query of promptQueries) {
265
243
  const { name, resolvedValue } = query;
266
- answers[name] = resolvedValue ?? options[name] ?? (await prompts(query))[name];
244
+ answers[name] = resolvedValue ?? options[name] ?? (await prompts(query, { onCancel }))[name];
267
245
  }
268
246
 
269
247
  const { slug, name, description, package: projectPackage, author, license, repo } = answers;
@@ -282,6 +260,33 @@ async function askForSubstitutionDataAsync(
282
260
  };
283
261
  }
284
262
 
263
+ /**
264
+ * Checks whether the target directory is empty and if not, asks the user to confirm if he wants to continue.
265
+ */
266
+ async function confirmTargetDirAsync(targetDir: string): Promise<void> {
267
+ const files = await fs.readdir(targetDir);
268
+
269
+ if (files.length === 0) {
270
+ return;
271
+ }
272
+ const { shouldContinue } = await prompts(
273
+ {
274
+ type: 'confirm',
275
+ name: 'shouldContinue',
276
+ message: `The target directory ${chalk.magenta(
277
+ targetDir
278
+ )} is not empty.\nDo you want to continue anyway?`,
279
+ initial: true,
280
+ },
281
+ {
282
+ onCancel: () => false,
283
+ }
284
+ );
285
+ if (!shouldContinue) {
286
+ process.exit(0);
287
+ }
288
+ }
289
+
285
290
  const program = new Command();
286
291
 
287
292
  program
@@ -301,6 +306,7 @@ program
301
306
  .option('-r, --repo <repo_url>', 'The URL to the repository.')
302
307
  .option('--with-readme', 'Whether to include README.md file.', false)
303
308
  .option('--with-changelog', 'Whether to include CHANGELOG.md file.', false)
309
+ .option('--no-example', 'Whether to skip creating the example app.', false)
304
310
  .action(main);
305
311
 
306
312
  program.parse(process.argv);
@@ -0,0 +1,153 @@
1
+ import spawnAsync from '@expo/spawn-async';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+
5
+ import { installDependencies } from './packageManager';
6
+ import { PackageManagerName } from './resolvePackageManager';
7
+ import { SubstitutionData } from './types';
8
+ import { newStep } from './utils';
9
+
10
+ // These dependencies will be removed from the example app (`expo init` adds them)
11
+ const DEPENDENCIES_TO_REMOVE = ['expo-status-bar', 'expo-splash-screen'];
12
+
13
+ /**
14
+ * Initializes a new Expo project as an example app.
15
+ */
16
+ export async function createExampleApp(
17
+ data: SubstitutionData,
18
+ targetDir: string,
19
+ packageManager: PackageManagerName
20
+ ): Promise<void> {
21
+ const exampleProjectSlug = `${data.project.slug}-example`;
22
+
23
+ await newStep('Initializing the example app', async (step) => {
24
+ await spawnAsync(
25
+ 'expo',
26
+ ['init', exampleProjectSlug, '--template', 'expo-template-blank-typescript'],
27
+ {
28
+ cwd: targetDir,
29
+ stdio: ['ignore', 'ignore', 'inherit'],
30
+ }
31
+ );
32
+ step.succeed('Initialized the example app');
33
+ });
34
+
35
+ // `expo init` creates a new folder with the same name as the project slug
36
+ const appTmpPath = path.join(targetDir, exampleProjectSlug);
37
+
38
+ // Path to the target example dir
39
+ const appTargetPath = path.join(targetDir, 'example');
40
+
41
+ await newStep('Configuring the example app', async (step) => {
42
+ // "example" folder already exists and contains template files,
43
+ // that should replace these created by `expo init`.
44
+ await moveFiles(appTargetPath, appTmpPath);
45
+
46
+ // Cleanup the "example" dir
47
+ await fs.rmdir(appTargetPath);
48
+
49
+ // Move the temporary example app to "example" dir
50
+ await fs.rename(appTmpPath, appTargetPath);
51
+
52
+ await addMissingAppConfigFields(appTargetPath, data);
53
+
54
+ step.succeed('Configured the example app');
55
+ });
56
+
57
+ await prebuildExampleApp(appTargetPath);
58
+
59
+ await modifyPackageJson(appTargetPath);
60
+
61
+ await newStep('Installing dependencies in the example app', async (step) => {
62
+ await installDependencies(packageManager, appTargetPath);
63
+ await podInstall(appTargetPath);
64
+ step.succeed('Installed dependencies in the example app');
65
+ });
66
+ }
67
+
68
+ /**
69
+ * Copies files from one directory to another.
70
+ */
71
+ async function moveFiles(fromPath: string, toPath: string): Promise<void> {
72
+ for (const file of await fs.readdir(fromPath)) {
73
+ await fs.move(path.join(fromPath, file), path.join(toPath, file), {
74
+ overwrite: true,
75
+ });
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Adds missing configuration that are required to run `expo prebuild`.
81
+ */
82
+ async function addMissingAppConfigFields(appPath: string, data: SubstitutionData): Promise<void> {
83
+ const appConfigPath = path.join(appPath, 'app.json');
84
+ const appConfig = await fs.readJson(appConfigPath);
85
+ const appId = `${data.project.package}.example`;
86
+
87
+ // Android package name needs to be added to app.json
88
+ if (!appConfig.expo.android) {
89
+ appConfig.expo.android = {};
90
+ }
91
+ appConfig.expo.android.package = appId;
92
+
93
+ // Specify iOS bundle identifier
94
+ if (!appConfig.expo.ios) {
95
+ appConfig.expo.ios = {};
96
+ }
97
+ appConfig.expo.ios.bundleIdentifier = appId;
98
+
99
+ await fs.writeJson(appConfigPath, appConfig, {
100
+ spaces: 2,
101
+ });
102
+ }
103
+
104
+ /**
105
+ * Applies necessary changes to **package.json** of the example app.
106
+ * It means setting the autolinking config and removing unnecessary dependencies.
107
+ */
108
+ async function modifyPackageJson(appPath: string): Promise<void> {
109
+ const packageJsonPath = path.join(appPath, 'package.json');
110
+ const packageJson = await fs.readJson(packageJsonPath);
111
+
112
+ if (!packageJson.expo) {
113
+ packageJson.expo = {};
114
+ }
115
+
116
+ // Set the native modules dir to the root folder,
117
+ // so that the autolinking can detect and link the module.
118
+ packageJson.expo.autolinking = {
119
+ nativeModulesDir: '..',
120
+ };
121
+
122
+ // Remove unnecessary dependencies
123
+ for (const dependencyToRemove of DEPENDENCIES_TO_REMOVE) {
124
+ delete packageJson.dependencies[dependencyToRemove];
125
+ }
126
+
127
+ await fs.writeJson(packageJsonPath, packageJson, {
128
+ spaces: 2,
129
+ });
130
+ }
131
+
132
+ /**
133
+ * Runs `expo prebuild` in the example app.
134
+ */
135
+ async function prebuildExampleApp(exampleAppPath: string): Promise<void> {
136
+ await newStep('Prebuilding the example app', async (step) => {
137
+ await spawnAsync('expo', ['prebuild', '--no-install'], {
138
+ cwd: exampleAppPath,
139
+ stdio: ['ignore', 'ignore', 'pipe'],
140
+ });
141
+ step.succeed('Prebuilt the example app');
142
+ });
143
+ }
144
+
145
+ /**
146
+ * Runs `pod install` in the iOS project at the given path.
147
+ */
148
+ async function podInstall(appPath: string): Promise<void> {
149
+ await spawnAsync('pod', ['install'], {
150
+ cwd: path.join(appPath, 'ios'),
151
+ stdio: ['ignore', 'ignore', 'pipe'],
152
+ });
153
+ }
@@ -0,0 +1,14 @@
1
+ import spawnAsync from '@expo/spawn-async';
2
+
3
+ import { PackageManagerName } from './resolvePackageManager';
4
+
5
+ export async function installDependencies(
6
+ packageManager: PackageManagerName,
7
+ appPath: string,
8
+ ...args: string[]
9
+ ) {
10
+ await spawnAsync(packageManager, ['install', ...args], {
11
+ cwd: appPath,
12
+ stdio: 'ignore',
13
+ });
14
+ }
@@ -0,0 +1,34 @@
1
+ import { execSync } from 'child_process';
2
+
3
+ export type PackageManagerName = 'npm' | 'pnpm' | 'yarn';
4
+
5
+ /** Determine which package manager to use for installing dependencies based on how the process was started. */
6
+ export function resolvePackageManager(): PackageManagerName {
7
+ // Attempt to detect if the user started the command using `yarn` or `pnpm`
8
+ const userAgent = process.env.npm_config_user_agent;
9
+
10
+ if (userAgent?.startsWith('yarn')) {
11
+ return 'yarn';
12
+ } else if (userAgent?.startsWith('pnpm')) {
13
+ return 'pnpm';
14
+ } else if (userAgent?.startsWith('npm')) {
15
+ return 'npm';
16
+ }
17
+
18
+ // Try availability
19
+ if (isPackageManagerAvailable('yarn')) {
20
+ return 'yarn';
21
+ } else if (isPackageManagerAvailable('pnpm')) {
22
+ return 'pnpm';
23
+ }
24
+
25
+ return 'npm';
26
+ }
27
+
28
+ function isPackageManagerAvailable(manager: PackageManagerName): boolean {
29
+ try {
30
+ execSync(`${manager} --version`, { stdio: 'ignore' });
31
+ return true;
32
+ } catch {}
33
+ return false;
34
+ }
package/src/types.ts ADDED
@@ -0,0 +1,39 @@
1
+ import { PromptObject } from 'prompts';
2
+
3
+ /**
4
+ * Possible command options.
5
+ */
6
+ export type CommandOptions = {
7
+ target: string;
8
+ source?: string;
9
+ name?: string;
10
+ description?: string;
11
+ package?: string;
12
+ author?: string;
13
+ license?: string;
14
+ repo?: string;
15
+ withReadme: boolean;
16
+ withChangelog: boolean;
17
+ example: boolean;
18
+ };
19
+
20
+ /**
21
+ * Represents an object that is passed to `ejs` when rendering the template.
22
+ */
23
+ export type SubstitutionData = {
24
+ project: {
25
+ slug: string;
26
+ name: string;
27
+ version: string;
28
+ description: string;
29
+ package: string;
30
+ };
31
+ author: string;
32
+ license: string;
33
+ repo: string;
34
+ };
35
+
36
+ export type CustomPromptObject = PromptObject & {
37
+ name: string;
38
+ resolvedValue?: string | null;
39
+ };
package/src/utils.ts ADDED
@@ -0,0 +1,28 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+
4
+ export type StepOptions = ora.Options;
5
+
6
+ export async function newStep<Result>(
7
+ title: string,
8
+ action: (step: ora.Ora) => Promise<Result> | Result,
9
+ options: StepOptions = {}
10
+ ): Promise<Result> {
11
+ const disabled = process.env.CI || process.env.EXPO_DEBUG;
12
+ const step = ora({
13
+ text: chalk.bold(title),
14
+ isEnabled: !disabled,
15
+ stream: disabled ? process.stdout : process.stderr,
16
+ ...options,
17
+ });
18
+
19
+ step.start();
20
+
21
+ try {
22
+ return await action(step);
23
+ } catch (error) {
24
+ step.fail();
25
+ console.error(error);
26
+ process.exit(1);
27
+ }
28
+ }