Skip to content

Commit

Permalink
feat: add ability to generate a local module (experimental)
Browse files Browse the repository at this point in the history
  • Loading branch information
satya164 committed Sep 22, 2023
1 parent 93f2907 commit 911ac61
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 56 deletions.
247 changes: 195 additions & 52 deletions packages/create-react-native-library/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const BINARIES = [
];

const COMMON_FILES = path.resolve(__dirname, '../templates/common');
const COMMON_LOCAL_FILES = path.resolve(__dirname, '../templates/common-local');
const JS_FILES = path.resolve(__dirname, '../templates/js-library');
const EXPO_FILES = path.resolve(__dirname, '../templates/expo-library');
const CPP_FILES = path.resolve(__dirname, '../templates/cpp-library');
Expand All @@ -27,6 +28,10 @@ const NATIVE_COMMON_FILES = path.resolve(
__dirname,
'../templates/native-common'
);
const NATIVE_COMMON_EXAMPLE_FILES = path.resolve(
__dirname,
'../templates/native-common-example'
);

const NATIVE_FILES = {
module_legacy: path.resolve(__dirname, '../templates/native-library-legacy'),
Expand Down Expand Up @@ -82,6 +87,8 @@ type ArgName =
| 'repo-url'
| 'languages'
| 'type'
| 'local'
| 'example'
| 'react-native-version';

type ProjectLanguages =
Expand Down Expand Up @@ -110,6 +117,7 @@ type Answers = {
repoUrl: string;
languages: ProjectLanguages;
type?: ProjectType;
example?: boolean;
reactNativeVersion?: string;
};

Expand Down Expand Up @@ -246,10 +254,60 @@ const args: Record<ArgName, yargs.Options> = {
description: 'Version of React Native to use, uses latest if not specified',
type: 'string',
},
'local': {
description: 'Whether to create a local library',
type: 'boolean',
},
'example': {
description: 'Whether to create a an example app',
type: 'boolean',
},
};

