@wc-toolkit/cem-generator-cli 0.1.1 → 0.1.3

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.
Files changed (3) hide show
  1. package/README.md +43 -7
  2. package/dist/cli.js +283 -28
  3. package/package.json +5 -4
package/README.md CHANGED
@@ -11,7 +11,7 @@ pnpm add -w @wc-toolkit/cem-generator-cli
11
11
  ## Usage
12
12
 
13
13
  ```bash
14
- # Create a config and choose framework plugins interactively
14
+ # Create a config and choose parser plugins and integrations interactively
15
15
  cem init
16
16
 
17
17
  # Create a config with selected plugins without prompting
@@ -21,7 +21,7 @@ cem init --plugin lit svelte
21
21
  cem generate
22
22
 
23
23
  # Specify custom tsconfig and output path
24
- cem generate --config ./tsconfig.json --output ./custom-elements.json
24
+ cem generate --tsconfig ./tsconfig.json --output ./custom-elements.json
25
25
 
26
26
  # Include/exclude specific files
27
27
  cem generate --include "src/components/**" --exclude "**/*.test.ts"
@@ -35,22 +35,58 @@ cem generate --no-inheritance
35
35
 
36
36
  ## Options
37
37
 
38
- `cem init` writes `cem-generator.config.mjs` and supports `--plugin <names...>`,
38
+ `cem init` writes `cem-generator.config.mjs`. In interactive mode it first asks
39
+ which parser plugins to use, then presents a multi-select list of integrations:
40
+ `react-wrappers`, `jsx-types`, `vuejs-types`, and `svelte-types`. The type
41
+ integrations use `./types` and strongly typed events by default. The command
42
+ also asks whether to add the generated manifest to `package.json` as the
43
+ `customElements` property. It supports `--plugin <names...>`,
39
44
  `--yes`, `--force`, and `--config <path>`.
40
45
 
46
+ When accepted, the package.json property uses the default manifest path:
47
+
48
+ ```json
49
+ {
50
+ "customElements": "custom-elements.json"
51
+ }
52
+ ```
53
+
54
+ Use `filePath` in the config when the manifest belongs in a build directory.
55
+
56
+ `cem generate` uses source-oriented include/exclude defaults when those flags
57
+ are omitted. Supplying either flag replaces its defaults. The TypeScript
58
+ configuration still controls which files enter the program.
59
+
60
+ The config file may set `filePath` to choose the generated manifest path. The
61
+ `--output` flag takes precedence when supplied.
62
+
63
+ When `cem init` installs dependencies, CLI mode installs
64
+ `@wc-toolkit/cem-generator-cli`, while code mode installs
65
+ `@wc-toolkit/cem-generator` and `tsx`. Selected parser and integration
66
+ packages are installed alongside the workflow package. The command prints the
67
+ `package.json` script to add and the package-manager command to run it.
68
+
69
+ In an interactive terminal, use the arrow keys to move through lists, Space to
70
+ toggle checkbox selections, and Enter to confirm. Non-interactive input falls
71
+ back to comma-separated selection numbers.
72
+
41
73
  | Option | Description | Default |
42
74
  |--------|-------------|---------|
43
- | `-c, --config <path>` | Path to tsconfig.json | `./tsconfig.json` |
75
+ | `--tsconfig <path>` | Path to tsconfig.json | `./tsconfig.json` |
76
+ | `-c, --config <path>` | Path to the generator config file | auto-detected |
44
77
  | `-o, --output <path>` | Output file path | `./custom-elements.json` |
45
- | `--include <patterns...>` | Glob patterns to include | - |
46
- | `--exclude <patterns...>` | Glob patterns to exclude | - |
78
+ | `--include <patterns...>` | Glob patterns to include | `src/**/*.{ts,tsx,js,jsx}` when `src/` exists |
79
+ | `--exclude <patterns...>` | Glob patterns to exclude | Tests, specs, stories, `dist/`, and `node_modules/` |
47
80
  | `--no-inheritance` | Disable inheritance materialization | - |
48
81
  | `--plugin <paths...>` | Additional plugin paths to load | - |
49
82
  | `--conflict-policy <policy>` | Detector conflict policy: `throw` \| `last-wins` | `last-wins` |
50
83
 
84
+ The config file may set `filePath` for the manifest path. The `--output` flag
85
+ takes precedence over `filePath`.
86
+
51
87
  ## Example
52
88
 
53
89
  ```bash
