@pauldvlp/vp-pkg-shadcn 0.4.0 → 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 pauldvlp
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 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,22 +137,181 @@ 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.0",
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)",
314
+ license: "MIT",
154
315
  homepage: "https://github.com/pauldvlp/vp-templates",
155
316
  repository: {
156
317
  type: "git",
@@ -177,9 +338,7 @@ var package_default = {
177
338
  prepack: "pnpm run build"
178
339
  },
179
340
  dependencies: {
180
- "@clack/prompts": "^0.11.0",
181
- bingo: "^0.9.3",
182
- zod: "^3.25.76"
341
+ "@clack/prompts": "^1.6.0"
183
342
  },
184
343
  devDependencies: {
185
344
  "@pauldvlp/template-kit": "workspace:*",
@@ -194,33 +353,26 @@ var package_default = {
194
353
 
195
354
  // src/template.ts
196
355
  var TEMPLATE_DIR = path2.join(path2.dirname(fileURLToPath(import.meta.url)), "..", "template");
197
- var template_default = createTemplate({
356
+ var template_default = defineTemplate({
198
357
  about: {
199
358
  name: package_default.name,
200
359
  description: package_default.description
201
360
  },
202
- options: {
203
- scope: z.string().describe("npm scope for the workspace package, e.g. @acme").default("@app"),
204
- // Unions of literals (not z.enum) so the value is settable on Bingo's CLI: bingo's arg parser
205
- // handles ZodUnion/ZodLiteral but not ZodEnum (a bare `--base radix` would otherwise mis-parse to
206
- // `base: true`). Both still render as an interactive `select`.
207
- base: z.union([z.literal("radix"), z.literal("base")]).describe("shadcn component library base (radix-ui or @base-ui)").default("radix"),
208
- 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),
209
- iconLibrary: z.union([z.literal("lucide"), z.literal("hugeicons"), z.literal("radix"), z.literal("tabler")]).describe("Icon library").default("hugeicons"),
210
- cssVariables: z.boolean().describe("Use CSS variables for theming").default(true),
211
- rtl: z.boolean().describe("Enable RTL support").default(false),
212
- pointer: z.boolean().describe("Use pointer cursor on interactive elements").default(false),
213
- components: z.string().describe("Comma-separated shadcn components to pre-install, e.g. button,card,dialog").default("button,badge"),
214
- install: z.boolean().describe("Install deps and apply the shadcn theme after scaffolding").default(true)
215
- },
216
- // Default the scope to the surrounding monorepo's scope (e.g. `@acme`) so it tracks the repo
217
- // instead of a fixed `@app`. Falls back to `@app` when run outside a pnpm workspace.
218
- prepare() {
219
- const monorepoScope = detectMonorepoScope();
220
- return {
221
- scope: () => monorepoScope ?? "@app"
222
- };
223
- },
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
+ ],
224
376
  async produce({ options }) {
225
377
  const scope = toScope(options.scope || "app");
226
378
  const files = readTree(TEMPLATE_DIR, (_rel, content) => content.split("@app").join(scope));
@@ -264,13 +416,9 @@ if (!getArg(argv, "directory") && !isHelp) {
264
416
  var status = await runTemplateCLI(template_default);
265
417
  process.exitCode = status;
266
418
  var finalArgv = process.argv.slice(2);
267
- var targetDir = getArg(finalArgv, "directory");
268
- if (!isHelp && targetDir && targetDir !== "." && path3.resolve(targetDir) !== process.cwd()) {
269
- fs2.rmSync(path3.join(targetDir, ".git"), { recursive: true, force: true });
270
- }
271
419
  if (!isHelp && (status === 0 || status === void 0)) {
272
420
  const ui = `${toScope(getArg(finalArgv, "scope") || detectMonorepoScope() || "app")}/ui`;
273
- prompts.note(
421
+ prompts2.note(
274
422
  [
275
423
  `1. Workspace glob the dir you chose (e.g. packages/*) so ${ui} is linked`,
276
424
  `2. Depend on it ${'"' + ui + '": "workspace:*"'} in your app's package.json`,
@@ -284,7 +432,7 @@ if (!isHelp && (status === 0 || status === void 0)) {
284
432
  `Wire ${ui} into your app`
285
433
  );
286
434
  if (isInteractive) {
287
- const ack = await prompts.confirm({ message: "Got it \u2014 continue?", initialValue: true });
288
- 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.");
289
437
  }
290
438
  }
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@pauldvlp/vp-pkg-shadcn",
3
- "version": "0.4.0",
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
+ "license": "MIT",
6
7
  "homepage": "https://github.com/pauldvlp/vp-templates",
7
8
  "repository": {
8
9
  "type": "git",
@@ -23,9 +24,7 @@
23
24
  "template"
24
25
  ],
25
26
  "dependencies": {
26
- "@clack/prompts": "^0.11.0",
27
- "bingo": "^0.9.3",
28
- "zod": "^3.25.76"
27
+ "@clack/prompts": "^1.6.0"
29
28
  },
30
29
  "devDependencies": {
31
30
  "@types/node": "^24",