@pauldvlp/vp-pkg-shadcn 0.5.0 → 0.5.2

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/dist/index.js CHANGED
@@ -11,7 +11,8 @@ import * as prompts from "@clack/prompts";
11
11
  var RENAME = {
12
12
  _gitignore: ".gitignore",
13
13
  _dockerignore: ".dockerignore",
14
- "_env.example": ".env.example"
14
+ "_env.example": ".env.example",
15
+ "_package.json": "package.json"
15
16
  };
16
17
  var DEFAULT_PRESET = "b30557okNu";
17
18
  var ICON_LIBS = {
@@ -101,6 +102,16 @@ function addIconDeps(pkg, iconLibrary) {
101
102
  const iconDeps = ICON_LIBS[iconLibrary] ?? ICON_LIBS.hugeicons;
102
103
  pkg.dependencies = { ...pkg.dependencies, ...iconDeps };
103
104
  }
105
+ function validateScope(value) {
106
+ return /^@?[A-Za-z0-9][A-Za-z0-9._/-]*$/.test(value.trim()) ? void 0 : "must be a package scope/name, e.g. @acme";
107
+ }
108
+ function validatePreset(value) {
109
+ return /^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(value.trim()) ? void 0 : "must be a style name or ui.shadcn.com code";
110
+ }
111
+ function validateComponents(value) {
112
+ const parts = value.split(",").map((c) => c.trim()).filter(Boolean);
113
+ return parts.every((c) => /^[a-z0-9][a-z0-9-]*$/.test(c)) ? void 0 : "comma-separated component names, e.g. button,card,dialog";
114
+ }
104
115
  function withRequiredComponents(csv, required = ["button", "badge"]) {
105
116
  const requested = csv.split(",").map((c) => c.trim()).filter(Boolean);
106
117
  return Array.from(/* @__PURE__ */ new Set([...required, ...requested]));
@@ -125,30 +136,36 @@ function uiComponentsJson(theme) {
125
136
  }
126
137
  };
127
138
  }
128
- function shadcnInitFlags(opts) {
139
+ function shadcnInitArgs(opts) {
129
140
  return [
130
- `--base ${opts.base}`,
131
- `--preset ${opts.preset}`,
141
+ "--base",
142
+ opts.base,
143
+ "--preset",
144
+ opts.preset,
132
145
  opts.cssVariables ? "--css-variables" : "--no-css-variables",
133
146
  opts.rtl ? "--rtl" : "--no-rtl",
134
147
  opts.pointer ? "--pointer" : "--no-pointer",
135
148
  "--no-reinstall",
136
149
  "-y",
137
150
  "-f"
138
- ].join(" ");
151
+ ];
139
152
  }
140
153
  function defineTemplate(config) {
141
154
  return config;
142
155
  }
143
- function writeTree(node, dir) {
156
+ function writeTree(node, dir, root = path.resolve(dir)) {
144
157
  for (const [name, value] of Object.entries(node)) {
145
- const p = path.join(dir, name);
158
+ const p = path.resolve(dir, name);
159
+ const rel = path.relative(root, p);
160
+ if (rel === "" || rel === ".." || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) {
161
+ throw new Error(`Refusing to write outside the target directory: ${name}`);
162
+ }
146
163
  if (typeof value === "string") {
147
164
  fs.mkdirSync(path.dirname(p), { recursive: true });
148
165
  fs.writeFileSync(p, value);
149
166
  } else {
150
167
  fs.mkdirSync(p, { recursive: true });
151
- writeTree(value, p);
168
+ writeTree(value, p, root);
152
169
  }
153
170
  }
154
171
  }
@@ -288,8 +305,9 @@ async function runTemplateCLI(template) {
288
305
  writeTree(creation.files, directory);
289
306
  if (creation.scripts?.length) {
290
307
  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 });
308
+ for (const [program, ...commandArgs] of step.commands) {
309
+ if (!program) continue;
310
+ execFileSync(program, commandArgs, { cwd: directory, stdio: "inherit" });
293
311
  }
294
312
  }
295
313
  }
@@ -302,75 +320,31 @@ async function runTemplateCLI(template) {
302
320
  }
303
321
 
304
322
  // src/template.ts
323
+ import { readFileSync } from "node:fs";
305
324
  import path2 from "node:path";
306
325
  import { fileURLToPath } from "node:url";
