@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.
- package/README.md +58 -11
- package/dist/index.mjs +61 -17
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,27 +1,74 @@
|
|
|
1
1
|
# @tenkit/cli
|
|
2
2
|
|
|
3
|
-
Public
|
|
3
|
+
Public CLI implementation for creating Tenkit Expo projects.
|
|
4
4
|
|
|
5
|
-
Most users should run Tenkit through the
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
16
|
-
multi-tenant products, App Variant builds, and Runtime Tenant experiences.
|
|
15
|
+
## Highlights
|
|
17
16
|
|
|
18
|
-
|
|
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
|
-
|
|
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
|
-
|
|
27
|
-
|
|
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.
|
|
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
|
|
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(
|
|
175
|
-
output.log(
|
|
176
|
-
output.log(
|
|
177
|
-
output.log(
|
|
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(
|
|
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(
|
|
350
|
-
const installResult = await runCommand(
|
|
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
|
-
...
|
|
446
|
+
...promptOptions,
|
|
404
447
|
validate(value) {
|
|
405
|
-
return
|
|
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.
|
|
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.
|
|
44
|
+
"@tenkit/template-generator": "0.1.2"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/fs-extra": "^11.0.4",
|