54
90
  # Basic manifest generation
55
- cem generate --config tsconfig.json --output custom-elements.json
91
+ cem generate --tsconfig tsconfig.json --output custom-elements.json
56
92
  ```
package/dist/cli.js CHANGED
@@ -6,6 +6,7 @@ import fs from "node:fs";
6
6
  import readline from "node:readline/promises";
7
7
  import { stdin as input, stdout as output } from "node:process";
8
8
  import { spawnSync } from "node:child_process";
9
+ import inquirer from "inquirer";
9
10
  import { generateCem, loadConfig, mergeConfig } from "@wc-toolkit/cem-generator";
10
11
  const __filename = fileURLToPath(import.meta.url);
11
12
  const __dirname = path.dirname(__filename);
@@ -18,6 +19,41 @@ const PLUGIN_CHOICES = [
18
19
  { name: "solid", packageName: "@wc-toolkit/cem-generator-solid", factory: "solidPlugin" },
19
20
  { name: "svelte", packageName: "@wc-toolkit/cem-generator-svelte", factory: "sveltePlugin" },
20
21
  ];
22
+ const INTEGRATION_CHOICES = [
23
+ {
24
+ name: "react-wrappers",
25
+ packageName: "@wc-toolkit/react-wrappers",
26
+ factory: "reactWrapperGeneratorPlugin",
27
+ options: { stronglyTypedEvents: true },
28
+ },
29
+ {
30
+ name: "jsx-types",
31
+ packageName: "@wc-toolkit/jsx-types",
32
+ factory: "jsxTypesGeneratorPlugin",
33
+ options: { outdir: "./types", stronglyTypedEvents: true },
34
+ },
35
+ {
36
+ name: "vuejs-types",
37
+ packageName: "@wc-toolkit/vuejs-types",
38
+ factory: "vuejsTypesGeneratorPlugin",
39
+ options: { outdir: "./types", stronglyTypedEvents: true },
40
+ },
41
+ {
42
+ name: "svelte-types",
43
+ packageName: "@wc-toolkit/svelte-types",
44
+ factory: "svelteTypesGeneratorPlugin",
45
+ options: { outdir: "./types", stronglyTypedEvents: true },
46
+ },
47
+ ];
48
+ const DEFAULT_SOURCE_INCLUDE = ["src/**/*.{ts,tsx,js,jsx}"];
49
+ const DEFAULT_MANIFEST_PATH = "./custom-elements.json";
50
+ const DEFAULT_SOURCE_EXCLUDE = [
51
+ "**/*.test.*",
52
+ "**/*.spec.*",
53
+ "**/*.stories.*",
54
+ "**/dist/**",
55
+ "**/node_modules/**",
56
+ ];
21
57
  const program = new Command();
22
58
  program
23
59
  .name("cem")
@@ -28,7 +64,7 @@ program
28
64
  .description("Generate a Custom Elements Manifest")
29
65
  .option("--tsconfig <path>", "Path to tsconfig.json", "./tsconfig.json")
30
66
  .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")
67
+ .option("-o, --output <path>", "Output file path")
32
68
  .option("--include <patterns...>", "Glob patterns to include")
33
69
  .option("--exclude <patterns...>", "Glob patterns to exclude")
34
70
  .option("--no-inheritance", "Disable inheritance materialization")
@@ -47,8 +83,8 @@ program
47
83
  });
48
84
  const cliOptions = {
49
85
  tsConfigPath: path.resolve(options.tsconfig),
50
- include: options.include,
51
- exclude: options.exclude,
86
+ include: options.include ?? getDefaultInclude(cwd),
87
+ exclude: options.exclude ?? DEFAULT_SOURCE_EXCLUDE,
52
88
  sort: options.sort !== false,
53
89
  };
54
90
  if (options.deprecatedLast) {
@@ -92,7 +128,7 @@ program
92
128
  console.log(`Using config file: ${configPath}`);
93
129
  }
94
130
  const manifest = generateCem(mergedOptions);
95
- const outputPath = path.resolve(options.output);
131
+ const outputPath = path.resolve(options.output ?? mergedOptions.filePath ?? "./custom-elements.json");
96
132
  const outputDir = path.dirname(outputPath);
97
133
  if (!fs.existsSync(outputDir)) {
98
134
  fs.mkdirSync(outputDir, { recursive: true });
@@ -111,28 +147,47 @@ program
111
147
  .option("-c, --config <path>", "Config file path", "./cem-generator.config.mjs")
112
148
  .option("--mode <mode>", "How to run the generator: cli | code")
113
149
  .option("--output <path>", "Generated code file path", "./generate-cem.ts")
114
- .option("--plugin <names...>", "Plugins to include (lit, fast, stencil, preact, vue, solid, svelte)")
150
+ .option("--plugin <names...>", "Parser plugins to include (lit, fast, stencil, preact, vue, solid, svelte)")
115
151
  .option("--install", "Install selected plugin packages")
116
152
  .option("--yes", "Create a config without prompting for plugins")
117
153
  .option("--force", "Overwrite existing output files")
118
154
  .action(async (options) => {
119
155
  try {
156
+ const cwd = process.cwd();
120
157
  let mode;
121
158
  let selectedNames;
159
+ let selectedIntegrationNames;
122
160
  if (options.yes) {
123
161
  mode = options.mode ? validateInitMode(options.mode) : "cli";
124
162
  selectedNames = [];
163
+ selectedIntegrationNames = [];
125
164
  }
126
165
  else if (options.mode) {
127
166
  mode = validateInitMode(options.mode);
128
- selectedNames = options.plugin ? validatePluginNames(options.plugin) : await promptForPlugins();
167
+ if (options.plugin) {
168
+ selectedNames = validatePluginNames(options.plugin);
169
+ selectedIntegrationNames = [];
170
+ }
171
+ else {
172
+ ({ selectedNames, selectedIntegrationNames } = await promptForPluginsAndIntegrations());
173
+ }
129
174
  }
130
175
  else {
131
- ({ mode, selectedNames } = options.plugin
176
+ ({ mode, selectedNames, selectedIntegrationNames } = options.plugin
132
177
  ? await promptForModeAndPlugins(options.plugin)
133
178
  : await promptForModeAndPlugins());
134
179
  }
135
180
  const selectedPlugins = PLUGIN_CHOICES.filter((plugin) => selectedNames.includes(plugin.name));
181
+ const selectedIntegrations = INTEGRATION_CHOICES.filter((integration) => selectedIntegrationNames.includes(integration.name));
182
+ const generatorPackage = mode === "cli"
183
+ ? "@wc-toolkit/cem-generator-cli"
184
+ : "@wc-toolkit/cem-generator";
185
+ const packagesToInstall = [
186
+ generatorPackage,
187
+ ...(mode === "code" ? ["tsx"] : []),
188
+ ...selectedPlugins.map((plugin) => plugin.packageName),
189
+ ...selectedIntegrations.map((integration) => integration.packageName),
190
+ ];
136
191
  const configPath = path.resolve(options.config);
137
192
  const outputPath = path.resolve(options.output);
138
193
  const targetPaths = mode === "cli" ? [configPath] : [configPath, outputPath];
@@ -140,18 +195,23 @@ program
140
195
  if (existingPath && !options.force) {
141
196
  throw new Error(`Output file already exists: ${existingPath}. Use --force to overwrite it.`);
142
197
  }
143
- const shouldInstall = options.install ?? (!options.yes && !options.mode && selectedPlugins.length > 0
198
+ const shouldInstall = options.install ?? (!options.yes && !options.mode
144
199
  ? await promptForInstall()
145
200
  : false);
146
- if (shouldInstall && selectedPlugins.length > 0) {
147
- installPluginDependencies(selectedPlugins.map((plugin) => plugin.packageName));
201
+ if (shouldInstall) {
202
+ installPluginDependencies(packagesToInstall);
148
203
  }
149
- fs.writeFileSync(configPath, createConfigSource(selectedPlugins), "utf-8");
204
+ const addManifestToPackageJson = !options.yes && await promptForPackageManifest(cwd);
205
+ fs.writeFileSync(configPath, createConfigSource(selectedPlugins, selectedIntegrations, getDefaultInclude(cwd), DEFAULT_SOURCE_EXCLUDE), "utf-8");
150
206
  console.log(`Created config file at ${configPath}`);
151
207
  if (mode === "code") {
152
208
  fs.writeFileSync(outputPath, createCodeSource(configPath, outputPath), "utf-8");
153
209
  console.log(`Created code file at ${outputPath}`);
154
210
  }
211
+ if (addManifestToPackageJson) {
212
+ updatePackageJsonManifestPath(cwd, DEFAULT_MANIFEST_PATH);
213
+ }
214
+ printNextSteps(mode, configPath, outputPath);
155
215
  }
156
216
  catch (error) {
157
217
  console.error("Error initializing project:", error instanceof Error ? error.message : error);
@@ -164,6 +224,9 @@ function validateInitMode(mode) {
164
224
  }
165
225
  return mode;
166
226
  }
227
+ function getDefaultInclude(cwd) {
228
+ return fs.existsSync(path.join(cwd, "src")) ? DEFAULT_SOURCE_INCLUDE : undefined;
229
+ }
167
230
  async function promptForMode(prompt) {
168
231
  const answer = await prompt.question("How will you run the generator? (1) CLI (2) code: ");
169
232
  if (answer.trim() === "1" || answer.trim().toLowerCase() === "cli")
@@ -174,7 +237,14 @@ async function promptForMode(prompt) {
174
237
  }
175
238
  async function promptForModeAndPlugins(pluginNames) {
176
239
  if (pluginNames) {
177
- return { mode: await promptForModeWithInput(), selectedNames: validatePluginNames(pluginNames) };
240
+ return {
241
+ mode: await promptForModeWithInput(),
242
+ selectedNames: validatePluginNames(pluginNames),
243
+ selectedIntegrationNames: [],
244
+ };
245
+ }
246
+ if (isInteractiveTerminal()) {
247
+ return promptForInteractiveSelections();
178
248
  }
179
249
  const prompt = readline.createInterface({ input, output });
180
250
  const lines = prompt[Symbol.asyncIterator]();
@@ -191,13 +261,37 @@ async function promptForModeAndPlugins(pluginNames) {
191
261
  const pluginsLine = await lines.next();
192
262
  if (pluginsLine.done)
193
263
  throw new Error("No plugin selection provided.");
194
- return { mode, selectedNames: parsePluginSelection(pluginsLine.value) };
264
+ const selectedNames = parsePluginSelection(pluginsLine.value);
265
+ console.log("Which project integrations should be included? (multi-select)");
266
+ INTEGRATION_CHOICES.forEach((integration, index) => console.log(` ${index + 1}. ${integration.name}`));
267
+ console.log("Enter numbers separated by commas, or press Enter for no integrations.");
268
+ output.write("Integrations: ");
269
+ const integrationsLine = await lines.next();
270
+ if (integrationsLine.done)
271
+ throw new Error("No integration selection provided.");
272
+ return {
273
+ mode,
274
+ selectedNames,
275
+ selectedIntegrationNames: parseIntegrationSelection(integrationsLine.value),
276
+ };
195
277
  }
196
278
  finally {
197
279
  prompt.close();
198
280
  }
199
281
  }
200
282
  async function promptForModeWithInput() {
283
+ if (isInteractiveTerminal()) {
284
+ const { mode } = await inquirer.prompt({
285
+ type: "select",
286
+ name: "mode",
287
+ message: "How will you run the generator?",
288
+ choices: [
289
+ { name: "CLI", value: "cli" },
290
+ { name: "Code", value: "code" },
291
+ ],
292
+ });
293
+ return mode;
294
+ }
201
295
  const prompt = readline.createInterface({ input, output });
202
296
  try {
203
297
  return await promptForMode(prompt);
@@ -207,6 +301,15 @@ async function promptForModeWithInput() {
207
301
  }
208
302
  }
209
303
  async function promptForInstall() {
304
+ if (isInteractiveTerminal()) {
305
+ const { install } = await inquirer.prompt({
306
+ type: "confirm",
307
+ name: "install",
308
+ message: "Install selected plugin packages now?",
309
+ default: true,
310
+ });
311
+ return install;
312
+ }
210
313
  const prompt = readline.createInterface({ input, output });
211
314
  try {
212
315
  const answer = await prompt.question("Install selected plugin packages now? (Y/n): ");
@@ -216,6 +319,35 @@ async function promptForInstall() {
216
319
  prompt.close();
217
320
  }
218
321
  }
322
+ async function promptForPackageManifest(cwd) {
323
+ const packageJsonPath = path.join(cwd, "package.json");
324
+ if (!fs.existsSync(packageJsonPath))
325
+ return false;
326
+ if (isInteractiveTerminal()) {
327
+ const { addManifest } = await inquirer.prompt({
328
+ type: "confirm",
329
+ name: "addManifest",
330
+ message: `Add \"customElements\": \"${DEFAULT_MANIFEST_PATH.slice(2)}\" to package.json?`,
331
+ default: false,
332
+ });
333
+ return addManifest;
334
+ }
335
+ const prompt = readline.createInterface({ input, output });
336
+ try {
337
+ const answer = await prompt.question(`Add "customElements": "${DEFAULT_MANIFEST_PATH.slice(2)}" to package.json? (y/N): `);
338
+ return /^y(es)?$/i.test(answer.trim());
339
+ }
340
+ finally {
341
+ prompt.close();
342
+ }
343
+ }
344
+ function updatePackageJsonManifestPath(cwd, manifestPath) {
345
+ const packageJsonPath = path.join(cwd, "package.json");
346
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
347
+ packageJson.customElements = manifestPath.replace(/^\.\//, "");
348
+ fs.writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, "utf-8");
349
+ console.log(`Updated package.json with customElements: ${packageJson.customElements}`);
350
+ }
219
351
  function installPluginDependencies(packages) {
220
352
  const packageManager = detectPackageManager();
221
353
  const args = packageManager === "npm"
@@ -231,6 +363,35 @@ function installPluginDependencies(packages) {
231
363
  throw new Error(`Failed to install plugin packages with ${packageManager}.`);
232
364
  }
233
365
  }
