Skip to content

Commit

Permalink
Add default 'create' command to create new app
Browse files Browse the repository at this point in the history
  • Loading branch information
Stephen Hanson committed Oct 29, 2023
1 parent 030b4b1 commit 5eb2655
Show file tree
Hide file tree
Showing 14 changed files with 701 additions and 144 deletions.
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@
"author": "thoughtbot, Inc.",
"license": "MIT",
"dependencies": {
"@inquirer/prompts": "^3.2.0",
"@thoughtbot/eslint-config": "^1.0.2",
"chalk": "^5.2.0",
"commander": "^10.0.1",
"eslint": "^8.45.0",
"eta": "^2.1.1",
"fs-extra": "^11.1.1",
"inquirer": "^9.2.0",
"ora": "^6.3.0",
"prettier": "^3.0.1",
"ts-node": "^10.9.1"
Expand All @@ -38,8 +38,12 @@
"@thoughtbot/eslint-config/prettier",
"@thoughtbot/eslint-config/typescript"
],
"ignorePatterns": [
"src/commands/templates"
],
"rules": {
"no-console": "off"
"no-console": "off",
"import/order": "off"
}
}
}
8 changes: 8 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ export default function runCli() {
'Perform React Native and Expo setup and redundant tasks without your pants falling down!',
)

.command('create', { isDefault: true })
.description('Create new app')
.argument(
'appName',
'The name of the app and directory it will be created in',
)
.action(buildAction(import('./commands/createApp')))

.command('eslint')
.description('Configure ESLint')
.action(buildAction(import('./commands/eslint')));
Expand Down
47 changes: 47 additions & 0 deletions src/commands/createApp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { input } from '@inquirer/prompts';
import { execSync, spawnSync } from 'child_process';
import createScaffold from './createScaffold';
import addEslint from './eslint';
import addTypescript from './typescript';

export async function createApp(name: string | undefined) {
const appName = name || (await getAppName());

spawnSync('npx', ['--yes', 'create-expo-app@latest', appName], {
stdio: 'inherit',
});

process.chdir(`./${appName}`);

execSync('git init');
execSync('git add .');
execSync('git commit -m "Initial commit"');

await addTypescript();
execSync('git add .');
execSync('git commit -m "Add TypeScript"');

await addEslint();
execSync('git add .');
execSync('git commit -m "Configure ESLint"');

await createScaffold();
execSync('git add .');
execSync('git commit -m "Add app scaffold"');
}

async function getAppName() {
return input({ message: 'What is the name of your app?' });
}

/**
* Commander requires this signature to be ...args: unknown[]
* Actual args are:
* ([<appName>, <Options hash>, <Command>])
* or ([<Options hash>, <Command>]) if <appName> not passed)
*/
export default function createAppAction(...args: unknown[]) {
// if argument ommitted, args[0] is options
const appNameArg = (args[0] as string[])[0];
return createApp(appNameArg);
}
20 changes: 20 additions & 0 deletions src/commands/createScaffold.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import chalk from 'chalk';
import { log } from 'console';
import fs from 'fs-extra';
import path from 'path';
import { URL, fileURLToPath } from 'url';

const dirname = fileURLToPath(new URL('.', import.meta.url));

export default async function createScaffold() {
console.log(chalk.bold('👖 Creating directory structure'));
console.log(`
src/
components/
util/
hooks/
test/
`);
fs.copySync(path.join(dirname, 'templates', 'scaffold', 'src'), './src');
log('✅ Created directories');
}
8 changes: 4 additions & 4 deletions src/commands/eslint.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { log } from 'console';
import path from 'path';
import { fileURLToPath, URL } from 'url';
import chalk from 'chalk';
import { log } from 'console';
import * as eta from 'eta';
import fs from 'fs-extra';
import path from 'path';
import { fileURLToPath, URL } from 'url';
import addDependency from '../util/addDependency';
import getProjectDir from '../util/getProjectDir';
import isEslintConfigured from '../util/isEslintConfigured';
Expand All @@ -12,7 +12,7 @@ import writeFile from '../util/writeFile';

const dirname = fileURLToPath(new URL('.', import.meta.url));

export default async function runEslint() {
export default async function addEslint() {
const projectDir = await getProjectDir();

if (await isEslintConfigured()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
StyleSheet,
TouchableOpacity,
TouchableOpacityProps,
} from 'react-native';

type ButtonProps = TouchableOpacityProps;

export default function PrimaryButton({
style,
text,
textStyle,
children,
...props
}: ButtonProps) {
return (
<TouchableOpacity
activeOpacity={0.6}
style={[styles.button, style]}
{...props}
/>
);
}

const styles = StyleSheet.create({
button: {
paddingVertical: 16,
paddingHorizontal: 14,
backgroundColor: '#08f',
borderRadius: 12,
justifyContent: 'center',
},
});
Empty file.
Empty file.
Empty file.
6 changes: 3 additions & 3 deletions src/commands/typescript.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { log } from 'console';
import path from 'path';
import { fileURLToPath, URL } from 'url';
import chalk from 'chalk';
import { log } from 'console';
import * as eta from 'eta';
import fs from 'fs-extra';
import path from 'path';
import { fileURLToPath, URL } from 'url';
import addDependency from '../util/addDependency';
import getProjectDir from '../util/getProjectDir';
import getProjectType from '../util/getProjectType';
Expand Down
8 changes: 4 additions & 4 deletions src/util/addDependency.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { exec } from 'child_process';
import * as path from 'path';
import { execSync } from 'child_process';
import * as fs from 'fs-extra';
import * as path from 'path';
import getProjectDir from './getProjectDir';

export default async function addDependency(deps: string, { dev = false }) {
const isYarn = await fs.exists(path.join(await getProjectDir(), 'yarn.lock'));

if (isYarn) {
exec(`yarn add ${dev ? '--dev' : ''} ${deps}`);
execSync(`yarn add ${dev ? '--dev' : ''} ${deps}`);
} else {
exec(`npm install ${dev ? '--save-dev' : '--save'} ${deps}`);
execSync(`npm install ${dev ? '--save-dev' : '--save'} ${deps}`);
}
}
4 changes: 2 additions & 2 deletions src/util/formatFile.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { exec } from 'child_process';
import { execSync } from 'child_process';

export default async function formatFile(filePath: string) {
exec(`npx prettier --write '${filePath}'`);
execSync(`npx prettier --write '${filePath}'`);
}
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@
"esm": true,
"experimentalSpecifierResolution": "node"
},
"include": ["./**/*"]
"include": ["./**/*"],
"exclude": ["./src/commands/templates/**/*"]
}
Loading

0 comments on commit 5eb2655

Please sign in to comment.