@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.
- package/README.md +7 -8
- package/dist/index.js +190 -43
- 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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
>
|
|
31
|
-
>
|
|
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
|
|
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
|
|
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
|
-
|
|
140
|
-
|
|
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.
|
|
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": "^
|
|
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 =
|
|
356
|
+
var template_default = defineTemplate({
|
|
199
357
|
about: {
|
|
200
358
|
name: package_default.name,
|
|
201
359
|
description: package_default.description
|
|
202
360
|
},
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
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
|
|
289
|
-
if (
|
|
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.
|
|
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": "^
|
|
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",
|