create-expo-module 0.1.1 → 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.
- package/build/create-expo-module.js +52 -31
- package/build/create-expo-module.js.map +1 -1
- package/build/createExampleApp.d.ts +6 -0
- package/build/createExampleApp.js +129 -0
- package/build/createExampleApp.js.map +1 -0
- package/build/packageManager.d.ts +2 -0
- package/build/packageManager.js +15 -0
- package/build/packageManager.js.map +1 -0
- package/build/resolvePackageManager.d.ts +3 -0
- package/build/resolvePackageManager.js +36 -0
- package/build/resolvePackageManager.js.map +1 -0
- package/build/types.d.ts +36 -0
- package/build/types.js +3 -0
- package/build/types.js.map +1 -0
- package/package.json +7 -5
- package/src/create-expo-module.ts +67 -75
- package/src/createExampleApp.ts +157 -0
- package/src/packageManager.ts +14 -0
- package/src/resolvePackageManager.ts +34 -0
- package/src/types.ts +39 -0
|
@@ -4,12 +4,17 @@ 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");
|
|
13
18
|
const packageJson = require('../package.json');
|
|
14
19
|
// `yarn run` may change the current working dir, then we should use `INIT_CWD` env.
|
|
15
20
|
const CWD = process.env.INIT_CWD || process.cwd();
|
|
@@ -24,10 +29,11 @@ const IGNORES_PATHS = ['.DS_Store', 'build', 'node_modules', 'package.json'];
|
|
|
24
29
|
*/
|
|
25
30
|
async function main(target, options) {
|
|
26
31
|
const targetDir = target ? path_1.default.join(CWD, target) : CWD;
|
|
27
|
-
options.target = targetDir;
|
|
28
32
|
await fs_extra_1.default.ensureDir(targetDir);
|
|
33
|
+
await confirmTargetDirAsync(targetDir);
|
|
34
|
+
options.target = targetDir;
|
|
29
35
|
const data = await askForSubstitutionDataAsync(targetDir, options);
|
|
30
|
-
const packageManager = await
|
|
36
|
+
const packageManager = await (0, resolvePackageManager_1.resolvePackageManager)();
|
|
31
37
|
const packagePath = options.source
|
|
32
38
|
? path_1.default.join(CWD, options.source)
|
|
33
39
|
: await downloadPackageAsync(targetDir);
|
|
@@ -46,6 +52,8 @@ async function main(target, options) {
|
|
|
46
52
|
const renderedContent = ejs_1.default.render(template, data);
|
|
47
53
|
await fs_extra_1.default.outputFile(toPath, renderedContent, { encoding: 'utf8' });
|
|
48
54
|
}
|
|
55
|
+
// Install dependencies and build
|
|
56
|
+
await postActionsAsync(packageManager, targetDir);
|
|
49
57
|
if (!options.source) {
|
|
50
58
|
// Files in the downloaded tarball are wrapped in `package` dir.
|
|
51
59
|
// We should remove it after all.
|
|
@@ -57,8 +65,10 @@ async function main(target, options) {
|
|
|
57
65
|
if (!options.withChangelog) {
|
|
58
66
|
await fs_extra_1.default.remove(path_1.default.join(targetDir, 'CHANGELOG.md'));
|
|
59
67
|
}
|
|
60
|
-
|
|
61
|
-
|
|
68
|
+
if (options.example) {
|
|
69
|
+
// Create "example" folder
|
|
70
|
+
await (0, createExampleApp_1.createExampleApp)(data, targetDir, packageManager);
|
|
71
|
+
}
|
|
62
72
|
console.log('✅ Successfully created Expo module');
|
|
63
73
|
}
|
|
64
74
|
/**
|
|
@@ -98,7 +108,7 @@ async function npmWhoamiAsync(targetDir) {
|
|
|
98
108
|
const { stdout } = await (0, spawn_async_1.default)('npm', ['whoami'], { cwd: targetDir });
|
|
99
109
|
return stdout.trim();
|
|
100
110
|
}
|
|
101
|
-
catch
|
|
111
|
+
catch {
|
|
102
112
|
return null;
|
|
103
113
|
}
|
|
104
114
|
}
|
|
@@ -114,35 +124,17 @@ async function downloadPackageAsync(targetDir) {
|
|
|
114
124
|
});
|
|
115
125
|
return path_1.default.join(targetDir, 'package');
|
|
116
126
|
}
|
|
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
|
-
],
|
|
129
|
-
});
|
|
130
|
-
return packageManager;
|
|
131
|
-
}
|
|
132
127
|
/**
|
|
133
128
|
* Installs dependencies and builds TypeScript files.
|
|
134
129
|
*/
|
|
135
130
|
async function postActionsAsync(packageManager, targetDir) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
cwd: targetDir,
|
|
139
|
-
stdio: 'ignore',
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
console.log('📦 Installing dependencies...');
|
|
143
|
-
await run('install');
|
|
131
|
+
console.log('📦 Installing module dependencies...');
|
|
132
|
+
await (0, packageManager_1.installDependencies)(packageManager, targetDir);
|
|
144
133
|
console.log('🛠 Compiling TypeScript files...');
|
|
145
|
-
await
|
|
134
|
+
await (0, spawn_async_1.default)(packageManager, ['run', 'build'], {
|
|
135
|
+
cwd: targetDir,
|
|
136
|
+
stdio: 'ignore',
|
|
137
|
+
});
|
|
146
138
|
}
|
|
147
139
|
/**
|
|
148
140
|
* Asks the user for some data necessary to render the template.
|
|
@@ -151,6 +143,7 @@ async function postActionsAsync(packageManager, targetDir) {
|
|
|
151
143
|
async function askForSubstitutionDataAsync(targetDir, options) {
|
|
152
144
|
var _a, _b;
|
|
153
145
|
const defaultPackageSlug = path_1.default.basename(targetDir);
|
|
146
|
+
const useDefaultSlug = options.target && (0, validate_npm_package_name_1.default)(defaultPackageSlug);
|
|
154
147
|
const defaultProjectName = defaultPackageSlug
|
|
155
148
|
.replace(/^./, (match) => match.toUpperCase())
|
|
156
149
|
.replace(/\W+(\w)/g, (_, p1) => p1.toUpperCase());
|
|
@@ -160,7 +153,8 @@ async function askForSubstitutionDataAsync(targetDir, options) {
|
|
|
160
153
|
name: 'slug',
|
|
161
154
|
message: 'What is the package slug?',
|
|
162
155
|
initial: defaultPackageSlug,
|
|
163
|
-
resolvedValue:
|
|
156
|
+
resolvedValue: useDefaultSlug ? defaultPackageSlug : null,
|
|
157
|
+
validate: (input) => (0, validate_npm_package_name_1.default)(input).validForNewPackages || 'Must be a valid npm package name',
|
|
164
158
|
},
|
|
165
159
|
{
|
|
166
160
|
type: 'text',
|
|
@@ -172,6 +166,7 @@ async function askForSubstitutionDataAsync(targetDir, options) {
|
|
|
172
166
|
type: 'text',
|
|
173
167
|
name: 'description',
|
|
174
168
|
message: 'How would you describe the module?',
|
|
169
|
+
validate: (input) => !!input || 'Cannot be empty',
|
|
175
170
|
},
|
|
176
171
|
{
|
|
177
172
|
type: 'text',
|
|
@@ -195,12 +190,17 @@ async function askForSubstitutionDataAsync(targetDir, options) {
|
|
|
195
190
|
type: 'text',
|
|
196
191
|
name: 'repo',
|
|
197
192
|
message: 'What is the repository URL?',
|
|
193
|
+
validate: (input) => /^https?:\/\//.test(input) || 'Must be a valid URL',
|
|
198
194
|
},
|
|
199
195
|
];
|
|
196
|
+
// Stop the process when the user cancels/exits the prompt.
|
|
197
|
+
const onCancel = () => {
|
|
198
|
+
process.exit(0);
|
|
199
|
+
};
|
|
200
200
|
const answers = {};
|
|
201
201
|
for (const query of promptQueries) {
|
|
202
202
|
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];
|
|
203
|
+
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
204
|
}
|
|
205
205
|
const { slug, name, description, package: projectPackage, author, license, repo } = answers;
|
|
206
206
|
return {
|
|
@@ -216,6 +216,26 @@ async function askForSubstitutionDataAsync(targetDir, options) {
|
|
|
216
216
|
repo,
|
|
217
217
|
};
|
|
218
218
|
}
|
|
219
|
+
/**
|
|
220
|
+
* Checks whether the target directory is empty and if not, asks the user to confirm if he wants to continue.
|
|
221
|
+
*/
|
|
222
|
+
async function confirmTargetDirAsync(targetDir) {
|
|
223
|
+
const files = await fs_extra_1.default.readdir(targetDir);
|
|
224
|
+
if (files.length === 0) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const { shouldContinue } = await (0, prompts_1.default)({
|
|
228
|
+
type: 'confirm',
|
|
229
|
+
name: 'shouldContinue',
|
|
230
|
+
message: `The target directory ${chalk_1.default.magenta(targetDir)} is not empty.\nDo you want to continue anyway?`,
|
|
231
|
+
initial: true,
|
|
232
|
+
}, {
|
|
233
|
+
onCancel: () => false,
|
|
234
|
+
});
|
|
235
|
+
if (!shouldContinue) {
|
|
236
|
+
process.exit(0);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
219
239
|
const program = new commander_1.Command();
|
|
220
240
|
program
|
|
221
241
|
.name(packageJson.name)
|
|
@@ -231,6 +251,7 @@ program
|
|
|
231
251
|
.option('-r, --repo <repo_url>', 'The URL to the repository.')
|
|
232
252
|
.option('--with-readme', 'Whether to include README.md file.', false)
|
|
233
253
|
.option('--with-changelog', 'Whether to include CHANGELOG.md file.', false)
|
|
254
|
+
.option('--no-example', 'Whether to skip creating the example app.', false)
|
|
234
255
|
.action(main);
|
|
235
256
|
program.parse(process.argv);
|
|
236
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 { 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 (e) {\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 { 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":";;;;;;AAAA,oEAA2C;AAC3C,wDAA0B;AAC1B,gDAAwB;AAExB,qDAAuD;AAIvD,kFAAkF;AAClF,MAAM,sBAAsB,GAAG,CAAC,iBAAiB,EAAE,oBAAoB,CAAC,CAAC;AAEzE;;GAEG;AACI,KAAK,UAAU,gBAAgB,CACpC,IAAsB,EACtB,SAAiB,EACjB,cAAkC;IAElC,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;IAE9C,MAAM,kBAAkB,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,UAAU,CAAC;IAE1D,MAAM,IAAA,qBAAU,EACd,MAAM,EACN,CAAC,MAAM,EAAE,kBAAkB,EAAE,YAAY,EAAE,gCAAgC,CAAC,EAC5E;QACE,GAAG,EAAE,SAAS;QACd,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC;KACvC,CACF,CAAC;IAEF,0EAA0E;IAC1E,MAAM,UAAU,GAAG,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,kBAAkB,CAAC,CAAC;IAE5D,iCAAiC;IACjC,MAAM,aAAa,GAAG,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IAEtD,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;IAElD,+DAA+D;IAC/D,oDAAoD;IACpD,MAAM,SAAS,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC;IAE3C,4BAA4B;IAC5B,MAAM,kBAAE,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IAE9B,kDAAkD;IAClD,MAAM,kBAAE,CAAC,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;IAE3C,MAAM,yBAAyB,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;IAErD,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;IACjD,MAAM,kBAAkB,CAAC,aAAa,CAAC,CAAC;IAExC,MAAM,iBAAiB,CAAC,aAAa,CAAC,CAAC;IAEvC,OAAO,CAAC,GAAG,CAAC,kDAAkD,CAAC,CAAC;IAChE,MAAM,IAAA,oCAAmB,EAAC,cAAc,EAAE,aAAa,CAAC,CAAC;IAEzD,OAAO,CAAC,GAAG,CAAC,8CAA8C,CAAC,CAAC;IAC5D,MAAM,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,CAAC;AAhDD,4CAgDC;AAED;;GAEG;AACH,KAAK,UAAU,SAAS,CAAC,QAAgB,EAAE,MAAc;IACvD,KAAK,MAAM,IAAI,IAAI,MAAM,kBAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE;QAC7C,MAAM,kBAAE,CAAC,IAAI,CAAC,cAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,cAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE;YAChE,SAAS,EAAE,IAAI;SAChB,CAAC,CAAC;KACJ;AACH,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,yBAAyB,CAAC,OAAe,EAAE,IAAsB;IAC9E,MAAM,aAAa,GAAG,cAAI,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IACrD,MAAM,SAAS,GAAG,MAAM,kBAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;IACnD,MAAM,KAAK,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,UAAU,CAAC;IAEhD,qDAAqD;IACrD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE;QAC3B,SAAS,CAAC,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;KAC7B;IACD,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,GAAG,KAAK,CAAC;IAEvC,gCAAgC;IAChC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE;QACvB,SAAS,CAAC,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC;KACzB;IACD,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,gBAAgB,GAAG,KAAK,CAAC;IAE5C,MAAM,kBAAE,CAAC,SAAS,CAAC,aAAa,EAAE,SAAS,EAAE;QAC3C,MAAM,EAAE,CAAC;KACV,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,iBAAiB,CAAC,OAAe;IAC9C,MAAM,eAAe,GAAG,cAAI,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;IAC3D,MAAM,WAAW,GAAG,MAAM,kBAAE,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;IAEvD,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE;QACrB,WAAW,CAAC,IAAI,GAAG,EAAE,CAAC;KACvB;IAED,iDAAiD;IACjD,0DAA0D;IAC1D,WAAW,CAAC,IAAI,CAAC,WAAW,GAAG;QAC7B,gBAAgB,EAAE,IAAI;KACvB,CAAC;IAEF,kCAAkC;IAClC,KAAK,MAAM,kBAAkB,IAAI,sBAAsB,EAAE;QACvD,OAAO,WAAW,CAAC,YAAY,CAAC,kBAAkB,CAAC,CAAC;KACrD;IAED,MAAM,kBAAE,CAAC,SAAS,CAAC,eAAe,EAAE,WAAW,EAAE;QAC/C,MAAM,EAAE,CAAC;KACV,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,kBAAkB,CAAC,cAAsB;IACtD,IAAI;QACF,MAAM,IAAA,qBAAU,EAAC,MAAM,EAAE,CAAC,UAAU,EAAE,cAAc,CAAC,EAAE;YACrD,GAAG,EAAE,cAAc;YACnB,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC;SACpC,CAAC,CAAC;KACJ;IAAC,OAAO,KAAU,EAAE;QACnB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC5B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;KACjB;AACH,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,UAAU,CAAC,OAAe;IACvC,IAAI;QACF,MAAM,IAAA,qBAAU,EAAC,KAAK,EAAE,CAAC,SAAS,CAAC,EAAE;YACnC,GAAG,EAAE,cAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC;YAC9B,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC;SACpC,CAAC,CAAC;KACJ;IAAC,OAAO,KAAU,EAAE;QACnB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC5B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;KACjB;AACH,CAAC","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,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
|
+
"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"]}
|
package/build/types.d.ts
ADDED
|
@@ -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 @@
|
|
|
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
|
+
"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",
|
|
@@ -31,19 +31,21 @@
|
|
|
31
31
|
"bugs": {
|
|
32
32
|
"url": "https://github.com/expo/expo/issues"
|
|
33
33
|
},
|
|
34
|
-
"homepage": "https://github.com/expo/expo/tree/
|
|
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.
|
|
40
|
+
"ejs": "^3.1.7",
|
|
40
41
|
"fs-extra": "^10.0.0",
|
|
41
|
-
"prompts": "^2.4.2"
|
|
42
|
+
"prompts": "^2.4.2",
|
|
43
|
+
"validate-npm-package-name": "^4.0.0"
|
|
42
44
|
},
|
|
43
45
|
"devDependencies": {
|
|
44
46
|
"@types/ejs": "^3.1.0",
|
|
45
47
|
"@types/prompts": "^2.0.14",
|
|
46
48
|
"expo-module-scripts": "^2.0.0"
|
|
47
49
|
},
|
|
48
|
-
"gitHead": "
|
|
50
|
+
"gitHead": "093d340c3fd67ff4375af3a471c7d044aee893c9"
|
|
49
51
|
}
|
|
@@ -1,10 +1,17 @@
|
|
|
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
|
|
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 { PackageManagerName, resolvePackageManager } from './resolvePackageManager';
|
|
14
|
+
import { CommandOptions, CustomPromptObject, SubstitutionData } from './types';
|
|
8
15
|
|
|
9
16
|
const packageJson = require('../package.json');
|
|
10
17
|
|
|
@@ -15,45 +22,6 @@ const CWD = process.env.INIT_CWD || process.cwd();
|
|
|
15
22
|
// from `$package.json` file instead of the original one.
|
|
16
23
|
const IGNORES_PATHS = ['.DS_Store', 'build', 'node_modules', 'package.json'];
|
|
17
24
|
|
|
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
25
|
/**
|
|
58
26
|
* The main function of the command.
|
|
59
27
|
*
|
|
@@ -63,11 +31,13 @@ type PackageManager = 'npm' | 'yarn';
|
|
|
63
31
|
async function main(target: string | undefined, options: CommandOptions) {
|
|
64
32
|
const targetDir = target ? path.join(CWD, target) : CWD;
|
|
65
33
|
|
|
66
|
-
options.target = targetDir;
|
|
67
34
|
await fs.ensureDir(targetDir);
|
|
35
|
+
await confirmTargetDirAsync(targetDir);
|
|
36
|
+
|
|
37
|
+
options.target = targetDir;
|
|
68
38
|
|
|
69
39
|
const data = await askForSubstitutionDataAsync(targetDir, options);
|
|
70
|
-
const packageManager = await
|
|
40
|
+
const packageManager = await resolvePackageManager();
|
|
71
41
|
const packagePath = options.source
|
|
72
42
|
? path.join(CWD, options.source)
|
|
73
43
|
: await downloadPackageAsync(targetDir);
|
|
@@ -90,6 +60,9 @@ async function main(target: string | undefined, options: CommandOptions) {
|
|
|
90
60
|
await fs.outputFile(toPath, renderedContent, { encoding: 'utf8' });
|
|
91
61
|
}
|
|
92
62
|
|
|
63
|
+
// Install dependencies and build
|
|
64
|
+
await postActionsAsync(packageManager, targetDir);
|
|
65
|
+
|
|
93
66
|
if (!options.source) {
|
|
94
67
|
// Files in the downloaded tarball are wrapped in `package` dir.
|
|
95
68
|
// We should remove it after all.
|
|
@@ -101,9 +74,10 @@ async function main(target: string | undefined, options: CommandOptions) {
|
|
|
101
74
|
if (!options.withChangelog) {
|
|
102
75
|
await fs.remove(path.join(targetDir, 'CHANGELOG.md'));
|
|
103
76
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
77
|
+
if (options.example) {
|
|
78
|
+
// Create "example" folder
|
|
79
|
+
await createExampleApp(data, targetDir, packageManager);
|
|
80
|
+
}
|
|
107
81
|
|
|
108
82
|
console.log('✅ Successfully created Expo module');
|
|
109
83
|
}
|
|
@@ -149,7 +123,7 @@ async function npmWhoamiAsync(targetDir: string): Promise<string | null> {
|
|
|
149
123
|
try {
|
|
150
124
|
const { stdout } = await spawnAsync('npm', ['whoami'], { cwd: targetDir });
|
|
151
125
|
return stdout.trim();
|
|
152
|
-
} catch
|
|
126
|
+
} catch {
|
|
153
127
|
return null;
|
|
154
128
|
}
|
|
155
129
|
}
|
|
@@ -169,38 +143,18 @@ async function downloadPackageAsync(targetDir: string): Promise<string> {
|
|
|
169
143
|
return path.join(targetDir, 'package');
|
|
170
144
|
}
|
|
171
145
|
|
|
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
|
-
],
|
|
184
|
-
});
|
|
185
|
-
return packageManager;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
146
|
/**
|
|
189
147
|
* Installs dependencies and builds TypeScript files.
|
|
190
148
|
*/
|
|
191
|
-
async function postActionsAsync(packageManager:
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
cwd: targetDir,
|
|
195
|
-
stdio: 'ignore',
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
console.log('📦 Installing dependencies...');
|
|
200
|
-
await run('install');
|
|
149
|
+
async function postActionsAsync(packageManager: PackageManagerName, targetDir: string) {
|
|
150
|
+
console.log('📦 Installing module dependencies...');
|
|
151
|
+
await installDependencies(packageManager, targetDir);
|
|
201
152
|
|
|
202
153
|
console.log('🛠 Compiling TypeScript files...');
|
|
203
|
-
await
|
|
154
|
+
await spawnAsync(packageManager, ['run', 'build'], {
|
|
155
|
+
cwd: targetDir,
|
|
156
|
+
stdio: 'ignore',
|
|
157
|
+
});
|
|
204
158
|
}
|
|
205
159
|
|
|
206
160
|
/**
|
|
@@ -212,6 +166,7 @@ async function askForSubstitutionDataAsync(
|
|
|
212
166
|
options: CommandOptions
|
|
213
167
|
): Promise<SubstitutionData> {
|
|
214
168
|
const defaultPackageSlug = path.basename(targetDir);
|
|
169
|
+
const useDefaultSlug = options.target && validateNpmPackage(defaultPackageSlug);
|
|
215
170
|
const defaultProjectName = defaultPackageSlug
|
|
216
171
|
.replace(/^./, (match) => match.toUpperCase())
|
|
217
172
|
.replace(/\W+(\w)/g, (_, p1) => p1.toUpperCase());
|
|
@@ -222,7 +177,9 @@ async function askForSubstitutionDataAsync(
|
|
|
222
177
|
name: 'slug',
|
|
223
178
|
message: 'What is the package slug?',
|
|
224
179
|
initial: defaultPackageSlug,
|
|
225
|
-
resolvedValue:
|
|
180
|
+
resolvedValue: useDefaultSlug ? defaultPackageSlug : null,
|
|
181
|
+
validate: (input) =>
|
|
182
|
+
validateNpmPackage(input).validForNewPackages || 'Must be a valid npm package name',
|
|
226
183
|
},
|
|
227
184
|
{
|
|
228
185
|
type: 'text',
|
|
@@ -234,6 +191,7 @@ async function askForSubstitutionDataAsync(
|
|
|
234
191
|
type: 'text',
|
|
235
192
|
name: 'description',
|
|
236
193
|
message: 'How would you describe the module?',
|
|
194
|
+
validate: (input) => !!input || 'Cannot be empty',
|
|
237
195
|
},
|
|
238
196
|
{
|
|
239
197
|
type: 'text',
|
|
@@ -257,13 +215,19 @@ async function askForSubstitutionDataAsync(
|
|
|
257
215
|
type: 'text',
|
|
258
216
|
name: 'repo',
|
|
259
217
|
message: 'What is the repository URL?',
|
|
218
|
+
validate: (input) => /^https?:\/\//.test(input) || 'Must be a valid URL',
|
|
260
219
|
},
|
|
261
220
|
];
|
|
262
221
|
|
|
222
|
+
// Stop the process when the user cancels/exits the prompt.
|
|
223
|
+
const onCancel = () => {
|
|
224
|
+
process.exit(0);
|
|
225
|
+
};
|
|
226
|
+
|
|
263
227
|
const answers: Record<string, string> = {};
|
|
264
228
|
for (const query of promptQueries) {
|
|
265
229
|
const { name, resolvedValue } = query;
|
|
266
|
-
answers[name] = resolvedValue ?? options[name] ?? (await prompts(query))[name];
|
|
230
|
+
answers[name] = resolvedValue ?? options[name] ?? (await prompts(query, { onCancel }))[name];
|
|
267
231
|
}
|
|
268
232
|
|
|
269
233
|
const { slug, name, description, package: projectPackage, author, license, repo } = answers;
|
|
@@ -282,6 +246,33 @@ async function askForSubstitutionDataAsync(
|
|
|
282
246
|
};
|
|
283
247
|
}
|
|
284
248
|
|
|
249
|
+
/**
|
|
250
|
+
* Checks whether the target directory is empty and if not, asks the user to confirm if he wants to continue.
|
|
251
|
+
*/
|
|
252
|
+
async function confirmTargetDirAsync(targetDir: string): Promise<void> {
|
|
253
|
+
const files = await fs.readdir(targetDir);
|
|
254
|
+
|
|
255
|
+
if (files.length === 0) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const { shouldContinue } = await prompts(
|
|
259
|
+
{
|
|
260
|
+
type: 'confirm',
|
|
261
|
+
name: 'shouldContinue',
|
|
262
|
+
message: `The target directory ${chalk.magenta(
|
|
263
|
+
targetDir
|
|
264
|
+
)} is not empty.\nDo you want to continue anyway?`,
|
|
265
|
+
initial: true,
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
onCancel: () => false,
|
|
269
|
+
}
|
|
270
|
+
);
|
|
271
|
+
if (!shouldContinue) {
|
|
272
|
+
process.exit(0);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
285
276
|
const program = new Command();
|
|
286
277
|
|
|
287
278
|
program
|
|
@@ -301,6 +292,7 @@ program
|
|
|
301
292
|
.option('-r, --repo <repo_url>', 'The URL to the repository.')
|
|
302
293
|
.option('--with-readme', 'Whether to include README.md file.', false)
|
|
303
294
|
.option('--with-changelog', 'Whether to include CHANGELOG.md file.', false)
|
|
295
|
+
.option('--no-example', 'Whether to skip creating the example app.', false)
|
|
304
296
|
.action(main);
|
|
305
297
|
|
|
306
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
|
+
}
|
|
@@ -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
|
+
};
|