@tenkit/cli 0.1.1 → 0.1.2

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 +58 -11
  2. package/dist/index.mjs +61 -17
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -1,27 +1,74 @@
1
1
  # @tenkit/cli
2
2
 
3
- Public Tenkit CLI implementation package.
3
+ Public CLI implementation for creating Tenkit Expo projects.
4
4
 
5
- Most users should run Tenkit through the package-manager create entrypoint:
5
+ Most users should run Tenkit through the create entrypoint:
6
6
 
7
7
  ```bash
8
8
  pnpm create tenkit@latest
9
9
  ```
10
10
 
11
- This package owns the real Public CLI implementation behind `create-tenkit`:
12
- parsing, prompts, create-flow orchestration, Template generation, dependency
13
- installation, git convenience policy, and final output.
11
+ `@tenkit/cli` owns the real create flow behind `create-tenkit`: option parsing,
12
+ prompts, Template generation, dependency installation, initial git setup, and
13
+ final next steps.
14
14
 
15
- Tenkit creates Expo and React Native projects for white-label apps,
16
- multi-tenant products, App Variant builds, and Runtime Tenant experiences.
15
+ ## Highlights
17
16
 
18
- ## Direct CLI
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.
19
+ - Support `pnpm`, `npm`, `npx`, Bun, and `bunx` launchers.
20
+ - Use the selected package manager for install commands, generated README
21
+ commands, and generated app-local commands.
22
+ - Treat install and git failures as follow-up work so successful generation is
23
+ not hidden by convenience-step failures.
19
24
 
20
- The package exposes a `tenkit` binary for direct usage:
25
+ ## Usage
26
+
27
+ ```bash
28
+ # Package-manager create entrypoints
29
+ pnpm create tenkit@latest
30
+ npm create tenkit@latest
31
+ npx create-tenkit@latest
32
+ bun create tenkit@latest
33
+ bunx create-tenkit@latest
34
+ ```
35
+
36
+ Direct binary usage is also available:
21
37
 
22
38
  ```bash
23
39
  tenkit --help
24
40
  ```
25
41
 
26
- For public project creation, prefer `pnpm create tenkit@latest` so the package
27
- manager resolves the create entrypoint correctly.
42
+ ## Common Options
43
+
44
+ ```bash
45
+ tenkit --name studio-app --setup white-label --yes
46
+ tenkit --name venue-network --setup runtime-tenants --yes
47
+ tenkit --name franchise-app --setup generic-standalone --yes
48
+ ```
49
+
50
+ ```bash
51
+ tenkit --name demo --setup runtime-tenants --yes --no-install --no-git
52
+ ```
53
+
54
+ Supported public Setup Type slugs:
55
+
56
+ ```text
57
+ white-label
58
+ runtime-tenants
59
+ generic-standalone
60
+ ```
61
+
62
+ Override package-manager detection when needed:
63
+
64
+ ```bash
65
+ tenkit --package-manager pnpm
66
+ tenkit --package-manager npm
67
+ tenkit --package-manager bun
68
+ ```
69
+
70
+ ## Package Boundary
71
+
72
+ `@tenkit/cli` is the Public CLI implementation package. The `create-tenkit`
73
+ package is a thin create entrypoint that delegates here. Template source,
74
+ generation, and writer safety live in `@tenkit/template-generator`.
package/dist/index.mjs CHANGED
@@ -36,7 +36,7 @@ function resolveRealPath(path) {
36
36
  }
37
37
  //#endregion
38
38
  //#region src/constants.ts
39
- const CLI_VERSION = "0.1.1";
39
+ const CLI_VERSION = "0.1.2";
40
40
  const DEFAULT_PROJECT_NAME = "tenkit-app";
41
41
  const DEFAULT_PUBLIC_SETUP_SLUG = "white-label";
42
42
  const PROMPT_CANCELLED = Symbol("prompt-cancelled");
