Skip to content

Commit 460fda4

Browse files
authored
fix: use modern node & npm (#814)
This includes various fixes for modern npm (Which does not have the API from NPM 7 anymore), adds yarn support, moves create-probot-app to ESM, and requires Node 16. Requiring Node 16 for create-probot-app should help encourage people to use a modern node for new apps, so migratting probot to require Node 14 or 16 in the future should be easier.
1 parent a0fbe24 commit 460fda4

File tree

10 files changed

+6718
-11144
lines changed

10 files changed

+6718
-11144
lines changed

package-lock.json

Lines changed: 6595 additions & 11023 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@
1515
"test:template": "./script/test-template.sh",
1616
"lint": "prettier --ignore-path .gitignore --check '**/*.{js,ts,json,yml,md}'",
1717
"lint:fix": "prettier --ignore-path .gitignore --write '**/*.{js,ts,json,yml,md}'",
18-
"dev": "tsc-watch --onFirstSuccess \"npm run dev:make-cpa\"",
19-
"dev:make-cpa": "ts-node -e 'import { chBinMod } from \"./script/make-executable\"; chBinMod(\"create-probot-app\")'",
20-
"dev:make-tests": "ts-node -e 'import { chBinMod } from \"./script/make-executable\"; chBinMod(\"run-tests\")'",
18+
"dev:make-cpa": "node --input-type=module -e 'import { chBinMod } from \"./script/make-executable.js\"; chBinMod(\"create-probot-app\")'",
19+
"dev:make-tests": "node --input-type=module -e 'import { chBinMod } from \"./script/make-executable.js\"; chBinMod(\"run-tests\")'",
2120
"build": "npm run build:clean && tsc && npm run dev:make-cpa && npm run dev:make-tests",
2221
"build:clean": "rimraf bin",
2322
"build:source": "tsc && npm run dev:make-cpa",
@@ -30,39 +29,41 @@
3029
"author": "Brandon Keepers",
3130
"license": "ISC",
3231
"dependencies": {
33-
"chalk": "^4.1.2",
34-
"commander": "^9.0.0",
32+
"chalk": "^5.2.0",
33+
"commander": "^10.0.0",
3534
"conjecture": "^0.1.2",
3635
"cross-spawn": "^7.0.3",
3736
"egad": "^0.2.0",
38-
"fs-extra": "^11.0.0",
39-
"inquirer": "^8.2.4",
40-
"jsesc": "^3.0.1",
37+
"execa": "^6.1.0",
38+
"fs-extra": "^11.1.0",
39+
"inquirer": "^9.1.4",
40+
"jsesc": "^3.0.2",
4141
"lodash.camelcase": "^4.3.0",
4242
"lodash.kebabcase": "^4.1.1",
43-
"npm": "^9.0.0",
44-
"simple-git": "^3.0.3",
43+
"simple-git": "^3.15.1",
4544
"stringify-author": "^0.1.3",
4645
"validate-npm-package-name": "^5.0.0"
4746
},
4847
"devDependencies": {
4948
"@types/cross-spawn": "^6.0.2",
50-
"@types/fs-extra": "^9.0.1",
51-
"@types/inquirer": "^8.1.1",
52-
"@types/jsesc": "^3.0.0",
53-
"@types/lodash.camelcase": "^4.3.6",
54-
"@types/lodash.kebabcase": "^4.1.6",
55-
"@types/node": "^18.0.0",
49+
"@types/fs-extra": "^11.0.1",
50+
"@types/inquirer": "^9.0.3",
51+
"@types/jsesc": "^3.0.1",
52+
"@types/lodash.camelcase": "^4.3.7",
53+
"@types/lodash.kebabcase": "^4.1.7",
54+
"@types/node": "^18.11.18",
5655
"@types/npm": "^7.19.0",
57-
"@types/rimraf": "^3.0.0",
58-
"@types/shelljs": "^0.8.8",
56+
"@types/rimraf": "^3.0.2",
57+
"@types/shelljs": "^0.8.11",
5958
"@types/validate-npm-package-name": "^4.0.0",
60-
"prettier": "^2.2.1",
61-
"rimraf": "^3.0.2",
59+
"prettier": "^2.8.3",
60+
"rimraf": "^4.0.5",
6261
"semantic-release": "^19.0.2",
63-
"shelljs": "^0.8.4",
64-
"ts-node": "^10.0.0",
65-
"tsc-watch": "^6.0.0",
66-
"typescript": "^4.0.2"
62+
"shelljs": "^0.8.5",
63+
"typescript": "^4.9.4"
64+
},
65+
"type": "module",
66+
"engines": {
67+
"node": ">= 16.7.0"
6768
}
6869
}

script/make-executable.ts renamed to script/make-executable.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import fs from "fs";
22
import path from "path";
3+
import { fileURLToPath } from 'url';
34
import shell from "shelljs";
4-
import pkg from "../package.json";
5+
import pkg from "../package.json" assert { type: "json" };
56