366
+ function printNextSteps(mode, configPath, outputPath) {
367
+ console.log("\nNext steps:");
368
+ const packageManager = detectPackageManager();
369
+ if (mode === "cli") {
370
+ const defaultConfigPath = path.resolve(process.cwd(), "cem-generator.config.mjs");
371
+ const configArgument = configPath === defaultConfigPath ? "" : ` --config ${toCommandPath(configPath)}`;
372
+ printPackageScript(`cem generate${configArgument}`, packageManager);
373
+ }
374
+ else {
375
+ printPackageScript(`tsx ${toCommandPath(outputPath)}`, packageManager);
376
+ }
377
+ }
378
+ function printPackageScript(command, packageManager) {
379
+ const runCommand = packageManager === "npm"
380
+ ? "npm run cem"
381
+ : packageManager === "yarn"
382
+ ? "yarn cem"
383
+ : packageManager === "bun"
384
+ ? "bun run cem"
385
+ : "pnpm run cem";
386
+ console.log(" Add this script to package.json:");
387
+ console.log(` \"cem\": \"${command}\"`);
388
+ console.log(` Run it with: ${runCommand}`);
389
+ }
390
+ function toCommandPath(filePath) {
391
+ const relativePath = path.relative(process.cwd(), filePath).replaceAll(path.sep, "/");
392
+ const normalizedPath = relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
393
+ return /\s/.test(normalizedPath) ? JSON.stringify(normalizedPath) : normalizedPath;
394
+ }
234
395
  function detectPackageManager() {
235
396
  if (fs.existsSync(path.resolve("pnpm-lock.yaml")))
236
397
  return "pnpm";
@@ -248,41 +409,135 @@ function validatePluginNames(names) {
248
409
  }
249
410
  return [...new Set(names)];
250
411
  }
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);
412
+ function validateIntegrationNames(names) {
413
+ const validNames = new Set(INTEGRATION_CHOICES.map((integration) => integration.name));
414
+ const invalidNames = names.filter((name) => !validNames.has(name));
415
+ if (invalidNames.length > 0) {
416
+ throw new Error(`Unknown integration(s): ${invalidNames.join(", ")}. Choose from: ${[...validNames].join(", ")}`);
417
+ }
418
+ return [...new Set(names)];
419
+ }
420
+ async function promptForPluginsAndIntegrations() {
421
+ if (isInteractiveTerminal()) {
422
+ const { plugin, integrations } = await inquirer.prompt([
423
+ {
424
+ type: "select",
425
+ name: "plugin",
426
+ message: "Which framework plugins should be included?",
427
+ choices: PLUGIN_CHOICES.map((plugin) => ({ name: plugin.name, value: plugin.name })),
428
+ },
429
+ {
430
+ type: "checkbox",
431
+ name: "integrations",
432
+ message: "Which project integrations should be included?",
433
+ choices: INTEGRATION_CHOICES.map((integration) => ({
434
+ name: integration.name,
435
+ value: integration.name,
436
+ })),
437
+ },
438
+ ]);
439
+ return { selectedNames: [plugin], selectedIntegrationNames: integrations };
440
+ }
441
+ const prompt = readline.createInterface({ input, output });
442
+ const lines = prompt[Symbol.asyncIterator]();
443
+ try {
444
+ console.log("Which framework plugins should be included?");
445
+ PLUGIN_CHOICES.forEach((plugin, index) => console.log(` ${index + 1}. ${plugin.name}`));
446
+ console.log("Enter numbers separated by commas, or press Enter for vanilla only.");
447
+ output.write("Plugins: ");
448
+ const pluginsLine = await lines.next();
449
+ if (pluginsLine.done)
450
+ throw new Error("No plugin selection provided.");
451
+ console.log("Which project integrations should be included? (multi-select)");
452
+ INTEGRATION_CHOICES.forEach((integration, index) => console.log(` ${index + 1}. ${integration.name}`));
453
+ console.log("Enter numbers separated by commas, or press Enter for no integrations.");
454
+ output.write("Integrations: ");
455
+ const integrationsLine = await lines.next();
456
+ if (integrationsLine.done)
457
+ throw new Error("No integration selection provided.");
458
+ return {
459
+ selectedNames: parsePluginSelection(pluginsLine.value),
460
+ selectedIntegrationNames: parseIntegrationSelection(integrationsLine.value),
461
+ };
462
+ }
463
+ finally {
464
+ prompt.close();
465
+ }
466
+ }
467
+ async function promptForInteractiveSelections() {
468
+ const { mode, plugin, integrations } = await inquirer.prompt([
469
+ {
470
+ type: "select",
471
+ name: "mode",
472
+ message: "How will you run the generator?",
473
+ choices: [
474
+ { name: "CLI", value: "cli" },
475
+ { name: "Code", value: "code" },
476
+ ],
477
+ },
478
+ {
479
+ type: "select",
480
+ name: "plugin",
481
+ message: "Which framework plugins should be included?",
482
+ choices: PLUGIN_CHOICES.map((plugin) => ({ name: plugin.name, value: plugin.name })),
483
+ },
484
+ {
485
+ type: "checkbox",
486
+ name: "integrations",
487
+ message: "Which project integrations should be included?",
488
+ choices: INTEGRATION_CHOICES.map((integration) => ({
489
+ name: integration.name,
490
+ value: integration.name,
491
+ })),
492
+ },
493
+ ]);
494
+ return { mode, selectedNames: [plugin], selectedIntegrationNames: integrations };
495
+ }
496
+ function isInteractiveTerminal() {
497
+ return Boolean(input.isTTY && output.isTTY);
259
498
  }