307
-
308
- // package.json
309
- var package_default = {
310
- name: "@pauldvlp/vp-pkg-shadcn",
311
- version: "0.5.0",
312
- description: "Add a shared, fully customizable shadcn UI package (packages/ui) into an existing Vite+ monorepo, themeable via shadcn presets.",
313
- author: "pauldvlp (https://github.com/pauldvlp/vp-templates)",
314
- license: "MIT",
315
- homepage: "https://github.com/pauldvlp/vp-templates",
316
- repository: {
317
- type: "git",
318
- url: "git+https://github.com/pauldvlp/vp-templates.git",
319
- directory: "packages/vp-pkg-shadcn"
320
- },
321
- bugs: "https://github.com/pauldvlp/vp-templates/issues",
322
- keywords: [
323
- "vite-plus-generator",
324
- "vite-plus",
325
- "shadcn",
326
- "react",
327
- "template"
328
- ],
329
- bin: "./dist/index.js",
330
- type: "module",
331
- files: [
332
- "dist",
333
- "template"
334
- ],
335
- scripts: {
336
- build: "esbuild bin/index.ts --bundle --outfile=dist/index.js --format=esm --platform=node --target=node22 --packages=external",
337
- dev: "node bin/index.ts",
338
- prepack: "pnpm run build"
339
- },
340
- dependencies: {
341
- "@clack/prompts": "^1.6.0"
342
- },
343
- devDependencies: {
344
- "@pauldvlp/template-kit": "workspace:*",
345
- "@types/node": "^24",
346
- esbuild: "^0.25.0",
347
- typescript: "^5"
348
- },
349
- engines: {
350
- node: ">=22.18.0"
351
- }
352
- };
353
-
354
- // src/template.ts
355
326
  var TEMPLATE_DIR = path2.join(path2.dirname(fileURLToPath(import.meta.url)), "..", "template");
327
+ var { name: pkgName, description: pkgDescription } = JSON.parse(
328
+ readFileSync(new URL("../package.json", import.meta.url), "utf8")
329
+ );
356
330
  var template_default = defineTemplate({
357
331
  about: {
358
- name: package_default.name,
359
- description: package_default.description
332
+ name: pkgName,
333
+ description: pkgDescription
360
334
  },
361
335
  // This generator drops a package into an *existing* repo, so it must never create a git repo of its own.
362
336
  git: false,
363
337
  options: [
364
338
  // Default the scope to the surrounding monorepo's scope (e.g. `@acme`) so it tracks the repo
365
339
  // 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" },
340
+ { key: "scope", type: "string", message: "npm scope for the workspace package, e.g. @acme", default: () => detectMonorepoScope() ?? "@app", validate: validateScope },
367
341
  { 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 },
342
+ { 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 },
369
343
  { key: "iconLibrary", type: "select", message: "Icon library", choices: ["lucide", "hugeicons", "radix", "tabler"], default: "hugeicons" },
370
344
  { key: "cssVariables", type: "boolean", message: "Use CSS variables for theming", default: true },
371
345
  { key: "rtl", type: "boolean", message: "Enable RTL support", default: false },
372
346
  { 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" },
347
+ { key: "components", type: "string", message: "Comma-separated shadcn components to pre-install, e.g. button,card,dialog", default: "button,badge", validate: validateComponents },
374
348
  { key: "install", type: "boolean", message: "Install deps and apply the shadcn theme after scaffolding", default: true }
375
349
  ],
376
350
  async produce({ options }) {
@@ -384,11 +358,11 @@ var template_default = defineTemplate({
384
358
  `);
385
359
  const adds = withRequiredComponents(options.components);
386
360
  const ui = `${scope}/ui`;
387
- const initFlags = shadcnInitFlags(options);
361
+ const initArgs = shadcnInitArgs(options);
388
362
  const scripts = options.install ? [
389
- { commands: ["pnpm install --silent"], phase: 0 },
390
- { commands: [`pnpm --filter ${ui} exec shadcn init ${initFlags}`], phase: 1 },
391
- { commands: [`pnpm --filter ${ui} exec shadcn add ${adds.join(" ")} -y`], phase: 2 }
363
+ { commands: [["pnpm", "install", "--silent"]], phase: 0 },
364
+ { commands: [["pnpm", "--filter", ui, "exec", "shadcn", "init", ...initArgs]], phase: 1 },
365
+ { commands: [["pnpm", "--filter", ui, "exec", "shadcn", "add", ...adds, "-y"]], phase: 2 }
392
366
  ] : [];
393
367
  return {
394
368
  files,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pauldvlp/vp-pkg-shadcn",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
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",
File without changes