@tenkit/cli 0.1.2 → 0.2.0-next.0

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 +12 -1
  2. package/dist/index.mjs +204 -83
  3. package/package.json +4 -2
package/README.md CHANGED
@@ -15,7 +15,7 @@ final next steps.
15
15
  ## Highlights
16
16
 
17
17
  - Create a generated Expo project from a selected Tenkit Setup Type.
18
- - Ask only for project name and Setup Type in the default interactive flow.
18
+ - Prompt for project name, Setup Type, App Variant customization, and Styling Choice.
19
19
  - Support `pnpm`, `npm`, `npx`, Bun, and `bunx` launchers.
20
20
  - Use the selected package manager for install commands, generated README
21
21
  commands, and generated app-local commands.
@@ -45,6 +45,7 @@ tenkit --help
45
45
  tenkit --name studio-app --setup white-label --yes
46
46
  tenkit --name venue-network --setup runtime-tenants --yes
47
47
  tenkit --name franchise-app --setup generic-standalone --yes
48
+ tenkit --name unistyles-app --setup white-label --styling unistyles --yes
48
49
  ```
49
50
 
50
51
  ```bash
@@ -59,6 +60,16 @@ runtime-tenants
59
60
  generic-standalone
60
61
  ```
61
62
 
63
+ Supported Styling Option Values:
64
+
65
+ ```text
66
+ bare
67
+ uniwind
68
+ unistyles
69
+ ```
70
+
71
+ Bare remains the default when Styling is omitted or `--yes` accepts defaults.
72
+
62
73
  Override package-manager detection when needed:
63
74
 
64
75
  ```bash
package/dist/index.mjs CHANGED
@@ -4,8 +4,10 @@ import fs from "fs-extra";
4
4
  import { dirname, resolve } from "pathe";
5
5
  import { cancel, confirm, intro, isCancel, outro, select, text } from "@clack/prompts";
6
6
  import { Command } from "commander";
7
- import { SUPPORTED_PUBLIC_SETUP_SLUGS, formatSupportedGeneratedSetupTypes, generateProject, normalizeGeneratedSetupType, preflightWriteProject, writeProject } from "@tenkit/template-generator";
7
+ import { SUPPORTED_PUBLIC_SETUP_SLUGS, formatSupportedGeneratedSetupTypes, generateProject, normalizeGeneratedAccentColor, normalizeGeneratedSetupType, preflightWriteProject, writeProject } from "@tenkit/template-generator";
8
+ import { SUPPORTED_GENERATED_STYLING_CHOICES, normalizeGeneratedStylingChoice } from "@tenkit/template-generator/styling-definitions";
8
9
  import { spawn } from "node:child_process";
10
+ import { deriveAppVariantIdentities, deriveAppVariantIdentity, derivePackageName, getGeneratedSetupTypeDefinition, normalizeProjectName, validatePackageName } from "@tenkit/template-generator/setup-type-definitions";
9
11
  //#region src/adapters/workspace.ts
