Skip to content

Commit cbd834a

Browse files
committed
Initial commit in public repo.
Supported: * Initializing a new project. * Starting a project (if logged in w/ Exponent credentials -- anon access to come), running on physical device * Running a project in an iOS simulator * Running tests on the template project (with npm, waiting on yarnpkg/yarn#760 for yarn test support)
0 parents  commit cbd834a

25 files changed

+7946
-0
lines changed

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules
2+
build/
3+
yarn-error.log
4+
*.tgz
5+
.DS_Store

README.md

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# create-react-native-app
2+
3+
If you're reading this right now, `create-react-native-app` isn't yet ready for general consumption.
4+
5+
Please keep an eye on this README, we'll update with details when it's all ready to try out.
6+
7+
## TODO
8+
9+
* license, authors, changelog, etc
10+
* confirm packaging works as intended
11+
* npm run eject (bare and with exponentview)
12+
* npm run android?
13+
14+
## Console Colors
15+
16+
The convention for using chalk in console output:
17+
18+
* blue -> in progress
19+
* green -> success
20+
* yellow -> warn
21+
* red -> error
22+
* cyan -> something the user inputs
23+
* underline -> URL

create-react-native-app/.babelrc

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"presets": ["es2015", "stage-1"],
3+
"plugins": ["transform-runtime", "add-module-exports", "transform-flow-strip-types"]
4+
}

create-react-native-app/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# create-react-native-app
2+
3+
## Development
4+
5+
`yarn && gulp` will start a watcher that will build artifacts and place them in the build directory.

create-react-native-app/gulpfile.js

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
const path = require('path');
2+
const gulp = require('gulp');
3+
const babel = require('gulp-babel');
4+
const changed = require('gulp-changed');
5+
const plumber = require('gulp-plumber');
6+
const sourcemaps = require('gulp-sourcemaps');
7+
const rimraf = require('rimraf');
8+
9+
const paths = {
10+
source: 'src/**/*.js',
11+
build: 'build',
12+
sourceRoot: path.join(__dirname, 'src'),
13+
};
14+
15+
const tasks = {
16+
babel() {
17+
return gulp.src(paths.source)
18+
.pipe(changed(paths.build))
19+
.pipe(plumber())
20+
.pipe(sourcemaps.init())
21+
.pipe(babel())
22+
.pipe(sourcemaps.write('__sourcemaps__', { sourceRoot: paths.sourceRoot }))
23+
.pipe(gulp.dest(paths.build));
24+
},
25+
26+
watchBabel(done) {
27+
gulp.watch(paths.source, tasks.babel);
28+
done();
29+
},
30+
};
31+
32+
gulp.task('build', tasks.babel);
33+
gulp.task('babel', tasks.babel);
34+
gulp.task('watch', tasks.watchBabel);
35+
gulp.task('clean', done => {
36+
rimraf(paths.build, done);
37+
});
38+
39+
gulp.task('default', gulp.series('watch'));

create-react-native-app/package.json

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"name": "create-react-native-app",
3+
"version": "0.1.0",
4+
"description": "Create React Native apps with no build configuration.",
5+
"license": "BSD-3-Clause",
6+
"engines": {
7+
"node": ">=4"
8+
},
9+
"files": [
10+
"build"
11+
],
12+
"bin": {
13+
"create-react-native-app": "./build/index.js"
14+
},
15+
"dependencies": {
16+
"babel-runtime": "^6.9.2",
17+
"chalk": "^1.1.1",
18+
"cross-spawn": "^4.0.0",
19+
"fs-promise": "^1.0.0",
20+
"minimist": "^1.2.0",
21+
"path-exists": "^2.1.0",
22+
"semver": "^5.0.3",
23+
"source-map-support": "^0.4.1"
24+
},
25+
"devDependencies": {
26+
"babel-plugin-add-module-exports": "^0.2.1",
27+
"babel-plugin-transform-flow-strip-types": "^6.8.0",
28+
"babel-plugin-transform-runtime": "^6.9.0",
29+
"babel-preset-es2015": "^6.9.0",
30+
"babel-preset-stage-1": "^6.5.0",
31+
"gulp": "git+https://github.com/gulpjs/gulp#4.0",
32+
"gulp-babel": "^6.1.2",
33+
"gulp-changed": "^1.3.0",
34+
"gulp-plumber": "^1.1.0",
35+
"gulp-sourcemaps": "^1.6.0",
36+
"rimraf": "^2.5.2"
37+
}
38+
}

