@pauldvlp/vp-pkg-shadcn 0.4.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 +127 -0
- package/dist/index.js +290 -0
- package/package.json +46 -0
- package/template/package.json +41 -0
- package/template/src/components/.gitkeep +0 -0
- package/template/src/hooks/.gitkeep +0 -0
- package/template/src/lib/.gitkeep +0 -0
- package/template/src/styles/globals.css +6 -0
- package/template/tsconfig.json +21 -0
- package/template/vite.config.ts +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/pauldvlp/vp-templates/main/assets/cover.webp" alt="@pauldvlp/vp-templates" width="100%" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# @pauldvlp/vp-pkg-shadcn
|
|
6
|
+
|
|
7
|
+
A [Vite+](https://viteplus.dev) **package generator** that drops a shared, fully customizable
|
|
8
|
+
**shadcn** UI package into an **existing** monorepo:
|
|
9
|
+
|
|
10
|
+
- `packages/ui` — a shared shadcn design system (Tailwind v4, fonts, `lib/utils`, `components/ui/*`)
|
|
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.
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
Run it from the root of an existing repo (published under the [`@pauldvlp/create`](../create) manifest):
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Interactive (prompts for anything you don't pass)
|
|
23
|
+
vp create @pauldvlp:vp-pkg-shadcn
|
|
24
|
+
|
|
25
|
+
# Non-interactive, fully specified
|
|
26
|
+
vp create @pauldvlp:vp-pkg-shadcn -- \
|
|
27
|
+
--scope @acme --base base --preset vega --iconLibrary lucide --components button,card,dialog
|
|
28
|
+
```
|
|
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.
|
|
33
|
+
|
|
34
|
+
### Requirements
|
|
35
|
+
|
|
36
|
+
Your repo must be a **pnpm workspace that globs the directory you pick** (e.g. `packages/*`), so the
|
|
37
|
+
new `<scope>/ui` is linked and the post-scaffold `pnpm --filter <scope>/ui ...` steps resolve. The
|
|
38
|
+
generator does **not** edit your `pnpm-workspace.yaml` or any app — wiring a consumer is up to you
|
|
39
|
+
(see below).
|
|
40
|
+
|
|
41
|
+
### Where it lands (the `--directory`)
|
|
42
|
+
|
|
43
|
+
The generator emits the package contents at the **root** of its file tree, and Bingo writes them under
|
|
44
|
+
the target directory — which **defaults to `ui`** (so within `vp create`'s monorepo flow it lands at
|
|
45
|
+
`<chosen-parent>/ui`, e.g. `packages/ui`). Pass `--directory <path>` to place it elsewhere. `--scope`
|
|
46
|
+
defaults to the **surrounding monorepo's scope** (read from the nearest `pnpm-workspace.yaml`'s
|
|
47
|
+
`package.json` — e.g. a repo named `acme` → `@acme`), falling back to `@app` outside a workspace.
|
|
48
|
+
|
|
49
|
+
## Options
|
|
50
|
+
|
|
51
|
+
| Option | Type / values | Default | Notes |
|
|
52
|
+
| --------------- | ---------------------------------------------- | -------------- | --------------------------------------------------------------------------------------- |
|
|
53
|
+
| `--scope` | string | monorepo scope | npm scope for the package → `@scope/ui`. Defaults to the surrounding monorepo's scope (e.g. `@acme`), `@app` outside a workspace. `@` is added if you omit it. |
|
|
54
|
+
| `--base` | `radix` \| `base` | `radix` | shadcn component library (radix-ui or @base-ui). Honored by `shadcn init --base`. |
|
|
55
|
+
| `--preset` | style name or code | `b30557okNu` | A style (`nova`, `vega`, `maia`, `lyra`, `mira`, `luma`, `sera`, `rhea`) **or** a code from ui.shadcn.com. **Owns** color, fonts, radius, baseColor, menu styling. |
|
|
56
|
+
| `--iconLibrary` | `lucide` \| `hugeicons` \| `radix` \| `tabler` | `hugeicons` | Icon library (persists; not part of the preset). |
|
|
57
|
+
| `--cssVariables`| boolean | `true` | CSS variables for theming. |
|
|
58
|
+
| `--rtl` | boolean | `false` | RTL support. |
|
|
59
|
+
| `--pointer` | boolean | `false` | Pointer cursor on interactive elements. |
|
|
60
|
+
| `--components` | comma list | `button,badge` | shadcn components to pre-install. `button` + `badge` are always included. |
|
|
61
|
+
| `--install` | boolean | `true` | Run install + apply the shadcn theme after scaffolding. `false` = files only. |
|
|
62
|
+
|
|
63
|
+
## How it scaffolds
|
|
64
|
+
|
|
65
|
+
`produce()` emits `packages/ui` with a **minimal** `globals.css` (so Tailwind v4 is detectable),
|
|
66
|
+
resolves the package's `catalog:` specifiers to concrete versions (it can't assume your repo's
|
|
67
|
+
pnpm catalog), bakes `components.json` from your options, then runs:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pnpm install
|
|
71
|
+
pnpm --filter <scope>/ui exec shadcn init --base <base> --preset <preset> ... --no-reinstall -y -f
|
|
72
|
+
pnpm --filter <scope>/ui exec shadcn add button badge <extra> -y
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
shadcn `init` fills the theme tokens into `globals.css` and creates `lib/utils.ts`; `add` writes
|
|
76
|
+
components into `src/components/ui/`. The UI package `exports` (`./components/* → ./src/components/*.tsx`)
|
|
77
|
+
resolves them as `@scope/ui/components/ui/<name>`.
|
|
78
|
+
|
|
79
|
+
## Consume it from an app
|
|
80
|
+
|
|
81
|
+
This generator only creates the `ui` package — wiring it into a consuming app is three steps:
|
|
82
|
+
|
|
83
|
+
**1. Depend on it.**
|
|
84
|
+
|
|
85
|
+
```jsonc
|
|
86
|
+
// apps/<your-app>/package.json
|
|
87
|
+
"dependencies": { "@scope/ui": "workspace:*" }
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**2. Give the app Tailwind v4.** The app — not the `ui` package — compiles the styles, so it needs the
|
|
91
|
+
Tailwind plugin. Skip this if the app already uses Tailwind v4.
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
pnpm --filter <your-app> add -D @tailwindcss/vite tailwindcss
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
// apps/<your-app>/vite.config.ts
|
|
99
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
100
|
+
export default defineConfig({ plugins: [/* ...existing */ tailwindcss()] })
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
> `ui`'s `globals.css` already `@source`s `apps/**/*.{ts,tsx}` (and the `ui` package), so Tailwind
|
|
104
|
+
> scans your app's class names. If your app lives outside `apps/`, add an `@source` for its path.
|
|
105
|
+
|
|
106
|
+
**3. Import the styles once at the app's entry point**, then use components:
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
// apps/<your-app>/src/main.tsx
|
|
110
|
+
import '@scope/ui/globals.css'
|
|
111
|
+
import { Button } from '@scope/ui/components/ui/button'
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Re-theme later
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
pnpm --filter @scope/ui exec shadcn init --preset <new-code> --no-reinstall -y -f
|
|
118
|
+
pnpm --filter @scope/ui exec shadcn add <component>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Develop the generator
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
pnpm install
|
|
125
|
+
node bin/index.ts --help # list options
|
|
126
|
+
node bin/index.ts --directory /tmp/demo --scope @demo --base base --preset vega
|
|
127
|
+
```
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// bin/index.ts
|
|
4
|
+
import fs2 from "node:fs";
|
|
5
|
+
import path3 from "node:path";
|
|
6
|
+
import * as prompts from "@clack/prompts";
|
|
7
|
+
|
|
8
|
+
// ../template-kit/src/index.ts
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
var RENAME = {
|
|
12
|
+
_gitignore: ".gitignore"
|
|
13
|
+
};
|
|
14
|
+
var DEFAULT_PRESET = "b30557okNu";
|
|
15
|
+
var ICON_LIBS = {
|
|
16
|
+
hugeicons: { "@hugeicons/react": "^1.1.9", "@hugeicons/core-free-icons": "^4.2.1" },
|
|
17
|
+
lucide: { "lucide-react": "^0" },
|
|
18
|
+
radix: { "@radix-ui/react-icons": "^1" },
|
|
19
|
+
tabler: { "@tabler/icons-react": "^3" }
|
|
20
|
+
};
|
|
21
|
+
var CATALOG = {
|
|
22
|
+
"@types/node": "^24",
|
|
23
|
+
typescript: "^5",
|
|
24
|
+
vite: "npm:@voidzero-dev/vite-plus-core@latest",
|
|
25
|
+
vitest: "4.1.9",
|
|
26
|
+
"vite-plus": "^0.2.1",
|
|
27
|
+
"babel-plugin-react-compiler": "^1.0.0",
|
|
28
|
+
"@rolldown/plugin-babel": "^0.2.3"
|
|
29
|
+
};
|
|
30
|
+
function toScope(name) {
|
|
31
|
+
const n = name.trim();
|
|
32
|
+
return n.startsWith("@") ? n : `@${n}`;
|
|
33
|
+
}
|
|
34
|
+
function detectMonorepoScope() {
|
|
35
|
+
let dir = process.cwd();
|
|
36
|
+
for (let i = 0; i < 10; i++) {
|
|
37
|
+
const hasWorkspace = fs.existsSync(path.join(dir, "pnpm-workspace.yaml")) || fs.existsSync(path.join(dir, "pnpm-workspace.yml"));
|
|
38
|
+
const pkgPath = path.join(dir, "package.json");
|
|
39
|
+
if (hasWorkspace && fs.existsSync(pkgPath)) {
|
|
40
|
+
try {
|
|
41
|
+
const name = JSON.parse(fs.readFileSync(pkgPath, "utf8")).name;
|
|
42
|
+
if (typeof name === "string" && name) {
|
|
43
|
+
const head = name.startsWith("@") ? name.slice(1).split("/")[0] : name;
|
|
44
|
+
return toScope(head);
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const parent = path.dirname(dir);
|
|
50
|
+
if (parent === dir) break;
|
|
51
|
+
dir = parent;
|
|
52
|
+
}
|
|
53
|
+
return void 0;
|
|
54
|
+
}
|
|
55
|
+
function readTree(dir, transform, base = dir) {
|
|
56
|
+
const out = {};
|
|
57
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
58
|
+
const abs = path.join(dir, entry.name);
|
|
59
|
+
const name = RENAME[entry.name] ?? entry.name;
|
|
60
|
+
if (entry.isDirectory()) {
|
|
61
|
+
out[name] = readTree(abs, transform, base);
|
|
62
|
+
} else {
|
|
63
|
+
const rel = path.relative(base, abs);
|
|
64
|
+
out[name] = transform(rel, fs.readFileSync(abs, "utf8"));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
function setPath(tree, relPath, content) {
|
|
70
|
+
const parts = relPath.split("/");
|
|
71
|
+
let node = tree;
|
|
72
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
73
|
+
const key = parts[i];
|
|
74
|
+
if (typeof node[key] !== "object") node[key] = {};
|
|
75
|
+
node = node[key];
|
|
76
|
+
}
|
|
77
|
+
node[parts[parts.length - 1]] = content;
|
|
78
|
+
}
|
|
79
|
+
function patchJson(tree, relPath, mutate) {
|
|
80
|
+
const parts = relPath.split("/");
|
|
81
|
+
let node = tree;
|
|
82
|
+
for (let i = 0; i < parts.length - 1; i++) node = node[parts[i]];
|
|
83
|
+
const key = parts[parts.length - 1];
|
|
84
|
+
const json = JSON.parse(node[key]);
|
|
85
|
+
mutate(json);
|
|
86
|
+
node[key] = `${JSON.stringify(json, null, 2)}
|
|
87
|
+
`;
|
|
88
|
+
}
|
|
89
|
+
function resolveCatalogDeps(pkg, catalog = CATALOG) {
|
|
90
|
+
for (const field of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
91
|
+
const deps = pkg[field];
|
|
92
|
+
if (!deps) continue;
|
|
93
|
+
for (const [name, range] of Object.entries(deps)) {
|
|
94
|
+
if ((range === "catalog:" || range === "catalog:default") && catalog[name]) deps[name] = catalog[name];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function addIconDeps(pkg, iconLibrary) {
|
|
99
|
+
const iconDeps = ICON_LIBS[iconLibrary] ?? ICON_LIBS.hugeicons;
|
|
100
|
+
pkg.dependencies = { ...pkg.dependencies, ...iconDeps };
|
|
101
|
+
}
|
|
102
|
+
function withRequiredComponents(csv, required = ["button", "badge"]) {
|
|
103
|
+
const requested = csv.split(",").map((c) => c.trim()).filter(Boolean);
|
|
104
|
+
return Array.from(/* @__PURE__ */ new Set([...required, ...requested]));
|
|
105
|
+
}
|
|
106
|
+
function uiComponentsJson(theme) {
|
|
107
|
+
const { scope, base, cssVariables, iconLibrary, rtl } = theme;
|
|
108
|
+
return {
|
|
109
|
+
$schema: "https://ui.shadcn.com/schema.json",
|
|
110
|
+
style: `${base}-nova`,
|
|
111
|
+
rsc: false,
|
|
112
|
+
tsx: true,
|
|
113
|
+
tailwind: { config: "", css: "./src/styles/globals.css", baseColor: "neutral", cssVariables },
|
|
114
|
+
iconLibrary,
|
|
115
|
+
rtl,
|
|
116
|
+
base,
|
|
117
|
+
aliases: {
|
|
118
|
+
components: `${scope}/ui/components`,
|
|
119
|
+
ui: `${scope}/ui/components`,
|
|
120
|
+
lib: `${scope}/ui/lib`,
|
|
121
|
+
utils: `${scope}/ui/lib/utils`,
|
|
122
|
+
hooks: `${scope}/ui/hooks`
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function shadcnInitFlags(opts) {
|
|
127
|
+
return [
|
|
128
|
+
`--base ${opts.base}`,
|
|
129
|
+
`--preset ${opts.preset}`,
|
|
130
|
+
opts.cssVariables ? "--css-variables" : "--no-css-variables",
|
|
131
|
+
opts.rtl ? "--rtl" : "--no-rtl",
|
|
132
|
+
opts.pointer ? "--pointer" : "--no-pointer",
|
|
133
|
+
"--no-reinstall",
|
|
134
|
+
"-y",
|
|
135
|
+
"-f"
|
|
136
|
+
].join(" ");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// bin/index.ts
|
|
140
|
+
import { runTemplateCLI } from "bingo";
|
|
141
|
+
|
|
142
|
+
// src/template.ts
|
|
143
|
+
import path2 from "node:path";
|
|
144
|
+
import { fileURLToPath } from "node:url";
|
|
145
|
+
import { createTemplate } from "bingo";
|
|
146
|
+
import { z } from "zod";
|
|
147
|
+
|
|
148
|
+
// package.json
|
|
149
|
+
var package_default = {
|
|
150
|
+
name: "@pauldvlp/vp-pkg-shadcn",
|
|
151
|
+
version: "0.4.0",
|
|
152
|
+
description: "Add a shared, fully customizable shadcn UI package (packages/ui) into an existing Vite+ monorepo, themeable via shadcn presets.",
|
|
153
|
+
author: "pauldvlp (https://github.com/pauldvlp/vp-templates)",
|
|
154
|
+
homepage: "https://github.com/pauldvlp/vp-templates",
|
|
155
|
+
repository: {
|
|
156
|
+
type: "git",
|
|
157
|
+
url: "git+https://github.com/pauldvlp/vp-templates.git",
|
|
158
|
+
directory: "packages/vp-pkg-shadcn"
|
|
159
|
+
},
|
|
160
|
+
bugs: "https://github.com/pauldvlp/vp-templates/issues",
|
|
161
|
+
keywords: [
|
|
162
|
+
"vite-plus-generator",
|
|
163
|
+
"vite-plus",
|
|
164
|
+
"shadcn",
|
|
165
|
+
"react",
|
|
166
|
+
"template"
|
|
167
|
+
],
|
|
168
|
+
bin: "./dist/index.js",
|
|
169
|
+
type: "module",
|
|
170
|
+
files: [
|
|
171
|
+
"dist",
|
|
172
|
+
"template"
|
|
173
|
+
],
|
|
174
|
+
scripts: {
|
|
175
|
+
build: "esbuild bin/index.ts --bundle --outfile=dist/index.js --format=esm --platform=node --target=node22 --packages=external",
|
|
176
|
+
dev: "node bin/index.ts",
|
|
177
|
+
prepack: "pnpm run build"
|
|
178
|
+
},
|
|
179
|
+
dependencies: {
|
|
180
|
+
"@clack/prompts": "^0.11.0",
|
|
181
|
+
bingo: "^0.9.3",
|
|
182
|
+
zod: "^3.25.76"
|
|
183
|
+
},
|
|
184
|
+
devDependencies: {
|
|
185
|
+
"@pauldvlp/template-kit": "workspace:*",
|
|
186
|
+
"@types/node": "^24",
|
|
187
|
+
esbuild: "^0.25.0",
|
|
188
|
+
typescript: "^5"
|
|
189
|
+
},
|
|
190
|
+
engines: {
|
|
191
|
+
node: ">=22.18.0"
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// src/template.ts
|
|
196
|
+
var TEMPLATE_DIR = path2.join(path2.dirname(fileURLToPath(import.meta.url)), "..", "template");
|
|
197
|
+
var template_default = createTemplate({
|
|
198
|
+
about: {
|
|
199
|
+
name: package_default.name,
|
|
200
|
+
description: package_default.description
|
|
201
|
+
},
|
|
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
|
+
},
|
|
224
|
+
async produce({ options }) {
|
|
225
|
+
const scope = toScope(options.scope || "app");
|
|
226
|
+
const files = readTree(TEMPLATE_DIR, (_rel, content) => content.split("@app").join(scope));
|
|
227
|
+
patchJson(files, "package.json", (pkg) => {
|
|
228
|
+
resolveCatalogDeps(pkg);
|
|
229
|
+
addIconDeps(pkg, options.iconLibrary);
|
|
230
|
+
});
|
|
231
|
+
setPath(files, "components.json", `${JSON.stringify(uiComponentsJson({ ...options, scope }), null, 2)}
|
|
232
|
+
`);
|
|
233
|
+
const adds = withRequiredComponents(options.components);
|
|
234
|
+
const ui = `${scope}/ui`;
|
|
235
|
+
const initFlags = shadcnInitFlags(options);
|
|
236
|
+
const scripts = options.install ? [
|
|
237
|
+
{ commands: ["pnpm install --silent"], phase: 0 },
|
|
238
|
+
{ commands: [`pnpm --filter ${ui} exec shadcn init ${initFlags}`], phase: 1 },
|
|
239
|
+
{ commands: [`pnpm --filter ${ui} exec shadcn add ${adds.join(" ")} -y`], phase: 2 }
|
|
240
|
+
] : [];
|
|
241
|
+
return {
|
|
242
|
+
files,
|
|
243
|
+
scripts
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// bin/index.ts
|
|
249
|
+
var DOCS_URL = "https://github.com/pauldvlp/vp-templates/blob/main/packages/vp-pkg-shadcn/README.md";
|
|
250
|
+
function getArg(argv2, name) {
|
|
251
|
+
for (let i = 0; i < argv2.length; i++) {
|
|
252
|
+
const arg = argv2[i];
|
|
253
|
+
if (arg === `--${name}`) return argv2[i + 1];
|
|
254
|
+
if (arg.startsWith(`--${name}=`)) return arg.slice(name.length + 3);
|
|
255
|
+
}
|
|
256
|
+
return void 0;
|
|
257
|
+
}
|
|
258
|
+
var argv = process.argv.slice(2);
|
|
259
|
+
var isHelp = argv.some((arg) => arg === "--help" || arg === "-h");
|
|
260
|
+
var isInteractive = process.stdout.isTTY && !argv.includes("--no-interactive");
|
|
261
|
+
if (!getArg(argv, "directory") && !isHelp) {
|
|
262
|
+
process.argv.push("--directory", "ui");
|
|
263
|
+
}
|
|
264
|
+
var status = await runTemplateCLI(template_default);
|
|
265
|
+
process.exitCode = status;
|
|
266
|
+
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
|
+
if (!isHelp && (status === 0 || status === void 0)) {
|
|
272
|
+
const ui = `${toScope(getArg(finalArgv, "scope") || detectMonorepoScope() || "app")}/ui`;
|
|
273
|
+
prompts.note(
|
|
274
|
+
[
|
|
275
|
+
`1. Workspace glob the dir you chose (e.g. packages/*) so ${ui} is linked`,
|
|
276
|
+
`2. Depend on it ${'"' + ui + '": "workspace:*"'} in your app's package.json`,
|
|
277
|
+
`3. Tailwind v4 pnpm --filter <app> add -D @tailwindcss/vite tailwindcss`,
|
|
278
|
+
` then add tailwindcss() to the app's vite.config plugins`,
|
|
279
|
+
`4. Import the styles import '${ui}/globals.css' in the app entry (e.g. src/main.tsx)`,
|
|
280
|
+
``,
|
|
281
|
+
`Add components pnpm --filter ${ui} exec shadcn add <name>`,
|
|
282
|
+
`Docs ${DOCS_URL}`
|
|
283
|
+
].join("\n"),
|
|
284
|
+
`Wire ${ui} into your app`
|
|
285
|
+
);
|
|
286
|
+
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.");
|
|
289
|
+
}
|
|
290
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pauldvlp/vp-pkg-shadcn",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Add a shared, fully customizable shadcn UI package (packages/ui) into an existing Vite+ monorepo, themeable via shadcn presets.",
|
|
5
|
+
"author": "pauldvlp (https://github.com/pauldvlp/vp-templates)",
|
|
6
|
+
"homepage": "https://github.com/pauldvlp/vp-templates",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/pauldvlp/vp-templates.git",
|
|
10
|
+
"directory": "packages/vp-pkg-shadcn"
|
|
11
|
+
},
|
|
12
|
+
"bugs": "https://github.com/pauldvlp/vp-templates/issues",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"vite-plus-generator",
|
|
15
|
+
"vite-plus",
|
|
16
|
+
"shadcn",
|
|
17
|
+
"react",
|
|
18
|
+
"template"
|
|
19
|
+
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"template"
|
|
24
|
+
],
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@clack/prompts": "^0.11.0",
|
|
27
|
+
"bingo": "^0.9.3",
|
|
28
|
+
"zod": "^3.25.76"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^24",
|
|
32
|
+
"esbuild": "^0.25.0",
|
|
33
|
+
"typescript": "^5",
|
|
34
|
+
"@pauldvlp/template-kit": "0.0.0"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=22.18.0"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "esbuild bin/index.ts --bundle --outfile=dist/index.js --format=esm --platform=node --target=node22 --packages=external",
|
|
41
|
+
"dev": "node bin/index.ts"
|
|
42
|
+
},
|
|
43
|
+
"bin": {
|
|
44
|
+
"vp-pkg-shadcn": "./dist/index.js"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@app/ui",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
"./globals.css": "./src/styles/globals.css",
|
|
8
|
+
"./lib/*": "./src/lib/*.ts",
|
|
9
|
+
"./components/*": "./src/components/*.tsx",
|
|
10
|
+
"./hooks/*": "./src/hooks/*.ts"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"lint": "vp lint",
|
|
14
|
+
"check": "vp check"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@fontsource-variable/outfit": "^5.2.8",
|
|
18
|
+
"@fontsource-variable/space-grotesk": "^5.2.10",
|
|
19
|
+
"class-variance-authority": "^0.7.1",
|
|
20
|
+
"clsx": "^2.1.1",
|
|
21
|
+
"shadcn": "^4.11.0",
|
|
22
|
+
"tailwind-merge": "^3.6.0",
|
|
23
|
+
"tw-animate-css": "^1.4.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@tailwindcss/vite": "^4",
|
|
27
|
+
"@types/react": "^19.2.17",
|
|
28
|
+
"@types/react-dom": "^19.2.3",
|
|
29
|
+
"@vitejs/plugin-react": "^6.0.2",
|
|
30
|
+
"react": "^19.2.7",
|
|
31
|
+
"react-dom": "^19.2.7",
|
|
32
|
+
"tailwindcss": "^4",
|
|
33
|
+
"typescript": "~6.0.2",
|
|
34
|
+
"vite": "catalog:",
|
|
35
|
+
"vite-plus": "catalog:"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"react": "^19",
|
|
39
|
+
"react-dom": "^19"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"moduleDetection": "force",
|
|
8
|
+
"jsx": "react-jsx",
|
|
9
|
+
"types": ["vite-plus/client"],
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"strict": true,
|
|
12
|
+
"verbatimModuleSyntax": true,
|
|
13
|
+
"allowImportingTsExtensions": true,
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"paths": {
|
|
16
|
+
"@app/ui/*": ["./src/*"]
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"include": ["src"],
|
|
20
|
+
"exclude": ["node_modules", "dist"]
|
|
21
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
2
|
+
import react from '@vitejs/plugin-react'
|
|
3
|
+
import { defineConfig, lazyPlugins } from 'vite-plus'
|
|
4
|
+
|
|
5
|
+
// https://vite.dev/config/
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
lint: {
|
|
8
|
+
plugins: ['react', 'typescript', 'oxc'],
|
|
9
|
+
rules: {
|
|
10
|
+
'react/rules-of-hooks': 'error',
|
|
11
|
+
'react/only-export-components': [
|
|
12
|
+
'warn',
|
|
13
|
+
{
|
|
14
|
+
allowConstantExport: true
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
'vite-plus/prefer-vite-plus-imports': 'error'
|
|
18
|
+
},
|
|
19
|
+
options: {
|
|
20
|
+
typeAware: true,
|
|
21
|
+
typeCheck: true
|
|
22
|
+
},
|
|
23
|
+
jsPlugins: [
|
|
24
|
+
{
|
|
25
|
+
name: 'vite-plus',
|
|
26
|
+
specifier: 'vite-plus/oxlint-plugin'
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
plugins: lazyPlugins(() => [react(), tailwindcss()])
|
|
31
|
+
})
|