@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.
- package/README.md +71 -3
- package/dist/index.mjs +63 -18
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -1,6 +1,74 @@
|
|
|
1
1
|
# @tenkit/cli
|
|
2
2
|
|
|
3
|
-
Public
|
|
3
|
+
Public CLI implementation for creating Tenkit Expo projects.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
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.
|
|
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("");
|
|
@@ -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
|
-
|
|
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(
|
|
349
|
-
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" });
|
|
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
|
-
...
|
|
446
|
+
...promptOptions,
|
|
403
447
|
validate(value) {
|
|
404
|
-
return
|
|
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.
|
|
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.
|
|
44
|
+
"@tenkit/template-generator": "0.1.2"
|
|
40
45
|
},
|
|
41
46
|
"devDependencies": {
|
|
42
47
|
"@types/fs-extra": "^11.0.4",
|