create-expo-module 0.3.0 → 0.3.1

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.
@@ -11,6 +11,9 @@ const ejs_1 = __importDefault(require("ejs"));
11
11
  const fs_extra_1 = __importDefault(require("fs-extra"));
12
12
  const path_1 = __importDefault(require("path"));
13
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");
14
17
  const resolvePackageManager_1 = require("./resolvePackageManager");
15
18
  const packageJson = require('../package.json');
16
19
  // `yarn run` may change the current working dir, then we should use `INIT_CWD` env.
@@ -26,9 +29,9 @@ const IGNORES_PATHS = ['.DS_Store', 'build', 'node_modules', 'package.json'];
26
29
  */
27
30
  async function main(target, options) {
28
31
  const targetDir = target ? path_1.default.join(CWD, target) : CWD;
32
+ await fs_extra_1.default.ensureDir(targetDir);
29
33
  await confirmTargetDirAsync(targetDir);
30
34
  options.target = targetDir;
31
- await fs_extra_1.default.ensureDir(targetDir);
32
35
  const data = await askForSubstitutionDataAsync(targetDir, options);
33
36
  const packageManager = await (0, resolvePackageManager_1.resolvePackageManager)();
34
37
  const packagePath = options.source
@@ -49,6 +52,8 @@ async function main(target, options) {
49
52
  const renderedContent = ejs_1.default.render(template, data);
50
53
  await fs_extra_1.default.outputFile(toPath, renderedContent, { encoding: 'utf8' });
51
54
  }
55
+ // Install dependencies and build
56
+ await postActionsAsync(packageManager, targetDir);
52
57
  if (!options.source) {
53
58
  // Files in the downloaded tarball are wrapped in `package` dir.
54
59
  // We should remove it after all.
@@ -60,8 +65,10 @@ async function main(target, options) {
60
65
  if (!options.withChangelog) {
61
66
  await fs_extra_1.default.remove(path_1.default.join(targetDir, 'CHANGELOG.md'));
62
67
  }
63
- // Install dependencies and build
64
- await postActionsAsync(packageManager, targetDir);
68
+ if (options.example) {
69
+ // Create "example" folder
70
+ await (0, createExampleApp_1.createExampleApp)(data, targetDir, packageManager);
71
+ }
65
72
  console.log('✅ Successfully created Expo module');
66
73
  }
67
74
  /**
@@ -121,16 +128,13 @@ async function downloadPackageAsync(targetDir) {
121
128
  * Installs dependencies and builds TypeScript files.
122
129
  */
123
130
  async function postActionsAsync(packageManager, targetDir) {
124
- async function run(...args) {
125
- await (0, spawn_async_1.default)(packageManager, args, {
126
- cwd: targetDir,
127
- stdio: 'ignore',
128
- });
129
- }
130
- console.log('📦 Installing dependencies...');
131
- await run('install');
131
+ console.log('📦 Installing module dependencies...');
132
+ await (0, packageManager_1.installDependencies)(packageManager, targetDir);
132
133
  console.log('🛠 Compiling TypeScript files...');
133
- await run('run', 'build');
134
+ await (0, spawn_async_1.default)(packageManager, ['run', 'build'], {
135
+ cwd: targetDir,
136
+ stdio: 'ignore',
137
+ });
134
138
  }
135
139
  /**
136
140
  * Asks the user for some data necessary to render the template.
@@ -139,6 +143,7 @@ async function postActionsAsync(packageManager, targetDir) {
139
143
  async function askForSubstitutionDataAsync(targetDir, options) {
140
144
  var _a, _b;
141
145
  const defaultPackageSlug = path_1.default.basename(targetDir);
146
+ const useDefaultSlug = options.target && (0, validate_npm_package_name_1.default)(defaultPackageSlug);
142
147
  const defaultProjectName = defaultPackageSlug
143
148
  .replace(/^./, (match) => match.toUpperCase())
144
149
  .replace(/\W+(\w)/g, (_, p1) => p1.toUpperCase());
@@ -148,7 +153,8 @@ async function askForSubstitutionDataAsync(targetDir, options) {
148
153
  name: 'slug',
149
154
  message: 'What is the package slug?',
150
155
  initial: defaultPackageSlug,
151
- resolvedValue: options.target ? defaultPackageSlug : null,
156
+ resolvedValue: useDefaultSlug ? defaultPackageSlug : null,
157
+ validate: (input) => (0, validate_npm_package_name_1.default)(input).validForNewPackages || 'Must be a valid npm package name',
152
158
  },
153
159
  {
154
160
  type: 'text',
@@ -160,6 +166,7 @@ async function askForSubstitutionDataAsync(targetDir, options) {
160
166
  type: 'text',
161
167
  name: 'description',
162
168
  message: 'How would you describe the module?',
169
+ validate: (input) => !!input || 'Cannot be empty',
163
170
  },
164
171
  {
165
172
  type: 'text',
@@ -183,6 +190,7 @@ async function askForSubstitutionDataAsync(targetDir, options) {
183
190
  type: 'text',
184
191
  name: 'repo',
185
192
  message: 'What is the repository URL?',
193
+ validate: (input) => /^https?:\/\//.test(input) || 'Must be a valid URL',
186
194
  },
187
195
  ];
188
196
  // Stop the process when the user cancels/exits the prompt.
@@ -243,6 +251,7 @@ program
243
251
  .option('-r, --repo <repo_url>', 'The URL to the repository.')
244
252
  .option('--with-readme', 'Whether to include README.md file.', false)
245
253
  .option('--with-changelog', 'Whether to include CHANGELOG.md file.', false)
254
+ .option('--no-example', 'Whether to skip creating the example app.', false)
246
255
  .action(main);
247
256
  program.parse(process.argv);
248
257
  //# 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 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, { PromptObject } from 'prompts';\n\nimport { PackageManagerName, resolvePackageManager } from './resolvePackageManager';\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\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 confirmTargetDirAsync(targetDir);\n\n options.target = targetDir;\n await fs.ensureDir(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 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 * Installs dependencies and builds TypeScript files.\n */\nasync function postActionsAsync(packageManager: PackageManagerName, 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 // 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 .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 { PackageManagerName, resolvePackageManager } from './resolvePackageManager';\nimport { CommandOptions, CustomPromptObject, SubstitutionData } from './types';\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 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 // Install dependencies and build\n await postActionsAsync(packageManager, targetDir);\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('✅ 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 * Installs dependencies and builds TypeScript files.\n */\nasync function postActionsAsync(packageManager: PackageManagerName, targetDir: string) {\n console.log('📦 Installing module dependencies...');\n await installDependencies(packageManager, targetDir);\n\n console.log('🛠 Compiling TypeScript files...');\n await spawnAsync(packageManager, ['run', 'build'], {\n cwd: targetDir,\n stdio: 'ignore',\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,129 @@
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
+ // These dependencies will be removed from the example app (`expo init` adds them)
12
+ const DEPENDENCIES_TO_REMOVE = ['expo-status-bar', 'expo-splash-screen'];
13
+ /**
14
+ * Initializes a new Expo project as an example app.
15
+ */
16
+ async function createExampleApp(data, targetDir, packageManager) {
17
+ console.log('🎭 Creating the example app...');
18
+ const exampleProjectSlug = `${data.project.slug}-example`;
19
+ await (0, spawn_async_1.default)('expo', ['init', exampleProjectSlug, '--template', 'expo-template-blank-typescript'], {
20
+ cwd: targetDir,
21
+ stdio: ['ignore', 'ignore', 'inherit'],
22
+ });
23
+ // `expo init` creates a new folder with the same name as the project slug
24
+ const appTmpPath = path_1.default.join(targetDir, exampleProjectSlug);
25
+ // Path to the target example dir
26
+ const appTargetPath = path_1.default.join(targetDir, 'example');
27
+ console.log('🛠 Configuring the example app...');
28
+ // "example" folder already exists and contains template files,
29
+ // that should replace these created by `expo init`.
30
+ await moveFiles(appTargetPath, appTmpPath);
31
+ // Cleanup the "example" dir
32
+ await fs_extra_1.default.rmdir(appTargetPath);
33
+ // Move the temporary example app to "example" dir
34
+ await fs_extra_1.default.rename(appTmpPath, appTargetPath);
35
+ await addMissingAppConfigFields(appTargetPath, data);
36
+ console.log('👷 Prebuilding the example app...');
37
+ await prebuildExampleApp(appTargetPath);
38
+ await modifyPackageJson(appTargetPath);
39
+ console.log('📦 Installing dependencies in the example app...');
40
+ await (0, packageManager_1.installDependencies)(packageManager, appTargetPath);
41
+ console.log('🥥 Installing iOS pods in the example app...');
42
+ await podInstall(appTargetPath);
43
+ }
44
+ exports.createExampleApp = createExampleApp;
45
+ /**
46
+ * Copies files from one directory to another.
47
+ */
48
+ async function moveFiles(fromPath, toPath) {
49
+ for (const file of await fs_extra_1.default.readdir(fromPath)) {
50
+ await fs_extra_1.default.move(path_1.default.join(fromPath, file), path_1.default.join(toPath, file), {
51
+ overwrite: true,
52
+ });
53
+ }
54
+ }
55
+ /**
56
+ * Adds missing configuration that are required to run `expo prebuild`.
57
+ */
58
+ async function addMissingAppConfigFields(appPath, data) {
59
+ const appConfigPath = path_1.default.join(appPath, 'app.json');
60
+ const appConfig = await fs_extra_1.default.readJson(appConfigPath);
61
+ const appId = `${data.project.package}.example`;
62
+ // Android package name needs to be added to app.json
63
+ if (!appConfig.expo.android) {
64
+ appConfig.expo.android = {};
65
+ }
66
+ appConfig.expo.android.package = appId;
67
+ // Specify iOS bundle identifier
68
+ if (!appConfig.expo.ios) {
69
+ appConfig.expo.ios = {};
70
+ }
71
+ appConfig.expo.ios.bundleIdentifier = appId;
72
+ await fs_extra_1.default.writeJson(appConfigPath, appConfig, {
73
+ spaces: 2,
74
+ });
75
+ }
76
+ /**
77
+ * Applies necessary changes to **package.json** of the example app.
78
+ * It means setting the autolinking config and removing unnecessary dependencies.
79
+ */
80
+ async function modifyPackageJson(appPath) {
81
+ const packageJsonPath = path_1.default.join(appPath, 'package.json');
82
+ const packageJson = await fs_extra_1.default.readJson(packageJsonPath);
83
+ if (!packageJson.expo) {
84
+ packageJson.expo = {};
85
+ }
86
+ // Set the native modules dir to the root folder,
87
+ // so that the autolinking can detect and link the module.
88
+ packageJson.expo.autolinking = {
89
+ nativeModulesDir: '..',
90
+ };
91
+ // Remove unnecessary dependencies
92
+ for (const dependencyToRemove of DEPENDENCIES_TO_REMOVE) {
93
+ delete packageJson.dependencies[dependencyToRemove];
94
+ }
95
+ await fs_extra_1.default.writeJson(packageJsonPath, packageJson, {
96
+ spaces: 2,
97
+ });
98
+ }
99
+ /**
100
+ * Runs `expo prebuild` in the example app.
101
+ */
102
+ async function prebuildExampleApp(exampleAppPath) {
103
+ try {
104
+ await (0, spawn_async_1.default)('expo', ['prebuild', '--no-install'], {
105
+ cwd: exampleAppPath,
106
+ stdio: ['ignore', 'ignore', 'pipe'],
107
+ });
108
+ }
109
+ catch (error) {
110
+ console.error(error.stderr);
111
+ process.exit(1);
112
+ }
113
+ }
114
+ /**
115
+ * Runs `pod install` in the iOS project at the given path.
116
+ */
117
+ async function podInstall(appPath) {
118
+ try {
119
+ await (0, spawn_async_1.default)('pod', ['install'], {
120
+ cwd: path_1.default.join(appPath, 'ios'),
121
+ stdio: ['ignore', 'ignore', 'pipe'],
122
+ });
123
+ }
124
+ catch (error) {
125
+ console.error(error.stderr);
126
+ process.exit(1);
127
+ }
128
+ }
129
+ //# 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';\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 console.log('🎭 Creating the example app...');\n\n const exampleProjectSlug = `${data.project.slug}-example`;\n\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\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 console.log('🛠 Configuring the example app...');\n\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 console.log('👷 Prebuilding the example app...');\n await prebuildExampleApp(appTargetPath);\n\n await modifyPackageJson(appTargetPath);\n\n console.log('📦 Installing dependencies in the example app...');\n await installDependencies(packageManager, appTargetPath);\n\n console.log('🥥 Installing iOS pods in the example app...');\n await podInstall(appTargetPath);\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 try {\n await spawnAsync('expo', ['prebuild', '--no-install'], {\n cwd: exampleAppPath,\n stdio: ['ignore', 'ignore', 'pipe'],\n });\n } catch (error: any) {\n console.error(error.stderr);\n process.exit(1);\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 try {\n await spawnAsync('pod', ['install'], {\n cwd: path.join(appPath, 'ios'),\n stdio: ['ignore', 'ignore', 'pipe'],\n });\n } catch (error: any) {\n console.error(error.stderr);\n process.exit(1);\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,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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-expo-module",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
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",
@@ -39,12 +39,13 @@
39
39
  "download-tarball": "^2.0.0",
40
40
  "ejs": "^3.1.7",
41
41
  "fs-extra": "^10.0.0",
42
- "prompts": "^2.4.2"
42
+ "prompts": "^2.4.2",
43
+ "validate-npm-package-name": "^4.0.0"
43
44
  },
44
45
  "devDependencies": {
45
46
  "@types/ejs": "^3.1.0",
46
47
  "@types/prompts": "^2.0.14",
47
48
  "expo-module-scripts": "^2.0.0"
48
49
  },
49
- "gitHead": "5e7c69008fe51f86e96877116602ef1a50a4655f"
50
+ "gitHead": "093d340c3fd67ff4375af3a471c7d044aee893c9"
50
51
  }
@@ -5,9 +5,13 @@ import downloadTarball from 'download-tarball';
5
5
  import ejs from 'ejs';
6
6
  import fs from 'fs-extra';
7
7
  import path from 'path';
8
- import prompts, { PromptObject } from 'prompts';
8
+ import prompts from 'prompts';
9
+ import validateNpmPackage from 'validate-npm-package-name';
9
10
 
11
+ import { createExampleApp } from './createExampleApp';
12
+ import { installDependencies } from './packageManager';
10
13
  import { PackageManagerName, resolvePackageManager } from './resolvePackageManager';
14
+ import { CommandOptions, CustomPromptObject, SubstitutionData } from './types';
11
15
 
12
16
  const packageJson = require('../package.json');
13
17
 
@@ -18,43 +22,6 @@ const CWD = process.env.INIT_CWD || process.cwd();
18
22
  // from `$package.json` file instead of the original one.
19
23
  const IGNORES_PATHS = ['.DS_Store', 'build', 'node_modules', 'package.json'];
20
24
 
21
- /**
22
- * Possible command options.
23
- */
24
- type CommandOptions = {
25
- target: string;
26
- source?: string;
27
- name?: string;
28
- description?: string;
29
- package?: string;
30
- author?: string;
31
- license?: string;
32
- repo?: string;
33
- withReadme: boolean;
34
- withChangelog: boolean;
35
- };
36
-
37
- /**
38
- * Represents an object that is passed to `ejs` when rendering the template.
39
- */
40
- type SubstitutionData = {
41
- project: {
42
- slug: string;
43
- name: string;
44
- version: string;
45
- description: string;
46
- package: string;
47
- };
48
- author: string;
49
- license: string;
50
- repo: string;
51
- };
52
-
53
- type CustomPromptObject = PromptObject & {
54
- name: string;
55
- resolvedValue?: string | null;
56
- };
57
-
58
25
  /**
59
26
  * The main function of the command.
60
27
  *
@@ -64,10 +31,10 @@ type CustomPromptObject = PromptObject & {
64
31
  async function main(target: string | undefined, options: CommandOptions) {
65
32
  const targetDir = target ? path.join(CWD, target) : CWD;
66
33
 
34
+ await fs.ensureDir(targetDir);
67
35
  await confirmTargetDirAsync(targetDir);
68
36
 
69
37
  options.target = targetDir;
70
- await fs.ensureDir(targetDir);
71
38
 
72
39
  const data = await askForSubstitutionDataAsync(targetDir, options);
73
40
  const packageManager = await resolvePackageManager();
@@ -93,6 +60,9 @@ async function main(target: string | undefined, options: CommandOptions) {
93
60
  await fs.outputFile(toPath, renderedContent, { encoding: 'utf8' });
94
61
  }
95
62
 
63
+ // Install dependencies and build
64
+ await postActionsAsync(packageManager, targetDir);
65
+
96
66
  if (!options.source) {
97
67
  // Files in the downloaded tarball are wrapped in `package` dir.
98
68
  // We should remove it after all.
@@ -104,9 +74,10 @@ async function main(target: string | undefined, options: CommandOptions) {
104
74
  if (!options.withChangelog) {
105
75
  await fs.remove(path.join(targetDir, 'CHANGELOG.md'));
106
76
  }
107
-
108
- // Install dependencies and build
109
- await postActionsAsync(packageManager, targetDir);
77
+ if (options.example) {
78
+ // Create "example" folder
79
+ await createExampleApp(data, targetDir, packageManager);
80
+ }
110
81
 
111
82
  console.log('✅ Successfully created Expo module');
112
83
  }
@@ -176,18 +147,14 @@ async function downloadPackageAsync(targetDir: string): Promise<string> {
176
147
  * Installs dependencies and builds TypeScript files.
177
148
  */
178
149
  async function postActionsAsync(packageManager: PackageManagerName, targetDir: string) {
179
- async function run(...args: string[]) {
180
- await spawnAsync(packageManager, args, {
181
- cwd: targetDir,
182
- stdio: 'ignore',
183
- });
184
- }
185
-
186
- console.log('📦 Installing dependencies...');
187
- await run('install');
150
+ console.log('📦 Installing module dependencies...');
151
+ await installDependencies(packageManager, targetDir);
188
152
 
189
153
  console.log('🛠 Compiling TypeScript files...');
190
- await run('run', 'build');
154
+ await spawnAsync(packageManager, ['run', 'build'], {
155
+ cwd: targetDir,
156
+ stdio: 'ignore',
157
+ });
191
158
  }
192
159
 
193
160
  /**
@@ -199,6 +166,7 @@ async function askForSubstitutionDataAsync(
199
166
  options: CommandOptions
200
167
  ): Promise<SubstitutionData> {
201
168
  const defaultPackageSlug = path.basename(targetDir);
169
+ const useDefaultSlug = options.target && validateNpmPackage(defaultPackageSlug);
202
170
  const defaultProjectName = defaultPackageSlug
203
171
  .replace(/^./, (match) => match.toUpperCase())
204
172
  .replace(/\W+(\w)/g, (_, p1) => p1.toUpperCase());
@@ -209,7 +177,9 @@ async function askForSubstitutionDataAsync(
209
177
  name: 'slug',
210
178
  message: 'What is the package slug?',
211
179
  initial: defaultPackageSlug,
212
- resolvedValue: options.target ? defaultPackageSlug : null,
180
+ resolvedValue: useDefaultSlug ? defaultPackageSlug : null,
181
+ validate: (input) =>
182
+ validateNpmPackage(input).validForNewPackages || 'Must be a valid npm package name',
213
183
  },
214
184
  {
215
185
  type: 'text',
@@ -221,6 +191,7 @@ async function askForSubstitutionDataAsync(
221
191
  type: 'text',
222
192
  name: 'description',
223
193
  message: 'How would you describe the module?',
194
+ validate: (input) => !!input || 'Cannot be empty',
224
195
  },
225
196
  {
226
197
  type: 'text',
@@ -244,6 +215,7 @@ async function askForSubstitutionDataAsync(
244
215
  type: 'text',
245
216
  name: 'repo',
246
217
  message: 'What is the repository URL?',
218
+ validate: (input) => /^https?:\/\//.test(input) || 'Must be a valid URL',
247
219
  },
248
220
  ];
249
221
 
@@ -320,6 +292,7 @@ program
320
292
  .option('-r, --repo <repo_url>', 'The URL to the repository.')
321
293
  .option('--with-readme', 'Whether to include README.md file.', false)
322
294
  .option('--with-changelog', 'Whether to include CHANGELOG.md file.', false)
295
+ .option('--no-example', 'Whether to skip creating the example app.', false)
323
296
  .action(main);
324
297
 
325
298
  program.parse(process.argv);
@@ -0,0 +1,157 @@
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
+
9
+ // These dependencies will be removed from the example app (`expo init` adds them)
10
+ const DEPENDENCIES_TO_REMOVE = ['expo-status-bar', 'expo-splash-screen'];
11
+
12
+ /**
13
+ * Initializes a new Expo project as an example app.
14
+ */
15
+ export async function createExampleApp(
16
+ data: SubstitutionData,
17
+ targetDir: string,
18
+ packageManager: PackageManagerName
19
+ ): Promise<void> {
20
+ console.log('🎭 Creating the example app...');
21
+
22
+ const exampleProjectSlug = `${data.project.slug}-example`;
23
+
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
+
33
+ // `expo init` creates a new folder with the same name as the project slug
34
+ const appTmpPath = path.join(targetDir, exampleProjectSlug);
35
+
36
+ // Path to the target example dir
37
+ const appTargetPath = path.join(targetDir, 'example');
38
+
39
+ console.log('🛠 Configuring the example app...');
40
+
41
+ // "example" folder already exists and contains template files,
42
+ // that should replace these created by `expo init`.
43
+ await moveFiles(appTargetPath, appTmpPath);
44
+
45
+ // Cleanup the "example" dir
46
+ await fs.rmdir(appTargetPath);
47
+
48
+ // Move the temporary example app to "example" dir
49
+ await fs.rename(appTmpPath, appTargetPath);
50
+
51
+ await addMissingAppConfigFields(appTargetPath, data);
52
+
53
+ console.log('👷 Prebuilding the example app...');
54
+ await prebuildExampleApp(appTargetPath);
55
+
56
+ await modifyPackageJson(appTargetPath);
57
+
58
+ console.log('📦 Installing dependencies in the example app...');
59
+ await installDependencies(packageManager, appTargetPath);
60
+
61
+ console.log('🥥 Installing iOS pods in the example app...');
62
+ await podInstall(appTargetPath);
63
+ }
64
+
65
+ /**
66
+ * Copies files from one directory to another.
67
+ */
68
+ async function moveFiles(fromPath: string, toPath: string): Promise<void> {
69
+ for (const file of await fs.readdir(fromPath)) {
70
+ await fs.move(path.join(fromPath, file), path.join(toPath, file), {
71
+ overwrite: true,
72
+ });
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Adds missing configuration that are required to run `expo prebuild`.
78
+ */
79
+ async function addMissingAppConfigFields(appPath: string, data: SubstitutionData): Promise<void> {
80
+ const appConfigPath = path.join(appPath, 'app.json');
81
+ const appConfig = await fs.readJson(appConfigPath);
82
+ const appId = `${data.project.package}.example`;
83
+
84
+ // Android package name needs to be added to app.json
85
+ if (!appConfig.expo.android) {
86
+ appConfig.expo.android = {};
87
+ }
88
+ appConfig.expo.android.package = appId;
89
+
90
+ // Specify iOS bundle identifier
91
+ if (!appConfig.expo.ios) {
92
+ appConfig.expo.ios = {};
93
+ }
94
+ appConfig.expo.ios.bundleIdentifier = appId;
95
+
96
+ await fs.writeJson(appConfigPath, appConfig, {
97
+ spaces: 2,
98
+ });
99
+ }
100
+
101
+ /**
102
+ * Applies necessary changes to **package.json** of the example app.
103
+ * It means setting the autolinking config and removing unnecessary dependencies.
104
+ */
105
+ async function modifyPackageJson(appPath: string): Promise<void> {
106
+ const packageJsonPath = path.join(appPath, 'package.json');
107
+ const packageJson = await fs.readJson(packageJsonPath);
108
+
109
+ if (!packageJson.expo) {
110
+ packageJson.expo = {};
111
+ }
112
+
113
+ // Set the native modules dir to the root folder,
114
+ // so that the autolinking can detect and link the module.
115
+ packageJson.expo.autolinking = {
116
+ nativeModulesDir: '..',
117
+ };
118
+
119
+ // Remove unnecessary dependencies
120
+ for (const dependencyToRemove of DEPENDENCIES_TO_REMOVE) {
121
+ delete packageJson.dependencies[dependencyToRemove];
122
+ }
123
+
124
+ await fs.writeJson(packageJsonPath, packageJson, {
125
+ spaces: 2,
126
+ });
127
+ }
128
+
129
+ /**
130
+ * Runs `expo prebuild` in the example app.
131
+ */
132
+ async function prebuildExampleApp(exampleAppPath: string): Promise<void> {
133
+ try {
134
+ await spawnAsync('expo', ['prebuild', '--no-install'], {
135
+ cwd: exampleAppPath,
136
+ stdio: ['ignore', 'ignore', 'pipe'],
137
+ });
138
+ } catch (error: any) {
139
+ console.error(error.stderr);
140
+ process.exit(1);
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Runs `pod install` in the iOS project at the given path.
146
+ */
147
+ async function podInstall(appPath: string): Promise<void> {
148
+ try {
149
+ await spawnAsync('pod', ['install'], {
150
+ cwd: path.join(appPath, 'ios'),
151
+ stdio: ['ignore', 'ignore', 'pipe'],
152
+ });
153
+ } catch (error: any) {
154
+ console.error(error.stderr);
155
+ process.exit(1);
156
+ }
157
+ }
@@ -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
+ }
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
+ };