8000 Upgrade commander to version 13.1.0 and refactor CLI options handling · Create-Node-App/create-node-app@e75ef11 · GitHub
[go: up one dir, main page]

Skip to content

Commit e75ef11

Browse files
Upgrade commander to version 13.1.0 and refactor CLI options handling
1 parent 5e325f4 commit e75ef11

File tree

6 files changed

+133
-33
lines changed

6 files changed

+133
-33
lines changed

package-lock.json

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

packages/create-awesome-node-app/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"@create-node-app/core": "*",
4949
"axios": "^1.6.0",
5050
"ci-info": "^4.0.0",
51+
"commander": "^13.1.0",
5152
"prompts": "^2.4.1",
5253
"semver": "^7.5.2",
5354
"yargs": "^17.0.1"

packages/create-awesome-node-app/src/index.ts

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1-
import program from "commander";
1+
import { Command } from "commander";
22
import chalk from "chalk";
33
import semver from "semver";
44
import {
55
createNodeApp,
66
checkForLatestVersion,
77
checkNodeVersion,
8+
CnaOptions,
89
} from "@create-node-app/core";
910
import { getCnaOptions } from "./options";
1011
import packageJson from "../package.json";
12+
import { TemplateOrExtension } from "@create-node-app/core/loaders";
13+
14+
const program = new Command();
1115