async function create(argv: yargs.Arguments<any>) {
const folder = path.join(process.cwd(), argv.name);
let local = false;
let folder = path.join(process.cwd(), argv.name);

if (typeof argv.local === 'boolean') {
local = argv.local;
} else {
let hasPackageJson = await fs.pathExists(
path.join(process.cwd(), 'package.json')
);

if (hasPackageJson) {
// If we're under a project with package.json, ask the user if they want to create a local library
const answers = await prompts([
{
type: 'confirm',
name: 'local',
message: `Looks like you're under a project folder. Do you want to create a local library?`,
initial: true,
},
{
type: (previous: boolean) => {
if (previous) {
return 'text';
}

return null;
},
name: 'folder',
message: `Where to create the local library?`,
initial: argv.name.includes('/')
? argv.name
: `packages/${argv.name}`,
validate: (input) => Boolean(input) || 'Cannot be empty',
},
]);

local = answers.local;

if (local) {
folder = path.join(process.cwd(), answers.folder);
}
}
}

if (await fs.pathExists(folder)) {
console.log(
Expand Down Expand Up @@ -315,22 +373,22 @@ async function create(argv: yargs.Arguments<any>) {
validate: (input) => Boolean(input) || 'Cannot be empty',
},
'author-name': {
type: 'text',
type: local ? null : 'text',
name: 'authorName',
message: 'What is the name of package author?',
initial: name,
validate: (input) => Boolean(input) || 'Cannot be empty',
},
'author-email': {
type: 'text',
type: local ? null : 'text',
name: 'authorEmail',
message: 'What is the email address for the package author?',
initial: email,
validate: (input) =>
/^\S+@\S+$/.test(input) || 'Must be a valid email address',
},
'author-url': {
type: 'text',
type: local ? null : 'text',
name: 'authorUrl',
message: 'What is the URL for the package author?',
// @ts-ignore: this is supported, but types are wrong
Expand All @@ -348,7 +406,7 @@ async function create(argv: yargs.Arguments<any>) {
validate: (input) => /^https?:\/\//.test(input) || 'Must be a valid URL',
},
'repo-url': {
type: 'text',
type: local ? null : 'text',
name: 'repoUrl',
message: 'What is the URL for the repository?',
// @ts-ignore: this is supported, but types are wrong
Expand Down Expand Up @@ -438,6 +496,7 @@ async function create(argv: yargs.Arguments<any>) {
repoUrl,
type = 'module-mixed',
languages = type === 'library' ? 'js' : 'java-objc',
example = !local,
reactNativeVersion,
} = {
...argv,
Expand Down Expand Up @@ -503,7 +562,7 @@ async function create(argv: yargs.Arguments<any>) {
? 'mixed'
: 'legacy';

const example = type === 'library' ? 'expo' : 'native';
const exampleType = type === 'library' ? 'expo' : 'native';
const project = slug.replace(/^(react-native-|@[^/]+\/)/, '');

let namespace: string | undefined;
Expand Down Expand Up @@ -553,7 +612,7 @@ async function create(argv: yargs.Arguments<any>) {
url: authorUrl,
},
repo: repoUrl,
example,
example: exampleType,
year: new Date().getFullYear(),
};

Expand Down Expand Up @@ -589,7 +648,7 @@ async function create(argv: yargs.Arguments<any>) {
await fs.mkdirp(folder);

if (reactNativeVersion != null) {
if (example === 'expo') {
if (exampleType === 'expo') {
console.warn(
`${kleur.yellow('⚠')} Ignoring --react-native-version for Expo example`
);
Expand All @@ -602,32 +661,46 @@ async function create(argv: yargs.Arguments<any>) {
}
}

const spinner = ora('Generating example').start();
const spinner = ora().start();

await generateExampleApp({
type: example,
dest: folder,
slug: options.project.slug,
projectName: options.project.name,
arch,
reactNativeVersion,
});
if (example) {
spinner.text = 'Generating example app';

await generateExampleApp({
type: exampleType,
dest: folder,
slug: options.project.slug,
projectName: options.project.name,
arch,
reactNativeVersion,
});
}

spinner.text = 'Copying files';

await copyDir(COMMON_FILES, folder);
if (local) {
await copyDir(COMMON_LOCAL_FILES, folder);
} else {
await copyDir(COMMON_FILES, folder);
}

if (languages === 'js') {
await copyDir(JS_FILES, folder);
await copyDir(EXPO_FILES, folder);
} else {
await copyDir(
path.join(EXAMPLE_FILES, 'example'),
path.join(folder, 'example')
);
if (example) {
await copyDir(
path.join(EXAMPLE_FILES, 'example'),
path.join(folder, 'example')
);
}

await copyDir(NATIVE_COMMON_FILES, folder);

if (example) {
await copyDir(NATIVE_COMMON_EXAMPLE_FILES, folder);
}

if (moduleType === 'module') {
await copyDir(NATIVE_FILES[`${moduleType}_${arch}`], folder);
} else {
Expand Down Expand Up @@ -664,44 +737,113 @@ async function create(argv: yargs.Arguments<any>) {
}
}

// Set `react` and `react-native` versions of root `package.json` from example `package.json`
const examplePackageJson = fs.readJSONSync(
path.join(folder, 'example', 'package.json')
);
const rootPackageJson = fs.readJSONSync(path.join(folder, 'package.json'));
rootPackageJson.devDependencies.react = examplePackageJson.dependencies.react;
rootPackageJson.devDependencies['react-native'] =
examplePackageJson.dependencies['react-native'];
if (example) {
// Set `react` and `react-native` versions of root `package.json` from example `package.json`
const examplePackageJson = await fs.readJSON(
path.join(folder, 'example', 'package.json')
);
const rootPackageJson = await fs.readJSON(
path.join(folder, 'package.json')
);

fs.writeJSONSync(path.join(folder, 'package.json'), rootPackageJson, {
spaces: 2,
});
rootPackageJson.devDependencies.react =
examplePackageJson.dependencies.react;
rootPackageJson.devDependencies['react-native'] =
examplePackageJson.dependencies['react-native'];

try {
await spawn('git', ['init'], { cwd: folder });
await spawn('git', ['branch', '-M', 'main'], { cwd: folder });
await spawn('git', ['add', '.'], { cwd: folder });
await spawn('git', ['commit', '-m', 'chore: initial commit'], {
cwd: folder,
await fs.writeJSON(path.join(folder, 'package.json'), rootPackageJson, {
spaces: 2,
});
} catch (e) {
// Ignore error
}

if (!local) {
try {
await spawn('git', ['init'], { cwd: folder });
await spawn('git', ['branch', '-M', 'main'], { cwd: folder });
await spawn('git', ['add', '.'], { cwd: folder });
await spawn('git', ['commit', '-m', 'chore: initial commit'], {
cwd: folder,
});
} catch (e) {
// Ignore error
}
}

spinner.succeed(
`Project created successfully at ${kleur.yellow(argv.name)}!\n`
`Project created successfully at ${kleur.yellow(
path.relative(process.cwd(), folder)
)}!\n`
);

const platforms = {
ios: { name: 'iOS', color: 'cyan' },
android: { name: 'Android', color: 'green' },
...(example === 'expo'
? ({ web: { name: 'Web', color: 'blue' } } as const)
: null),
} as const;
if (local) {
let linked;

const packageManager = (await fs.pathExists(
path.join(process.cwd(), 'yarn.lock')
))
? 'yarn'
: 'npm';

const packageJsonPath = path.join(process.cwd(), 'package.json');

if (await fs.pathExists(packageJsonPath)) {
const packageJson = await fs.readJSON(packageJsonPath);
const isReactNativeProject = Boolean(
packageJson.dependencies?.['react-native']
);

if (isReactNativeProject) {
packageJson.dependencies = packageJson.dependencies || {};
packageJson.dependencies[slug] =
packageManager === 'yarn'
? `link:./${path.relative(process.cwd(), folder)}`
: `file:./${path.relative(process.cwd(), folder)}`;

await fs.writeJSON(packageJsonPath, packageJson, {
spaces: 2,
});

linked = true;
}
}

console.log(
dedent(`
${kleur.magenta(
`${kleur.bold('Get started')} with the project`
)}${kleur.gray(':')}
${
(linked
? `- Run ${kleur.blue(
`${packageManager} install`
)} to link the library\n`
: `- Link the library at ${kleur.blue(
path.relative(process.cwd(), folder)
)} based on your project setup'\n`) +
`- Run ${kleur.blue(
'cd ios; pod install; cd -'
)} to install dependencies with CocoaPods\n` +
`- Run ${kleur.blue('npx react-native run-android')} or ${kleur.blue(
'npx react-native run-ios'
)} to build and run the app\n` +
`- Import from ${kleur.blue(slug)} and use it in your app.`
}
console.log(
dedent(`
${kleur.yellow(`Good luck!`)}
`)
);
} else {
const platforms = {
ios: { name: 'iOS', color: 'cyan' },
android: { name: 'Android', color: 'green' },
...(exampleType === 'expo'
? ({ web: { name: 'Web', color: 'blue' } } as const)
: null),
} as const;

console.log(
dedent(`
${kleur.magenta(
`${kleur.bold('Get started')} with the project`
)}${kleur.gray(':')}
Expand All @@ -722,7 +864,8 @@ async function create(argv: yargs.Arguments<any>) {
`See ${kleur.bold('CONTRIBUTING.md')} for more details. Good luck!`
)}
`)
);
);
}
}
// eslint-disable-next-line babel/no-unused-expressions
yargs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,9 @@ export default async function generateExampleApp({
scripts.web = 'expo start --web';
}

await fs.writeFile(
path.join(directory, 'package.json'),
JSON.stringify(pkg, null, 2)
);
await fs.writeJSON(pkg, path.join(directory, 'package.json'), {
spaces: 2,
});

// If the library is on new architecture, enable new arch for iOS and Android
if (arch === 'new') {
Expand Down
Loading

0 comments on commit 911ac61

Please sign in to comment.