@wc-toolkit/cem-generator-cli 0.1.1

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 wc-toolkit contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # @wc-toolkit/cem-generator-cli
2
+
3
+ CLI tool for generating Custom Elements Manifests.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add -w @wc-toolkit/cem-generator-cli
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ # Create a config and choose framework plugins interactively
15
+ cem init
16
+
17
+ # Create a config with selected plugins without prompting
18
+ cem init --plugin lit svelte
19
+
20
+ # Generate manifest using tsconfig.json in current directory
21
+ cem generate
22
+
23
+ # Specify custom tsconfig and output path
24
+ cem generate --config ./tsconfig.json --output ./custom-elements.json
25
+
26
+ # Include/exclude specific files
27
+ cem generate --include "src/components/**" --exclude "**/*.test.ts"
28
+
29
+ # Use custom plugins
30
+ cem generate --plugin ./my-plugin.js
31
+
32
+ # Disable inheritance materialization
33
+ cem generate --no-inheritance
34
+ ```
35
+
36
+ ## Options
37
+
38
+ `cem init` writes `cem-generator.config.mjs` and supports `--plugin <names...>`,
39
+ `--yes`, `--force`, and `--config <path>`.
40
+
41
+ | Option | Description | Default |
42
+ |--------|-------------|---------|
43
+ | `-c, --config <path>` | Path to tsconfig.json | `./tsconfig.json` |
44
+ | `-o, --output <path>` | Output file path | `./custom-elements.json` |
45
+ | `--include <patterns...>` | Glob patterns to include | - |
46
+ | `--exclude <patterns...>` | Glob patterns to exclude | - |
47
+ | `--no-inheritance` | Disable inheritance materialization | - |
48
+ | `--plugin <paths...>` | Additional plugin paths to load | - |
49
+ | `--conflict-policy <policy>` | Detector conflict policy: `throw` \| `last-wins` | `last-wins` |
50
+
51
+ ## Example
52
+
53
+ ```bash
54
+ # Basic manifest generation
55
+ cem generate --config tsconfig.json --output custom-elements.json
56
+ ```
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,303 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { fileURLToPath } from "node:url";
4
+ import path from "node:path";
5
+ import fs from "node:fs";
6
+ import readline from "node:readline/promises";
7
+ import { stdin as input, stdout as output } from "node:process";
8
+ import { spawnSync } from "node:child_process";
9
+ import { generateCem, loadConfig, mergeConfig } from "@wc-toolkit/cem-generator";
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+ const PLUGIN_CHOICES = [
13
+ { name: "lit", packageName: "@wc-toolkit/cem-generator-lit", factory: "litPlugin" },
14
+ { name: "fast", packageName: "@wc-toolkit/cem-generator-fast", factory: "fastPlugin" },
15
+ { name: "stencil", packageName: "@wc-toolkit/cem-generator-stencil", factory: "stencilPlugin" },
16
+ { name: "preact", packageName: "@wc-toolkit/cem-generator-preact", factory: "preactPlugin" },
17
+ { name: "vue", packageName: "@wc-toolkit/cem-generator-vue", factory: "vuePlugin" },
18
+ { name: "solid", packageName: "@wc-toolkit/cem-generator-solid", factory: "solidPlugin" },
19
+ { name: "svelte", packageName: "@wc-toolkit/cem-generator-svelte", factory: "sveltePlugin" },
20
+ ];
21
+ const program = new Command();
22
+ program
23
+ .name("cem")
24
+ .description("Custom Elements Manifest Generator")
25
+ .version("0.1.0");
26
+ program
27
+ .command("generate")
28
+ .description("Generate a Custom Elements Manifest")
29
+ .option("--tsconfig <path>", "Path to tsconfig.json", "./tsconfig.json")
30
+ .option("-c, --config <path>", "Path to cem-generator config file (auto-detected if omitted)")
31
+ .option("-o, --output <path>", "Output file path", "./custom-elements.json")
32
+ .option("--include <patterns...>", "Glob patterns to include")
33
+ .option("--exclude <patterns...>", "Glob patterns to exclude")
34
+ .option("--no-inheritance", "Disable inheritance materialization")
35
+ .option("--plugin <paths...>", "Additional plugin paths to load")
36
+ .option("--conflict-policy <policy>", "Detector conflict policy: throw | last-wins", "last-wins")
37
+ .option("--no-sort", "Disable alphabetical sorting of manifest entries")
38
+ .option("--deprecated-last", "Move deprecated items to end of sorted lists")
39
+ .option("--validate-exported-types <severity>", "Exported-type validation: off | warning | error")
40
+ .option("--validation-invariants <severity>", "Manifest invariant validation: off | warning | error")
41
+ .action(async (options) => {
42
+ try {
43
+ const cwd = process.cwd();
44
+ const { options: fileOptions, configPath } = await loadConfig({
45
+ cwd,
46
+ configPath: options.config,
47
+ });
48
+ const cliOptions = {
49
+ tsConfigPath: path.resolve(options.tsconfig),
50
+ include: options.include,
51
+ exclude: options.exclude,
52
+ sort: options.sort !== false,
53
+ };
54
+ if (options.deprecatedLast) {
55
+ cliOptions.deprecatedLast = true;
56
+ }
57
+ if (options.validateExportedTypes) {
58
+ if (!["off", "warning", "error"].includes(options.validateExportedTypes)) {
59
+ throw new Error("--validate-exported-types must be one of: off, warning, error");
60
+ }
61
+ cliOptions.validation = { exportTypes: options.validateExportedTypes };
62
+ }
63
+ if (options.validationInvariants) {
64
+ if (!["off", "warning", "error"].includes(options.validationInvariants)) {
65
+ throw new Error("--validation-invariants must be one of: off, warning, error");
66
+ }
67
+ cliOptions.validation = {
68
+ ...(cliOptions.validation ?? {}),
69
+ invariants: options.validationInvariants,
70
+ };
71
+ }
72
+ if (options.inheritance === false) {
73
+ cliOptions.inheritance = false;
74
+ }
75
+ cliOptions.conflictPolicy = options.conflictPolicy;
76
+ const plugins = [];
77
+ if (options.plugin && options.plugin.length > 0) {
78
+ for (const pluginPath of options.plugin) {
79
+ const resolvedPath = path.resolve(pluginPath);
80
+ const pluginModule = await import(resolvedPath);
81
+ const plugin = pluginModule.default ?? pluginModule;
82
+ if (plugin) {
83
+ plugins.push(plugin);
84
+ }
85
+ }
86
+ }
87
+ if (plugins.length > 0) {
88
+ cliOptions.plugins = plugins;
89
+ }
90
+ const mergedOptions = mergeConfig(cliOptions, fileOptions);
91
+ if (configPath) {
92
+ console.log(`Using config file: ${configPath}`);
93
+ }
94
+ const manifest = generateCem(mergedOptions);
95
+ const outputPath = path.resolve(options.output);
96
+ const outputDir = path.dirname(outputPath);
97
+ if (!fs.existsSync(outputDir)) {
98
+ fs.mkdirSync(outputDir, { recursive: true });
99
+ }
100
+ fs.writeFileSync(outputPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
101
+ console.log(`Generated manifest at ${outputPath}`);
102
+ }
103
+ catch (error) {
104
+ console.error("Error generating manifest:", error instanceof Error ? error.message : error);
105
+ process.exit(1);
106
+ }
107
+ });
108
+ program
109
+ .command("init")
110
+ .description("Set up the CLI or code workflow for generating a Custom Elements Manifest")
111
+ .option("-c, --config <path>", "Config file path", "./cem-generator.config.mjs")
112
+ .option("--mode <mode>", "How to run the generator: cli | code")
113
+ .option("--output <path>", "Generated code file path", "./generate-cem.ts")
114
+ .option("--plugin <names...>", "Plugins to include (lit, fast, stencil, preact, vue, solid, svelte)")
115
+ .option("--install", "Install selected plugin packages")
116
+ .option("--yes", "Create a config without prompting for plugins")
117
+ .option("--force", "Overwrite existing output files")
118
+ .action(async (options) => {
119
+ try {
120
+ let mode;
121
+ let selectedNames;
122
+ if (options.yes) {
123
+ mode = options.mode ? validateInitMode(options.mode) : "cli";
124
+ selectedNames = [];
125
+ }
126
+ else if (options.mode) {
127
+ mode = validateInitMode(options.mode);
128
+ selectedNames = options.plugin ? validatePluginNames(options.plugin) : await promptForPlugins();
129
+ }
130
+ else {
131
+ ({ mode, selectedNames } = options.plugin
132
+ ? await promptForModeAndPlugins(options.plugin)
133
+ : await promptForModeAndPlugins());
134
+ }
135
+ const selectedPlugins = PLUGIN_CHOICES.filter((plugin) => selectedNames.includes(plugin.name));
136
+ const configPath = path.resolve(options.config);
137
+ const outputPath = path.resolve(options.output);
138
+ const targetPaths = mode === "cli" ? [configPath] : [configPath, outputPath];
139
+ const existingPath = targetPaths.find((targetPath) => fs.existsSync(targetPath));
140
+ if (existingPath && !options.force) {
141
+ throw new Error(`Output file already exists: ${existingPath}. Use --force to overwrite it.`);
142
+ }
143
+ const shouldInstall = options.install ?? (!options.yes && !options.mode && selectedPlugins.length > 0
144
+ ? await promptForInstall()
145
+ : false);
146
+ if (shouldInstall && selectedPlugins.length > 0) {
147
+ installPluginDependencies(selectedPlugins.map((plugin) => plugin.packageName));
148
+ }
149
+ fs.writeFileSync(configPath, createConfigSource(selectedPlugins), "utf-8");
150
+ console.log(`Created config file at ${configPath}`);
151
+ if (mode === "code") {
152
+ fs.writeFileSync(outputPath, createCodeSource(configPath, outputPath), "utf-8");
153
+ console.log(`Created code file at ${outputPath}`);
154
+ }
155
+ }
156
+ catch (error) {
157
+ console.error("Error initializing project:", error instanceof Error ? error.message : error);
158
+ process.exit(1);
159
+ }
160
+ });
161
+ function validateInitMode(mode) {
162
+ if (mode !== "cli" && mode !== "code") {
163
+ throw new Error(`Unknown mode: ${mode}. Choose either cli or code.`);
164
+ }
165
+ return mode;
166
+ }
167
+ async function promptForMode(prompt) {
168
+ const answer = await prompt.question("How will you run the generator? (1) CLI (2) code: ");
169
+ if (answer.trim() === "1" || answer.trim().toLowerCase() === "cli")
170
+ return "cli";
171
+ if (answer.trim() === "2" || answer.trim().toLowerCase() === "code")
172
+ return "code";
173
+ throw new Error("Invalid mode selection. Choose 1 for CLI or 2 for code.");
174
+ }
175
+ async function promptForModeAndPlugins(pluginNames) {
176
+ if (pluginNames) {
177
+ return { mode: await promptForModeWithInput(), selectedNames: validatePluginNames(pluginNames) };
178
+ }
179
+ const prompt = readline.createInterface({ input, output });
180
+ const lines = prompt[Symbol.asyncIterator]();
181
+ try {
182
+ output.write("How will you run the generator? (1) CLI (2) code: ");
183
+ const modeLine = await lines.next();
184
+ if (modeLine.done)
185
+ throw new Error("No mode selected.");
186
+ const mode = validateInitMode(modeLine.value.trim() === "1" ? "cli" : modeLine.value.trim() === "2" ? "code" : modeLine.value.trim());
187
+ console.log("Which framework plugins should be included?");
188
+ PLUGIN_CHOICES.forEach((plugin, index) => console.log(` ${index + 1}. ${plugin.name}`));
189
+ console.log("Enter numbers separated by commas, or press Enter for vanilla only.");
190
+ output.write("Plugins: ");
191
+ const pluginsLine = await lines.next();
192
+ if (pluginsLine.done)
193
+ throw new Error("No plugin selection provided.");
194
+ return { mode, selectedNames: parsePluginSelection(pluginsLine.value) };
195
+ }
196
+ finally {
197
+ prompt.close();
198
+ }
199
+ }
200
+ async function promptForModeWithInput() {
201
+ const prompt = readline.createInterface({ input, output });
202
+ try {
203
+ return await promptForMode(prompt);
204
+ }
205
+ finally {
206
+ prompt.close();
207
+ }
208
+ }
209
+ async function promptForInstall() {
210
+ const prompt = readline.createInterface({ input, output });
211
+ try {
212
+ const answer = await prompt.question("Install selected plugin packages now? (Y/n): ");
213
+ return answer.trim() === "" || /^y(es)?$/i.test(answer.trim());
214
+ }
215
+ finally {
216
+ prompt.close();
217
+ }
218
+ }
219
+ function installPluginDependencies(packages) {
220
+ const packageManager = detectPackageManager();
221
+ const args = packageManager === "npm"
222
+ ? ["install", "-D", ...packages]
223
+ : packageManager === "yarn"
224
+ ? ["add", "-D", ...packages]
225
+ : packageManager === "bun"
226
+ ? ["add", "-d", ...packages]
227
+ : ["add", "-D", ...packages];
228
+ console.log(`Installing plugin packages with ${packageManager}...`);
229
+ const result = spawnSync(packageManager, args, { stdio: "inherit" });
230
+ if (result.status !== 0) {
231
+ throw new Error(`Failed to install plugin packages with ${packageManager}.`);
232
+ }
233
+ }
234
+ function detectPackageManager() {
235
+ if (fs.existsSync(path.resolve("pnpm-lock.yaml")))
236
+ return "pnpm";
237
+ if (fs.existsSync(path.resolve("yarn.lock")))
238
+ return "yarn";
239
+ if (fs.existsSync(path.resolve("bun.lockb")) || fs.existsSync(path.resolve("bun.lock")))
240
+ return "bun";
241
+ return "npm";
242
+ }
243
+ function validatePluginNames(names) {
244
+ const validNames = new Set(PLUGIN_CHOICES.map((plugin) => plugin.name));
245
+ const invalidNames = names.filter((name) => !validNames.has(name));
246
+ if (invalidNames.length > 0) {
247
+ throw new Error(`Unknown plugin(s): ${invalidNames.join(", ")}. Choose from: ${[...validNames].join(", ")}`);
248
+ }
249
+ return [...new Set(names)];
250
+ }
251
+ async function promptForPlugins(prompt) {
252
+ console.log("Which framework plugins should be included?");
253
+ PLUGIN_CHOICES.forEach((plugin, index) => console.log(` ${index + 1}. ${plugin.name}`));
254
+ console.log("Enter numbers separated by commas, or press Enter for vanilla only.");
255
+ const ownPrompt = prompt ? undefined : readline.createInterface({ input, output });
256
+ const answer = await (prompt ?? ownPrompt).question("Plugins: ");
257
+ ownPrompt?.close();
258
+ return parsePluginSelection(answer);
259
+ }
260
+ function parsePluginSelection(answer) {
261
+ const values = answer
262
+ .split(",")
263
+ .map((value) => value.trim())
264
+ .filter(Boolean);
265
+ const names = values.map((value) => {
266
+ const index = Number(value) - 1;
267
+ if (!Number.isInteger(index) || index < 0 || index >= PLUGIN_CHOICES.length) {
268
+ throw new Error(`Invalid plugin selection: ${value}`);
269
+ }
270
+ return PLUGIN_CHOICES[index].name;
271
+ });
272
+ return validatePluginNames(names);
273
+ }
274
+ function createConfigSource(plugins) {
275
+ const imports = plugins.map((plugin) => `import { ${plugin.factory} } from "${plugin.packageName}";`);
276
+ const factories = plugins.map((plugin) => ` ${plugin.factory}(),`);
277
+ return [
278
+ ...imports,
279
+ imports.length ? "" : undefined,
280
+ "export default {",
281
+ ...(factories.length ? [" plugins: [", ...factories, " ],"] : []),
282
+ "};",
283
+ "",
284
+ ].filter((line) => line !== undefined).join("\n");
285
+ }
286
+ function createCodeSource(configPath, outputPath) {
287
+ let configImport = path.relative(path.dirname(outputPath), configPath).replaceAll(path.sep, "/");
288
+ if (!configImport.startsWith("."))
289
+ configImport = `./${configImport}`;
290
+ return [
291
+ 'import { generateCem } from "@wc-toolkit/cem-generator";',
292
+ 'import fs from "node:fs";',
293
+ `import config from "${configImport}";`,
294
+ "",
295
+ "const manifest = generateCem(config);",
296
+ 'fs.writeFileSync("./custom-elements.json", JSON.stringify(manifest, null, 2) + "\\n");',
297
+ "",
298
+ ].join("\n");
299
+ }
300
+ program.parseAsync(process.argv).catch((error) => {
301
+ console.error("Error:", error);
302
+ process.exit(1);
303
+ });
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@wc-toolkit/cem-generator-cli",
3
+ "version": "0.1.1",
4
+ "description": "CLI for generating Custom Elements Manifests.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/wc-toolkit/cem-generator.git",
9
+ "directory": "packages/cli"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/wc-toolkit/cem-generator/issues"
13
+ },
14
+ "homepage": "https://wc-toolkit.github.io/cem-generator/guide/cli/",
15
+ "keywords": [
16
+ "custom-elements",
17
+ "custom-elements-manifest",
18
+ "web-components",
19
+ "cli"
20
+ ],
21
+ "type": "module",
22
+ "bin": {
23
+ "cem": "./dist/cli.js"
24
+ },
25
+ "main": "./dist/cli.js",
26
+ "types": "./dist/cli.d.ts",
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "publishConfig": {
31
+ "access": "public",
32
+ "provenance": true
33
+ },
34
+ "dependencies": {
35
+ "commander": "^12.1.0",
36
+ "custom-elements-manifest": "^2.1.0",
37
+ "@wc-toolkit/cem-generator": "0.1.1",
38
+ "@wc-toolkit/cem-generator-utils": "0.1.0"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^26.4.1",
42
+ "typescript": "^5.9.0"
43
+ },
44
+ "scripts": {
45
+ "build": "tsc",
46
+ "test": "node --test tests/*.test.mjs",
47
+ "dev": "tsx src/cli.ts",
48
+ "demo": "node dist/cli.js generate --config ../../examples/tsconfig.json --output ../../examples/custom-elements-demo.json --conflict-policy last-wins"
49
+ }
50
+ }