@pauldvlp/vp-react-ts-shadcn 0.5.0 → 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.
Files changed (2) hide show
  1. package/dist/index.js +43 -67
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -63,6 +63,19 @@ function addIconDeps(pkg, iconLibrary) {
63
63
  const iconDeps = ICON_LIBS[iconLibrary] ?? ICON_LIBS.hugeicons;
64
64
  pkg.dependencies = { ...pkg.dependencies, ...iconDeps };
65
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
+ }
66
79
  function withRequiredComponents(csv, required = ["button", "badge"]) {
67
80
  const requested = csv.split(",").map((c) => c.trim()).filter(Boolean);
68
81
  return Array.from(/* @__PURE__ */ new Set([...required, ...requested]));
@@ -106,30 +119,36 @@ function appComponentsJson(theme) {
106
119
  }
107
120
  };
108
121
  }
109
- function shadcnInitFlags(opts) {
122
+ function shadcnInitArgs(opts) {
110
123
  return [
111
- `--base ${opts.base}`,
112
- `--preset ${opts.preset}`,
124
+ "--base",
125
+ opts.base,
126
+ "--preset",
127
+ opts.preset,
113
128
  opts.cssVariables ? "--css-variables" : "--no-css-variables",
114
129
  opts.rtl ? "--rtl" : "--no-rtl",
115
130
  opts.pointer ? "--pointer" : "--no-pointer",
116
131
  "--no-reinstall",
117
132
  "-y",
118
133
  "-f"
119
- ].join(" ");
134
+ ];
120
135
  }
121
136
  function defineTemplate(config) {
122
137
  return config;
123
138
  }
124
- function writeTree(node, dir) {
139
+ function writeTree(node, dir, root = path.resolve(dir)) {
125
140
  for (const [name, value] of Object.entries(node)) {
126
- const p = path.join(dir, name);
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
+ }
127
146
  if (typeof value === "string") {
128
147
  fs.mkdirSync(path.dirname(p), { recursive: true });
129
148
  fs.writeFileSync(p, value);
130
149
  } else {
131
150
  fs.mkdirSync(p, { recursive: true });
132
- writeTree(value, p);
151
+ writeTree(value, p, root);
133
152
  }
134
153
  }
135
154
  }