1216
const main = async () => {
1317
let projectName = "my-project";
@@ -16,31 +20,33 @@ const main = async () => {
1620
.version(packageJson.version)
1721
.arguments("[project-directory]")
1822
.usage(`${chalk.green("[project-directory]")} [options]`)
19-
.action((name) => {
20-
projectName = name || projectName;
21-
})
22-
.option("--verbose", "print additional logs")
23-
.option("--info", "print environment debug info")
23+
.option("-v, --verbose", "print additional logs")
24+
.option("-i, --info", "print environment debug info")
2425
.option(
2526
"--no-install",
2627
"Generate package.json without installing dependencies"
2728
)
2829
.option(
29-
"--template <template>",
30+
"-t, --template <template>",
3031
"specify a template for the created project"
3132
)
3233
.option(
33-
"--extend [extensions...]",
34+
"--addons [extensions...]",
3435
"specify extensions to apply for the boilerplate generation"
3536
)
3637
.option("--use-yarn", "use yarn instead of npm or pnpm")
3738
.option("--use-pnpm", "use pnpm instead of yarn or npm")
38-
.parse(process.argv);
39+
.option("--interactive", "run in interactive mode to select options", false)
40+
.action((providedProjectName, _options) => {
41+
projectName = providedProjectName || projectName;
42+
});
43+
44+
program.parse(process.argv);
3945

46+
const opts = program.opts();
4047
checkNodeVersion(packageJson.engines.node, packageJson.name);
4148

4249
const latest = await checkForLatestVersion("create-awesome-node-app");
43-
4450
if (latest && semver.lt(packageJson.version, latest)) {
4551
console.log();
4652
console.error(
@@ -52,19 +58,26 @@ const main = async () => {
5258
return;
5359
}
5460

55-
const { useYarn, usePnpm, ...opts } = program.opts();
61+
const options = await getCnaOptions({ ...opts, projectName });
5662

63+
const { useYarn, usePnpm, ...restOptions } = options;
5764
const packageManager = useYarn ? "yarn" : usePnpm ? "pnpm" : "npm";
58-
const templatesOrExtensions = [opts.template]
59-
.concat(opts.extend || [])
60-
.filter(Boolean)
61-
.map((templateOrExtension) => ({
62-
url: templateOrExtension,
63-
}));
65+
66+
const templatesOrExtensions: TemplateOrExtension[] = [restOptions.template]
67+
.concat(Array.isArray(restOptions.addons) ? restOptions.addons : [])
68+
.concat(Array.isArray(restOptions.extend) ? restOptions.extend : [])
69+
.reduce((acc, templateOrExtension) => {
70+
if (!templateOrExtension) {
71+
return acc;
72+
}
73+
return acc.concat({
74+
url: templateOrExtension,
75+
});
76+
}, [] as TemplateOrExtension[]);
6477

6578
return createNodeApp(
6679
projectName,
67-
{ ...opts, packageManager, templatesOrExtensions, projectName },
80+
{ ...restOptions, packageManager, templatesOrExtensions, projectName },
6881
getCnaOptions
6982
);
7083
};

packages/create-awesome-node-app/src/options.ts

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,88 @@ import {
88
getTemplateCategories,
99
getTemplatesForCategory,
1010
getExtensionsGroupedByCategory,
11+
TemplateData,
1112
} from "./templates";
1213

1314
const PACKAGE_MANAGERS = ["npm", "yarn", "pnpm"];
1415

15-
export const getCnaOptions = async (options: CnaOptions) => {
16-
if (isCI) {
16+
const isValidUrl = (url: string): boolean => {
17+
try {
18+
new URL(url);
19+
return true;
20+
} catch {
21+
return false;
22+
}
23+
};
24+
25+
export const getCnaOptions = async (
26+
options: CnaOptions
27+
): Promise<CnaOptions> => {
28+
const categories = await getTemplateCategories();
29+
30+
if (isCI || !options.interactive) {
31+
let matchedTemplate: TemplateData | undefined;
32+
33+
// Handle cases where templates/extensions are not valid URLs
34+
if (options.template && !isValidUrl(options.template)) {
35+
const allTemplates = (
36+
await Promise.all(
37+
categories.map((category) => getTemplatesForCategory(category))
38+
)
39+
).flat();
40+
matchedTemplate = allTemplates.find(
41+
(template) => template.slug === options.template
42+
);
43+
if (matchedTemplate) {
44+
options.template = matchedTemplate.url;
45+
46+
// Apply initial values for custom options
47+
if (matchedTemplate.customOptions) {
48+
matchedTemplate.customOptions.forEach((customOption) => {
49+
if (customOption.name && customOption.initial !== undefined) {
50+
options[customOption.name] = customOption.initial;
51+
}
52+
});
53+
}
54+
} else {
55+
throw new Error(
56+
`Invalid template slug: '${options.template}'. Please provide a valid template slug.`
57+
);
58+
}
59+
}
60+
61+
if (options.addons && Array.isArray(options.addons)) {
62+
const extensionsGroupedByCategory = await getExtensionsGroupedByCategory([
63+
matchedTemplate?.type || "custom",
64+
"all",
65+
]);
66+
67+
options.addons = options.addons.map((addon) => {
68+
if (!isValidUrl(addon)) {
69+
for (const extensions of Object.values(extensionsGroupedByCategory)) {
70+
const matchedExtension = extensions.find(
71+
(extension) => extension.slug === addon
72+
);
73+
if (matchedExtension) {
74+
return matchedExtension.url;
75+
}
76+
}
77+
throw new Error(
78+
`Invalid extension slug: '${addon}'. Please provide a valid extension slug.`
79+
);
80+
}
81+
return addon;
82+
});
83+
}
84+
85+
// Non-interactive mode: Use provided options directly
1786
if (options.verbose) {
1887
console.log(JSON.stringify(options, null, 2));
1988
}
2089

2190
return options;
2291
}
2392

24-
const categories = await getTemplateCategories();
25-
2693
const categoriesOptions = [
2794
...categories.map((category) => ({
2895
title: category,

packages/create-awesome-node-app/src/templates.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const TEMPLATE_DATA_FILE_URL =
66

77
export type TemplateOrExtensionData = {
88
name: string;
9+
slug: string;
910
description: string;
1011
url: string;
1112
category: string;
@@ -64,7 +65,13 @@ const getTemplateData = async () => {
6465
return templateDataCache.data;
6566
};
6667

67-
export const getTemplateCategories = async () => {
68+
export const getTemplateCategories = async (
69+
cliArgs?: Record<string, string>
70+
) => {
71+
if (cliArgs?.category) {
72+
return [cliArgs.category];
73+
}
74+
6875
const templateData = await getTemplateData();
6976

7077
// Ensure that the categories are unique
@@ -77,18 +84,31 @@ export const getTemplateCategories = async () => {
7784
return Array.from(categories);
7885
};
7986

80-
export const getTemplatesForCategory = async (category: string) => {
87+
export const getTemplatesForCategory = async (
88+
category?: string,
89+
cliArgs?: Record<string, string>
90+
) => {
91+
const selectedCategory = cliArgs?.category || category;
92+
if (!selectedCategory) {
93+
throw new Error("Category is required in non-interactive mode.");
94+
}
95+
8196
const templateData = await getTemplateData();
8297

8398
const templates = templateData.templates.filter(
84-
(template) => template.category === category
99+
(template) => template.category === selectedCategory
85100
);
86101

87102
return templates;
88103
};
89104

90-
export const getExtensionsGroupedByCategory = async (type: ExtensionType) => {
91-
const safeType = Array.isArray(type) ? type : [type];
105+
export const getExtensionsGroupedB B67E yCategory = async (
106+
type: ExtensionType,
107+
cliArgs?: Record<string, string>
108+
) => {
109+
const selectedType = cliArgs?.type ? cliArgs.type.split(",") : type;
110+
111+
const safeType = Array.isArray(selectedType) ? selectedType : [selectedType];
92112

93113
const templateData = await getTemplateData();
94114

packages/create-node-app-core/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
},
3838
"dependencies": {
3939
"chalk": "^4.1.0",
40-
"commander": "^7.0.0",
4140
"cross-spawn": "^6.0.5",
4241
"debug": "^4.2.0",
4342
"download": "^3.3.0",

0 commit comments

Comments
 (0)
0