Skip to content

Commit 39f9c07

Browse files
committed
feat(CLI): Script to create a basic typescript boilerplate with no build configuration
1 parent 6d9607d commit 39f9c07

File tree

2 files changed

+212
-11
lines changed

2 files changed

+212
-11
lines changed

packages/cli/createTsLib.js

Lines changed: 210 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,222 @@
1-
'use strict';
1+
"use strict";
22

3-
const validateProjectName = require('validate-npm-package-name');
4-
const chalk = require('chalk');
5-
const commander = require('commander');
6-
const path = require('path');
3+
const validateProjectName = require("validate-npm-package-name");
4+
const chalk = require("chalk");
5+
const semver = require("semver");
6+
const spawn = require('cross-spawn');
7+
const commander = require("commander");
8+
const fs = require("fs-extra");
9+
const path = require("path");
710

8-
const packageJson = require('./package.json');
11+
const packageJson = require("./package.json");
912

1013
let projectName;
1114

1215
const program = new commander.Command(packageJson.name)
1316
.version(packageJson.version)
14-
.arguments('<project-directory>')
15-
.usage(`${chalk.green('<project-directory>')}`)
17+
.arguments("<project-directory>")
18+
.usage(`${chalk.green("<project-directory>")}`)
1619
.action(name => {
1720
projectName = name;
1821
})
19-
.on('--help', () => {
20-
console.log(` Only ${chalk.green('<project-directory>')} is required.`);
22+
.on("--help", () => {
23+
console.log(` Only ${chalk.green("<project-directory>")} is required.`);
2124
console.log();
2225
})
23-
.parse(process.argv);
26+
.parse(process.argv);
27+
28+
if (typeof projectName === "undefined") {
29+
console.error("Please specify the project directory:");
30+
console.log(
31+
` ${chalk.cyan(program.name())} ${chalk.green("<project-directory>")}`
32+
);
33+
console.log();
34+
console.log("For example:");
35+
console.log(
36+
` ${chalk.cyan(program.name())} ${chalk.green("my-typescript-lib")}`
37+
);
38+
console.log();
39+
console.log(
40+
`Run ${chalk.cyan(`${program.name()} --help`)} to see all options.`
41+
);
42+
process.exit(1);
43+
}
44+
45+
createApp(projectName);
46+
47+
function createApp(name) {
48+
const root = path.resolve(name);
49+
const appName = path.basename(root);
50+
51+
checkAppName(appName);
52+
fs.ensureDirSync(name);
53+
if (!isSafeToCreateProjectIn(root, name)) {
54+
process.exit(1);
55+
}
56+
57+
console.log(`Creating a new TypeScript library in ${chalk.green(root)}.`);
58+
console.log();
59+
60+
const originalDirectory = process.cwd();
61+
process.chdir(root);
62+
63+
if (!semver.satisfies(process.version, ">=6.0.0")) {
64+
console.log(
65+
chalk.yellow(
66+
`You are using Node ${process.version} so the project will be bootstrapped with an old unsupported version of tools.\n\n` +
67+
`Please update to Node 6 or higher for a better, fully supported experience.\n`
68+
)
69+
);
70+
}
71+
72+
const npmInfo = checkNpmVersion();
73+
if (!npmInfo.hasMinNpm) {
74+
if (npmInfo.npmVersion) {
75+
console.log(
76+
chalk.yellow(
77+
`You are using npm ${npmInfo.npmVersion} so the project will be boostrapped with an old unsupported version of tools.\n\n` +
78+
`Please update to npm 3 or higher for a better, fully supported experience.\n`
79+
)
80+
);
81+
}
82+
}
83+
run(root, appName, originalDirectory);
84+
}
85+
86+
function run(root, appName, originalDirectory) {
87+
console.log("debug creating library ", root, appName, originalDirectory);
88+
const templatePath = path.resolve(__dirname, "template");
89+
if (fs.existsSync(templatePath)) {
90+
fs.copySync(templatePath, root);
91+
let packageJsonPath = path.join(root, "package.json");
92+
let packageJson = require(packageJsonPath);
93+
packageJson.name = appName;
94+
packageJson.version = "0.0.1";
95+
96+
fs.writeFileSync(
97+
packageJsonPath,
98+
JSON.stringify(packageJson, null, 2)
99+
);
100+
console.log('Installing packages. This might take a couple of minutes.');
101+
return install();
102+
} else {
103+
console.error(
104+
`Could not locate supplied template: ${chalk.green(templatePath)}`
105+
);
106+
return;
107+
}
108+
}
109+
110+
function install() {
111+
return new Promise((resolve, reject) => {
112+
let command;
113+
let args;
114+
115+
command = 'npm';
116+
args = [
117+
'install',
118+
'--save',
119+
'--save-exact',
120+
'--loglevel',
121+
'error',
122+
];
123+
124+
const child = spawn(command, args, { stdio: 'inherit' });
125+
child.on('close', code => {
126+
if (code !== 0) {
127+
reject({
128+
command: `${command} ${args.join(' ')}`,
129+
});
130+
return;
131+
}
132+
resolve();
133+
});
134+
});
135+
}
136+
137+
function getInstallPackage(version) {
138+
let packageToInstall = "react-scripts";
139+
const validSemver = semver.valid(version);
140+
if (validSemver) {
141+
packageToInstall += `@${validSemver}`;
142+
} else if (version) {
143+
// for tar.gz or alternative paths
144+
packageToInstall = version;
145+
}
146+
return packageToInstall;
147+
}
148+
149+
function checkAppName(appName) {
150+
const validationResult = validateProjectName(appName);
151+
if (!validationResult.validForNewPackages) {
152+
console.error(
153+
`Could not create a project called ${chalk.red(
154+
`"${appName}"`
155+
)} because of npm naming restrictions:`
156+
);
157+
printValidationResults(validationResult.errors);
158+
printValidationResults(validationResult.warnings);
159+
process.exit(1);
160+
}
161+
}
162+
163+
function printValidationResults(results) {
164+
if (typeof results !== "undefined") {
165+
results.forEach(error => {
166+
console.error(chalk.red(` * ${error}`));
167+
});
168+
}
169+
}
170+
171+
function isSafeToCreateProjectIn(root, name) {
172+
const validFiles = [
173+
".DS_Store",
174+
"Thumbs.db",
175+
".git",
176+
".gitignore",
177+
".idea",
178+
"README.md",
179+
"LICENSE",
180+
"web.iml",
181+
".hg",
182+
".hgignore",
183+
".hgcheck"
184+
];
185+
console.log();
186+
187+
const conflicts = fs
188+
.readdirSync(root)
189+
.filter(file => !validFiles.includes(file));
190+
if (conflicts.length < 1) {
191+
return true;
192+
}
193+
194+
console.log(
195+
`The directory ${chalk.green(name)} contains files that could conflict:`
196+
);
197+
console.log();
198+
for (const file of conflicts) {
199+
console.log(` ${file}`);
200+
}
201+
console.log();
202+
console.log(
203+
"Either try using a new directory name, or remove the files listed above."
204+
);
205+
206+
return false;
207+
}
208+
209+
function checkNpmVersion() {
210+
let hasMinNpm = false;
211+
let npmVersion = null;
212+
try {
213+
npmVersion = execSync("npm --version").toString().trim();
214+
hasMinNpm = semver.gte(npmVersion, "3.0.0");
215+
} catch (err) {
216+
// ignore
217+
}
218+
return {
219+
hasMinNpm: hasMinNpm,
220+
npmVersion: npmVersion
221+
};
222+
}

packages/cli/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
"dependencies": {
4242
"chalk": "^2.0.1",
4343
"commander": "^2.11.0",
44+
"cross-spawn": "^5.1.0",
45+
"fs-extra": "^4.0.0",
4446
"semver": "^5.3.0",
4547
"validate-npm-package-name": "^3.0.0"
4648
}

0 commit comments

Comments
 (0)