@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.
- package/README.md +12 -1
- package/dist/index.mjs +204 -83
- 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
|
-
-
|
|
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.
|
|
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({
|
|
99
|
-
if (
|
|
100
|
-
|
|
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
|
-
|
|
116
|
+
enabled: false,
|
|
105
117
|
skippedReason: "git-unavailable"
|
|
106
118
|
};
|
|
107
|
-
if (await isInsideGitWorktree(runCommand, targetDir)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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({
|
|
125
|
+
async function prepareInitialGitSetup({ enabled, runCommand, probeDir }) {
|
|
126
126
|
const gitPlan = await resolveGitMode({
|
|
127
|
-
|
|
128
|
-
env,
|
|
127
|
+
enabled,
|
|
129
128
|
runCommand,
|
|
130
129
|
targetDir: probeDir
|
|
131
130
|
});
|
|
132
131
|
return { async run(targetDir) {
|
|
133
|
-
if (!gitPlan.
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
|
247
|
-
|
|
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
|
|
257
|
-
|
|
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
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
|
|
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 =
|
|
318
|
-
|
|
319
|
-
|
|
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
|
|
330
|
-
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
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
}
|