@@ -269,8 +288,9 @@ async function runTemplateCLI(template) {
269
288
  writeTree(creation.files, directory);
270
289
  if (creation.scripts?.length) {
271
290
  for (const step of [...creation.scripts].sort((a, b) => a.phase - b.phase)) {
272
- for (const command of step.commands) {
273
- execFileSync(command, { cwd: directory, stdio: "inherit", shell: true });
291
+ for (const [program, ...commandArgs] of step.commands) {
292
+ if (!program) continue;
293
+ execFileSync(program, commandArgs, { cwd: directory, stdio: "inherit" });
274
294
  }
275
295
  }
276
296
  }
@@ -283,75 +303,31 @@ async function runTemplateCLI(template) {
283
303
  }
284
304
 
285
305
  // src/template.ts
306
+ import { readFileSync } from "node:fs";
286
307
  import path2 from "node:path";
287
308
  import { fileURLToPath } from "node:url";
288
-
289
- // package.json
290
- var package_default = {
291
- name: "@pauldvlp/vp-react-ts-shadcn",
292
- version: "0.5.0",
293
- description: "Vite+ monorepo template: one frontend app (website) + a shared shadcn UI package, themeable via shadcn presets.",
294
- author: "pauldvlp (https://github.com/pauldvlp/vp-templates)",
295
- license: "MIT",
296
- homepage: "https://github.com/pauldvlp/vp-templates",
297
- repository: {
298
- type: "git",
299
- url: "git+https://github.com/pauldvlp/vp-templates.git",
300
- directory: "packages/vp-react-ts-shadcn"
301
- },
302
- bugs: "https://github.com/pauldvlp/vp-templates/issues",
303
- keywords: [
304
- "vite-plus-generator",
305
- "vite-plus",
306
- "shadcn",
307
- "react",
308
- "template"
309
- ],
310
- bin: "./dist/index.js",
311
- type: "module",
312
- files: [
313
- "dist",
314
- "template"
315
- ],
316
- scripts: {
317
- build: "esbuild bin/index.ts --bundle --outfile=dist/index.js --format=esm --platform=node --target=node22 --packages=external",
318
- dev: "node bin/index.ts",
319
- prepack: "pnpm run build"
320
- },
321
- dependencies: {
322
- "@clack/prompts": "^1.6.0"
323
- },
324
- devDependencies: {
325
- "@pauldvlp/template-kit": "workspace:*",
326
- "@types/node": "^24",
327
- esbuild: "^0.25.0",
328
- typescript: "^5"
329
- },
330
- engines: {
331
- node: ">=22.18.0"
332
- }
333
- };
334
-
335
- // src/template.ts
336
309
  var TEMPLATE_DIR = path2.join(path2.dirname(fileURLToPath(import.meta.url)), "..", "template");
310
+ var { name: pkgName, description: pkgDescription } = JSON.parse(
311
+ readFileSync(new URL("../package.json", import.meta.url), "utf8")
312
+ );
337
313
  var template_default = defineTemplate({
338
314
  about: {
339
- name: package_default.name,
340
- description: package_default.description
315
+ name: pkgName,
316
+ description: pkgDescription
341
317
  },
342
318
  options: [
343
319
  // Default the name to the target directory the user chose, so they don't retype it.
344
- { key: "name", type: "string", message: "Root project / package name", default: ({ directory }) => dirName(directory) },
320
+ { key: "name", type: "string", message: "Root project / package name", default: ({ directory }) => dirName(directory), validate: validateName },
345
321
  // Lazily default the package scope to `@<name>` (prefixing `@` unless the name already has one),
346
322
  // so it tracks the project name instead of a fixed value. Falls back to `@app` when no name is set.
347
- { key: "scope", type: "string", message: "npm scope for workspace packages, e.g. @acme (defaults to @<name>)", default: ({ options }) => options.name ? toScope(options.name) : "@app" },
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 },
348
324
  { key: "base", type: "select", message: "shadcn component library base (radix-ui or @base-ui)", choices: ["radix", "base"], default: "radix" },
349
- { 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 },
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 },
350
326
  { key: "iconLibrary", type: "select", message: "Icon library", choices: ["lucide", "hugeicons", "radix", "tabler"], default: "hugeicons" },
351
327
  { key: "cssVariables", type: "boolean", message: "Use CSS variables for theming", default: true },
352
328
  { key: "rtl", type: "boolean", message: "Enable RTL support", default: false },
353
329
  { key: "pointer", type: "boolean", message: "Use pointer cursor on interactive elements", default: false },
354
- { key: "components", type: "string", message: "Comma-separated shadcn components to pre-install, e.g. button,card,dialog", default: "button,badge" },
330
+ { key: "components", type: "string", message: "Comma-separated shadcn components to pre-install, e.g. button,card,dialog", default: "button,badge", validate: validateComponents },
355
331
  { key: "install", type: "boolean", message: "Install deps and apply the shadcn theme after scaffolding", default: true }
356
332
  ],
357
333
  async produce({ options }) {
@@ -373,11 +349,11 @@ var template_default = defineTemplate({
373
349
  }
374
350
  const adds = withRequiredComponents(options.components);
375
351
  const ui = `${scope}/ui`;
376
- const initFlags = shadcnInitFlags(options);
352
+ const initArgs = shadcnInitArgs(options);
377
353
  const scripts = options.install ? [
378
- { commands: ["pnpm install --silent"], phase: 0 },
379
- { commands: [`pnpm --filter ${ui} exec shadcn init ${initFlags}`], phase: 1 },
380
- { commands: [`pnpm --filter ${ui} exec shadcn add ${adds.join(" ")} -y`], phase: 2 }
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 }
381
357
  ] : [];
382
358
  return {
383
359
  files,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pauldvlp/vp-react-ts-shadcn",
3
- "version": "0.5.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",