@@ -61,9 +61,10 @@ function supportedSetupValues() {
61
61
  //#region src/adapters/command-runner.ts
62
62
  function defaultRunCommand(command, args, cwd, options = {}) {
63
63
  return new Promise((resolveCommand) => {
64
+ const stdio = options.stdio ?? "inherit";
64
65
  const child = spawn(command, [...args], {
65
66
  cwd,
66
- stdio: options.stdio ?? "inherit"
67
+ stdio
67
68
  });
68
69
  child.on("error", () => {
69
70
  resolveCommand({
@@ -135,7 +136,7 @@ async function prepareInitialGitSetup({ explicitGitMode, env, runCommand, probeD
135
136
  gitSkippedReason: gitPlan.skippedReason,
136
137
  gitFailed: false
137
138
  };
138
- const initResult = await runCommand("git", ["init"], targetDir);
139
+ const initResult = await runCommand("git", ["init"], targetDir, { stdio: "ignore" });
139
140
  const gitInitialized = initResult.ok;
140
141
  if (!initResult.ok) return {
141
142
  gitInitialized,
@@ -147,11 +148,11 @@ async function prepareInitialGitSetup({ explicitGitMode, env, runCommand, probeD
147
148
  gitCommitted: false,
148
149
  gitFailed: false
149
150
  };
150
- const commitResult = (await runCommand("git", ["add", "--all"], targetDir)).ok ? await runCommand("git", [
151
+ const commitResult = (await runCommand("git", ["add", "--all"], targetDir, { stdio: "ignore" })).ok ? await runCommand("git", [
151
152
  "commit",
152
153
  "-m",
153
154
  "Initial commit"
154
- ], targetDir) : {
155
+ ], targetDir, { stdio: "ignore" }) : {
155
156
  ok: false,
156
157
  code: 1
157
158
  };
@@ -163,6 +164,38 @@ async function prepareInitialGitSetup({ explicitGitMode, env, runCommand, probeD
163
164
  } };
164
165
  }
165
166
  //#endregion
167
+ //#region src/create/package-manager.ts
168
+ const SUPPORTED_PACKAGE_MANAGERS = [
169
+ "pnpm",
170
+ "npm",
171
+ "bun"
172
+ ];
173
+ function normalizePackageManagerInput(packageManager) {
174
+ if (packageManager === void 0) return;
175
+ const normalized = packageManager.trim();
176
+ if (!normalized) throw new Error(`Package manager must be one of: ${SUPPORTED_PACKAGE_MANAGERS.join(", ")}.`);
177
+ if (isPublicCliPackageManager(normalized)) return normalized;
178
+ throw new Error(`Unsupported package manager ${JSON.stringify(packageManager)}. Expected one of: ${SUPPORTED_PACKAGE_MANAGERS.join(", ")}.`);
179
+ }
180
+ function detectPackageManager(userAgent) {
181
+ if (userAgent?.startsWith("pnpm")) return "pnpm";
182
+ if (userAgent?.startsWith("bun")) return "bun";
183
+ if (userAgent?.startsWith("npm")) return "npm";
184
+ return "pnpm";
185
+ }
186
+ function resolvePackageManager({ packageManager, userAgent }) {
187
+ return normalizePackageManagerInput(packageManager) ?? detectPackageManager(userAgent);
188
+ }
189
+ function formatInstallCommand(packageManager) {
190
+ return `${packageManager} install`;
191
+ }
192
+ function formatRunCommand(packageManager, script) {
193
+ return `${packageManager} run ${script}`;
194
+ }
195
+ function isPublicCliPackageManager(value) {
196
+ return SUPPORTED_PACKAGE_MANAGERS.some((packageManager) => packageManager === value);
197
+ }
198
+ //#endregion
166
199
  //#region src/create/create-messages.ts
167
200
  function logFinalOutput(result, output) {
168
201
  const projectShellArg = formatShellArg(result.projectName);
@@ -171,13 +204,13 @@ function logFinalOutput(result, output) {
171
204
  output.log("");
172
205
  output.log("Next steps:");
173
206
  output.log(`- cd ${projectShellArg}`);
174
- if (result.installFailed || !result.installed) output.log("- pnpm install");
175
- output.log("- pnpm run android");
176
- output.log("- pnpm run ios");
177
- output.log("- pnpm run web");
207
+ if (result.installFailed || !result.installed) output.log(`- ${formatInstallCommand(result.packageManager)}`);
208
+ output.log(`- ${formatRunCommand(result.packageManager, "android")}`);
209
+ output.log(`- ${formatRunCommand(result.packageManager, "ios")}`);
210
+ output.log(`- ${formatRunCommand(result.packageManager, "web")}`);
178
211
  if (result.installFailed) {
179
212
  output.log("");
180
- output.log("Dependency installation failed. Run pnpm install in the generated project.");
213
+ output.log(`Dependency installation failed. Run ${formatInstallCommand(result.packageManager)} in the generated project.`);
181
214
  }
182
215
  if (result.gitSkippedReason === "git-unavailable") {
183
216
  output.log("");
@@ -281,6 +314,10 @@ async function resolveCreateOptions(options, env) {
281
314
  const projectName = await readProjectName(options, env);
282
315
  const setupType = await readSetupType(options, env);
283
316
  const packageName = options.packageName !== void 0 ? validatePackageName(options.packageName) : derivePackageName(projectName);
317
+ const packageManager = resolvePackageManager({
318
+ packageManager: options.packageManager,
319
+ userAgent: env.packageManagerUserAgent
320
+ });
284
321
  const targetDir = resolve(env.cwd, projectName);
285
322
  await assertTargetIsSafe(targetDir);
286
323
  return {
@@ -288,6 +325,7 @@ async function resolveCreateOptions(options, env) {
288
325
  packageName,
289
326
  setupType,
290
327
  targetDir,
328
+ packageManager,
291
329
  install: options.install !== false,
292
330
  git: parseGitMode(options.git),
293
331
  dryRun: options.dryRun === true
@@ -306,7 +344,8 @@ async function runCreateFlow(options, env) {
306
344
  const tree = generate({
307
345
  setupType: resolvedOptions.setupType,
308
346
  projectName: resolvedOptions.projectName,
309
- packageName: resolvedOptions.packageName
347
+ packageName: resolvedOptions.packageName,
348
+ packageManager: resolvedOptions.packageManager
310
349
  });
311
350
  if (resolvedOptions.dryRun) {
312
351
  await preflightWriteProject({
@@ -321,6 +360,7 @@ async function runCreateFlow(options, env) {
321
360
  projectName: resolvedOptions.projectName,
322
361
  packageName: resolvedOptions.packageName,
323
362
  setupType: resolvedOptions.setupType,
363
+ packageManager: resolvedOptions.packageManager,
324
364
  installed: false,
325
365
  installFailed: false,
326
366
  gitInitialized: false,
@@ -346,8 +386,8 @@ async function runCreateFlow(options, env) {
346
386
  let installed = false;
347
387
  let installFailed = false;
348
388
  if (resolvedOptions.install) {
349
- env.output.log("Installing dependencies with pnpm...");
350
- const installResult = await runCommand("pnpm", ["install"], writeResult.targetDir);
389
+ env.output.log(`Installing dependencies with ${resolvedOptions.packageManager}...`);
390
+ const installResult = await runCommand(resolvedOptions.packageManager, ["install"], writeResult.targetDir, { stdio: "ignore" });
351
391
  installed = installResult.ok;
352
392
  installFailed = !installResult.ok;
353
393
  }
@@ -358,6 +398,7 @@ async function runCreateFlow(options, env) {
358
398
  projectName: resolvedOptions.projectName,
359
399
  packageName: resolvedOptions.packageName,
360
400
  setupType: resolvedOptions.setupType,
401
+ packageManager: resolvedOptions.packageManager,
361
402
  installed,
362
403
  installFailed,
363
404
  gitInitialized: gitResult.gitInitialized,
@@ -376,6 +417,7 @@ function normalizeCommanderOptions(options) {
376
417
  packageName: options.packageName,
377
418
  setup: options.setup,
378
419
  setupType: options.setupType,
420
+ packageManager: options.packageManager,
379
421
  yes: options.yes,
380
422
  install: options.install,
381
423
  git: parseGitMode(options.git),
@@ -384,7 +426,7 @@ function normalizeCommanderOptions(options) {
384
426
  }
385
427
  function createProgram(env) {
386
428
  const program = new Command();
387
- 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("--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({
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({
388
430
  writeOut: (message) => env.output.log(message.trimEnd()),
389
431
  writeErr: (message) => env.output.error(message.trimEnd())
390
432
  }).exitOverride().showHelpAfterError().action(async (options) => {
@@ -399,13 +441,14 @@ function createProgram(env) {
399
441
  function createPromptAdapter() {
400
442
  return {
401
443
  async text(options) {
444
+ const { defaultValue, validate, ...promptOptions } = options;
402
445
  const answer = await text({
403
- ...options,
446
+ ...promptOptions,
404
447
  validate(value) {
405
- return options.validate(value);
448
+ return validate(value === "" || value === void 0 ? defaultValue : value);
406
449
  }
407
450
  });
408
- return isCancel(answer) ? PROMPT_CANCELLED : String(answer);
451
+ return isCancel(answer) ? PROMPT_CANCELLED : answer === "" || answer === void 0 ? defaultValue : String(answer);
409
452
  },
410
453
  async select(options) {
411
454
  const answer = await select({
@@ -440,6 +483,7 @@ async function main(argv = process.argv.slice(2), io = process) {
440
483
  workspaceRoot,
441
484
  isInteractive: io.stdin.isTTY === true && io.stdout.isTTY === true,
442
485
  isCi: process.env.CI === "true",
486
+ packageManagerUserAgent: process.env.npm_config_user_agent,
443
487
  output,
444
488
  prompts: createPromptAdapter()
445
489
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenkit/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Public Tenkit CLI implementation package.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/brilliantinsane/tenkit#readme",
@@ -41,7 +41,7 @@
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.1"
44
+ "@tenkit/template-generator": "0.1.2"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/fs-extra": "^11.0.4",