@pauldvlp/vp-react-ts-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 +21 -0
- package/README.md +4 -6
- package/dist/index.js +193 -36
- package/package.json +3 -3
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,9 +9,8 @@ A [Vite+](https://viteplus.dev) **monorepo generator** that scaffolds a minimal
|
|
|
9
9
|
- `apps/website` — a React + Vite+ app (Tailwind v4, React Compiler)
|
|
10
10
|
- `packages/ui` — a shared **shadcn** design system consumed by the app
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
you choose.
|
|
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.
|
|
15
14
|
|
|
16
15
|
## Usage
|
|
17
16
|
|
|
@@ -26,9 +25,8 @@ vp create @pauldvlp:vp-react-ts-shadcn -- \
|
|
|
26
25
|
--name my-app --scope @acme --base base --preset vega --iconLibrary lucide --components button,card,dialog
|
|
27
26
|
```
|
|
28
27
|
|
|
29
|
-
>
|
|
30
|
-
>
|
|
31
|
-
> Bingo's CLI does not accept `--no-x` / `--x=false` cleanly. Omit them and answer the prompt.
|
|
28
|
+
> **Boolean** options (`--cssVariables`, `--rtl`, `--pointer`, `--install`) accept both forms: pass
|
|
29
|
+
> `--rtl` to enable or `--no-rtl` to disable. Omit any option to answer it at the interactive prompt.
|
|
32
30
|
|
|
33
31
|
## Options
|
|
34
32
|
|
package/dist/index.js
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// bin/index.ts
|
|
4
|
-
import { runTemplateCLI } from "bingo";
|
|
5
|
-
|
|
6
|
-
// src/template.ts
|
|
7
|
-
import path2 from "node:path";
|
|
8
|
-
import { fileURLToPath } from "node:url";
|
|
9
|
-
|
|
10
3
|
// ../template-kit/src/index.ts
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
11
5
|
import fs from "node:fs";
|
|
12
6
|
import path from "node:path";
|
|
7
|
+
import * as prompts from "@clack/prompts";
|
|
13
8
|
var RENAME = {
|
|
14
|
-
_gitignore: ".gitignore"
|
|
9
|
+
_gitignore: ".gitignore",
|
|
10
|
+
_dockerignore: ".dockerignore",
|
|
11
|
+
"_env.example": ".env.example"
|
|
15
12
|
};
|
|
16
13
|
var DEFAULT_PRESET = "b30557okNu";
|
|
17
14
|
var ICON_LIBS = {
|
|
@@ -24,6 +21,10 @@ function toScope(name) {
|
|
|
24
21
|
const n = name.trim();
|
|
25
22
|
return n.startsWith("@") ? n : `@${n}`;
|
|
26
23
|
}
|
|
24
|
+
function dirName(directory) {
|
|
25
|
+
const base = path.basename(directory || "");
|
|
26
|
+
return base && base !== "." ? base : "my-app";
|
|
27
|
+
}
|
|
27
28
|
function readTree(dir, transform, base = dir) {
|
|
28
29
|
const out = {};
|
|
29
30
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
@@ -117,17 +118,181 @@ function shadcnInitFlags(opts) {
|
|
|
117
118
|
"-f"
|
|
118
119
|
].join(" ");
|
|
119
120
|
}
|
|
121
|
+
function defineTemplate(config) {
|
|
122
|
+
return config;
|
|
123
|
+
}
|
|
124
|
+
function writeTree(node, dir) {
|
|
125
|
+
for (const [name, value] of Object.entries(node)) {
|
|
126
|
+
const p = path.join(dir, name);
|
|
127
|
+
if (typeof value === "string") {
|
|
128
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
129
|
+
fs.writeFileSync(p, value);
|
|
130
|
+
} else {
|
|
131
|
+
fs.mkdirSync(p, { recursive: true });
|
|
132
|
+
writeTree(value, p);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function parseArgv(argv, descriptors) {
|
|
137
|
+
const booleanKeys = new Set(descriptors.filter((d) => d.type === "boolean").map((d) => d.key));
|
|
138
|
+
const values = {};
|
|
139
|
+
let directory;
|
|
140
|
+
for (let i = 0; i < argv.length; i++) {
|
|
141
|
+
const arg = argv[i];
|
|
142
|
+
if (!arg.startsWith("--")) continue;
|
|
143
|
+
let body = arg.slice(2);
|
|
144
|
+
if (body.startsWith("no-") && booleanKeys.has(body.slice(3))) {
|
|
145
|
+
values[body.slice(3)] = false;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
let inline;
|
|
149
|
+
const eq = body.indexOf("=");
|
|
150
|
+
if (eq !== -1) {
|
|
151
|
+
inline = body.slice(eq + 1);
|
|
152
|
+
body = body.slice(0, eq);
|
|
153
|
+
}
|
|
154
|
+
if (body === "directory") {
|
|
155
|
+
directory = inline ?? argv[++i];
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (booleanKeys.has(body)) {
|
|
159
|
+
values[body] = inline === void 0 ? true : inline !== "false";
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
values[body] = inline ?? argv[++i] ?? "";
|
|
163
|
+
}
|
|
164
|
+
return { values, directory };
|
|
165
|
+
}
|
|
166
|
+
function resolveDefault(d, options, directory) {
|
|
167
|
+
return typeof d.default === "function" ? d.default({ options, directory }) : d.default;
|
|
168
|
+
}
|
|
169
|
+
async function promptForOption(d, def) {
|
|
170
|
+
if (d.type === "boolean") {
|
|
171
|
+
return prompts.confirm({ message: d.message, initialValue: def === void 0 ? false : Boolean(def) });
|
|
172
|
+
}
|
|
173
|
+
if (d.type === "select") {
|
|
174
|
+
return prompts.select({
|
|
175
|
+
message: d.message,
|
|
176
|
+
options: (d.choices ?? []).map((c) => ({ value: c, label: c })),
|
|
177
|
+
initialValue: def
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
const fallback = def === void 0 ? void 0 : String(def);
|
|
181
|
+
return prompts.text({
|
|
182
|
+
message: d.message,
|
|
183
|
+
placeholder: fallback,
|
|
184
|
+
defaultValue: fallback,
|
|
185
|
+
// When a default exists, an empty submit means "use the default" — don't run validation on it,
|
|
186
|
+
// otherwise a rule like the port check would reject pressing Enter to accept 3000.
|
|
187
|
+
validate: d.validate ? (value) => !value && fallback !== void 0 ? void 0 : d.validate(value ?? "") : void 0
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
function printHelp(template) {
|
|
191
|
+
const lines = [];
|
|
192
|
+
if (template.about?.name) lines.push(template.about.name);
|
|
193
|
+
if (template.about?.description) lines.push(template.about.description);
|
|
194
|
+
lines.push("", "Options:", " --directory <dir> Where to scaffold");
|
|
195
|
+
for (const d of template.options) {
|
|
196
|
+
const hint = d.type === "select" && d.choices ? ` <${d.choices.join("|")}>` : d.type === "boolean" ? ` / --no-${d.key}` : " <value>";
|
|
197
|
+
lines.push(` --${d.key}${hint} ${d.message}`);
|
|
198
|
+
}
|
|
199
|
+
console.log(lines.join("\n"));
|
|
200
|
+
}
|
|
201
|
+
function initGit(dir) {
|
|
202
|
+
try {
|
|
203
|
+
execFileSync("git", ["rev-parse", "--is-inside-work-tree"], { cwd: dir, stdio: "ignore" });
|
|
204
|
+
return;
|
|
205
|
+
} catch {
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
execFileSync("git", ["init"], { cwd: dir, stdio: "ignore" });
|
|
209
|
+
execFileSync("git", ["add", "-A"], { cwd: dir, stdio: "ignore" });
|
|
210
|
+
let identity = [];
|
|
211
|
+
try {
|
|
212
|
+
execFileSync("git", ["config", "user.email"], { cwd: dir, stdio: "ignore" });
|
|
213
|
+
} catch {
|
|
214
|
+
identity = ["-c", "user.name=create", "-c", "user.email=create@users.noreply.github.com"];
|
|
215
|
+
}
|
|
216
|
+
execFileSync("git", [...identity, "commit", "-m", "feat: initialize project", "--no-gpg-sign"], { cwd: dir, stdio: "ignore" });
|
|
217
|
+
} catch {
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
async function runTemplateCLI(template) {
|
|
221
|
+
const argv = process.argv.slice(2);
|
|
222
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
223
|
+
printHelp(template);
|
|
224
|
+
return 0;
|
|
225
|
+
}
|
|
226
|
+
const interactive = Boolean(process.stdout.isTTY) && !argv.includes("--no-interactive");
|
|
227
|
+
const { values, directory: dirArg } = parseArgv(argv, template.options);
|
|
228
|
+
prompts.intro(template.about?.name ?? "create");
|
|
229
|
+
let directory = dirArg;
|
|
230
|
+
if (!directory) {
|
|
231
|
+
if (interactive) {
|
|
232
|
+
const answer = await prompts.text({ message: "Where should we create your project?", placeholder: "my-app", defaultValue: "my-app" });
|
|
233
|
+
if (prompts.isCancel(answer)) {
|
|
234
|
+
prompts.cancel("Cancelled.");
|
|
235
|
+
return 2;
|
|
236
|
+
}
|
|
237
|
+
directory = answer || "my-app";
|
|
238
|
+
} else {
|
|
239
|
+
directory = "my-app";
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const options = {};
|
|
243
|
+
for (const d of template.options) {
|
|
244
|
+
const def = resolveDefault(d, options, directory);
|
|
245
|
+
if (d.key in values) {
|
|
246
|
+
const value = values[d.key];
|
|
247
|
+
if (d.validate && typeof value === "string") {
|
|
248
|
+
const error = d.validate(value);
|
|
249
|
+
if (error) {
|
|
250
|
+
prompts.cancel(`Invalid --${d.key}: ${error}`);
|
|
251
|
+
return 1;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
options[d.key] = value;
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (interactive) {
|
|
258
|
+
const answer = await promptForOption(d, def);
|
|
259
|
+
if (prompts.isCancel(answer)) {
|
|
260
|
+
prompts.cancel("Cancelled.");
|
|
261
|
+
return 2;
|
|
262
|
+
}
|
|
263
|
+
options[d.key] = answer === "" && def !== void 0 ? def : answer;
|
|
264
|
+
} else {
|
|
265
|
+
options[d.key] = def;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const creation = await template.produce({ options });
|
|
269
|
+
writeTree(creation.files, directory);
|
|
270
|
+
if (creation.scripts?.length) {
|
|
271
|
+
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 });
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (template.git !== false) initGit(directory);
|
|
278
|
+
if (creation.suggestions?.length) {
|
|
279
|
+
prompts.note(creation.suggestions.join("\n"), "Next steps");
|
|
280
|
+
}
|
|
281
|
+
prompts.outro("Done!");
|
|
282
|
+
return 0;
|
|
283
|
+
}
|
|
120
284
|
|
|
121
285
|
// src/template.ts
|
|
122
|
-
import
|
|
123
|
-
import {
|
|
286
|
+
import path2 from "node:path";
|
|
287
|
+
import { fileURLToPath } from "node:url";
|
|
124
288
|
|
|
125
289
|
// package.json
|
|
126
290
|
var package_default = {
|
|
127
291
|
name: "@pauldvlp/vp-react-ts-shadcn",
|
|
128
|
-
version: "0.
|
|
292
|
+
version: "0.5.0",
|
|
129
293
|
description: "Vite+ monorepo template: one frontend app (website) + a shared shadcn UI package, themeable via shadcn presets.",
|
|
130
294
|
author: "pauldvlp (https://github.com/pauldvlp/vp-templates)",
|
|
295
|
+
license: "MIT",
|
|
131
296
|
homepage: "https://github.com/pauldvlp/vp-templates",
|
|
132
297
|
repository: {
|
|
133
298
|
type: "git",
|
|
@@ -154,8 +319,7 @@ var package_default = {
|
|
|
154
319
|
prepack: "pnpm run build"
|
|
155
320
|
},
|
|
156
321
|
dependencies: {
|
|
157
|
-
|
|
158
|
-
zod: "^3.25.76"
|
|
322
|
+
"@clack/prompts": "^1.6.0"
|
|
159
323
|
},
|
|
160
324
|
devDependencies: {
|
|
161
325
|
"@pauldvlp/template-kit": "workspace:*",
|
|
@@ -170,33 +334,26 @@ var package_default = {
|
|
|
170
334
|
|
|
171
335
|
// src/template.ts
|
|
172
336
|
var TEMPLATE_DIR = path2.join(path2.dirname(fileURLToPath(import.meta.url)), "..", "template");
|
|
173
|
-
var template_default =
|
|
337
|
+
var template_default = defineTemplate({
|
|
174
338
|
about: {
|
|
175
339
|
name: package_default.name,
|
|
176
340
|
description: package_default.description
|
|
177
341
|
},
|
|
178
|
-
options:
|
|
179
|
-
name
|
|
180
|
-
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
preset:
|
|
186
|
-
iconLibrary:
|
|
187
|
-
cssVariables:
|
|
188
|
-
rtl:
|
|
189
|
-
pointer:
|
|
190
|
-
components:
|
|
191
|
-
install:
|
|
192
|
-
|
|
193
|
-
// Lazily default the package scope to `@<name>` (prefixing `@` unless the name already has one),
|
|
194
|
-
// so it tracks the project name instead of a fixed value. Falls back to `@app` when no name is set.
|
|
195
|
-
prepare({ options }) {
|
|
196
|
-
return {
|
|
197
|
-
scope: () => options.name ? toScope(options.name) : "@app"
|
|
198
|
-
};
|
|
199
|
-
},
|
|
342
|
+
options: [
|
|
343
|
+
// 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) },
|
|
345
|
+
// Lazily default the package scope to `@<name>` (prefixing `@` unless the name already has one),
|
|
346
|
+
// 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" },
|
|
348
|
+
{ 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 },
|
|
350
|
+
{ key: "iconLibrary", type: "select", message: "Icon library", choices: ["lucide", "hugeicons", "radix", "tabler"], default: "hugeicons" },
|
|
351
|
+
{ key: "cssVariables", type: "boolean", message: "Use CSS variables for theming", default: true },
|
|
352
|
+
{ key: "rtl", type: "boolean", message: "Enable RTL support", default: false },
|
|
353
|
+
{ 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" },
|
|
355
|
+
{ key: "install", type: "boolean", message: "Install deps and apply the shadcn theme after scaffolding", default: true }
|
|
356
|
+
],
|
|
200
357
|
async produce({ options }) {
|
|
201
358
|
const scope = toScope(options.scope || options.name || "app");
|
|
202
359
|
const files = readTree(
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pauldvlp/vp-react-ts-shadcn",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
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
|
+
"license": "MIT",
|
|
6
7
|
"homepage": "https://github.com/pauldvlp/vp-templates",
|
|
7
8
|
"repository": {
|
|
8
9
|
"type": "git",
|
|
@@ -23,8 +24,7 @@
|
|
|
23
24
|
"template"
|
|
24
25
|
],
|
|
25
26
|
"dependencies": {
|
|
26
|
-
"
|
|
27
|
-
"zod": "^3.25.76"
|
|
27
|
+
"@clack/prompts": "^1.6.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/node": "^24",
|