10
12
  function isPackageJsonShape(value) {
11
13
  return typeof value === "object" && value !== null;
@@ -36,9 +38,10 @@ function resolveRealPath(path) {
36
38
  }
37
39
  //#endregion
38
40
  //#region src/constants.ts
39
- const CLI_VERSION = "0.1.2";
41
+ const CLI_VERSION = "0.2.0-next.0";
40
42
  const DEFAULT_PROJECT_NAME = "tenkit-app";
41
43
  const DEFAULT_PUBLIC_SETUP_SLUG = "white-label";
44
+ const DEFAULT_STYLING_CHOICE = "bare";
42
45
  const PROMPT_CANCELLED = Symbol("prompt-cancelled");
43
46
  const SETUP_PROMPT_CHOICES = [
44
47
  {
@@ -54,9 +57,21 @@ const SETUP_PROMPT_CHOICES = [
54
57
  label: "Generic + Standalone Apps"
55
58
  }
56
59
  ];
60
+ const STYLING_PROMPT_LABELS = {
61
+ bare: "Bare",
62
+ uniwind: "Uniwind",
63
+ unistyles: "Unistyles"
64
+ };
65
+ const STYLING_PROMPT_CHOICES = SUPPORTED_GENERATED_STYLING_CHOICES.map((value) => ({
66
+ value,
67
+ label: STYLING_PROMPT_LABELS[value]
68
+ }));
57
69
  function supportedSetupValues() {
58
70
  return SUPPORTED_PUBLIC_SETUP_SLUGS;
59
71
  }
72
+ function supportedStylingValues() {
73
+ return SUPPORTED_GENERATED_STYLING_CHOICES;
74
+ }
60
75
  //#endregion
61
76
  //#region src/adapters/command-runner.ts
62
77
  function defaultRunCommand(command, args, cwd, options = {}) {
@@ -64,6 +79,10 @@ function defaultRunCommand(command, args, cwd, options = {}) {
64
79
  const stdio = options.stdio ?? "inherit";
65
80
  const child = spawn(command, [...args], {
66
81
  cwd,
82
+ env: options.env ? {
83
+ ...process.env,
84
+ ...options.env
85
+ } : process.env,
67
86
  stdio
68
87
  });
69
88
  child.on("error", () => {
@@ -81,13 +100,6 @@ function defaultRunCommand(command, args, cwd, options = {}) {
81
100
  });
82
101
  }
83
102
  //#endregion
84
- //#region src/errors.ts
85
- var CreateFlowCancelledError = class extends Error {
86
- constructor() {
87
- super("Create cancelled.");
88
- }
89
- };
90
- //#endregion
91
103
  //#region src/adapters/git.ts
92
104
  async function isGitAvailable(runCommand, cwd) {
93
105
  return (await runCommand("git", ["--version"], cwd, { stdio: "ignore" })).ok;
@@ -95,42 +107,29 @@ async function isGitAvailable(runCommand, cwd) {
95
107
  async function isInsideGitWorktree(runCommand, cwd) {
96
108
  return (await runCommand("git", ["rev-parse", "--is-inside-work-tree"], cwd, { stdio: "ignore" })).ok;
97
109
  }
98
- async function resolveGitMode({ explicitGitMode, env, runCommand, targetDir }) {
99
- if (explicitGitMode === false || explicitGitMode === "none") return {
100
- mode: false,
110
+ async function resolveGitMode({ enabled, runCommand, targetDir }) {
111
+ if (!enabled) return {
112
+ enabled: false,
101
113
  skippedReason: "disabled"
102
114
  };
103
115
  if (!await isGitAvailable(runCommand, targetDir)) return {
104
- mode: false,
116
+ enabled: false,
105
117
  skippedReason: "git-unavailable"
106
118
  };
107
- if (await isInsideGitWorktree(runCommand, targetDir) && explicitGitMode === void 0) {
108
- if (!env.isInteractive || env.isCi) return {
109
- mode: false,
110
- skippedReason: "nested-worktree"
111
- };
112
- const answer = await env.prompts.confirm({
113
- message: "Initialize a nested git repository?",
114
- initialValue: false
115
- });
116
- if (answer === PROMPT_CANCELLED) throw new CreateFlowCancelledError();
117
- if (!answer) return {
118
- mode: false,
119
- skippedReason: "nested-worktree"
120
- };
121
- }
122
- if (explicitGitMode === "init") return { mode: "init" };
123
- return { mode: "commit" };
119
+ if (await isInsideGitWorktree(runCommand, targetDir)) return {
120
+ enabled: false,
121
+ skippedReason: "nested-worktree"
122
+ };
123
+ return { enabled: true };
124
124
  }
125
- async function prepareInitialGitSetup({ explicitGitMode, env, runCommand, probeDir }) {
125
+ async function prepareInitialGitSetup({ enabled, runCommand, probeDir }) {
126
126
  const gitPlan = await resolveGitMode({
127
- explicitGitMode,
128
- env,
127
+ enabled,
129
128
  runCommand,
130
129
  targetDir: probeDir
131
130
  });
132
131
  return { async run(targetDir) {
133
- if (!gitPlan.mode) return {
132
+ if (!gitPlan.enabled) return {
134
133
  gitInitialized: false,
135
134
  gitCommitted: false,
136
135
  gitSkippedReason: gitPlan.skippedReason,
@@ -143,11 +142,6 @@ async function prepareInitialGitSetup({ explicitGitMode, env, runCommand, probeD
143
142
  gitCommitted: false,
144
143
  gitFailed: true
145
144
  };
146
- if (gitPlan.mode === "init") return {
147
- gitInitialized,
148
- gitCommitted: false,
149
- gitFailed: false
150
- };
151
145
  const commitResult = (await runCommand("git", ["add", "--all"], targetDir, { stdio: "ignore" })).ok ? await runCommand("git", [
152
146
  "commit",
153
147
  "-m",
@@ -183,9 +177,6 @@ function detectPackageManager(userAgent) {
183
177
  if (userAgent?.startsWith("npm")) return "npm";
184
178
  return "pnpm";
185
179
  }
186
- function resolvePackageManager({ packageManager, userAgent }) {
187
- return normalizePackageManagerInput(packageManager) ?? detectPackageManager(userAgent);
188
- }
189
180
  function formatInstallCommand(packageManager) {
190
181
  return `${packageManager} install`;
191
182
  }
@@ -228,33 +219,32 @@ function formatShellArg(value) {
228
219
  return `'${value.replace(/'/g, `'\\''`)}'`;
229
220
  }
230
221
  //#endregion
222
+ //#region src/errors.ts
223
+ var CreateFlowCancelledError = class extends Error {
224
+ constructor() {
225
+ super("Create cancelled.");
226
+ }
227
+ };
228
+ //#endregion
231
229
  //#region src/create/validation.ts
232
- function isPathSeparatorPresent(value) {
233
- return value.includes("/") || value.includes("\\");
234
- }
235
230
  function validateProjectName(value) {
236
- const projectName = value.trim();
237
- if (projectName.length === 0) throw new Error("Project name is required.");
238
- if (projectName === "." || projectName === "..") throw new Error("Project name must be a child folder name.");
239
- if (isPathSeparatorPresent(projectName)) throw new Error("Project name must not contain path separators.");
240
- if (/[\0-\x1F<>:"|?*]/.test(projectName)) throw new Error("Project name contains characters that are unsafe for a project folder.");
241
- return projectName;
242
- }
243
- function slugifyPackageName(projectName) {
244
- return projectName.trim().toLowerCase().replace(/['"]/g, "").replace(/[^a-z0-9._-]+/g, "-").replace(/^[._-]+|[._-]+$/g, "").replace(/-{2,}/g, "-");
231
+ try {
232
+ return normalizeProjectName(value);
233
+ } catch {
234
+ throw new Error("Project name must contain a usable Latin letter or number.");
235
+ }
245
236
  }
246
- function validatePackageName(value) {
247
- const packageName = value.trim();
248
- if (packageName.length === 0) throw new Error("Package name is required.");
249
- if (packageName.length > 214) throw new Error("Package name must be 214 characters or fewer.");
250
- if (packageName !== packageName.toLowerCase()) throw new Error("Package name must be lowercase.");
251
- if (isPathSeparatorPresent(packageName)) throw new Error("Package name must not contain path separators.");
252
- if (packageName.startsWith(".") || packageName.startsWith("_")) throw new Error("Package name must not start with \".\" or \"_\".");
253
- if (!/^[a-z0-9][a-z0-9._-]*$/.test(packageName)) throw new Error("Package name must contain only lowercase letters, numbers, \".\", \"_\", and \"-\".");
254
- return packageName;
237
+ function normalizeAppVariantNameInput(value) {
238
+ return deriveAppVariantIdentity(value).displayName;
255
239
  }
256
- function derivePackageName(projectName) {
257
- return validatePackageName(slugifyPackageName(projectName));
240
+ function normalizeAppVariantAccentInput(value) {
241
+ const accent = value.trim();
242
+ const normalizedInput = accent.startsWith("#") ? accent : `#${accent}`;
243
+ try {
244
+ const normalizedAccent = normalizeGeneratedAccentColor(normalizedInput);
245
+ if (normalizedAccent) return normalizedAccent;
246
+ } catch {}
247
+ throw new Error(`Invalid App Variant Accent ${JSON.stringify(accent)}. Expected a six-digit hex color such as "#208AEF".`);
258
248
  }
259
249
  function normalizeSetupInput(setup, setupType) {
260
250
  if (setup !== void 0 && setupType !== void 0 && setup !== setupType) throw new Error("Use either --setup or --setup-type, not both with different values.");
@@ -265,11 +255,26 @@ function normalizeSetupInput(setup, setupType) {
265
255
  throw new Error(`Unsupported Setup Type ${JSON.stringify(selectedSetup)}. Expected ${formatSupportedGeneratedSetupTypes()}.`);
266
256
  }
267
257
  }
268
- function parseGitMode(value) {
269
- if (value === false) return false;
270
- if (value === void 0) return;
271
- if (value === "init" || value === "commit" || value === "none") return value;
272
- throw new Error("Git mode must be one of: init, commit, none.");
258
+ function normalizeStylingInput(value) {
259
+ try {
260
+ return normalizeGeneratedStylingChoice(value);
261
+ } catch {
262
+ throw new Error(`Unsupported Styling Choice ${JSON.stringify(value)}. Expected one of: ${supportedStylingValues().join(", ")}.`);
263
+ }
264
+ }
265
+ function normalizeAppVariantCustomization(setupType, appVariantNamesInput, appVariantAccentsInput) {
266
+ const definition = getGeneratedSetupTypeDefinition(setupType);
267
+ const appVariantNames = appVariantNamesInput === void 0 ? definition.appVariants.map(({ defaultName }) => defaultName) : appVariantNamesInput.split(",").map((name) => name.trim());
268
+ if (appVariantNames.some((name) => name.length === 0)) throw new Error("App Variant names must not contain empty items.");
269
+ if (appVariantNames.length !== definition.appVariants.length) throw new Error(`Expected exactly ${definition.appVariants.length} App Variant names for ${definition.publicSlug}.`);
270
+ deriveAppVariantIdentities(appVariantNames);
271
+ const rawAccents = appVariantAccentsInput === void 0 ? definition.appVariants.map(({ defaultAccent }) => defaultAccent) : appVariantAccentsInput.split(",").map((accent) => accent.trim());
272
+ if (rawAccents.some((accent) => accent.length === 0)) throw new Error("App Variant Accents must not contain empty items.");
273
+ if (rawAccents.length !== definition.appVariants.length) throw new Error(`Expected exactly ${definition.appVariants.length} App Variant Accents for ${definition.publicSlug}.`);
274
+ return {
275
+ appVariantNames,
276
+ appVariantAccents: rawAccents.map(normalizeAppVariantAccentInput)
277
+ };
273
278
  }
274
279
  //#endregion
275
280
  //#region src/create/resolve-create-options.ts
@@ -291,7 +296,66 @@ async function readProjectName(options, env) {
291
296
  }
292
297
  });
293
298
  if (answer === PROMPT_CANCELLED) throw new CreateFlowCancelledError();
294
- return validateProjectName(answer);
299
+ const projectName = validateProjectName(answer);
300
+ env.output.log(`Project folder/package: ${projectName}`);
301
+ return projectName;
302
+ }
303
+ async function readAppVariantCustomization(setupType, options, env) {
304
+ if (options.appVariantNamesInput !== void 0 || options.appVariantAccentsInput !== void 0) return normalizeAppVariantCustomization(setupType, options.appVariantNamesInput, options.appVariantAccentsInput);
305
+ if (options.yes || !env.isInteractive) return normalizeAppVariantCustomization(setupType, void 0, void 0);
306
+ const customize = await env.prompts.confirm({
307
+ message: "Customize App Variant names and Accent colors?",
308
+ initialValue: false
309
+ });
310
+ if (customize === PROMPT_CANCELLED) throw new CreateFlowCancelledError();
311
+ if (!customize) return normalizeAppVariantCustomization(setupType, void 0, void 0);
312
+ const definition = getGeneratedSetupTypeDefinition(setupType);
313
+ const appVariantNames = [];
314
+ const appVariantAccents = [];
315
+ for (const appVariant of definition.appVariants) {
316
+ const name = await env.prompts.text({
317
+ message: `App Variant name: ${appVariant.defaultName}`,
318
+ placeholder: appVariant.defaultName,
319
+ defaultValue: appVariant.defaultName,
320
+ validate(value) {
321
+ try {
322
+ normalizeAppVariantNameInput(value ?? "");
323
+ return;
324
+ } catch (error) {
325
+ return error instanceof Error ? error.message : String(error);
326
+ }
327
+ }
328
+ });
329
+ if (name === PROMPT_CANCELLED) throw new CreateFlowCancelledError();
330
+ const accent = await env.prompts.text({
331
+ message: `App Variant Accent: ${appVariant.defaultName}`,
332
+ placeholder: appVariant.defaultAccent,
333
+ defaultValue: appVariant.defaultAccent,
334
+ validate(value) {
335
+ try {
336
+ normalizeAppVariantAccentInput(value ?? "");
337
+ return;
338
+ } catch (error) {
339
+ return error instanceof Error ? error.message : String(error);
340
+ }
341
+ }
342
+ });
343
+ if (accent === PROMPT_CANCELLED) throw new CreateFlowCancelledError();
344
+ appVariantNames.push(name);
345
+ appVariantAccents.push(accent);
346
+ }
347
+ return normalizeAppVariantCustomization(setupType, appVariantNames.join(","), appVariantAccents.join(","));
348
+ }
349
+ async function readStylingChoice(options, env) {
350
+ if (options.styling !== void 0) return normalizeStylingInput(options.styling);
351
+ if (options.yes || !env.isInteractive) return DEFAULT_STYLING_CHOICE;
352
+ const answer = await env.prompts.select({
353
+ message: "Styling Choice",
354
+ initialValue: DEFAULT_STYLING_CHOICE,
355
+ options: STYLING_PROMPT_CHOICES
356
+ });
357
+ if (answer === PROMPT_CANCELLED) throw new CreateFlowCancelledError();
358
+ return normalizeStylingInput(answer);
295
359
  }
296
360
  async function readSetupType(options, env) {
297
361
  if (options.setup !== void 0 || options.setupType !== void 0) return normalizeSetupInput(options.setup, options.setupType);
@@ -305,6 +369,35 @@ async function readSetupType(options, env) {
305
369
  if (answer === PROMPT_CANCELLED) throw new CreateFlowCancelledError();
306
370
  return normalizeGeneratedSetupType(answer);
307
371
  }
372
+ async function readPackageManager(options, env) {
373
+ const detectedPackageManager = detectPackageManager(env.packageManagerUserAgent);
374
+ if (options.packageManager !== void 0) {
375
+ const packageManager = normalizePackageManagerInput(options.packageManager);
376
+ if (!packageManager) throw new Error("Package manager is required.");
377
+ return packageManager;
378
+ }
379
+ if (options.yes || !env.isInteractive) return detectedPackageManager;
380
+ const answer = await env.prompts.select({
381
+ message: "Package manager",
382
+ initialValue: detectedPackageManager,
383
+ options: SUPPORTED_PACKAGE_MANAGERS.map((packageManager) => ({
384
+ value: packageManager,
385
+ label: packageManager
386
+ }))
387
+ });
388
+ if (answer === PROMPT_CANCELLED) throw new CreateFlowCancelledError();
389
+ return answer;
390
+ }
391
+ async function readEnabledChoice({ explicitValue, message, options, env }) {
392
+ if (explicitValue !== void 0) return explicitValue;
393
+ if (options.yes || !env.isInteractive) return true;
394
+ const answer = await env.prompts.confirm({
395
+ message,
396
+ initialValue: true
397
+ });
398
+ if (answer === PROMPT_CANCELLED) throw new CreateFlowCancelledError();
399
+ return answer;
400
+ }
308
401
  async function assertTargetIsSafe(targetDir) {
309
402
  if (!await fs.pathExists(targetDir)) return;
310
403
  if (!(await fs.stat(targetDir)).isDirectory()) throw new Error(`Generated project target ${targetDir} exists but is not a directory.`);
@@ -313,10 +406,21 @@ async function assertTargetIsSafe(targetDir) {
313
406
  async function resolveCreateOptions(options, env) {
314
407
  const projectName = await readProjectName(options, env);
315
408
  const setupType = await readSetupType(options, env);
409
+ const { appVariantNames, appVariantAccents } = await readAppVariantCustomization(setupType, options, env);
410
+ const stylingChoice = await readStylingChoice(options, env);
316
411
  const packageName = options.packageName !== void 0 ? validatePackageName(options.packageName) : derivePackageName(projectName);
317
- const packageManager = resolvePackageManager({
318
- packageManager: options.packageManager,
319
- userAgent: env.packageManagerUserAgent
412
+ const packageManager = await readPackageManager(options, env);
413
+ const git = await readEnabledChoice({
414
+ explicitValue: options.git,
415
+ message: "Initialize Git?",
416
+ options,
417
+ env
418
+ });
419
+ const install = await readEnabledChoice({
420
+ explicitValue: options.install,
421
+ message: "Install dependencies?",
422
+ options,
423
+ env
320
424
  });
321
425
  const targetDir = resolve(env.cwd, projectName);
322
426
  await assertTargetIsSafe(targetDir);
@@ -324,10 +428,13 @@ async function resolveCreateOptions(options, env) {
324
428
  projectName,
325
429
  packageName,
326
430
  setupType,
431
+ stylingChoice,
432
+ appVariantNames,
433
+ appVariantAccents,
327
434
  targetDir,
328
435
  packageManager,
329
- install: options.install !== false,
330
- git: parseGitMode(options.git),
436
+ install,
437
+ git,
331
438
  dryRun: options.dryRun === true
332
439
  };
333
440
  }
@@ -343,6 +450,9 @@ async function runCreateFlow(options, env) {
343
450
  }));
344
451
  const tree = generate({
345
452
  setupType: resolvedOptions.setupType,
453
+ stylingChoice: resolvedOptions.stylingChoice,
454
+ appVariantAccents: resolvedOptions.appVariantAccents,
455
+ appVariantNames: resolvedOptions.appVariantNames,
346
456
  projectName: resolvedOptions.projectName,
347
457
  packageName: resolvedOptions.packageName,
348
458
  packageManager: resolvedOptions.packageManager
@@ -360,6 +470,9 @@ async function runCreateFlow(options, env) {
360
470
  projectName: resolvedOptions.projectName,
361
471
  packageName: resolvedOptions.packageName,
362
472
  setupType: resolvedOptions.setupType,
473
+ stylingChoice: resolvedOptions.stylingChoice,
474
+ appVariantAccents: resolvedOptions.appVariantAccents,
475
+ appVariantNames: resolvedOptions.appVariantNames,
363
476
  packageManager: resolvedOptions.packageManager,
364
477
  installed: false,
365
478
  installFailed: false,
@@ -373,8 +486,7 @@ async function runCreateFlow(options, env) {
373
486
  }
374
487
  const gitProbeCwd = await fs.pathExists(resolvedOptions.targetDir) ? resolvedOptions.targetDir : env.cwd;
375
488
  const gitSetup = await prepareInitialGitSetup({
376
- explicitGitMode: resolvedOptions.git,
377
- env,
489
+ enabled: resolvedOptions.git,
378
490
  runCommand,
379
491
  probeDir: gitProbeCwd
380
492
  });
@@ -398,6 +510,9 @@ async function runCreateFlow(options, env) {
398
510
  projectName: resolvedOptions.projectName,
399
511
  packageName: resolvedOptions.packageName,
400
512
  setupType: resolvedOptions.setupType,
513
+ stylingChoice: resolvedOptions.stylingChoice,
514
+ appVariantAccents: resolvedOptions.appVariantAccents,
515
+ appVariantNames: resolvedOptions.appVariantNames,
401
516
  packageManager: resolvedOptions.packageManager,
402
517
  installed,
403
518
  installFailed,
@@ -417,16 +532,19 @@ function normalizeCommanderOptions(options) {
417
532
  packageName: options.packageName,
418
533
  setup: options.setup,
419
534
  setupType: options.setupType,
535
+ styling: options.styling,
536
+ appVariantNamesInput: options.variantNames,
537
+ appVariantAccentsInput: options.variantAccents,
420
538
  packageManager: options.packageManager,
421
539
  yes: options.yes,
422
540
  install: options.install,
423
- git: parseGitMode(options.git),
541
+ git: options.git,
424
542
  dryRun: options.dryRun
425
543
  };
426
544
  }
427
545
  function createProgram(env) {
428
546
  const program = new Command();
429
- program.name("tenkit").description("Create a generated Tenkit Expo project.").version(CLI_VERSION).allowExcessArguments(false).option("--name <name>", `project folder name, defaults to ${DEFAULT_PROJECT_NAME} with --yes`).option("--package-name <name>", "generated package.json name override").option("-s, --setup <setup>", `public Setup slug: ${supportedSetupValues().join(", ")}`).option("--setup-type <setupType>", "canonical Setup Type ID or public Setup slug").option("--package-manager <manager>", `install and generated command package manager: ${SUPPORTED_PACKAGE_MANAGERS.join(", ")}`).option("--yes", "skip prompts and accept defaults").option("--no-install", "skip dependency installation").option("--git <mode>", "git behavior: init, commit, none").option("--no-git", "skip git initialization").option("--dry-run", "validate options and print the create plan without writing files").configureOutput({
547
+ program.name("tenkit").description("Create a generated Tenkit Expo project.").version(CLI_VERSION).allowExcessArguments(false).option("--name <name>", `project folder name, defaults to ${DEFAULT_PROJECT_NAME} with --yes`).option("--package-name <name>", "generated package.json name override").option("-s, --setup <setup>", `public Setup slug: ${supportedSetupValues().join(", ")}`).option("--setup-type <setupType>", "canonical Setup Type ID or public Setup slug").option("--styling <styling>", `Styling Choice: ${supportedStylingValues().join(", ")}`).option("--variant-names <names>", "ordered comma-separated App Variant names").option("--variant-accents <colors>", "ordered comma-separated App Variant Accent colors").option("--package-manager <manager>", `install and generated command package manager: ${SUPPORTED_PACKAGE_MANAGERS.join(", ")}`).option("--yes", "skip prompts and accept defaults").option("--install", "install dependencies").option("--no-install", "skip dependency installation").option("--git", "initialize Git and create Initial commit").option("--no-git", "skip git initialization").option("--dry-run", "validate options and print the create plan without writing files").configureOutput({
430
548
  writeOut: (message) => env.output.log(message.trimEnd()),
431
549
  writeErr: (message) => env.output.error(message.trimEnd())
432
550
  }).exitOverride().showHelpAfterError().action(async (options) => {
@@ -452,13 +570,17 @@ function createPromptAdapter() {
452
570
  },
453
571
  async select(options) {
454
572
  const answer = await select({
455
- ...options,
573
+ message: options.message,
574
+ initialValue: options.initialValue,
456
575
  options: options.options.map((option) => ({
457
576
  value: option.value,
458
577
  label: option.label
459
578
  }))
460
579
  });
461
- return isCancel(answer) ? PROMPT_CANCELLED : answer;
580
+ if (isCancel(answer)) return PROMPT_CANCELLED;
581
+ const selectedOption = options.options.find((option) => option.value === answer);
582
+ if (!selectedOption) throw new Error(`Prompt returned unsupported selection ${JSON.stringify(answer)}.`);
583
+ return selectedOption.value;
462
584
  },
463
585
  async confirm(options) {
464
586
  const answer = await confirm(options);
@@ -482,7 +604,6 @@ async function main(argv = process.argv.slice(2), io = process) {
482
604
  cwd: process.env.INIT_CWD ?? process.cwd(),
483
605
  workspaceRoot,
484
606
  isInteractive: io.stdin.isTTY === true && io.stdout.isTTY === true,
485
- isCi: process.env.CI === "true",
486
607
  packageManagerUserAgent: process.env.npm_config_user_agent,
487
608
  output,
488
609
  prompts: createPromptAdapter()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenkit/cli",
3
- "version": "0.1.2",
3
+ "version": "0.2.0-next.0",
4
4
  "description": "Public Tenkit CLI implementation package.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/brilliantinsane/tenkit#readme",
@@ -41,12 +41,13 @@
41
41
  "commander": "^15.0.0",
42
42
  "fs-extra": "^11.3.5",
43
43
  "pathe": "^2.0.3",
44
- "@tenkit/template-generator": "0.1.2"
44
+ "@tenkit/template-generator": "0.2.0-next.0"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/fs-extra": "^11.0.4",
48
48
  "@types/node": "^25.9.3",
49
49
  "tsdown": "^0.22.3",
50
+ "tsx": "^4.22.4",
50
51
  "typescript": "~6.0.3",
51
52
  "vitest": "^4.1.9"
52
53
  },
@@ -61,6 +62,7 @@
61
62
  "build": "pnpm -F @tenkit/template-generator build && tsdown src/index.ts --format esm --dts",
62
63
  "test": "vitest run",
63
64
  "typecheck": "tsc --noEmit --pretty false",
65
+ "verify:matrix": "pnpm -F @tenkit/template-generator build && tsx scripts/verify-generation-matrix.ts",
64
66
  "pack:dry-run": "pnpm pack --dry-run"
65
67
  }
66
68
  }