@tenkit/cli 0.1.0 → 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 +71 -3
  2. package/dist/index.mjs +63 -18
  3. package/package.json +8 -3
package/README.md CHANGED
@@ -1,6 +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
- The package-manager create entrypoint is `create-tenkit`; this package owns parsing, prompts,
6
- create-flow orchestration, install and git convenience policy, and final output.
5
+ Most users should run Tenkit through the create entrypoint:
6
+
7
+ ```bash
8
+ pnpm create tenkit@latest
9
+ ```
10
+
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
+
15
+ ## Highlights
16
+
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.
24
+
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:
37
+
38
+ ```bash
39
+ tenkit --help
40
+ ```
41
+
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.0";
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("");
@@ -246,7 +279,8 @@ async function readProjectName(options, env) {
246
279
  if (!env.isInteractive) throw new Error("Missing --name. Pass --name or use --yes to accept the default.");
247
280
  const answer = await env.prompts.text({
248
281
  message: "Project name",
249
- initialValue: DEFAULT_PROJECT_NAME,
282
+ placeholder: DEFAULT_PROJECT_NAME,
283
+ defaultValue: DEFAULT_PROJECT_NAME,
250
284
  validate(value) {
251
285
  try {
252
286
  validateProjectName(value ?? "");
@@ -280,6 +314,10 @@ async function resolveCreateOptions(options, env) {
280
314
  const projectName = await readProjectName(options, env);
281
315
  const setupType = await readSetupType(options, env);
282
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
+ });
283
321
  const targetDir = resolve(env.cwd, projectName);
284
322
  await assertTargetIsSafe(targetDir);
285
323
  return {
@@ -287,6 +325,7 @@ async function resolveCreateOptions(options, env) {
287
325
  packageName,
288
326
  setupType,
289
327
  targetDir,
328
+ packageManager,
290
329
  install: options.install !== false,
291
330
  git: parseGitMode(options.git),
292
331
  dryRun: options.dryRun === true
@@ -305,7 +344,8 @@ async function runCreateFlow(options, env) {
305
344
  const tree = generate({
306
345
  setupType: resolvedOptions.setupType,
307
346
  projectName: resolvedOptions.projectName,
308
- packageName: resolvedOptions.packageName
347
+ packageName: resolvedOptions.packageName,
348
+ packageManager: resolvedOptions.packageManager
309
349
  });
310
350
  if (resolvedOptions.dryRun) {
311
351
  await preflightWriteProject({
@@ -320,6 +360,7 @@ async function runCreateFlow(options, env) {
320
360
  projectName: resolvedOptions.projectName,
321
361
  packageName: resolvedOptions.packageName,
322
362
  setupType: resolvedOptions.setupType,
363
+ packageManager: resolvedOptions.packageManager,
323
364
  installed: false,
324
365
  installFailed: false,
325
366
  gitInitialized: false,
@@ -345,8 +386,8 @@ async function runCreateFlow(options, env) {
345
386
  let installed = false;
346
387
  let installFailed = false;
347
388
  if (resolvedOptions.install) {
348
- env.output.log("Installing dependencies with pnpm...");
349
- 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" });
350
391
  installed = installResult.ok;
351
392
  installFailed = !installResult.ok;
352
393
  }
@@ -357,6 +398,7 @@ async function runCreateFlow(options, env) {
357
398
  projectName: resolvedOptions.projectName,
358
399
  packageName: resolvedOptions.packageName,
359
400
  setupType: resolvedOptions.setupType,
401
+ packageManager: resolvedOptions.packageManager,
360
402
  installed,
361
403
  installFailed,
362
404
  gitInitialized: gitResult.gitInitialized,
@@ -375,6 +417,7 @@ function normalizeCommanderOptions(options) {
375
417
  packageName: options.packageName,
376
418
  setup: options.setup,
377
419
  setupType: options.setupType,
420
+ packageManager: options.packageManager,
378
421
  yes: options.yes,
379
422
  install: options.install,
380
423
  git: parseGitMode(options.git),
@@ -383,7 +426,7 @@ function normalizeCommanderOptions(options) {
383
426
  }
384
427
  function createProgram(env) {
385
428
  const program = new Command();
386
- 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({
387
430
  writeOut: (message) => env.output.log(message.trimEnd()),
388
431
  writeErr: (message) => env.output.error(message.trimEnd())
389
432
  }).exitOverride().showHelpAfterError().action(async (options) => {
@@ -398,13 +441,14 @@ function createProgram(env) {
398
441
  function createPromptAdapter() {
399
442
  return {
400
443
  async text(options) {
444
+ const { defaultValue, validate, ...promptOptions } = options;
401
445
  const answer = await text({
402
- ...options,
446
+ ...promptOptions,
403
447
  validate(value) {
404
- return options.validate(value);
448
+ return validate(value === "" || value === void 0 ? defaultValue : value);
405
449
  }
406
450
  });
407
- return isCancel(answer) ? PROMPT_CANCELLED : String(answer);
451
+ return isCancel(answer) ? PROMPT_CANCELLED : answer === "" || answer === void 0 ? defaultValue : String(answer);
408
452
  },
409
453
  async select(options) {
410
454
  const answer = await select({
@@ -439,6 +483,7 @@ async function main(argv = process.argv.slice(2), io = process) {
439
483
  workspaceRoot,
440
484
  isInteractive: io.stdin.isTTY === true && io.stdout.isTTY === true,
441
485
  isCi: process.env.CI === "true",
486
+ packageManagerUserAgent: process.env.npm_config_user_agent,
442
487
  output,
443
488
  prompts: createPromptAdapter()
444
489
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenkit/cli",
3
- "version": "0.1.0",
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",
@@ -13,11 +13,16 @@
13
13
  "directory": "packages/cli"
14
14
  },
15
15
  "keywords": [
16
+ "app-variants",
16
17
  "cli",
17
18
  "expo",
19
+ "expo-cli",
20
+ "multi-tenant",
18
21
  "react-native",
22
+ "runtime-tenants",
19
23
  "tenkit",
20
- "typescript"
24
+ "typescript",
25
+ "white-label"
21
26
  ],
22
27
  "type": "module",
23
28
  "bin": {
@@ -36,7 +41,7 @@
36
41
  "commander": "^15.0.0",
37
42
  "fs-extra": "^11.3.5",
38
43
  "pathe": "^2.0.3",
39
- "@tenkit/template-generator": "0.1.0"
44
+ "@tenkit/template-generator": "0.1.2"
40
45
  },
41
46
  "devDependencies": {
42
47
  "@types/fs-extra": "^11.0.4",