@tenkit/cli 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Tenkit
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,6 @@
1
+ # @tenkit/cli
2
+
3
+ Public Tenkit CLI implementation package.
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.
@@ -0,0 +1,9 @@
1
+ //#region src/cli.d.ts
2
+ type CliIo = {
3
+ stdout: Pick<NodeJS.WriteStream, 'write' | 'isTTY'>;
4
+ stderr: Pick<NodeJS.WriteStream, 'write'>;
5
+ stdin: Pick<NodeJS.ReadStream, 'isTTY'>;
6
+ };
7
+ declare function main(argv?: string[], io?: CliIo): Promise<number>;
8
+ //#endregion
9
+ export { main };
package/dist/index.mjs ADDED
@@ -0,0 +1,467 @@
1
+ #!/usr/bin/env node
2
+ import { fileURLToPath } from "node:url";
3
+ import fs from "fs-extra";
4
+ import { dirname, resolve } from "pathe";
5
+ import { cancel, confirm, intro, isCancel, outro, select, text } from "@clack/prompts";
6
+ import { Command } from "commander";
7
+ import { SUPPORTED_PUBLIC_SETUP_SLUGS, formatSupportedGeneratedSetupTypes, generateProject, normalizeGeneratedSetupType, preflightWriteProject, writeProject } from "@tenkit/template-generator";
8
+ import { spawn } from "node:child_process";
9
+ //#region src/adapters/workspace.ts
10
+ function isPackageJsonShape(value) {
11
+ return typeof value === "object" && value !== null;
12
+ }
13
+ async function findTenkitWorkspaceRoot(startUrl) {
14
+ let current = dirname(fileURLToPath(startUrl));
15
+ while (true) {
16
+ const packageJsonPath = resolve(current, "package.json");
17
+ if (await fs.pathExists(packageJsonPath)) {
18
+ const packageJson = await fs.readJson(packageJsonPath);
19
+ if (isPackageJsonShape(packageJson) && packageJson.name === "tenkit-workspace") return current;
20
+ }
21
+ const parent = dirname(current);
22
+ if (parent === current) return;
23
+ current = parent;
24
+ }
25
+ }
26
+ function isDirectCliRun(entryUrl, argvEntry) {
27
+ if (!argvEntry) return false;
28
+ return resolveRealPath(fileURLToPath(entryUrl)) === resolveRealPath(argvEntry);
29
+ }
30
+ function resolveRealPath(path) {
31
+ try {
32
+ return fs.realpathSync(path);
33
+ } catch {
34
+ return resolve(path);
35
+ }
36
+ }
37
+ //#endregion
38
+ //#region src/constants.ts
39
+ const CLI_VERSION = "0.1.0";
40
+ const DEFAULT_PROJECT_NAME = "tenkit-app";
41
+ const DEFAULT_PUBLIC_SETUP_SLUG = "white-label";
42
+ const PROMPT_CANCELLED = Symbol("prompt-cancelled");
43
+ const SETUP_PROMPT_CHOICES = [
44
+ {
45
+ value: "white-label",
46
+ label: "White Label Apps"
47
+ },
48
+ {
49
+ value: "runtime-tenants",
50
+ label: "Runtime Tenant App"
51
+ },
52
+ {
53
+ value: "generic-standalone",
54
+ label: "Generic + Standalone Apps"
55
+ }
56
+ ];
57
+ function supportedSetupValues() {
58
+ return SUPPORTED_PUBLIC_SETUP_SLUGS;
59
+ }
60
+ //#endregion
61
+ //#region src/adapters/command-runner.ts
62
+ function defaultRunCommand(command, args, cwd, options = {}) {
63
+ return new Promise((resolveCommand) => {
64
+ const child = spawn(command, [...args], {
65
+ cwd,
66
+ stdio: options.stdio ?? "inherit"
67
+ });
68
+ child.on("error", () => {
69
+ resolveCommand({
70
+ ok: false,
71
+ code: 1
72
+ });
73
+ });
74
+ child.on("close", (code) => {
75
+ resolveCommand({
76
+ ok: code === 0,
77
+ code: code ?? 1
78
+ });
79
+ });
80
+ });
81
+ }
82
+ //#endregion
83
+ //#region src/errors.ts
84
+ var CreateFlowCancelledError = class extends Error {
85
+ constructor() {
86
+ super("Create cancelled.");
87
+ }
88
+ };
89
+ //#endregion
90
+ //#region src/adapters/git.ts
91
+ async function isGitAvailable(runCommand, cwd) {
92
+ return (await runCommand("git", ["--version"], cwd, { stdio: "ignore" })).ok;
93
+ }
94
+ async function isInsideGitWorktree(runCommand, cwd) {
95
+ return (await runCommand("git", ["rev-parse", "--is-inside-work-tree"], cwd, { stdio: "ignore" })).ok;
96
+ }
97
+ async function resolveGitMode({ explicitGitMode, env, runCommand, targetDir }) {
98
+ if (explicitGitMode === false || explicitGitMode === "none") return {
99
+ mode: false,
100
+ skippedReason: "disabled"
101
+ };
102
+ if (!await isGitAvailable(runCommand, targetDir)) return {
103
+ mode: false,
104
+ skippedReason: "git-unavailable"
105
+ };
106
+ if (await isInsideGitWorktree(runCommand, targetDir) && explicitGitMode === void 0) {
107
+ if (!env.isInteractive || env.isCi) return {
108
+ mode: false,
109
+ skippedReason: "nested-worktree"
110
+ };
111
+ const answer = await env.prompts.confirm({
112
+ message: "Initialize a nested git repository?",
113
+ initialValue: false
114
+ });
115
+ if (answer === PROMPT_CANCELLED) throw new CreateFlowCancelledError();
116
+ if (!answer) return {
117
+ mode: false,
118
+ skippedReason: "nested-worktree"
119
+ };
120
+ }
121
+ if (explicitGitMode === "init") return { mode: "init" };
122
+ return { mode: "commit" };
123
+ }
124
+ async function prepareInitialGitSetup({ explicitGitMode, env, runCommand, probeDir }) {
125
+ const gitPlan = await resolveGitMode({
126
+ explicitGitMode,
127
+ env,
128
+ runCommand,
129
+ targetDir: probeDir
130
+ });
131
+ return { async run(targetDir) {
132
+ if (!gitPlan.mode) return {
133
+ gitInitialized: false,
134
+ gitCommitted: false,
135
+ gitSkippedReason: gitPlan.skippedReason,
136
+ gitFailed: false
137
+ };
138
+ const initResult = await runCommand("git", ["init"], targetDir);
139
+ const gitInitialized = initResult.ok;
140
+ if (!initResult.ok) return {
141
+ gitInitialized,
142
+ gitCommitted: false,
143
+ gitFailed: true
144
+ };
145
+ if (gitPlan.mode === "init") return {
146
+ gitInitialized,
147
+ gitCommitted: false,
148
+ gitFailed: false
149
+ };
150
+ const commitResult = (await runCommand("git", ["add", "--all"], targetDir)).ok ? await runCommand("git", [
151
+ "commit",
152
+ "-m",
153
+ "Initial commit"
154
+ ], targetDir) : {
155
+ ok: false,
156
+ code: 1
157
+ };
158
+ return {
159
+ gitInitialized,
160
+ gitCommitted: commitResult.ok,
161
+ gitFailed: !commitResult.ok
162
+ };
163
+ } };
164
+ }
165
+ //#endregion
166
+ //#region src/create/create-messages.ts
167
+ function logFinalOutput(result, output) {
168
+ const projectShellArg = formatShellArg(result.projectName);
169
+ output.log("");
170
+ output.log(result.status === "dry-run" ? "Tenkit create plan is valid." : "Your Tenkit project is ready.");
171
+ output.log("");
172
+ output.log("Next steps:");
173
+ 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");
178
+ if (result.installFailed) {
179
+ output.log("");
180
+ output.log("Dependency installation failed. Run pnpm install in the generated project.");
181
+ }
182
+ if (result.gitSkippedReason === "git-unavailable") {
183
+ output.log("");
184
+ output.log("Git was not available. Run git init when ready.");
185
+ } else if (result.gitSkippedReason === "nested-worktree") {
186
+ output.log("");
187
+ output.log("Git initialization was skipped because the project is inside an existing git worktree.");
188
+ } else if (result.gitFailed) {
189
+ output.log("");
190
+ output.log("Git setup did not complete. Run git init, git add --all, and git commit -m \"Initial commit\" when ready.");
191
+ }
192
+ }
193
+ function formatShellArg(value) {
194
+ if (/^[A-Za-z0-9._/-]+$/.test(value)) return value;
195
+ return `'${value.replace(/'/g, `'\\''`)}'`;
196
+ }
197
+ //#endregion
198
+ //#region src/create/validation.ts
199
+ function isPathSeparatorPresent(value) {
200
+ return value.includes("/") || value.includes("\\");
201
+ }
202
+ function validateProjectName(value) {
203
+ const projectName = value.trim();
204
+ if (projectName.length === 0) throw new Error("Project name is required.");
205
+ if (projectName === "." || projectName === "..") throw new Error("Project name must be a child folder name.");
206
+ if (isPathSeparatorPresent(projectName)) throw new Error("Project name must not contain path separators.");
207
+ if (/[\0-\x1F<>:"|?*]/.test(projectName)) throw new Error("Project name contains characters that are unsafe for a project folder.");
208
+ return projectName;
209
+ }
210
+ function slugifyPackageName(projectName) {
211
+ return projectName.trim().toLowerCase().replace(/['"]/g, "").replace(/[^a-z0-9._-]+/g, "-").replace(/^[._-]+|[._-]+$/g, "").replace(/-{2,}/g, "-");
212
+ }
213
+ function validatePackageName(value) {
214
+ const packageName = value.trim();
215
+ if (packageName.length === 0) throw new Error("Package name is required.");
216
+ if (packageName.length > 214) throw new Error("Package name must be 214 characters or fewer.");
217
+ if (packageName !== packageName.toLowerCase()) throw new Error("Package name must be lowercase.");
218
+ if (isPathSeparatorPresent(packageName)) throw new Error("Package name must not contain path separators.");
219
+ if (packageName.startsWith(".") || packageName.startsWith("_")) throw new Error("Package name must not start with \".\" or \"_\".");
220
+ if (!/^[a-z0-9][a-z0-9._-]*$/.test(packageName)) throw new Error("Package name must contain only lowercase letters, numbers, \".\", \"_\", and \"-\".");
221
+ return packageName;
222
+ }
223
+ function derivePackageName(projectName) {
224
+ return validatePackageName(slugifyPackageName(projectName));
225
+ }
226
+ function normalizeSetupInput(setup, setupType) {
227
+ if (setup !== void 0 && setupType !== void 0 && setup !== setupType) throw new Error("Use either --setup or --setup-type, not both with different values.");
228
+ const selectedSetup = setup ?? setupType ?? "white-label";
229
+ try {
230
+ return normalizeGeneratedSetupType(selectedSetup);
231
+ } catch {
232
+ throw new Error(`Unsupported Setup Type ${JSON.stringify(selectedSetup)}. Expected ${formatSupportedGeneratedSetupTypes()}.`);
233
+ }
234
+ }
235
+ function parseGitMode(value) {
236
+ if (value === false) return false;
237
+ if (value === void 0) return;
238
+ if (value === "init" || value === "commit" || value === "none") return value;
239
+ throw new Error("Git mode must be one of: init, commit, none.");
240
+ }
241
+ //#endregion
242
+ //#region src/create/resolve-create-options.ts
243
+ async function readProjectName(options, env) {
244
+ if (options.name !== void 0) return validateProjectName(options.name);
245
+ if (options.yes) return DEFAULT_PROJECT_NAME;
246
+ if (!env.isInteractive) throw new Error("Missing --name. Pass --name or use --yes to accept the default.");
247
+ const answer = await env.prompts.text({
248
+ message: "Project name",
249
+ initialValue: DEFAULT_PROJECT_NAME,
250
+ validate(value) {
251
+ try {
252
+ validateProjectName(value ?? "");
253
+ return;
254
+ } catch (error) {
255
+ return error instanceof Error ? error.message : String(error);
256
+ }
257
+ }
258
+ });
259
+ if (answer === PROMPT_CANCELLED) throw new CreateFlowCancelledError();
260
+ return validateProjectName(answer);
261
+ }
262
+ async function readSetupType(options, env) {
263
+ if (options.setup !== void 0 || options.setupType !== void 0) return normalizeSetupInput(options.setup, options.setupType);
264
+ if (options.yes) return normalizeGeneratedSetupType(DEFAULT_PUBLIC_SETUP_SLUG);
265
+ if (!env.isInteractive) throw new Error("Missing --setup. Pass --setup or use --yes to accept the default.");
266
+ const answer = await env.prompts.select({
267
+ message: "Setup Type",
268
+ initialValue: DEFAULT_PUBLIC_SETUP_SLUG,
269
+ options: SETUP_PROMPT_CHOICES
270
+ });
271
+ if (answer === PROMPT_CANCELLED) throw new CreateFlowCancelledError();
272
+ return normalizeGeneratedSetupType(answer);
273
+ }
274
+ async function assertTargetIsSafe(targetDir) {
275
+ if (!await fs.pathExists(targetDir)) return;
276
+ if (!(await fs.stat(targetDir)).isDirectory()) throw new Error(`Generated project target ${targetDir} exists but is not a directory.`);
277
+ if ((await fs.readdir(targetDir)).length > 0) throw new Error(`Generated project target ${targetDir} already exists and is not empty.`);
278
+ }
279
+ async function resolveCreateOptions(options, env) {
280
+ const projectName = await readProjectName(options, env);
281
+ const setupType = await readSetupType(options, env);
282
+ const packageName = options.packageName !== void 0 ? validatePackageName(options.packageName) : derivePackageName(projectName);
283
+ const targetDir = resolve(env.cwd, projectName);
284
+ await assertTargetIsSafe(targetDir);
285
+ return {
286
+ projectName,
287
+ packageName,
288
+ setupType,
289
+ targetDir,
290
+ install: options.install !== false,
291
+ git: parseGitMode(options.git),
292
+ dryRun: options.dryRun === true
293
+ };
294
+ }
295
+ //#endregion
296
+ //#region src/create/run-create.ts
297
+ async function runCreateFlow(options, env) {
298
+ const resolvedOptions = await resolveCreateOptions(options, env);
299
+ const runCommand = env.runCommand ?? defaultRunCommand;
300
+ const generate = env.generate ?? generateProject;
301
+ const write = env.write ?? ((writeOptions) => writeProject({
302
+ ...writeOptions,
303
+ overwrite: "never"
304
+ }));
305
+ const tree = generate({
306
+ setupType: resolvedOptions.setupType,
307
+ projectName: resolvedOptions.projectName,
308
+ packageName: resolvedOptions.packageName
309
+ });
310
+ if (resolvedOptions.dryRun) {
311
+ await preflightWriteProject({
312
+ targetDir: resolvedOptions.targetDir,
313
+ tree,
314
+ overwrite: "never",
315
+ forbiddenTargetRoots: env.workspaceRoot ? [env.workspaceRoot] : []
316
+ });
317
+ const result = {
318
+ status: "dry-run",
319
+ targetDir: resolvedOptions.targetDir,
320
+ projectName: resolvedOptions.projectName,
321
+ packageName: resolvedOptions.packageName,
322
+ setupType: resolvedOptions.setupType,
323
+ installed: false,
324
+ installFailed: false,
325
+ gitInitialized: false,
326
+ gitCommitted: false,
327
+ gitSkippedReason: "dry-run",
328
+ gitFailed: false
329
+ };
330
+ logFinalOutput(result, env.output);
331
+ return result;
332
+ }
333
+ const gitProbeCwd = await fs.pathExists(resolvedOptions.targetDir) ? resolvedOptions.targetDir : env.cwd;
334
+ const gitSetup = await prepareInitialGitSetup({
335
+ explicitGitMode: resolvedOptions.git,
336
+ env,
337
+ runCommand,
338
+ probeDir: gitProbeCwd
339
+ });
340
+ const writeResult = await write({
341
+ targetDir: resolvedOptions.targetDir,
342
+ tree,
343
+ forbiddenTargetRoots: env.workspaceRoot ? [env.workspaceRoot] : []
344
+ });
345
+ let installed = false;
346
+ let installFailed = false;
347
+ if (resolvedOptions.install) {
348
+ env.output.log("Installing dependencies with pnpm...");
349
+ const installResult = await runCommand("pnpm", ["install"], writeResult.targetDir);
350
+ installed = installResult.ok;
351
+ installFailed = !installResult.ok;
352
+ }
353
+ const gitResult = await gitSetup.run(writeResult.targetDir);
354
+ const result = {
355
+ status: "created",
356
+ targetDir: writeResult.targetDir,
357
+ projectName: resolvedOptions.projectName,
358
+ packageName: resolvedOptions.packageName,
359
+ setupType: resolvedOptions.setupType,
360
+ installed,
361
+ installFailed,
362
+ gitInitialized: gitResult.gitInitialized,
363
+ gitCommitted: gitResult.gitCommitted,
364
+ gitSkippedReason: gitResult.gitSkippedReason,
365
+ gitFailed: gitResult.gitFailed
366
+ };
367
+ logFinalOutput(result, env.output);
368
+ return result;
369
+ }
370
+ //#endregion
371
+ //#region src/commands/create.ts
372
+ function normalizeCommanderOptions(options) {
373
+ return {
374
+ name: options.name,
375
+ packageName: options.packageName,
376
+ setup: options.setup,
377
+ setupType: options.setupType,
378
+ yes: options.yes,
379
+ install: options.install,
380
+ git: parseGitMode(options.git),
381
+ dryRun: options.dryRun
382
+ };
383
+ }
384
+ function createProgram(env) {
385
+ 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({
387
+ writeOut: (message) => env.output.log(message.trimEnd()),
388
+ writeErr: (message) => env.output.error(message.trimEnd())
389
+ }).exitOverride().showHelpAfterError().action(async (options) => {
390
+ intro("Create Tenkit");
391
+ await runCreateFlow(normalizeCommanderOptions(options), env);
392
+ outro("Done");
393
+ });
394
+ return program;
395
+ }
396
+ //#endregion
397
+ //#region src/prompts/create-prompts.ts
398
+ function createPromptAdapter() {
399
+ return {
400
+ async text(options) {
401
+ const answer = await text({
402
+ ...options,
403
+ validate(value) {
404
+ return options.validate(value);
405
+ }
406
+ });
407
+ return isCancel(answer) ? PROMPT_CANCELLED : String(answer);
408
+ },
409
+ async select(options) {
410
+ const answer = await select({
411
+ ...options,
412
+ options: options.options.map((option) => ({
413
+ value: option.value,
414
+ label: option.label
415
+ }))
416
+ });
417
+ return isCancel(answer) ? PROMPT_CANCELLED : answer;
418
+ },
419
+ async confirm(options) {
420
+ const answer = await confirm(options);
421
+ return isCancel(answer) ? PROMPT_CANCELLED : answer;
422
+ }
423
+ };
424
+ }
425
+ //#endregion
426
+ //#region src/cli.ts
427
+ async function main(argv = process.argv.slice(2), io = process) {
428
+ const output = {
429
+ log(message = "") {
430
+ io.stdout.write(`${message}\n`);
431
+ },
432
+ error(message) {
433
+ io.stderr.write(`${message}\n`);
434
+ }
435
+ };
436
+ const workspaceRoot = await findTenkitWorkspaceRoot(import.meta.url);
437
+ const program = createProgram({
438
+ cwd: process.env.INIT_CWD ?? process.cwd(),
439
+ workspaceRoot,
440
+ isInteractive: io.stdin.isTTY === true && io.stdout.isTTY === true,
441
+ isCi: process.env.CI === "true",
442
+ output,
443
+ prompts: createPromptAdapter()
444
+ });
445
+ try {
446
+ await program.parseAsync(argv, { from: "user" });
447
+ return 0;
448
+ } catch (error) {
449
+ if (error instanceof CreateFlowCancelledError) {
450
+ cancel(error.message);
451
+ return 130;
452
+ }
453
+ if (error && typeof error === "object" && "code" in error && error.code === "commander.helpDisplayed") return 0;
454
+ if (error && typeof error === "object" && "code" in error && error.code === "commander.version") return 0;
455
+ const message = error instanceof Error ? error.message : String(error);
456
+ output.error(message);
457
+ return 1;
458
+ }
459
+ }
460
+ //#endregion
461
+ //#region src/index.ts
462
+ if (isDirectCliRun(import.meta.url, process.argv[1])) {
463
+ const exitCode = await main();
464
+ process.exitCode = exitCode;
465
+ }
466
+ //#endregion
467
+ export { main };
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@tenkit/cli",
3
+ "version": "0.1.0",
4
+ "description": "Public Tenkit CLI implementation package.",
5
+ "license": "MIT",
6
+ "homepage": "https://github.com/brilliantinsane/tenkit#readme",
7
+ "bugs": {
8
+ "url": "https://github.com/brilliantinsane/tenkit/issues"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/brilliantinsane/tenkit.git",
13
+ "directory": "packages/cli"
14
+ },
15
+ "keywords": [
16
+ "cli",
17
+ "expo",
18
+ "react-native",
19
+ "tenkit",
20
+ "typescript"
21
+ ],
22
+ "type": "module",
23
+ "bin": {
24
+ "tenkit": "./dist/index.mjs"
25
+ },
26
+ "exports": {
27
+ ".": "./dist/index.mjs"
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "package.json",
32
+ "README.md"
33
+ ],
34
+ "dependencies": {
35
+ "@clack/prompts": "^1.6.0",
36
+ "commander": "^15.0.0",
37
+ "fs-extra": "^11.3.5",
38
+ "pathe": "^2.0.3",
39
+ "@tenkit/template-generator": "0.1.0"
40
+ },
41
+ "devDependencies": {
42
+ "@types/fs-extra": "^11.0.4",
43
+ "@types/node": "^25.9.3",
44
+ "tsdown": "^0.22.3",
45
+ "typescript": "~6.0.3",
46
+ "vitest": "^4.1.9"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public",
50
+ "provenance": true
51
+ },
52
+ "engines": {
53
+ "node": ">=22.14.0"
54
+ },
55
+ "scripts": {
56
+ "build": "pnpm -F @tenkit/template-generator build && tsdown src/index.ts --format esm --dts",
57
+ "test": "vitest run",
58
+ "typecheck": "tsc --noEmit --pretty false",
59
+ "pack:dry-run": "pnpm pack --dry-run"
60
+ }
61
+ }