7+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
68
/**
79
* Converts TS file under ./bin/ into an executable file.
810
*
@@ -14,10 +16,10 @@ import pkg from "../package.json";
1416
*
1517
* @param {string} name the name of the built JS file, e.g. 'create-probot-app'
1618
*/
17-
export function chBinMod(name: string): void {
18-
const binList: Record<string, string> = pkg.bin;
19-
const jsFilePath: string = binList[name];
20-
const distributableBinary: string = path.join(__dirname, "..", jsFilePath);
19+
export function chBinMod(name) {
20+
const binList = pkg.bin;
21+
const jsFilePath = binList[name];
22+
const distributableBinary = path.join(__dirname, "..", jsFilePath);
2123

2224
try {
2325
if (fs.existsSync(distributableBinary)) {

src/create-probot-app.ts

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,20 @@
11
#!/usr/bin/env node
2-
import { askUser, runCliManager } from "./helpers/user-interaction";
3-
import { initGit } from "./helpers/init-git";
4-
import { installAndBuild } from "./helpers/run-npm";
5-
import { makeScaffolding } from "./helpers/filesystem";
6-
import { printSuccess, red } from "./helpers/write-help";
2+
import { askUser, runCliManager } from "./helpers/user-interaction.js";
3+
import { initGit } from "./helpers/init-git.js";
4+
import { installAndBuild } from "./helpers/run-npm.js";
5+
import { makeScaffolding } from "./helpers/filesystem.js";
6+
import { printSuccess, red } from "./helpers/write-help.js";
77

8-
async function main(): Promise<void> {
9-
runCliManager()
10-
.then((cliConfig) => askUser(cliConfig))
11-
.then((config) => makeScaffolding(config))
12-
.then(async (config) => {
13-
if (config.gitInit) await initGit(config.destination);
14-
return config;
15-
})
16-
.then(async (config) => await installAndBuild(config))
17-
.then((config) => printSuccess(config.appName, config.destination))
18-
.catch((err) => {
19-
console.log(red(err));
20-
process.exit(1);
21-
});
22-
}
23-
24-
main();
8+
runCliManager()
9+
.then((cliConfig) => askUser(cliConfig))
10+
.then((config) => makeScaffolding(config))
11+
.then(async (config) => {
12+
if (config.gitInit) await initGit(config.destination);
13+
return config;
14+
})
15+
.then(async (config) => await installAndBuild(config))
16+
.then((config) => printSuccess(config.appName, config.destination))
17+
.catch((err) => {
18+
console.log(red(err));
19+
process.exit(1);
20+
});

src/helpers/filesystem.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
import path from "path";
2-
import fs from "fs-extra";
1+
import * as path from "path";
2+
import { fileURLToPath } from 'url';
3+
import * as fs from "fs";
34
import { generate } from "egad";
4-
import { Config } from "./user-interaction";
5-
import { yellow, green } from "./write-help";
5+
import { Config } from "./user-interaction.js";
6+
import { yellow, green } from "./write-help.js";
7+
8+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
69

710
export const templatesSourcePath = path.join(__dirname, "../../templates/");
811

@@ -62,9 +65,11 @@ export async function makeScaffolding(config: Config): Promise<Config> {
6265
[
6366
path.join(templatesSourcePath, "__common__"),
6467
path.join(templatesSourcePath, config.template),
65-
].forEach((source) => fs.copySync(source, tempDestPath));
68+
].forEach((source) => fs.cpSync(source, tempDestPath, {
69+
recursive: true
70+
}));
6671

67-
fs.removeSync(path.join(tempDestPath, "__description__.txt"));
72+
fs.rmSync(path.join(tempDestPath, "__description__.txt"));
6873

6974
if (fs.existsSync(path.join(tempDestPath, "gitignore")))
7075
fs.renameSync(
@@ -76,7 +81,9 @@ export async function makeScaffolding(config: Config): Promise<Config> {
7681
overwrite: config.overwrite,
7782
});
7883

79-
fs.removeSync(tempDestPath);
84+
fs.rmSync(tempDestPath, {
85+
recursive: true
86+
});
8087

8188
result.forEach((fileInfo) => {
8289
console.log(

src/helpers/init-git.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import fs from "fs-extra";
2-
import path from "path";
3-
import spawn from "cross-spawn";
1+
import * as fs from "fs-extra";
2+
import * as path from "path";
3+
import * as spawn from "cross-spawn";
44
import simplegit from "simple-git";
55

6-
import { green, yellow, red } from "./write-help";
6+
import { green, yellow, red } from "./write-help.js";
77

88
function isInGitRepo(path: string): boolean {
99
const gitRevParse = spawn.sync(

src/helpers/run-npm.ts

Lines changed: 42 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
1-
import npm from "npm";
2-
import { bold, yellow } from "./write-help";
3-
import { Config } from "./user-interaction";
1+
import { bold, yellow } from "./write-help.js";
2+
import { Config } from "./user-interaction.js";
3+
import { execa } from "execa";
4+
5+
export function detectPackageManager(): string {
6+
const { npm_config_user_agent: userAgent } = process.env;
7+
if (userAgent) {
8+
if (userAgent.includes("yarn")) return "yarn";
9+
if (userAgent.includes("npm")) return "npm";
10+
}
11+
return "npm";
12+
}
413

514
/**
615
* Run `npm install` in `destination` folder, then run `npm run build`
@@ -10,52 +19,36 @@ import { Config } from "./user-interaction";
1019
*
1120
* @returns Promise which returns the input Config object
1221
*/
13-
export function installAndBuild(config: Config): Promise<Config> {
14-
class NpmError extends Error {
15-
constructor(msg: string, command: string) {
16-
const message = `
17-
18-
Could not ${msg}.
19-
Try running ${bold("npm " + command)} yourself.
20-
21-
`;
22-
super(message);
23-
}
22+
export async function installAndBuild(config: Config): Promise<Config> {
23+
const originalDir = process.cwd();
24+
process.chdir(config.destination);
25+
const packageManager = detectPackageManager();
26+
console.log(
27+
yellow("\nInstalling dependencies. This may take a few minutes...\n")
28+
);
29+
try {
30+
await execa(packageManager, ["install"]);
31+
} catch (error: any) {
32+
process.chdir(originalDir);
33+
throw new Error(
34+
`\nCould not install npm dependencies.\nTry running ${bold(
35+
"npm install"
36+
)} yourself.\n`
37+
);
2438
}
25-
26-
return new Promise((resolve, reject) => {
27-
npm.load(function (err) {
28-
if (err) reject(err);
29-
30-
const defaultPrefix = npm.prefix;
31-
32-
const rejectWithError = (error: NpmError) => {
33-
npm.prefix = defaultPrefix;
34-
reject(error);
35-
};
36-
37-
const resolveWithConfig = () => {
38-
npm.prefix = defaultPrefix;
39-
resolve(config);
40-
};
41-
42-
console.log(
43-
yellow("\nInstalling dependencies. This may take a few minutes...\n")
39+
if (config.toBuild) {
40+
console.log(yellow("\n\nCompile application...\n"));
41+
try {
42+
await execa(packageManager, ["run", "build"]);
43+
} catch (error: any) {
44+
process.chdir(originalDir);
45+
throw new Error(
46+
`\nCould not build application.\nTry running ${bold(
47+
"npm run build"
48+
)} yourself.\n`
4449
);
45-
46-
npm.prefix = config.destination;
47-
npm.commands.install([], function (err) {
48-
if (err)
49-
rejectWithError(new NpmError("install npm dependencies", "install"));
50-
else if (config.toBuild) {
51-
console.log(yellow("\n\nCompile application...\n"));
52-
npm.commands["run-script"](["build"], function (err) {
53-
if (err)
54-
rejectWithError(new NpmError("build application", "run build"));
55-
else resolveWithConfig();
56-
});
57-
} else resolveWithConfig();
58-
});
59-
});
60-
});
50+
}
51+
}
52+
process.chdir(originalDir);
53+
return config;
6154
}

src/helpers/user-interaction.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
1-
import path from "path";
1+
import * as fs from "fs";
2+
import * as path from "path";
3+
import { fileURLToPath } from "url";
24

35
import { guessEmail, guessAuthor, guessGitHubUsername } from "conjecture";
46
import camelCase from "lodash.camelcase";
5-
import commander from "commander";
7+
import * as commander from "commander";
68
import inquirer, { Answers, Question, QuestionCollection } from "inquirer";
79
import jsesc from "jsesc";
810
import kebabCase from "lodash.kebabcase";
911
import stringifyAuthor from "stringify-author";
1012
import validatePackageName from "validate-npm-package-name";
1113

12-
import { blue, red, printHelpAndFail } from "./write-help";
13-
import { getTemplates, ensureValidDestination } from "./filesystem";
14+
import { blue, red, printHelpAndFail } from "./write-help.js";
15+
import { getTemplates, ensureValidDestination } from "./filesystem.js";
16+
17+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
1418

1519
const templateDelimiter = " => ";
1620

@@ -197,7 +201,7 @@ export async function runCliManager(): Promise<CliConfig> {
197201
// TSC mangles output directory when using normal import methods for
198202
// package.json. See
199203
// https://github.com/Microsoft/TypeScript/issues/24715#issuecomment-542490675
200-
const pkg = require(require.resolve("../../package.json"));
204+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "..", "package.json"), "utf-8"));
201205

202206
const program = new commander.Command("create-probot-app")
203207
.arguments("[destination]")

src/run-tests.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env node
22
import fs from "fs";
33
import path from "path";
4-
import { red } from "./helpers/write-help";
4+
import { red } from "./helpers/write-help.js";
55

66
/**
77
* Ensure `package.json` from templated app meets all requirements

tsconfig.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
{
22
"compilerOptions": {
33
"allowJs": false,
4-
"module": "commonjs",
5-
"target": "es2015",
4+
"module": "ESNext",
5+
"target": "es2022",
66
"moduleResolution": "node",
77
"strict": true,
88
"noImplicitAny": true,
9-
"resolveJsonModule": true,
10-
"esModuleInterop": true,
9+
"allowSyntheticDefaultImports": true,
1110
"skipLibCheck": false,
1211
"outDir": "bin",
1312
"baseUrl": ".",

0 commit comments

Comments
 (0)