@pauldvlp/vp-pkg-shadcn 0.4.1 → 0.5.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.
Files changed (3) hide show
  1. package/README.md +7 -8
  2. package/dist/index.js +190 -43
  3. package/package.json +2 -4
package/README.md CHANGED
@@ -9,10 +9,10 @@ A [Vite+](https://viteplus.dev) **package generator** that drops a shared, fully
9
9
 
10
10
  - `packages/ui` — a shared shadcn design system (Tailwind v4, fonts, `lib/utils`, `components/ui/*`)
11
11
 
12
- It's a [Bingo](https://create.bingo) template, so options can be passed on the `vp create` command
13
- line (anything after `--`) and the **shadcn theme is materialized at create time** from the preset
14
- you choose. Unlike [`@pauldvlp/vp-react-ts-shadcn`](../vp-react-ts-shadcn) (which scaffolds a whole
15
- monorepo), this only emits `packages/ui` and leaves the rest of your repo untouched.
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. Unlike
14
+ [`@pauldvlp/vp-react-ts-shadcn`](../vp-react-ts-shadcn) (which scaffolds a whole monorepo), this only
15
+ emits `packages/ui` and leaves the rest of your repo untouched.
16
16
 
17
17
  ## Usage
18
18
 
@@ -27,9 +27,8 @@ vp create @pauldvlp:vp-pkg-shadcn -- \
27
27
  --scope @acme --base base --preset vega --iconLibrary lucide --components button,card,dialog
28
28
  ```
29
29
 
30
- > Only **string/enum** options parse reliably as `vp create -- --flag value`. **Boolean** options
31
- > (`--cssVariables`, `--rtl`, `--pointer`, `--install`) are best left to the interactive prompt
32
- > Bingo's CLI does not accept `--no-x` / `--x=false` cleanly. Omit them and answer the prompt.
30
+ > **Boolean** options (`--cssVariables`, `--rtl`, `--pointer`, `--install`) accept both forms: pass
31
+ > `--rtl` to enable or `--no-rtl` to disable. Omit any option to answer it at the interactive prompt.
33
32
 
34
33
  ### Requirements
35
34
 
@@ -40,7 +39,7 @@ generator does **not** edit your `pnpm-workspace.yaml` or any app — wiring a c
40
39
 
41
40
  ### Where it lands (the `--directory`)
42
41
 
43
- The generator emits the package contents at the **root** of its file tree, and Bingo writes them under
42
+ The generator emits the package contents at the **root** of its file tree, and the runtime writes them under
44
43
  the target directory — which **defaults to `ui`** (so within `vp create`'s monorepo flow it lands at
45
44
  `<chosen-parent>/ui`, e.g. `packages/ui`). Pass `--directory <path>` to place it elsewhere. `--scope`
46
45
  defaults to the **surrounding monorepo's scope** (read from the nearest `pnpm-workspace.yaml`'s
package/dist/index.js CHANGED
@@ -1,15 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // bin/index.ts
4
- import fs2 from "node:fs";
5
- import path3 from "node:path";
6
- import * as prompts from "@clack/prompts";
4
+ import * as prompts2 from "@clack/prompts";
7
5
 
8
6
  // ../template-kit/src/index.ts
7
+ import { execFileSync } from "node:child_process";
9
8
  import fs from "node:fs";
10
9
  import path from "node:path";
10
+ import * as prompts from "@clack/prompts";
11
11
  var RENAME = {
12
- _gitignore: ".gitignore"
12
+ _gitignore: ".gitignore",
13
+ _dockerignore: ".dockerignore",
14
+ "_env.example": ".env.example"
13
15
  };
14
16
  var DEFAULT_PRESET = "b30557okNu";
15
17
  var ICON_LIBS = {
@@ -135,20 +137,178 @@ function shadcnInitFlags(opts) {
135
137
  "-f"
136
138
  ].join(" ");
137
139
  }
138
-
139
- // bin/index.ts
140
- import { runTemplateCLI } from "bingo";
140
+ function defineTemplate(config) {
141
+ return config;
142
+ }
143
+ function writeTree(node, dir) {
144
+ for (const [name, value] of Object.entries(node)) {
145
+ const p = path.join(dir, name);
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);
152
+ }
153
+ }
154
+ }
155
+ function parseArgv(argv2, 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 < argv2.length; i++) {
160
+ const arg = argv2[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 ?? argv2[++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 ?? argv2[++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 argv2 = process.argv.slice(2);
241
+ if (argv2.includes("--help") || argv2.includes("-h")) {
242
+ printHelp(template);
243
+ return 0;
244
+ }
245
+ const interactive = Boolean(process.stdout.isTTY) && !argv2.includes("--no-interactive");
246
+ const { values, directory: dirArg } = parseArgv(argv2, 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 command of step.commands) {
292
+ execFileSync(command, { cwd: directory, stdio: "inherit", shell: true });
293
+ }
294
+ }
295
+ }
296
+ if (template.git !== false) initGit(directory);
297
+ if (creation.suggestions?.length) {
298
+ prompts.note(creation.suggestions.join("\n"), "Next steps");
299
+ }
300
+ prompts.outro("Done!");
301
+ return 0;
302
+ }
141
303
 
142
304
  // src/template.ts
143
305
  import path2 from "node:path";
144
306
  import { fileURLToPath } from "node:url";
145
- import { createTemplate } from "bingo";
146
- import { z } from "zod";
147
307
 
148
308
  // package.json
149
309
  var package_default = {
150
310
  name: "@pauldvlp/vp-pkg-shadcn",
151
- version: "0.4.1",
311
+ version: "0.5.0",
152
312
  description: "Add a shared, fully customizable shadcn UI package (packages/ui) into an existing Vite+ monorepo, themeable via shadcn presets.",
153
313
  author: "pauldvlp (https://github.com/pauldvlp/vp-templates)",
154
314
  license: "MIT",
@@ -178,9 +338,7 @@ var package_default = {
178
338
  prepack: "pnpm run build"
179
339
  },
180
340
  dependencies: {
181
- "@clack/prompts": "^0.11.0",
182
- bingo: "^0.9.3",
183
- zod: "^3.25.76"
341
+ "@clack/prompts": "^1.6.0"
184
342
  },
185
343
  devDependencies: {
186
344
  "@pauldvlp/template-kit": "workspace:*",
@@ -195,33 +353,26 @@ var package_default = {
195
353
 
196
354
  // src/template.ts
197
355
  var TEMPLATE_DIR = path2.join(path2.dirname(fileURLToPath(import.meta.url)), "..", "template");
198
- var template_default = createTemplate({
356
+ var template_default = defineTemplate({
199
357
  about: {
200
358
  name: package_default.name,
201
359
  description: package_default.description
202
360
  },
203
- options: {
204
- scope: z.string().describe("npm scope for the workspace package, e.g. @acme").default("@app"),
205
- // Unions of literals (not z.enum) so the value is settable on Bingo's CLI: bingo's arg parser
206
- // handles ZodUnion/ZodLiteral but not ZodEnum (a bare `--base radix` would otherwise mis-parse to
207
- // `base: true`). Both still render as an interactive `select`.
208
- base: z.union([z.literal("radix"), z.literal("base")]).describe("shadcn component library base (radix-ui or @base-ui)").default("radix"),
209
- 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),
210
- iconLibrary: z.union([z.literal("lucide"), z.literal("hugeicons"), z.literal("radix"), z.literal("tabler")]).describe("Icon library").default("hugeicons"),
211
- cssVariables: z.boolean().describe("Use CSS variables for theming").default(true),
212
- rtl: z.boolean().describe("Enable RTL support").default(false),
213
- pointer: z.boolean().describe("Use pointer cursor on interactive elements").default(false),
214
- components: z.string().describe("Comma-separated shadcn components to pre-install, e.g. button,card,dialog").default("button,badge"),
215
- install: z.boolean().describe("Install deps and apply the shadcn theme after scaffolding").default(true)
216
- },
217
- // Default the scope to the surrounding monorepo's scope (e.g. `@acme`) so it tracks the repo
218
- // instead of a fixed `@app`. Falls back to `@app` when run outside a pnpm workspace.
219
- prepare() {
220
- const monorepoScope = detectMonorepoScope();
221
- return {
222
- scope: () => monorepoScope ?? "@app"
223
- };
224
- },
361
+ // This generator drops a package into an *existing* repo, so it must never create a git repo of its own.
362
+ git: false,
363
+ options: [
364
+ // Default the scope to the surrounding monorepo's scope (e.g. `@acme`) so it tracks the repo
365
+ // instead of a fixed `@app`. Falls back to `@app` when run outside a pnpm workspace.
366
+ { key: "scope", type: "string", message: "npm scope for the workspace package, e.g. @acme", default: () => detectMonorepoScope() ?? "@app" },
367
+ { key: "base", type: "select", message: "shadcn component library base (radix-ui or @base-ui)", choices: ["radix", "base"], default: "radix" },
368
+ { 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 },
369
+ { key: "iconLibrary", type: "select", message: "Icon library", choices: ["lucide", "hugeicons", "radix", "tabler"], default: "hugeicons" },
370
+ { key: "cssVariables", type: "boolean", message: "Use CSS variables for theming", default: true },
371
+ { key: "rtl", type: "boolean", message: "Enable RTL support", default: false },
372
+ { key: "pointer", type: "boolean", message: "Use pointer cursor on interactive elements", default: false },
373
+ { key: "components", type: "string", message: "Comma-separated shadcn components to pre-install, e.g. button,card,dialog", default: "button,badge" },
374
+ { key: "install", type: "boolean", message: "Install deps and apply the shadcn theme after scaffolding", default: true }
375
+ ],
225
376
  async produce({ options }) {
226
377
  const scope = toScope(options.scope || "app");
227
378
  const files = readTree(TEMPLATE_DIR, (_rel, content) => content.split("@app").join(scope));
@@ -265,13 +416,9 @@ if (!getArg(argv, "directory") && !isHelp) {
265
416
  var status = await runTemplateCLI(template_default);
266
417
  process.exitCode = status;
267
418
  var finalArgv = process.argv.slice(2);
268
- var targetDir = getArg(finalArgv, "directory");
269
- if (!isHelp && targetDir && targetDir !== "." && path3.resolve(targetDir) !== process.cwd()) {
270
- fs2.rmSync(path3.join(targetDir, ".git"), { recursive: true, force: true });
271
- }
272
419
  if (!isHelp && (status === 0 || status === void 0)) {
273
420
  const ui = `${toScope(getArg(finalArgv, "scope") || detectMonorepoScope() || "app")}/ui`;
274
- prompts.note(
421
+ prompts2.note(
275
422
  [
276
423
  `1. Workspace glob the dir you chose (e.g. packages/*) so ${ui} is linked`,
277
424
  `2. Depend on it ${'"' + ui + '": "workspace:*"'} in your app's package.json`,
@@ -285,7 +432,7 @@ if (!isHelp && (status === 0 || status === void 0)) {
285
432
  `Wire ${ui} into your app`
286
433
  );
287
434
  if (isInteractive) {
288
- const ack = await prompts.confirm({ message: "Got it \u2014 continue?", initialValue: true });
289
- if (prompts.isCancel(ack)) prompts.cancel("Okay \u2014 see the docs link above when you\u2019re ready.");
435
+ const ack = await prompts2.confirm({ message: "Got it \u2014 continue?", initialValue: true });
436
+ if (prompts2.isCancel(ack)) prompts2.cancel("Okay \u2014 see the docs link above when you\u2019re ready.");
290
437
  }
291
438
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pauldvlp/vp-pkg-shadcn",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Add a shared, fully customizable shadcn UI package (packages/ui) into an existing Vite+ monorepo, themeable via shadcn presets.",
5
5
  "author": "pauldvlp (https://github.com/pauldvlp/vp-templates)",
6
6
  "license": "MIT",
@@ -24,9 +24,7 @@
24
24
  "template"
25
25
  ],
26
26
  "dependencies": {
27
- "@clack/prompts": "^0.11.0",
28
- "bingo": "^0.9.3",
29
- "zod": "^3.25.76"
27
+ "@clack/prompts": "^1.6.0"
30
28
  },
31
29
  "devDependencies": {
32
30
  "@types/node": "^24",