@pauldvlp/vp-react-ts-shadcn 0.4.1 → 0.5.1
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 +4 -6
- package/dist/index.js +223 -91
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -9,9 +9,8 @@ A [Vite+](https://viteplus.dev) **monorepo generator** that scaffolds a minimal
|
|
|
9
9
|
- `apps/website` — a React + Vite+ app (Tailwind v4, React Compiler)
|
|
10
10
|
- `packages/ui` — a shared **shadcn** design system consumed by the app
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
you choose.
|
|
12
|
+
Options can be passed on the `vp create` command line (anything after `--`) or answered interactively,
|
|
13
|
+
and the **shadcn theme is materialized at create time** from the preset you choose.
|
|
15
14
|
|
|
16
15
|
## Usage
|
|
17
16
|
|
|
@@ -26,9 +25,8 @@ vp create @pauldvlp:vp-react-ts-shadcn -- \
|
|
|
26
25
|
--name my-app --scope @acme --base base --preset vega --iconLibrary lucide --components button,card,dialog
|
|
27
26
|
```
|
|
28
27
|
|
|
29
|
-
>
|
|
30
|
-
>
|
|
31
|
-
> Bingo's CLI does not accept `--no-x` / `--x=false` cleanly. Omit them and answer the prompt.
|
|
28
|
+
> **Boolean** options (`--cssVariables`, `--rtl`, `--pointer`, `--install`) accept both forms: pass
|
|
29
|
+
> `--rtl` to enable or `--no-rtl` to disable. Omit any option to answer it at the interactive prompt.
|
|
32
30
|
|
|
33
31
|
## Options
|
|
34
32
|
|
package/dist/index.js
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// bin/index.ts
|
|
4
|
-
import { runTemplateCLI } from "bingo";
|
|
5
|
-
|
|
6
|
-
// src/template.ts
|
|
7
|
-
import path2 from "node:path";
|
|
8
|
-
import { fileURLToPath } from "node:url";
|
|
9
|
-
|
|
10
3
|
// ../template-kit/src/index.ts
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
11
5
|
import fs from "node:fs";
|
|
12
6
|
import path from "node:path";
|
|
7
|
+
import * as prompts from "@clack/prompts";
|
|
13
8
|
var RENAME = {
|
|
14
|
-
_gitignore: ".gitignore"
|
|
9
|
+
_gitignore: ".gitignore",
|
|
10
|
+
_dockerignore: ".dockerignore",
|
|
11
|
+
"_env.example": ".env.example"
|
|
15
12
|
};
|
|
16
13
|
var DEFAULT_PRESET = "b30557okNu";
|
|
17
14
|
var ICON_LIBS = {
|
|
@@ -24,6 +21,10 @@ function toScope(name) {
|
|
|
24
21
|
const n = name.trim();
|
|
25
22
|
return n.startsWith("@") ? n : `@${n}`;
|
|
26
23
|
}
|
|
24
|
+
function dirName(directory) {
|
|
25
|
+
const base = path.basename(directory || "");
|
|
26
|
+
return base && base !== "." ? base : "my-app";
|
|
27
|
+
}
|
|
27
28
|
function readTree(dir, transform, base = dir) {
|
|
28
29
|
const out = {};
|
|
29
30
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
@@ -62,6 +63,19 @@ function addIconDeps(pkg, iconLibrary) {
|
|
|
62
63
|
const iconDeps = ICON_LIBS[iconLibrary] ?? ICON_LIBS.hugeicons;
|
|
63
64
|
pkg.dependencies = { ...pkg.dependencies, ...iconDeps };
|
|
64
65
|
}
|
|
66
|
+
function validateScope(value) {
|
|
67
|
+
return /^@?[A-Za-z0-9][A-Za-z0-9._/-]*$/.test(value.trim()) ? void 0 : "must be a package scope/name, e.g. @acme";
|
|
68
|
+
}
|
|
69
|
+
function validateName(value) {
|
|
70
|
+
return /^@?[A-Za-z0-9][A-Za-z0-9._/-]*$/.test(value.trim()) ? void 0 : "must be a package name, e.g. my-app or @acme/app";
|
|
71
|
+
}
|
|
72
|
+
function validatePreset(value) {
|
|
73
|
+
return /^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(value.trim()) ? void 0 : "must be a style name or ui.shadcn.com code";
|
|
74
|
+
}
|
|
75
|
+
function validateComponents(value) {
|
|
76
|
+
const parts = value.split(",").map((c) => c.trim()).filter(Boolean);
|
|
77
|
+
return parts.every((c) => /^[a-z0-9][a-z0-9-]*$/.test(c)) ? void 0 : "comma-separated component names, e.g. button,card,dialog";
|
|
78
|
+
}
|
|
65
79
|
function withRequiredComponents(csv, required = ["button", "badge"]) {
|
|
66
80
|
const requested = csv.split(",").map((c) => c.trim()).filter(Boolean);
|
|
67
81
|
return Array.from(/* @__PURE__ */ new Set([...required, ...requested]));
|
|
@@ -105,99 +119,217 @@ function appComponentsJson(theme) {
|
|
|
105
119
|
}
|
|
106
120
|
};
|
|
107
121
|
}
|
|
108
|
-
function
|
|
122
|
+
function shadcnInitArgs(opts) {
|
|
109
123
|
return [
|
|
110
|
-
|
|
111
|
-
|
|
124
|
+
"--base",
|
|
125
|
+
opts.base,
|
|
126
|
+
"--preset",
|
|
127
|
+
opts.preset,
|
|
112
128
|
opts.cssVariables ? "--css-variables" : "--no-css-variables",
|
|
113
129
|
opts.rtl ? "--rtl" : "--no-rtl",
|
|
114
130
|
opts.pointer ? "--pointer" : "--no-pointer",
|
|
115
131
|
"--no-reinstall",
|
|
116
132
|
"-y",
|
|
117
133
|
"-f"
|
|
118
|
-
]
|
|
134
|
+
];
|
|
119
135
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
},
|
|
138
|
-
bugs: "https://github.com/pauldvlp/vp-templates/issues",
|
|
139
|
-
keywords: [
|
|
140
|
-
"vite-plus-generator",
|
|
141
|
-
"vite-plus",
|
|
142
|
-
"shadcn",
|
|
143
|
-
"react",
|
|
144
|
-
"template"
|
|
145
|
-
],
|
|
146
|
-
bin: "./dist/index.js",
|
|
147
|
-
type: "module",
|
|
148
|
-
files: [
|
|
149
|
-
"dist",
|
|
150
|
-
"template"
|
|
151
|
-
],
|
|
152
|
-
scripts: {
|
|
153
|
-
build: "esbuild bin/index.ts --bundle --outfile=dist/index.js --format=esm --platform=node --target=node22 --packages=external",
|
|
154
|
-
dev: "node bin/index.ts",
|
|
155
|
-
prepack: "pnpm run build"
|
|
156
|
-
},
|
|
157
|
-
dependencies: {
|
|
158
|
-
bingo: "^0.9.3",
|
|
159
|
-
zod: "^3.25.76"
|
|
160
|
-
},
|
|
161
|
-
devDependencies: {
|
|
162
|
-
"@pauldvlp/template-kit": "workspace:*",
|
|
163
|
-
"@types/node": "^24",
|
|
164
|
-
esbuild: "^0.25.0",
|
|
165
|
-
typescript: "^5"
|
|
166
|
-
},
|
|
167
|
-
engines: {
|
|
168
|
-
node: ">=22.18.0"
|
|
136
|
+
function defineTemplate(config) {
|
|
137
|
+
return config;
|
|
138
|
+
}
|
|
139
|
+
function writeTree(node, dir, root = path.resolve(dir)) {
|
|
140
|
+
for (const [name, value] of Object.entries(node)) {
|
|
141
|
+
const p = path.resolve(dir, name);
|
|
142
|
+
const rel = path.relative(root, p);
|
|
143
|
+
if (rel === "" || rel === ".." || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) {
|
|
144
|
+
throw new Error(`Refusing to write outside the target directory: ${name}`);
|
|
145
|
+
}
|
|
146
|
+
if (typeof value === "string") {
|
|
147
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
148
|
+
fs.writeFileSync(p, value);
|
|
149
|
+
} else {
|
|
150
|
+
fs.mkdirSync(p, { recursive: true });
|
|
151
|
+
writeTree(value, p, root);
|
|
152
|
+
}
|
|
169
153
|
}
|
|
170
|
-
}
|
|
154
|
+
}
|
|
155
|
+
function parseArgv(argv, descriptors) {
|
|
156
|
+
const booleanKeys = new Set(descriptors.filter((d) => d.type === "boolean").map((d) => d.key));
|
|
157
|
+
const values = {};
|
|
158
|
+
let directory;
|
|
159
|
+
for (let i = 0; i < argv.length; i++) {
|
|
160
|
+
const arg = argv[i];
|
|
161
|
+
if (!arg.startsWith("--")) continue;
|
|
162
|
+
let body = arg.slice(2);
|
|
163
|
+
if (body.startsWith("no-") && booleanKeys.has(body.slice(3))) {
|
|
164
|
+
values[body.slice(3)] = false;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
let inline;
|
|
168
|
+
const eq = body.indexOf("=");
|
|
169
|
+
if (eq !== -1) {
|
|
170
|
+
inline = body.slice(eq + 1);
|
|
171
|
+
body = body.slice(0, eq);
|
|
172
|
+
}
|
|
173
|
+
if (body === "directory") {
|
|
174
|
+
directory = inline ?? argv[++i];
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (booleanKeys.has(body)) {
|
|
178
|
+
values[body] = inline === void 0 ? true : inline !== "false";
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
values[body] = inline ?? argv[++i] ?? "";
|
|
182
|
+
}
|
|
183
|
+
return { values, directory };
|
|
184
|
+
}
|
|
185
|
+
function resolveDefault(d, options, directory) {
|
|
186
|
+
return typeof d.default === "function" ? d.default({ options, directory }) : d.default;
|
|
187
|
+
}
|
|
188
|
+
async function promptForOption(d, def) {
|
|
189
|
+
if (d.type === "boolean") {
|
|
190
|
+
return prompts.confirm({ message: d.message, initialValue: def === void 0 ? false : Boolean(def) });
|
|
191
|
+
}
|
|
192
|
+
if (d.type === "select") {
|
|
193
|
+
return prompts.select({
|
|
194
|
+
message: d.message,
|
|
195
|
+
options: (d.choices ?? []).map((c) => ({ value: c, label: c })),
|
|
196
|
+
initialValue: def
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
const fallback = def === void 0 ? void 0 : String(def);
|
|
200
|
+
return prompts.text({
|
|
201
|
+
message: d.message,
|
|
202
|
+
placeholder: fallback,
|
|
203
|
+
defaultValue: fallback,
|
|
204
|
+
// When a default exists, an empty submit means "use the default" — don't run validation on it,
|
|
205
|
+
// otherwise a rule like the port check would reject pressing Enter to accept 3000.
|
|
206
|
+
validate: d.validate ? (value) => !value && fallback !== void 0 ? void 0 : d.validate(value ?? "") : void 0
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
function printHelp(template) {
|
|
210
|
+
const lines = [];
|
|
211
|
+
if (template.about?.name) lines.push(template.about.name);
|
|
212
|
+
if (template.about?.description) lines.push(template.about.description);
|
|
213
|
+
lines.push("", "Options:", " --directory <dir> Where to scaffold");
|
|
214
|
+
for (const d of template.options) {
|
|
215
|
+
const hint = d.type === "select" && d.choices ? ` <${d.choices.join("|")}>` : d.type === "boolean" ? ` / --no-${d.key}` : " <value>";
|
|
216
|
+
lines.push(` --${d.key}${hint} ${d.message}`);
|
|
217
|
+
}
|
|
218
|
+
console.log(lines.join("\n"));
|
|
219
|
+
}
|
|
220
|
+
function initGit(dir) {
|
|
221
|
+
try {
|
|
222
|
+
execFileSync("git", ["rev-parse", "--is-inside-work-tree"], { cwd: dir, stdio: "ignore" });
|
|
223
|
+
return;
|
|
224
|
+
} catch {
|
|
225
|
+
}
|
|
226
|
+
try {
|
|
227
|
+
execFileSync("git", ["init"], { cwd: dir, stdio: "ignore" });
|
|
228
|
+
execFileSync("git", ["add", "-A"], { cwd: dir, stdio: "ignore" });
|
|
229
|
+
let identity = [];
|
|
230
|
+
try {
|
|
231
|
+
execFileSync("git", ["config", "user.email"], { cwd: dir, stdio: "ignore" });
|
|
232
|
+
} catch {
|
|
233
|
+
identity = ["-c", "user.name=create", "-c", "user.email=create@users.noreply.github.com"];
|
|
234
|
+
}
|
|
235
|
+
execFileSync("git", [...identity, "commit", "-m", "feat: initialize project", "--no-gpg-sign"], { cwd: dir, stdio: "ignore" });
|
|
236
|
+
} catch {
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
async function runTemplateCLI(template) {
|
|
240
|
+
const argv = process.argv.slice(2);
|
|
241
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
242
|
+
printHelp(template);
|
|
243
|
+
return 0;
|
|
244
|
+
}
|
|
245
|
+
const interactive = Boolean(process.stdout.isTTY) && !argv.includes("--no-interactive");
|
|
246
|
+
const { values, directory: dirArg } = parseArgv(argv, template.options);
|
|
247
|
+
prompts.intro(template.about?.name ?? "create");
|
|
248
|
+
let directory = dirArg;
|
|
249
|
+
if (!directory) {
|
|
250
|
+
if (interactive) {
|
|
251
|
+
const answer = await prompts.text({ message: "Where should we create your project?", placeholder: "my-app", defaultValue: "my-app" });
|
|
252
|
+
if (prompts.isCancel(answer)) {
|
|
253
|
+
prompts.cancel("Cancelled.");
|
|
254
|
+
return 2;
|
|
255
|
+
}
|
|
256
|
+
directory = answer || "my-app";
|
|
257
|
+
} else {
|
|
258
|
+
directory = "my-app";
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
const options = {};
|
|
262
|
+
for (const d of template.options) {
|
|
263
|
+
const def = resolveDefault(d, options, directory);
|
|
264
|
+
if (d.key in values) {
|
|
265
|
+
const value = values[d.key];
|
|
266
|
+
if (d.validate && typeof value === "string") {
|
|
267
|
+
const error = d.validate(value);
|
|
268
|
+
if (error) {
|
|
269
|
+
prompts.cancel(`Invalid --${d.key}: ${error}`);
|
|
270
|
+
return 1;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
options[d.key] = value;
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (interactive) {
|
|
277
|
+
const answer = await promptForOption(d, def);
|
|
278
|
+
if (prompts.isCancel(answer)) {
|
|
279
|
+
prompts.cancel("Cancelled.");
|
|
280
|
+
return 2;
|
|
281
|
+
}
|
|
282
|
+
options[d.key] = answer === "" && def !== void 0 ? def : answer;
|
|
283
|
+
} else {
|
|
284
|
+
options[d.key] = def;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
const creation = await template.produce({ options });
|
|
288
|
+
writeTree(creation.files, directory);
|
|
289
|
+
if (creation.scripts?.length) {
|
|
290
|
+
for (const step of [...creation.scripts].sort((a, b) => a.phase - b.phase)) {
|
|
291
|
+
for (const [program, ...commandArgs] of step.commands) {
|
|
292
|
+
if (!program) continue;
|
|
293
|
+
execFileSync(program, commandArgs, { cwd: directory, stdio: "inherit" });
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (template.git !== false) initGit(directory);
|
|
298
|
+
if (creation.suggestions?.length) {
|
|
299
|
+
prompts.note(creation.suggestions.join("\n"), "Next steps");
|
|
300
|
+
}
|
|
301
|
+
prompts.outro("Done!");
|
|
302
|
+
return 0;
|
|
303
|
+
}
|
|
171
304
|
|
|
172
305
|
// src/template.ts
|
|
306
|
+
import { readFileSync } from "node:fs";
|
|
307
|
+
import path2 from "node:path";
|
|
308
|
+
import { fileURLToPath } from "node:url";
|
|
173
309
|
var TEMPLATE_DIR = path2.join(path2.dirname(fileURLToPath(import.meta.url)), "..", "template");
|
|
174
|
-
var
|
|
310
|
+
var { name: pkgName, description: pkgDescription } = JSON.parse(
|
|
311
|
+
readFileSync(new URL("../package.json", import.meta.url), "utf8")
|
|
312
|
+
);
|
|
313
|
+
var template_default = defineTemplate({
|
|
175
314
|
about: {
|
|
176
|
-
name:
|
|
177
|
-
description:
|
|
178
|
-
},
|
|
179
|
-
options: {
|
|
180
|
-
name: z.string().describe("Root project / package name").default("my-app"),
|
|
181
|
-
scope: z.string().describe("npm scope for workspace packages, e.g. @acme (defaults to @<name>)"),
|
|
182
|
-
// Unions of literals (not z.enum) so the value is settable on Bingo's CLI: bingo's arg parser
|
|
183
|
-
// handles ZodUnion/ZodLiteral but not ZodEnum (a bare `--base radix` would otherwise mis-parse to
|
|
184
|
-
// `base: true`). Both still render as an interactive `select`.
|
|
185
|
-
base: z.union([z.literal("radix"), z.literal("base")]).describe("shadcn component library base (radix-ui or @base-ui)").default("radix"),
|
|
186
|
-
preset: z.string().describe("shadcn preset: a style name (nova, vega, maia, lyra, mira, luma, sera, rhea) or a code from ui.shadcn.com").default(DEFAULT_PRESET),
|
|
187
|
-
iconLibrary: z.union([z.literal("lucide"), z.literal("hugeicons"), z.literal("radix"), z.literal("tabler")]).describe("Icon library").default("hugeicons"),
|
|
188
|
-
cssVariables: z.boolean().describe("Use CSS variables for theming").default(true),
|
|
189
|
-
rtl: z.boolean().describe("Enable RTL support").default(false),
|
|
190
|
-
pointer: z.boolean().describe("Use pointer cursor on interactive elements").default(false),
|
|
191
|
-
components: z.string().describe("Comma-separated shadcn components to pre-install, e.g. button,card,dialog").default("button,badge"),
|
|
192
|
-
install: z.boolean().describe("Install deps and apply the shadcn theme after scaffolding").default(true)
|
|
193
|
-
},
|
|
194
|
-
// Lazily default the package scope to `@<name>` (prefixing `@` unless the name already has one),
|
|
195
|
-
// so it tracks the project name instead of a fixed value. Falls back to `@app` when no name is set.
|
|
196
|
-
prepare({ options }) {
|
|
197
|
-
return {
|
|
198
|
-
scope: () => options.name ? toScope(options.name) : "@app"
|
|
199
|
-
};
|
|
315
|
+
name: pkgName,
|
|
316
|
+
description: pkgDescription
|
|
200
317
|
},
|
|
318
|
+
options: [
|
|
319
|
+
// Default the name to the target directory the user chose, so they don't retype it.
|
|
320
|
+
{ key: "name", type: "string", message: "Root project / package name", default: ({ directory }) => dirName(directory), validate: validateName },
|
|
321
|
+
// Lazily default the package scope to `@<name>` (prefixing `@` unless the name already has one),
|
|
322
|
+
// so it tracks the project name instead of a fixed value. Falls back to `@app` when no name is set.
|
|
323
|
+
{ key: "scope", type: "string", message: "npm scope for workspace packages, e.g. @acme (defaults to @<name>)", default: ({ options }) => options.name ? toScope(options.name) : "@app", validate: validateScope },
|
|
324
|
+
{ key: "base", type: "select", message: "shadcn component library base (radix-ui or @base-ui)", choices: ["radix", "base"], default: "radix" },
|
|
325
|
+
{ key: "preset", type: "string", message: "shadcn preset: a style name (nova, vega, maia, lyra, mira, luma, sera, rhea) or a code from ui.shadcn.com", default: DEFAULT_PRESET, validate: validatePreset },
|
|
326
|
+
{ key: "iconLibrary", type: "select", message: "Icon library", choices: ["lucide", "hugeicons", "radix", "tabler"], default: "hugeicons" },
|
|
327
|
+
{ key: "cssVariables", type: "boolean", message: "Use CSS variables for theming", default: true },
|
|
328
|
+
{ key: "rtl", type: "boolean", message: "Enable RTL support", default: false },
|
|
329
|
+
{ key: "pointer", type: "boolean", message: "Use pointer cursor on interactive elements", default: false },
|
|
330
|
+
{ key: "components", type: "string", message: "Comma-separated shadcn components to pre-install, e.g. button,card,dialog", default: "button,badge", validate: validateComponents },
|
|
331
|
+
{ key: "install", type: "boolean", message: "Install deps and apply the shadcn theme after scaffolding", default: true }
|
|
332
|
+
],
|
|
201
333
|
async produce({ options }) {
|
|
202
334
|
const scope = toScope(options.scope || options.name || "app");
|
|
203
335
|
const files = readTree(
|
|
@@ -217,11 +349,11 @@ var template_default = createTemplate({
|
|
|
217
349
|
}
|
|
218
350
|
const adds = withRequiredComponents(options.components);
|
|
219
351
|
const ui = `${scope}/ui`;
|
|
220
|
-
const
|
|
352
|
+
const initArgs = shadcnInitArgs(options);
|
|
221
353
|
const scripts = options.install ? [
|
|
222
|
-
{ commands: ["pnpm install --silent"], phase: 0 },
|
|
223
|
-
{ commands: [
|
|
224
|
-
{ commands: [
|
|
354
|
+
{ commands: [["pnpm", "install", "--silent"]], phase: 0 },
|
|
355
|
+
{ commands: [["pnpm", "--filter", ui, "exec", "shadcn", "init", ...initArgs]], phase: 1 },
|
|
356
|
+
{ commands: [["pnpm", "--filter", ui, "exec", "shadcn", "add", ...adds, "-y"]], phase: 2 }
|
|
225
357
|
] : [];
|
|
226
358
|
return {
|
|
227
359
|
files,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pauldvlp/vp-react-ts-shadcn",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Vite+ monorepo template: one frontend app (website) + a shared shadcn UI package, themeable via shadcn presets.",
|
|
5
5
|
"author": "pauldvlp (https://github.com/pauldvlp/vp-templates)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,8 +24,7 @@
|
|
|
24
24
|
"template"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"
|
|
28
|
-
"zod": "^3.25.76"
|
|
27
|
+
"@clack/prompts": "^1.6.0"
|
|
29
28
|
},
|
|
30
29
|
"devDependencies": {
|
|
31
30
|
"@types/node": "^24",
|