yehle 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +183 -0
- package/bin/cli.js +8 -0
- package/dist/cli/animated-intro.js +135 -0
- package/dist/cli/logger.js +48 -0
- package/dist/cli/prompts.js +95 -0
- package/dist/cli/tasks.js +68 -0
- package/dist/core/constants.js +5 -0
- package/dist/core/fs.js +185 -0
- package/dist/core/git.js +90 -0
- package/dist/core/pkg-manager.js +65 -0
- package/dist/core/shell.js +105 -0
- package/dist/core/template-registry.js +229 -0
- package/dist/core/utils.js +57 -0
- package/dist/index.js +37 -0
- package/dist/resources/index.js +33 -0
- package/dist/resources/package/command.js +148 -0
- package/dist/resources/package/config.js +159 -0
- package/dist/resources/package/setup.js +106 -0
- package/dist/resources/package/typescript.js +19 -0
- package/package.json +63 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.capitalizeFirstLetter = capitalizeFirstLetter;
|
|
4
|
+
exports.toSlug = toSlug;
|
|
5
|
+
exports.sleep = sleep;
|
|
6
|
+
exports.truncate = truncate;
|
|
7
|
+
const utils_1 = require("consola/utils");
|
|
8
|
+
/**
|
|
9
|
+
* Capitalizes the first letter of the input string.
|
|
10
|
+
* @param string - The input string to capitalize.
|
|
11
|
+
* @returns The string with the first character converted to uppercase.
|
|
12
|
+
*/
|
|
13
|
+
function capitalizeFirstLetter(string) {
|
|
14
|
+
return string[0].toUpperCase() + string.slice(1);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Convert an arbitrary string into a URL and npm-friendly slug.
|
|
18
|
+
* @param value - The input string to slugify (e.g., package or repo name).
|
|
19
|
+
* @returns A normalized slug suitable for package/repo names.
|
|
20
|
+
*/
|
|
21
|
+
function toSlug(value) {
|
|
22
|
+
// Normalize case/whitespace and extract last path-like segment (supports URLs and Windows paths)
|
|
23
|
+
const normalized = value.trim().toLowerCase();
|
|
24
|
+
const segments = normalized.split(/[\\/]+/).filter(Boolean);
|
|
25
|
+
let base = segments.length ? segments[segments.length - 1] : normalized;
|
|
26
|
+
// Handle npm scopes like "@scope/name" (base will typically be "name", but keep safe)
|
|
27
|
+
base = base.replace(/^@/, "");
|
|
28
|
+
// Strip common VCS suffix if present
|
|
29
|
+
base = base.replace(/\.git$/, "");
|
|
30
|
+
// Replace invalid characters with a hyphen
|
|
31
|
+
base = base.replaceAll(/[^a-z0-9._-]+/g, "-");
|
|
32
|
+
// Collapse multiple hyphens
|
|
33
|
+
base = base.replaceAll(/-+/g, "-");
|
|
34
|
+
// Trim leading and trailing hyphens
|
|
35
|
+
base = base.replace(/^-+/, "");
|
|
36
|
+
base = base.replace(/-+$/, "");
|
|
37
|
+
return base;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Sleep for the specified number of milliseconds.
|
|
41
|
+
* @param ms - The number of milliseconds to sleep.
|
|
42
|
+
*/
|
|
43
|
+
function sleep(ms) {
|
|
44
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Truncate a string to a maximum visible length, respecting ANSI sequences.
|
|
48
|
+
* If truncated, appends "...". ANSI styling is not preserved in the truncated section.
|
|
49
|
+
* @param s - The input string to truncate.
|
|
50
|
+
* @param max - The maximum visible length.
|
|
51
|
+
*/
|
|
52
|
+
function truncate(s, max) {
|
|
53
|
+
const raw = (0, utils_1.stripAnsi)(s);
|
|
54
|
+
if (raw.length <= max)
|
|
55
|
+
return s;
|
|
56
|
+
return `${raw.slice(0, Math.max(0, max - 3))}...`;
|
|
57
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = run;
|
|
7
|
+
const cac_1 = __importDefault(require("cac"));
|
|
8
|
+
const resources_1 = require("./resources");
|
|
9
|
+
function run() {
|
|
10
|
+
const app = (0, cac_1.default)("yehle");
|
|
11
|
+
// Register commands
|
|
12
|
+
(0, resources_1.registerResourcesCli)(app);
|
|
13
|
+
// Register the help command
|
|
14
|
+
app.help();
|
|
15
|
+
// Parse argv and, on parse errors, re-run with contextual --help
|
|
16
|
+
const args = process.argv.slice(2).filter(Boolean);
|
|
17
|
+
// Show global help when just the root command is called
|
|
18
|
+
if (args.length === 0) {
|
|
19
|
+
app.outputHelp();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
// Attempt to run the command
|
|
23
|
+
try {
|
|
24
|
+
app.parse(process.argv);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// If the command failed (due to incorrect arguments, missing commands, etc)
|
|
28
|
+
// Attempt to show help for the command by appending --help to the original args
|
|
29
|
+
try {
|
|
30
|
+
app.parse([...process.argv, "--help"]);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Final fallback: show top-level help
|
|
34
|
+
app.outputHelp();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.registerResourcesCli = registerResourcesCli;
|
|
7
|
+
const logger_1 = __importDefault(require("../cli/logger"));
|
|
8
|
+
const command_1 = __importDefault(require("./package/command"));
|
|
9
|
+
async function registerResourcesCli(app) {
|
|
10
|
+
// Top-level description
|
|
11
|
+
app.usage("<resource> [options]");
|
|
12
|
+
// Register the `package` command
|
|
13
|
+
app
|
|
14
|
+
.command("package", "Generate a package")
|
|
15
|
+
.option("--name <name>", "Package name")
|
|
16
|
+
.option("--lang <lang>", "Target language (e.g., typescript)")
|
|
17
|
+
.option("--public", "Public package (will setup for publishing to a package registry)")
|
|
18
|
+
.option("--template <template>", "Starter template for the package")
|
|
19
|
+
.action(async (options) => {
|
|
20
|
+
try {
|
|
21
|
+
await (0, command_1.default)({
|
|
22
|
+
lang: options.lang,
|
|
23
|
+
name: options.name,
|
|
24
|
+
template: options.template,
|
|
25
|
+
public: options.public ? Boolean(options.public) : undefined,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
30
|
+
logger_1.default.error(msg);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.generatePackage = generatePackage;
|
|
40
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
41
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
42
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
43
|
+
const logger_1 = __importStar(require("../../cli/logger"));
|
|
44
|
+
const tasks_1 = __importDefault(require("../../cli/tasks"));
|
|
45
|
+
const git_1 = require("../../core/git");
|
|
46
|
+
const pkg_manager_1 = require("../../core/pkg-manager");
|
|
47
|
+
const utils_1 = require("../../core/utils");
|
|
48
|
+
const config_1 = require("./config");
|
|
49
|
+
const setup_1 = require("./setup");
|
|
50
|
+
async function generatePackage(options = {}) {
|
|
51
|
+
await logger_1.default.intro("generating package...");
|
|
52
|
+
// Gather configuration (skip prompts when options are provided)
|
|
53
|
+
const generateConfig = await (0, config_1.getGeneratePackageConfiguration)({
|
|
54
|
+
lang: options.lang,
|
|
55
|
+
name: options.name,
|
|
56
|
+
template: options.template,
|
|
57
|
+
public: options.public,
|
|
58
|
+
});
|
|
59
|
+
let packageManagerVersion = "";
|
|
60
|
+
const packageManager = pkg_manager_1.LANGUAGE_PACKAGE_MANAGER[generateConfig.lang];
|
|
61
|
+
const resolvedTargetDir = node_path_1.default.resolve(process.cwd(), (0, utils_1.toSlug)(generateConfig.name));
|
|
62
|
+
// Preflight checks
|
|
63
|
+
console.log();
|
|
64
|
+
await tasks_1.default.runWithTasks("Preflight checks", async () => {
|
|
65
|
+
// Check target directory
|
|
66
|
+
let isEmpty = true;
|
|
67
|
+
if (node_fs_1.default.existsSync(resolvedTargetDir)) {
|
|
68
|
+
try {
|
|
69
|
+
const files = node_fs_1.default.readdirSync(resolvedTargetDir);
|
|
70
|
+
isEmpty = files.length === 0;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
isEmpty = true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (!isEmpty)
|
|
77
|
+
throw new Error(`Target directory is not empty: ${resolvedTargetDir}`);
|
|
78
|
+
// Check package manager availability
|
|
79
|
+
packageManagerVersion = await (0, pkg_manager_1.ensurePackageManager)(packageManager);
|
|
80
|
+
});
|
|
81
|
+
// Create the package
|
|
82
|
+
let targetDir = "";
|
|
83
|
+
await tasks_1.default.runWithTasks("Preparing package", undefined, [
|
|
84
|
+
{
|
|
85
|
+
title: "Create package directory",
|
|
86
|
+
task: async () => {
|
|
87
|
+
targetDir = await (0, setup_1.createPackageDirectory)(process.cwd(), (0, utils_1.toSlug)(generateConfig.name));
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
title: `Add "${generateConfig.template}" template`,
|
|
92
|
+
task: async () => {
|
|
93
|
+
await (0, setup_1.writePackageTemplateFiles)(targetDir, generateConfig);
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
title: "Modify template with user preferences",
|
|
98
|
+
task: async () => {
|
|
99
|
+
await (0, setup_1.applyTemplateModifications)(targetDir, generateConfig, packageManagerVersion);
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
]);
|
|
103
|
+
let githubSecrets = [];
|
|
104
|
+
await tasks_1.default.runWithTasks("Finishing up", undefined, [
|
|
105
|
+
{
|
|
106
|
+
title: "Initialize git",
|
|
107
|
+
task: async () => {
|
|
108
|
+
await (0, git_1.initGitRepo)(targetDir);
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
title: "Make initial commit",
|
|
113
|
+
task: async () => {
|
|
114
|
+
await (0, git_1.makeInitialCommit)(targetDir);
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
title: "Fetch github secrets list",
|
|
119
|
+
task: async () => {
|
|
120
|
+
githubSecrets = await (0, setup_1.getRequiredGithubSecrets)(targetDir);
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
]);
|
|
124
|
+
const installCmd = (0, pkg_manager_1.getInstallScript)(packageManager);
|
|
125
|
+
let currentStep = 1;
|
|
126
|
+
console.log();
|
|
127
|
+
console.log(chalk_1.default.bold("Package generated successfully! Next steps:"));
|
|
128
|
+
console.log();
|
|
129
|
+
const cdCommand = `cd ${(0, utils_1.toSlug)(generateConfig.name)}`;
|
|
130
|
+
console.log(` ${currentStep}. Enter your package directory using ${(0, logger_1.primaryText)(cdCommand)},`);
|
|
131
|
+
currentStep += 1;
|
|
132
|
+
console.log(` ${currentStep}. Push your initial commit with ${(0, logger_1.primaryText)("git push -u origin main")}`);
|
|
133
|
+
currentStep += 1;
|
|
134
|
+
if (githubSecrets.length > 0) {
|
|
135
|
+
console.log(` ${currentStep}. Configure the following repository secrets in your GitHub project :`);
|
|
136
|
+
currentStep += 1;
|
|
137
|
+
githubSecrets.forEach((secret) => {
|
|
138
|
+
console.log(` - ${(0, logger_1.primaryText)(secret)}`);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
console.log(` ${currentStep}. Install dependencies with ${(0, logger_1.primaryText)(installCmd)}`);
|
|
142
|
+
currentStep += 1;
|
|
143
|
+
console.log(` ${currentStep}. Happy building, fellow wizard!`);
|
|
144
|
+
console.log();
|
|
145
|
+
console.log(`Stuck? Open an issue at ${(0, logger_1.primaryText)("https://github.com/agrawal-rohit/yehle/issues")}`);
|
|
146
|
+
console.log();
|
|
147
|
+
}
|
|
148
|
+
exports.default = generatePackage;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.templatePublicPaths = exports.Language = void 0;
|
|
7
|
+
exports.getGeneratePackageConfiguration = getGeneratePackageConfiguration;
|
|
8
|
+
exports.getPackageLanguage = getPackageLanguage;
|
|
9
|
+
exports.getPackageName = getPackageName;
|
|
10
|
+
exports.getPackageTemplate = getPackageTemplate;
|
|
11
|
+
exports.getPackageVisibility = getPackageVisibility;
|
|
12
|
+
exports.promptAuthorName = promptAuthorName;
|
|
13
|
+
exports.promptAuthorGitEmail = promptAuthorGitEmail;
|
|
14
|
+
exports.promptAuthorGitUsername = promptAuthorGitUsername;
|
|
15
|
+
const logger_1 = require("../../cli/logger");
|
|
16
|
+
const prompts_1 = __importDefault(require("../../cli/prompts"));
|
|
17
|
+
const tasks_1 = __importDefault(require("../../cli/tasks"));
|
|
18
|
+
const constants_1 = require("../../core/constants");
|
|
19
|
+
const git_1 = require("../../core/git");
|
|
20
|
+
const pkg_manager_1 = require("../../core/pkg-manager");
|
|
21
|
+
const template_registry_1 = require("../../core/template-registry");
|
|
22
|
+
const utils_1 = require("../../core/utils");
|
|
23
|
+
/** Supported programming languages for the package. */
|
|
24
|
+
var Language;
|
|
25
|
+
(function (Language) {
|
|
26
|
+
Language["TYPESCRIPT"] = "typescript";
|
|
27
|
+
})(Language || (exports.Language = Language = {}));
|
|
28
|
+
exports.templatePublicPaths = {
|
|
29
|
+
shared: [
|
|
30
|
+
"CODE_OF_CONDUCT.md",
|
|
31
|
+
"CONTRIBUTING.md",
|
|
32
|
+
"issue_template",
|
|
33
|
+
"pull_request_template.md",
|
|
34
|
+
],
|
|
35
|
+
[Language.TYPESCRIPT]: ["release.mustache.yml"],
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Gather relevant configuration to proceed with the package creation
|
|
39
|
+
* @returns A JSON object with the package configuration
|
|
40
|
+
*/
|
|
41
|
+
async function getGeneratePackageConfiguration(cliFlags = {}) {
|
|
42
|
+
const lang = await getPackageLanguage(cliFlags);
|
|
43
|
+
const name = await getPackageName(lang, cliFlags);
|
|
44
|
+
const template = await getPackageTemplate(lang, cliFlags);
|
|
45
|
+
const isPublic = await getPackageVisibility(lang, cliFlags);
|
|
46
|
+
let authorName;
|
|
47
|
+
let authorGitEmail;
|
|
48
|
+
let authorGitUsername;
|
|
49
|
+
if (isPublic) {
|
|
50
|
+
authorName = await promptAuthorName();
|
|
51
|
+
authorGitEmail = await promptAuthorGitEmail();
|
|
52
|
+
authorGitUsername = await promptAuthorGitUsername();
|
|
53
|
+
}
|
|
54
|
+
const answers = {
|
|
55
|
+
lang: lang,
|
|
56
|
+
name: name,
|
|
57
|
+
template: template,
|
|
58
|
+
public: isPublic,
|
|
59
|
+
authorName: authorName,
|
|
60
|
+
authorGitEmail: authorGitEmail,
|
|
61
|
+
authorGitUsername: authorGitUsername,
|
|
62
|
+
};
|
|
63
|
+
return answers;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Gets the package language based on CLI flags or prompts the user if not provided.
|
|
67
|
+
* @param cliFlags - CLI flags that may include a predefined language selection.
|
|
68
|
+
* @returns The selected programming language type.
|
|
69
|
+
*/
|
|
70
|
+
async function getPackageLanguage(cliFlags = {}) {
|
|
71
|
+
const languageOptions = Object.keys(Language).map((key) => ({
|
|
72
|
+
label: (0, utils_1.capitalizeFirstLetter)(Language[key]),
|
|
73
|
+
value: Language[key],
|
|
74
|
+
}));
|
|
75
|
+
const language = cliFlags.lang ??
|
|
76
|
+
(await prompts_1.default.selectInput("Which language would you prefer to use?", {
|
|
77
|
+
options: languageOptions,
|
|
78
|
+
}, Language.TYPESCRIPT));
|
|
79
|
+
const validLanguages = new Set(languageOptions.map((opt) => opt.value));
|
|
80
|
+
if (!validLanguages.has(language))
|
|
81
|
+
throw new Error(`Unsupported language: ${language} (valid: ${Array.from(validLanguages).join(", ")})`);
|
|
82
|
+
return language;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Gets the package name based on CLI flags or prompts the user if not provided.
|
|
86
|
+
* Also validates the package name against the selected language.
|
|
87
|
+
* @param language - The selected programming language.
|
|
88
|
+
* @param cliFlags - CLI flags that may include a predefined name.
|
|
89
|
+
* @returns The validated package name.
|
|
90
|
+
*/
|
|
91
|
+
async function getPackageName(language, cliFlags = {}) {
|
|
92
|
+
const name = cliFlags.name ??
|
|
93
|
+
(await prompts_1.default.textInput("What should we call your package?", { required: true }, "my-package"));
|
|
94
|
+
(0, pkg_manager_1.validatePackageName)(name, language);
|
|
95
|
+
return name;
|
|
96
|
+
}
|
|
97
|
+
async function getPackageTemplate(language, cliFlags = {}) {
|
|
98
|
+
let candidateTemplates = [];
|
|
99
|
+
// If it's running in local mode, fetch templates without spinner
|
|
100
|
+
if (constants_1.IS_LOCAL_MODE) {
|
|
101
|
+
candidateTemplates = await (0, template_registry_1.listAvailableTemplates)(language, "package");
|
|
102
|
+
}
|
|
103
|
+
// Otherwise, show a loading spinner until the templates are fetched from Github
|
|
104
|
+
else {
|
|
105
|
+
console.log();
|
|
106
|
+
await tasks_1.default.runWithTasks("Checking available package templates", async () => {
|
|
107
|
+
candidateTemplates = await (0, template_registry_1.listAvailableTemplates)(language, "package");
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
if (!candidateTemplates || candidateTemplates.length === 0)
|
|
111
|
+
throw new Error(`No templates found for language: ${language}`);
|
|
112
|
+
const templateOptions = candidateTemplates.map((template) => ({
|
|
113
|
+
label: (0, utils_1.capitalizeFirstLetter)(template),
|
|
114
|
+
value: template,
|
|
115
|
+
}));
|
|
116
|
+
let template = cliFlags.template;
|
|
117
|
+
// If only a single template is available, just use that
|
|
118
|
+
if (templateOptions.length === 1) {
|
|
119
|
+
template = templateOptions[0].value;
|
|
120
|
+
console.log((0, logger_1.primaryText)(`(Only one package template is available, using "${template}".)`));
|
|
121
|
+
}
|
|
122
|
+
if (!template)
|
|
123
|
+
template = await prompts_1.default.selectInput("Which starter template would you like to use?", { options: templateOptions }, candidateTemplates[0]);
|
|
124
|
+
if (!candidateTemplates.includes(template))
|
|
125
|
+
throw new Error(`Unsupported template: ${template} (valid: ${Array.from(candidateTemplates).join(", ")})`);
|
|
126
|
+
return template;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Gets the package visibility (public or private) based on CLI flags or prompts the user if not provided.
|
|
130
|
+
* Uses the package registry for the selected language in the prompt.
|
|
131
|
+
* @param language - The selected programming language.
|
|
132
|
+
* @param cliFlags - CLI flags that may include a predefined visibility setting.
|
|
133
|
+
* @returns The boolean indicating if the package is public.
|
|
134
|
+
*/
|
|
135
|
+
async function getPackageVisibility(language, cliFlags = {}) {
|
|
136
|
+
const packageRegistry = pkg_manager_1.LANGUAGE_PACKAGE_REGISTRY[language];
|
|
137
|
+
const isPublic = cliFlags.public ??
|
|
138
|
+
(await prompts_1.default.confirmInput(`Should this package be publicly available? (released to the ${packageRegistry} registry)`, undefined, true));
|
|
139
|
+
return isPublic;
|
|
140
|
+
}
|
|
141
|
+
/** Prompt for author's full name (defaults to Git config when available). */
|
|
142
|
+
async function promptAuthorName() {
|
|
143
|
+
const gitName = await (0, git_1.getGitUsername)();
|
|
144
|
+
return await prompts_1.default.textInput("What is the author's name?", undefined, gitName);
|
|
145
|
+
}
|
|
146
|
+
/** Prompt for author's Git email (defaults to Git config when available). */
|
|
147
|
+
async function promptAuthorGitEmail() {
|
|
148
|
+
const inferredGitEmail = await (0, git_1.getGitEmail)();
|
|
149
|
+
return await prompts_1.default.textInput("What is the author's email?", undefined, inferredGitEmail);
|
|
150
|
+
}
|
|
151
|
+
/** Prompt for author's GitHub username (suggests a value based on Git name). */
|
|
152
|
+
async function promptAuthorGitUsername() {
|
|
153
|
+
const gitName = await (0, git_1.getGitUsername)();
|
|
154
|
+
const suggestedUsername = gitName
|
|
155
|
+
? gitName.toLowerCase().replaceAll(/\s+/g, "")
|
|
156
|
+
: undefined;
|
|
157
|
+
const finalGitUserName = await prompts_1.default.textInput("Under which GitHub account would this repository be stored?", undefined, suggestedUsername);
|
|
158
|
+
return (0, utils_1.toSlug)(finalGitUserName);
|
|
159
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createPackageDirectory = createPackageDirectory;
|
|
7
|
+
exports.applyTemplateModifications = applyTemplateModifications;
|
|
8
|
+
exports.getRequiredGithubSecrets = getRequiredGithubSecrets;
|
|
9
|
+
exports.writePackageTemplateFiles = writePackageTemplateFiles;
|
|
10
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
const MIT_json_1 = __importDefault(require("spdx-license-list/licenses/MIT.json"));
|
|
13
|
+
const fs_1 = require("../../core/fs");
|
|
14
|
+
const template_registry_1 = require("../../core/template-registry");
|
|
15
|
+
const config_1 = require("./config");
|
|
16
|
+
/**
|
|
17
|
+
* Creates the package directory based on the provided package name.
|
|
18
|
+
* @param cwd - Current working directory (e.g., process.cwd()).
|
|
19
|
+
* @param packageName - The name of the package, used as the directory name.
|
|
20
|
+
* @returns The absolute path to the created package directory.
|
|
21
|
+
*/
|
|
22
|
+
async function createPackageDirectory(cwd, packageName) {
|
|
23
|
+
const targetDir = node_path_1.default.resolve(cwd, packageName);
|
|
24
|
+
await (0, fs_1.ensureDirAsync)(targetDir);
|
|
25
|
+
return targetDir;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create the package.json using the provided answers and package manager version.
|
|
29
|
+
* @param targetDir - Absolute path to the package directory.
|
|
30
|
+
* @param generateConfig - Generate configuration describing the new package.
|
|
31
|
+
* @param packageManagerVersion - A string like "pnpm@9.0.0" to record in package.json's packageManager.
|
|
32
|
+
* @returns The final package.json object that was persisted to disk.
|
|
33
|
+
* @throws If an existing package.json is invalid JSON.
|
|
34
|
+
*/
|
|
35
|
+
async function applyTemplateModifications(targetDir, generateConfig, packageManagerVersion) {
|
|
36
|
+
const chosenTemplateDir = await (0, template_registry_1.resolveTemplatesDir)(generateConfig.lang, `package/${generateConfig.template}`);
|
|
37
|
+
const hasPlayground = await (0, fs_1.isDirAsync)(node_path_1.default.join(chosenTemplateDir, "playground"));
|
|
38
|
+
const templateMetadata = {
|
|
39
|
+
packageManagerVersion,
|
|
40
|
+
templateHasPlayground: hasPlayground,
|
|
41
|
+
...generateConfig,
|
|
42
|
+
};
|
|
43
|
+
await (0, fs_1.renderMustacheTemplates)(targetDir, templateMetadata);
|
|
44
|
+
// Remove public files after rendering, so templated files match by basename
|
|
45
|
+
if (!generateConfig.public) {
|
|
46
|
+
const publicFiles = [
|
|
47
|
+
...config_1.templatePublicPaths.shared,
|
|
48
|
+
...(config_1.templatePublicPaths[generateConfig.lang] ?? []),
|
|
49
|
+
];
|
|
50
|
+
await (0, fs_1.removeFilesByBasename)(targetDir, publicFiles);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function getRequiredGithubSecrets(targetDir) {
|
|
54
|
+
const secrets = new Set();
|
|
55
|
+
try {
|
|
56
|
+
const workflowsDir = node_path_1.default.join(targetDir, ".github", "workflows");
|
|
57
|
+
const entries = await node_fs_1.default.promises.readdir(workflowsDir, {
|
|
58
|
+
withFileTypes: true,
|
|
59
|
+
});
|
|
60
|
+
const files = entries
|
|
61
|
+
.filter((e) => e.isFile())
|
|
62
|
+
.map((e) => node_path_1.default.join(workflowsDir, e.name));
|
|
63
|
+
const secretRegex = /secrets\.([A-Z0-9_]+)/g;
|
|
64
|
+
for (const file of files) {
|
|
65
|
+
const content = await node_fs_1.default.promises.readFile(file, "utf8");
|
|
66
|
+
for (const match of content.matchAll(secretRegex)) {
|
|
67
|
+
const key = match[1];
|
|
68
|
+
if (key && key.toUpperCase() !== "GITHUB_TOKEN") {
|
|
69
|
+
secrets.add(key);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// No workflows directory found; ignore
|
|
76
|
+
}
|
|
77
|
+
return Array.from(secrets).sort((a, b) => a.localeCompare(b));
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Write the chosen template files for a resource into the target directory.
|
|
81
|
+
* @param targetDir - Package root directory to write into.
|
|
82
|
+
* @param generateConfig - Generate configuration describing the new package.
|
|
83
|
+
* @returns A promise that resolves when the template files have been written.
|
|
84
|
+
*/
|
|
85
|
+
async function writePackageTemplateFiles(targetDir, generateConfig) {
|
|
86
|
+
// Global shared: templates/shared
|
|
87
|
+
const globalShared = await (0, template_registry_1.resolveTemplatesDir)("shared");
|
|
88
|
+
await (0, fs_1.copyDirSafeAsync)(globalShared, targetDir);
|
|
89
|
+
// Language shared: templates/<lang>/shared
|
|
90
|
+
const langShared = await (0, template_registry_1.resolveTemplatesDir)(generateConfig.lang, "shared");
|
|
91
|
+
await (0, fs_1.copyDirSafeAsync)(langShared, targetDir);
|
|
92
|
+
// Item-specific shared: templates/<lang>/package/shared
|
|
93
|
+
const itemShared = await (0, template_registry_1.resolveTemplatesDir)(generateConfig.lang, "package/shared");
|
|
94
|
+
await (0, fs_1.copyDirSafeAsync)(itemShared, targetDir);
|
|
95
|
+
// Item-specific template: templates/<lang>/package/<template>
|
|
96
|
+
const chosenTemplateDir = await (0, template_registry_1.resolveTemplatesDir)(generateConfig.lang, `package/${generateConfig.template}`);
|
|
97
|
+
await (0, fs_1.copyDirSafeAsync)(chosenTemplateDir, targetDir);
|
|
98
|
+
// Add MIT license
|
|
99
|
+
if (generateConfig.public && generateConfig.authorName) {
|
|
100
|
+
const year = new Date().getFullYear().toString();
|
|
101
|
+
const licenseText = MIT_json_1.default.licenseText
|
|
102
|
+
.replace("<year>", year)
|
|
103
|
+
.replace("<copyright holders>", generateConfig.authorName);
|
|
104
|
+
await (0, fs_1.writeFileAsync)(node_path_1.default.join(targetDir, "LICENSE"), licenseText);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.validateTypescriptPackageName = validateTypescriptPackageName;
|
|
7
|
+
const validate_npm_package_name_1 = __importDefault(require("validate-npm-package-name"));
|
|
8
|
+
/**
|
|
9
|
+
* Validates a JavaScript package name to ensure it conforms to NPM package name rules.
|
|
10
|
+
* @param {string} name - The package name to validate.
|
|
11
|
+
* @returns {true | string} true if the name is valid for new packages; otherwise an error string describing the issues.
|
|
12
|
+
*/
|
|
13
|
+
function validateTypescriptPackageName(name) {
|
|
14
|
+
const res = (0, validate_npm_package_name_1.default)(name);
|
|
15
|
+
if (res.validForNewPackages)
|
|
16
|
+
return true;
|
|
17
|
+
const errors = [...(res.errors || []), ...(res.warnings || [])].join(", ");
|
|
18
|
+
return `Invalid package name: ${errors}`;
|
|
19
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.0.8",
|
|
3
|
+
"name": "yehle",
|
|
4
|
+
"description": "An opinionated book of spells for the modern developer",
|
|
5
|
+
"bin": "./bin/cli.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"bin",
|
|
9
|
+
"README.md",
|
|
10
|
+
"LICENSE"
|
|
11
|
+
],
|
|
12
|
+
"author": "Rohit Agrawal <https://github.com/agrawal-rohit>",
|
|
13
|
+
"repository": "https://github.com/agrawal-rohit/yehle",
|
|
14
|
+
"homepage": "https://github.com/agrawal-rohit/yehle#readme",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"private": false,
|
|
17
|
+
"bugs": "https://github.com/agrawal-rohit/yehle/issues",
|
|
18
|
+
"scripts": {
|
|
19
|
+
"test": "vitest",
|
|
20
|
+
"test-mutations": "stryker run",
|
|
21
|
+
"build": "tsc",
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"lint": "pnpm exec biome lint --write",
|
|
24
|
+
"format": "pnpm exec biome format --write",
|
|
25
|
+
"check": "pnpm run typecheck && pnpm exec biome check --write",
|
|
26
|
+
"check:ci": "pnpm run typecheck && pnpm exec biome check",
|
|
27
|
+
"cov": "vitest run --coverage --passWithNoTests",
|
|
28
|
+
"prepack": "pnpm run build",
|
|
29
|
+
"prepare": "husky"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"cac": "^6.7.14",
|
|
33
|
+
"chalk": "^5.6.2",
|
|
34
|
+
"consola": "^3.2.3",
|
|
35
|
+
"giget": "^2.0.0",
|
|
36
|
+
"listr2": "^9.0.5",
|
|
37
|
+
"log-update": "^7.0.2",
|
|
38
|
+
"mustache": "^4.2.0",
|
|
39
|
+
"spdx-license-list": "^6.10.0",
|
|
40
|
+
"validate-npm-package-name": "7.0.1"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@biomejs/biome": "2.2.3",
|
|
44
|
+
"@commitlint/cli": "^19.8.1",
|
|
45
|
+
"@commitlint/config-conventional": "^19.8.1",
|
|
46
|
+
"@stryker-mutator/core": "^9.4.0",
|
|
47
|
+
"@stryker-mutator/vitest-runner": "^9.4.0",
|
|
48
|
+
"@stryker-mutator/typescript-checker": "^9.4.0",
|
|
49
|
+
"@types/jest": "^30.0.0",
|
|
50
|
+
"@types/mustache": "^4.2.5",
|
|
51
|
+
"@types/node": "^24.3.1",
|
|
52
|
+
"@types/validate-npm-package-name": "^4.0.2",
|
|
53
|
+
"@vitest/coverage-v8": "3.2.4",
|
|
54
|
+
"git-cliff": "^2.10.1",
|
|
55
|
+
"glob": ">=10.5.0",
|
|
56
|
+
"husky": "^9.1.7",
|
|
57
|
+
"jest": "^30.1.3",
|
|
58
|
+
"lint-staged": "^16.1.6",
|
|
59
|
+
"typescript": "^5.9.2",
|
|
60
|
+
"vitest": "^3.2.4"
|
|
61
|
+
},
|
|
62
|
+
"packageManager": "pnpm@10.15.1+sha512.34e538c329b5553014ca8e8f4535997f96180a1d0f614339357449935350d924e22f8614682191264ec33d1462ac21561aff97f6bb18065351c162c7e8f6de67"
|
|
63
|
+
}
|