Skip to content

Commit 18be075

Browse files
committed
Add npm-init example
1 parent 32be048 commit 18be075

File tree

1 file changed

+161
-0
lines changed

1 file changed

+161
-0
lines changed

examples/npm-init.ts

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import { InteractiveCommand, InteractiveOption } from "../src/index.ts";
2+
import * as inquirer from "@inquirer/prompts";
3+
import fs from "node:fs";
4+
import path from "node:path";
5+
6+
const program = new InteractiveCommand();
7+
8+
const createPackageJson = <T extends Record<string, unknown>>(record: T): T => {
9+
const { test, ...otherProperties } = record;
10+
11+
return Object.fromEntries(
12+
Object.entries({
13+
...otherProperties,
14+
...(test
15+
? {
16+
scripts: {
17+
test: test ?? 'echo "Error: no test specified" && exit 1',
18+
},
19+
}
20+
: {}),
21+
}).filter(([_, value]) => value !== undefined),
22+
) as T;
23+
};
24+
25+
program
26+
.version("1.0.0", "-V, --display-version", "output the version number")
27+
.hook("preSubcommand", async (thisCommand, actionCommand) => {
28+
if (actionCommand.name() !== "init") {
29+
return;
30+
}
31+
32+
// Without package.json, npm init prompts for the following:
33+
// - name
34+
// - version
35+
// - description
36+
// - entry point
37+
// - test command
38+
// - git repository
39+
// - keywords
40+
// - author
41+
// - license
42+
// When package.json exists, npm init prompts for the following:
43+
// - name
44+
// - version
45+
// - description
46+
// - git repository
47+
// - author
48+
// - license
49+
50+
let packageJson: Record<string, unknown> = {};
51+
52+
try {
53+
packageJson = JSON.parse(
54+
await fs.promises.readFile("package.json", "utf8"),
55+
) as Record<string, unknown>;
56+
57+
// Disable some prompts when package.json exists
58+
const promptsToDisable = [
59+
// Entry point
60+
"main",
61+
"test",
62+
"keywords",
63+
];
64+
for (const promptToDisable of promptsToDisable) {
65+
(
66+
actionCommand.options.find(
67+
(option) => option.attributeName() === promptToDisable,
68+
) as InteractiveOption
69+
)
70+
// eslint-disable-next-line unicorn/no-useless-undefined
71+
.prompt(undefined);
72+
}
73+
} catch {}
74+
75+
for (const [key, value] of Object.entries(packageJson)) {
76+
actionCommand.setOptionValueWithSource(key, value, "config");
77+
}
78+
79+
actionCommand.setOptionValueWithSource(
80+
"name",
81+
packageJson.name ?? path.basename(process.cwd()),
82+
"config",
83+
);
84+
})
85+
.command("init")
86+
.requiredOption("-n, --name <name>", "package name")
87+
.requiredOption("-v, --version <version>", "version", "1.0.0")
88+
.option("-d, --description [description]", "description")
89+
.option("-m, --main [entry]", "entry point", "index.js")
90+
.option("-t, --test [test]", "test command")
91+
.option("-g, --git [git]", "git repository")
92+
.addOption(
93+
new InteractiveOption("-k, --keywords [keywords...]", "keywords").argParser(
94+
(value) => value.split(/[ ,]+/g).filter(Boolean),
95+
),
96+
)
97+
.option("-a, --author [author]", "author")
98+
.option("-l, --license [license]", "license", "ISC")
99+
.addOption(
100+
// This option is defined here for generating the help message
101+
new InteractiveOption("-y, --yes", "confirm the creation of package.json")
102+
// eslint-disable-next-line unicorn/no-useless-undefined
103+
.prompt(undefined),
104+
)
105+
.action(
106+
async (
107+
{
108+
confirm,
109+
// This option is for the subcommand "init". When positional
110+
// options are not enabled, this value is always set to the default
111+
// value (true)
112+
interactive,
113+
...otherOptions
114+
},
115+
command: InteractiveCommand,
116+
) => {
117+
let newConfirm = confirm === true || confirm === undefined;
118+
119+
const isInteractive = Boolean(command.optsWithGlobals().interactive);
120+
if (isInteractive) {
121+
console.log(
122+
JSON.stringify(createPackageJson(otherOptions), undefined, 2),
123+
);
124+
125+
newConfirm = await inquirer.confirm({
126+
message: "Is this OK?",
127+
default: Boolean(newConfirm),
128+
});
129+
}
130+
131+
if (!newConfirm) {
132+
console.log("Aborted.");
133+
return;
134+
}
135+
136+
console.log("TODO: create package.json with the following contents:");
137+
console.log(
138+
JSON.stringify(createPackageJson(otherOptions), undefined, 2),
139+
);
140+
},
141+
);
142+
143+
await program
144+
.interactive("-I, --no-interactive", "disable interactive mode")
145+
.parseAsync()
146+
.catch((error) => {
147+
if (
148+
!(
149+
error instanceof Error &&
150+
error.message.startsWith("User force closed the prompt with")
151+
)
152+
) {
153+
// eslint-disable-next-line @typescript-eslint/no-throw-literal
154+
throw error;
155+
}
156+
});
157+
158+
// Try the following commands, with and without a package.json in the current working directory:
159+
// npm-init init
160+
// npm-init init --name foo
161+
// npm-init init -I --name foo --version "2.0.0"

0 commit comments

Comments
 (0)