create-react-native-app/src/index.js

+231
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
#!/usr/bin/env node
2+
3+
// @flow
4+
5+
// DON'T MODIFY THIS FILE
6+
// IF AT ALL POSSIBLE, MAKE ANY CHANGES IN THE SCRIPTS PACKAGE
7+
8+
import fsp from 'fs-promise';
9+
import chalk from 'chalk';
10+
import minimist from 'minimist';
11+
import path from 'path';
12+
import pathExists from 'path-exists';
13+
import semver from 'semver';
14+
import spawn from 'cross-spawn';
15+
16+
const argv = minimist(process.argv.slice(2));
17+
18+
/**
19+
* Arguments:
20+
* --version - to print current version
21+
* --verbose - to print npm logs during init
22+
* --scripts-version <alternative package>
23+
* Example of valid values:
24+
* - a specific npm version: "0.22.0-rc1"
25+
* - a .tgz archive from npm: "https://registry.npmjs.org/react-native-scripts/-/react-native-scripts-0.20.0.tgz"
26+
* - a package from `tasks/clean_pack.sh`: "/home/adam/create-react-native-app/react-native-scripts-0.22.0.tgz"
27+
*/
28+
const commands = argv._;
29+
if (commands.length === 0) {
30+
if (argv.version) {
31+
const version = require('../package.json').version;
32+
console.log(`create-react-native-app version: ${version}`);
33+
process.exit();
34+
}
35+
console.error(
36+
'Usage: create-react-native-app <project-directory> [--verbose]'
37+
);
38+
process.exit(1);
39+
}
40+
41+
createApp(commands[0], !!argv.verbose, argv['scripts-version']).then(() => {});
42+
43+
async function createApp(name: string, verbose: boolean, version: ?string): Promise<void> {
44+
const root = path.resolve(name);
45+
const appName = path.basename(root);
46+
47+
const packageToInstall = getInstallPackage(version);
48+
const packageName = getPackageName(packageToInstall);
49+
checkAppName(appName, packageName);
50+
51+
if (!await pathExists(name)) {
52+
await fsp.mkdir(root);
53+
} else if (!await isSafeToCreateProjectIn(root)) {
54+
console.log(`The directory \`${name}\` contains file(s) that could conflict. Aborting.`);
55+
process.exit(1);
56+
}
57+
58+
console.log(`Creating a new React Native app in ${root}.`);
59+
console.log();
60+
61+
const packageJson = {
62+
name: appName,
63+
version: '0.1.0',
64+
private: true,
65+
};
66+
await fsp.writeFile(
67+
path.join(root, 'package.json'),
68+
JSON.stringify(packageJson, null, 2)
69+
);
70+
process.chdir(root);
71+
72+
console.log('Installing packages. This might take a couple minutes.');
73+
console.log('Installing react-native-scripts...');
74+
console.log();
75+
76+
await run(root, appName, version, verbose, packageToInstall, packageName);
77+
}
78+
79+
function install(packageToInstall: string, verbose: boolean,
80+
callback: (code: number, command: string, args: Array<string>) => Promise<void>
81+
): void {
82+
let args = [
83+
'add',
84+
'--dev',
85+
'--exact',
86+
packageToInstall,
87+
];
88+
const proc = spawn('yarnpkg', args, {stdio: 'inherit'});
89+
90+
let yarnExists = true;
91+
proc.on('error', function(err) {
92+
if (err.code === 'ENOENT') {
93+
yarnExists = false;
94+
}
95+
});
96+
97+
proc.on('close', function(code) {
98+
if (yarnExists) {
99+
callback(code, 'yarnpkg', args).then(() => {}, (e) => { throw e; });
100+
return;
101+
}
102+
// No Yarn installed, continuing with npm.
103+
args = ['install'];
104+
105+
if (verbose) {
106+
args.push('--verbose');
107+
}
108+
109+
args = args.concat([
110+
'--save-dev',
111+
'--save-exact',
112+
packageToInstall,
113+
]);
114+
115+
const npmProc = spawn('npm', args, {stdio: 'inherit'});
116+
npmProc.on('close', function(code) {
117+
callback(code, 'npm', args).then(() => {}, (e) => { throw e; });;
118+
});
119+
});
120+
}
121+
122+
async function run(root: string, appName: string, version: ?string, verbose: boolean,
123+
packageToInstall: string, packageName: string): Promise<void> {
124+
125+
install(packageToInstall, verbose, async (code: number, command: string, args: Array<string>) => {
126+
if (code !== 0) {
127+
console.error(`\`${command} ${args.join(' ')}\` failed`);
128+
return;
129+
}
130+
131+
await checkNodeVersion(packageName);
132+
133+
const scriptsPath = path.resolve(
134+
process.cwd(),
135+
'node_modules',
136+
packageName,
137+
'build',
138+
'scripts',
139+
'init.js'
140+
);
141+
142+
// $FlowFixMe (dikaiosune) maybe there's a way to convince flow this is legit?
143+
const init = require(scriptsPath);
144+
await init(root, appName, verbose);
145+
});
146+
}
147+
148+
function getInstallPackage(version: ?string): string {
149+
let packageToInstall = 'react-native-scripts';
150+
const validSemver = semver.valid(version);
151+
if (validSemver) {
152+
packageToInstall += '@' + validSemver;
153+
} else if (version) {
154+
// for tar.gz or alternative paths
155+
packageToInstall = version;
156+
}
157+
return packageToInstall;
158+
}
159+
160+
// Extract package name from tarball url or path.
161+
function getPackageName(installPackage: string): string {
162+
if (installPackage.indexOf('.tgz') > -1) {
163+
// The package name could be with or without semver version, e.g. react-scripts-0.2.0-alpha.1.tgz
164+
// However, this function returns package name only wihout semver version.
165+
const matches = installPackage.match(/^.+\/(.+?)(?:-\d+.+)?\.tgz$/);
166+
if (matches && matches.length >= 2) {
167+
return matches[1];
168+
} else {
169+
throw new Error(`Provided scripts package (${installPackage}) doesn't have a valid filename.`);
170+
}
171+
} else if (installPackage.indexOf('@') > 0) {
172+
// Do not match @scope/ when stripping off @version or @tag
173+
return installPackage.charAt(0) + installPackage.substr(1).split('@')[0];
174+
}
175+
return installPackage;
176+
}
177+
178+
async function checkNodeVersion(packageName: string): Promise<void> {
179+
const packageJsonPath = path.resolve(
180+
process.cwd(),
181+
'node_modules',
182+
packageName,
183+
'package.json'
184+
);
185+
186+
const packageJson = JSON.parse(await fsp.readFile(packageJsonPath));
187+
if (!packageJson.engines || !packageJson.engines.node) {
188+
return;
189+
}
190+
191+
if (!semver.satisfies(process.version, packageJson.engines.node)) {
192+
console.error(
193+
chalk.red(
194+
'You are currently running Node %s but create-react-native-app requires %s.' +
195+
' Please use a supported version of Node.\n'
196+
),
197+
process.version,
198+
packageJson.engines.node
199+
);
200+
process.exit(1);
201+
}
202+
}
203+
204+
function checkAppName(appName: string, packageName: string): void {
205+
const allDependencies = ['react-native-scripts', 'exponent', 'vector-icons', 'react', 'react-native'];
206+
207+
if (allDependencies.indexOf(appName) >= 0) {
208+
console.error(
209+
chalk.red(
210+
'We cannot create a project called `' + appName + '` because a dependency with the same name exists.\n' +
211+
'Due to the way npm works, the following names are not allowed:\n\n'
212+
) +
213+
chalk.cyan(
214+
allDependencies.map((depName) => {
215+
return ' ' + depName;
216+
}).join('\n')
217+
) +
218+
chalk.red('\n\nPlease choose a different project name.')
219+
);
220+
process.exit(1);
221+
}
222+
}
223+
224+
// If project only contains files generated by GH, it’s safe
225+
async function isSafeToCreateProjectIn(root: string): Promise<boolean> {
226+
const validFiles = ['.DS_Store', 'Thumbs.db', '.git', '.gitignore', 'README.md', 'LICENSE'];
227+
return (await fsp.readdir(root))
228+
.every((file) => {
229+
return validFiles.indexOf(file) >= 0;
230+
});
231+
}

0 commit comments

Comments
 (0)