create-expo-module 0.3.0 → 0.4.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/src/prompts.ts ADDED
@@ -0,0 +1,80 @@
1
+ import path from 'path';
2
+ import { Answers, PromptObject } from 'prompts';
3
+ import validateNpmPackage from 'validate-npm-package-name';
4
+
5
+ import { findGitHubEmail, findGitHubProfileUrl, findMyName, guessRepoUrl } from './utils';
6
+
7
+ export default async function getPrompts(targetDir: string): Promise<PromptObject<string>[]> {
8
+ const targetBasename = path.basename(targetDir);
9
+
10
+ return [
11
+ {
12
+ type: 'text',
13
+ name: 'slug',
14
+ message: 'What is the name of the npm package?',
15
+ initial: validateNpmPackage(targetBasename).validForNewPackages ? targetBasename : undefined,
16
+ validate: (input) =>
17
+ validateNpmPackage(input).validForNewPackages || 'Must be a valid npm package name',
18
+ },
19
+ {
20
+ type: 'text',
21
+ name: 'name',
22
+ message: 'What is the native module name?',
23
+ initial: (_, answers: Answers<string>) => {
24
+ return answers.slug
25
+ .replace(/^@/, '')
26
+ .replace(/^./, (match) => match.toUpperCase())
27
+ .replace(/\W+(\w)/g, (_, p1) => p1.toUpperCase());
28
+ },
29
+ validate: (input) => !!input || 'The native module name cannot be empty',
30
+ },
31
+ {
32
+ type: 'text',
33
+ name: 'description',
34
+ message: 'How would you describe the module?',
35
+ initial: 'My new module',
36
+ validate: (input) => !!input || 'The description cannot be empty',
37
+ },
38
+ {
39
+ type: 'text',
40
+ name: 'package',
41
+ message: 'What is the Android package name?',
42
+ initial: (_, answers: Answers<string>) => {
43
+ const namespace = answers.slug
44
+ .replace(/\W/g, '')
45
+ .replace(/^(expo|reactnative)/, '')
46
+ .toLowerCase();
47
+ return `expo.modules.${namespace}`;
48
+ },
49
+ validate: (input) => !!input || 'The Android package name cannot be empty',
50
+ },
51
+ {
52
+ type: 'text',
53
+ name: 'authorName',
54
+ message: 'What is the name of the package author?',
55
+ initial: await findMyName(),
56
+ validate: (input) => !!input || 'Cannot be empty',
57
+ },
58
+ {
59
+ type: 'text',
60
+ name: 'authorEmail',
61
+ message: 'What is the email address of the author?',
62
+ initial: await findGitHubEmail(),
63
+ },
64
+ {
65
+ type: 'text',
66
+ name: 'authorUrl',
67
+ message: "What is the URL to the author's GitHub profile?",
68
+ initial: async (_, answers: Answers<string>) =>
69
+ await findGitHubProfileUrl(answers.authorEmail),
70
+ },
71
+ {
72
+ type: 'text',
73
+ name: 'repo',
74
+ message: 'What is the URL for the repository?',
75
+ initial: async (_, answers: Answers<string>) =>
76
+ await guessRepoUrl(answers.authorUrl, answers.slug),
77
+ validate: (input) => /^https?:\/\//.test(input) || 'Must be a valid URL',
78
+ },
79
+ ];
80
+ }
package/src/types.ts ADDED
@@ -0,0 +1,35 @@
1
+ import { PromptObject } from 'prompts';
2
+
3
+ /**
4
+ * Possible command options.
5
+ */
6
+ export type CommandOptions = {
7
+ target: string;
8
+ source?: string;
9
+ withReadme: boolean;
10
+ withChangelog: boolean;
11
+ example: boolean;
12
+ };
13
+
14
+ /**
15
+ * Represents an object that is passed to `ejs` when rendering the template.
16
+ */
17
+ export type SubstitutionData = {
18
+ project: {
19
+ slug: string;
20
+ name: string;
21
+ version: string;
22
+ description: string;
23
+ package: string;
24
+ };
25
+ author: string;
26
+ license: string;
27
+ repo: string;
28
+ };
29
+
30
+ export type CustomPromptObject = PromptObject & {
31
+ name: string;
32
+ resolvedValue?: string | null;
33
+ };
34
+
35
+ export type Answers = Record<string, string>;
package/src/utils.ts ADDED
@@ -0,0 +1,75 @@
1
+ import chalk from 'chalk';
2
+ import spawn from 'cross-spawn';
3
+ import githubUsername from 'github-username';
4
+ import ora from 'ora';
5
+
6
+ export type StepOptions = ora.Options;
7
+
8
+ export async function newStep<Result>(
9
+ title: string,
10
+ action: (step: ora.Ora) => Promise<Result> | Result,
11
+ options: StepOptions = {}
12
+ ): Promise<Result> {
13
+ const disabled = process.env.CI || process.env.EXPO_DEBUG;
14
+ const step = ora({
15
+ text: chalk.bold(title),
16
+ isEnabled: !disabled,
17
+ stream: disabled ? process.stdout : process.stderr,
18
+ ...options,
19
+ });
20
+
21
+ step.start();
22
+
23
+ try {
24
+ return await action(step);
25
+ } catch (error) {
26
+ step.fail();
27
+ console.error(error);
28
+ process.exit(1);
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Finds user's name by reading it from the git config.
34
+ */
35
+ export async function findMyName(): Promise<string> {
36
+ try {
37
+ return spawn.sync('git', ['config', '--get', 'user.name']).stdout.toString().trim();
38
+ } catch {
39
+ return '';
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Finds user's email by reading it from the git config.
45
+ */
46
+ export async function findGitHubEmail(): Promise<string> {
47
+ try {
48
+ return spawn.sync('git', ['config', '--get', 'user.email']).stdout.toString().trim();
49
+ } catch {
50
+ return '';
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Get the GitHub username from an email address if the email can be found in any commits on GitHub.
56
+ */
57
+ export async function findGitHubProfileUrl(email: string): Promise<string> {
58
+ try {
59
+ const username = (await githubUsername(email)) ?? '';
60
+ return `https://github.com/${username}`;
61
+ } catch {
62
+ return '';
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Guesses the repository URL based on the author profile URL and the package slug.
68
+ */
69
+ export async function guessRepoUrl(authorUrl: string, slug: string) {
70
+ if (/^https?:\/\/github.com\/[^/]+/.test(authorUrl)) {
71
+ const normalizedSlug = slug.replace(/^@/, '').replace(/\//g, '-');
72
+ return `${authorUrl}/${normalizedSlug}`;
73
+ }
74
+ return '';
75
+ }