260
499
  function parsePluginSelection(answer) {
500
+ return parseSelection(answer, PLUGIN_CHOICES, "plugin");
501
+ }
502
+ function parseIntegrationSelection(answer) {
503
+ return validateIntegrationNames(parseSelection(answer, INTEGRATION_CHOICES, "integration"));
504
+ }
505
+ function parseSelection(answer, choices, label) {
261
506
  const values = answer
262
507
  .split(",")
263
508
  .map((value) => value.trim())
264
509
  .filter(Boolean);
265
510
  const names = values.map((value) => {
266
511
  const index = Number(value) - 1;
267
- if (!Number.isInteger(index) || index < 0 || index >= PLUGIN_CHOICES.length) {
268
- throw new Error(`Invalid plugin selection: ${value}`);
512
+ if (!Number.isInteger(index) || index < 0 || index >= choices.length) {
513
+ throw new Error(`Invalid ${label} selection: ${value}`);
269
514
  }
270
- return PLUGIN_CHOICES[index].name;
515
+ return choices[index].name;
271
516
  });
272
- return validatePluginNames(names);
517
+ return names;
273
518
  }
274
- function createConfigSource(plugins) {
275
- const imports = plugins.map((plugin) => `import { ${plugin.factory} } from "${plugin.packageName}";`);
276
- const factories = plugins.map((plugin) => ` ${plugin.factory}(),`);
519
+ function createConfigSource(plugins, integrations = [], include, exclude = DEFAULT_SOURCE_EXCLUDE) {
520
+ const choices = [...plugins, ...integrations];
521
+ const imports = choices.map((plugin) => `import { ${plugin.factory} } from "${plugin.packageName}";`);
522
+ const factories = choices.map((plugin) => ` ${plugin.factory}(${getPluginOptionsSource(plugin)}),`);
277
523
  return [
278
524
  ...imports,
279
525
  imports.length ? "" : undefined,
280
526
  "export default {",
527
+ ...(include ? [` include: ${JSON.stringify(include)},`] : []),
528
+ ` exclude: ${JSON.stringify(exclude)},`,
281
529
  ...(factories.length ? [" plugins: [", ...factories, " ],"] : []),
282
530
  "};",
283
531
  "",
284
532
  ].filter((line) => line !== undefined).join("\n");
285
533
  }
534
+ function getPluginOptionsSource(plugin) {
535
+ if (!("options" in plugin) || !plugin.options)
536
+ return "";
537
+ return `{ ${Object.entries(plugin.options)
538
+ .map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
539
+ .join(", ")} }`;
540
+ }
286
541
  function createCodeSource(configPath, outputPath) {
287
542
  let configImport = path.relative(path.dirname(outputPath), configPath).replaceAll(path.sep, "/");
288
543
  if (!configImport.startsWith("."))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wc-toolkit/cem-generator-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "CLI for generating Custom Elements Manifests.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -34,8 +34,9 @@
34
34
  "dependencies": {
35
35
  "commander": "^12.1.0",
36
36
  "custom-elements-manifest": "^2.1.0",
37
- "@wc-toolkit/cem-generator": "0.1.1",
38
- "@wc-toolkit/cem-generator-utils": "0.1.0"
37
+ "inquirer": "^14.2.2",
38
+ "@wc-toolkit/cem-generator": "0.1.3",
39
+ "@wc-toolkit/cem-generator-utils": "0.1.1"
39
40
  },
40
41
  "devDependencies": {
41
42
  "@types/node": "^26.4.1",
@@ -45,6 +46,6 @@
45
46
  "build": "tsc",
46
47
  "test": "node --test tests/*.test.mjs",
47
48
  "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
+ "demo": "node dist/cli.js generate --tsconfig ../../examples/tsconfig.json --output ../../examples/custom-elements-demo.json --conflict-policy last-wins"
49
50
  }
50
51
  }