wensity 0.1.11 → 0.1.12
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 +164 -40
- package/dist/index.js +366 -61
- package/package.json +24 -12
- package/LICENSE +0 -21
package/README.md
CHANGED
|
@@ -1,63 +1,187 @@
|
|
|
1
1
|
# Wensity CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The command line tool for [Wensity](https://ui.wensity.com). Install React
|
|
4
|
+
components, UI primitives, and shareable Tailwind CSS presets into your project
|
|
5
|
+
as source code you own.
|
|
6
|
+
|
|
7
|
+
There is no `@wensity/ui` runtime dependency and no theme provider to wire up.
|
|
8
|
+
Everything the CLI installs lands in your repository as plain files you can read,
|
|
9
|
+
edit, and commit.
|
|
10
|
+
|
|
11
|
+
[](https://www.npmjs.com/package/wensity)
|
|
12
|
+
[](./LICENSE)
|
|
13
|
+
|
|
14
|
+
[Docs](https://ui.wensity.com/docs/cli) ·
|
|
15
|
+
[Components](https://ui.wensity.com/components) ·
|
|
16
|
+
[Preset studio](https://ui.wensity.com/create-preset) ·
|
|
17
|
+
[Registry](https://github.com/wensity/registry) ·
|
|
18
|
+
[wensity.com](https://wensity.com)
|
|
19
|
+
|
|
20
|
+
## Quick start
|
|
4
21
|
|
|
5
22
|
```bash
|
|
6
|
-
#
|
|
23
|
+
# New project, with an app scaffold
|
|
24
|
+
npx wensity@latest init --template next
|
|
25
|
+
|
|
26
|
+
# Existing project
|
|
7
27
|
npx wensity@latest init
|
|
8
28
|
|
|
9
|
-
#
|
|
29
|
+
# Add a component
|
|
30
|
+
npx wensity@latest add button
|
|
31
|
+
npx wensity@latest add liquid-multimodal-input
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Requires Node.js 20 or later. Works with npm, pnpm, yarn, and bun. The CLI
|
|
35
|
+
picks your package manager from the client that invoked it, falling back to the
|
|
36
|
+
lockfile in your project root, and installs component dependencies with it.
|
|
37
|
+
|
|
38
|
+
## Presets
|
|
39
|
+
|
|
40
|
+
A preset is a shareable code that captures a full visual style: color palette,
|
|
41
|
+
border radius, fonts, and control colors. Build one visually in the
|
|
42
|
+
[preset studio](https://ui.wensity.com/create-preset), copy the `wsty1` code,
|
|
43
|
+
and apply it.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# New project, styled from the start
|
|
10
47
|
npx wensity@latest init --preset <wsty1-code>
|
|
11
48
|
npx wensity@latest init --preset <wsty1-code> --template next
|
|
12
49
|
|
|
13
|
-
# Existing project
|
|
50
|
+
# Existing project, style and CSS only
|
|
14
51
|
npx wensity@latest apply --preset <wsty1-code>
|
|
15
52
|
|
|
16
|
-
#
|
|
53
|
+
# See what a code contains before applying it
|
|
17
54
|
npx wensity@latest preset inspect <wsty1-code>
|
|
18
|
-
|
|
19
|
-
# Install a primitive or component
|
|
20
|
-
npx wensity@latest add button
|
|
21
|
-
npx wensity@latest add liquid-multimodal-input
|
|
55
|
+
npx wensity@latest preset inspect <wsty1-code> --json
|
|
22
56
|
```
|
|
23
57
|
|
|
24
|
-
|
|
58
|
+
Applying a preset writes two things:
|
|
59
|
+
|
|
60
|
+
1. Style settings into `wensity.json` (icon library, radius, fonts, control
|
|
61
|
+
colors).
|
|
62
|
+
2. A delimited `--primitive-*` CSS block in your Tailwind CSS file.
|
|
25
63
|
|
|
26
|
-
|
|
64
|
+
Later `add` and `update` runs read that style automatically, so newly installed
|
|
65
|
+
components match what is already there. `apply` rewrites the CSS block in place
|
|
66
|
+
and never touches your component source.
|
|
27
67
|
|
|
28
|
-
|
|
68
|
+
Plain `init` with no `--preset` refuses to overwrite an existing `wensity.json`
|
|
69
|
+
unless you pass `--force`. To restyle an existing project, prefer
|
|
70
|
+
`apply --preset`.
|
|
29
71
|
|
|
30
|
-
|
|
31
|
-
- Tabler ↔ Lucide icon imports transform at install time for **UI primitives only**
|
|
32
|
-
- Mandatory dependency swap installs exactly one icon package when icons are used
|
|
72
|
+
## Icon libraries
|
|
33
73
|
|
|
34
|
-
|
|
74
|
+
Wensity primitives are authored against [Tabler](https://tabler.io/icons). If
|
|
75
|
+
your project uses [Lucide](https://lucide.dev), set that in your preset or
|
|
76
|
+
`wensity.json` and the CLI rewrites icon imports at install time for UI
|
|
77
|
+
primitives. It installs exactly one icon package, whichever you chose.
|
|
78
|
+
|
|
79
|
+
This transform applies to UI primitives only. Larger composed components ship
|
|
80
|
+
with their icons as authored.
|
|
35
81
|
|
|
36
82
|
## Commands
|
|
37
83
|
|
|
38
|
-
| Command |
|
|
39
|
-
|
|
40
|
-
| `wensity init
|
|
41
|
-
| `wensity apply --preset <code>` | Apply
|
|
42
|
-
| `wensity preset inspect <code>` | Decode a preset code |
|
|
43
|
-
| `wensity
|
|
44
|
-
| `wensity
|
|
45
|
-
| `wensity
|
|
46
|
-
| `wensity
|
|
47
|
-
| `wensity
|
|
48
|
-
| `wensity
|
|
49
|
-
| `wensity version` | Print
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
| |
|
|
56
|
-
|
|
57
|
-
|
|
|
58
|
-
|
|
|
84
|
+
| Command | What it does |
|
|
85
|
+
| --- | --- |
|
|
86
|
+
| `wensity init` | Create `wensity.json` and local helper files. Optional `--preset` applies style and CSS, optional `--template` scaffolds an app |
|
|
87
|
+
| `wensity apply --preset <code>` | Apply a preset to an existing project, style and CSS only |
|
|
88
|
+
| `wensity preset inspect <code>` | Decode and print a preset code, add `--json` for machine output |
|
|
89
|
+
| `wensity list` | List registry components and blocks |
|
|
90
|
+
| `wensity add <slug...>` | Download component source into your project, skipping files that already exist |
|
|
91
|
+
| `wensity update <slug...>` | Re-download components and overwrite the local copies |
|
|
92
|
+
| `wensity login` | Save a Wensity API token for Pro component access |
|
|
93
|
+
| `wensity logout` | Remove saved credentials |
|
|
94
|
+
| `wensity whoami` | Verify the saved token and print the account and plan |
|
|
95
|
+
| `wensity version` | Print the CLI version |
|
|
96
|
+
|
|
97
|
+
Useful flags:
|
|
98
|
+
|
|
99
|
+
| Flag | Effect |
|
|
100
|
+
| --- | --- |
|
|
101
|
+
| `--template next\|vite\|astro\|react-router` | Scaffold a new app during `init` |
|
|
102
|
+
| `--type component\|block` | Narrow `list`, `add`, and `update` |
|
|
103
|
+
| `--api-url <url>` | Point at a different Wensity origin, defaults to `https://ui.wensity.com` |
|
|
104
|
+
| `--force` | Allow `init` to overwrite an existing `wensity.json` |
|
|
105
|
+
| `--json` | Machine readable output where supported |
|
|
106
|
+
|
|
107
|
+
## Authentication
|
|
108
|
+
|
|
109
|
+
Free components need no account. Wensity Pro components and blocks require an
|
|
110
|
+
API token from the [dashboard](https://ui.wensity.com/dashboard).
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
npx wensity@latest login
|
|
114
|
+
npx wensity@latest whoami
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The token is written to `~/.config/wensity/credentials.json` with `0600`
|
|
118
|
+
permissions. The location honours `XDG_CONFIG_HOME`, and can be overridden with
|
|
119
|
+
`WENSITY_CONFIG_HOME` or `WENSITY_CREDENTIALS_FILE`. Access is checked server
|
|
120
|
+
side on every request, so the token is the only secret involved and it never
|
|
121
|
+
leaves your machine except as a bearer header to the Wensity API.
|
|
122
|
+
|
|
123
|
+
Run `wensity logout` to delete it.
|
|
124
|
+
|
|
125
|
+
## Wensity CLI or the shadcn CLI
|
|
126
|
+
|
|
127
|
+
Free Wensity components are published to a public shadcn registry, so you can
|
|
128
|
+
install them with either tool. Use whichever fits your project.
|
|
129
|
+
|
|
130
|
+
| | Wensity CLI | shadcn CLI with `@wensity/*` |
|
|
131
|
+
| --- | --- | --- |
|
|
59
132
|
| Full editable source | Yes | Yes |
|
|
60
|
-
| `@wensity/ui` runtime | Never | Never |
|
|
61
|
-
|
|
|
133
|
+
| `@wensity/ui` runtime dependency | Never | Never |
|
|
134
|
+
| Stored presets and `wensity.json` style | Yes | No |
|
|
135
|
+
| Lucide delivery | Yes, via icon transform | No, payloads are Tabler canonical |
|
|
136
|
+
| Project scaffolding (`--template`) | Yes | No |
|
|
137
|
+
| Wensity Pro components | Yes, with a token | Not published |
|
|
138
|
+
|
|
139
|
+
The public registry lives at [wensity/registry](https://github.com/wensity/registry):
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
npx shadcn@latest add @wensity/button
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Configuration
|
|
146
|
+
|
|
147
|
+
`wensity.json` sits at your project root:
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"$schema": "https://ui.wensity.com/schema/wensity.json",
|
|
152
|
+
"version": 1,
|
|
153
|
+
"registry": "https://ui.wensity.com/api/registry",
|
|
154
|
+
"components": "src/components/wensity",
|
|
155
|
+
"utils": "src/lib/utils",
|
|
156
|
+
"tailwind": {
|
|
157
|
+
"config": "tailwind.config.ts",
|
|
158
|
+
"css": "src/app/globals.css"
|
|
159
|
+
},
|
|
160
|
+
"aliases": {
|
|
161
|
+
"components": "@/components/wensity",
|
|
162
|
+
"utils": "@/lib/utils"
|
|
163
|
+
},
|
|
164
|
+
"style": {
|
|
165
|
+
"iconLibrary": "tabler",
|
|
166
|
+
"radius": "medium"
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`init` also records a `style` block with fonts and control colors, which is
|
|
172
|
+
what `--preset` overwrites. Paths and aliases are read on every `add` and
|
|
173
|
+
`update`, so you can relocate the components directory and the CLI follows.
|
|
174
|
+
|
|
175
|
+
## Source and issues
|
|
176
|
+
|
|
177
|
+
The CLI is open source at [wensity/cli](https://github.com/wensity/cli), MIT
|
|
178
|
+
licensed. Issues and pull requests are welcome there.
|
|
179
|
+
|
|
180
|
+
For a bug in a component's source rather than the CLI itself, open an issue on
|
|
181
|
+
[wensity/registry](https://github.com/wensity/registry) instead.
|
|
182
|
+
|
|
183
|
+
## License
|
|
184
|
+
|
|
185
|
+
MIT. See [LICENSE](./LICENSE).
|
|
62
186
|
|
|
63
|
-
|
|
187
|
+
Copyright (c) 2026 Wensity Private Limited.
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/commands.ts
|
|
4
|
-
import { mkdir as mkdir6, readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
|
|
4
|
+
import { mkdir as mkdir6, readFile as readFile6, stat, writeFile as writeFile6 } from "fs/promises";
|
|
5
5
|
import { dirname as dirname7, join as join7 } from "path";
|
|
6
6
|
|
|
7
7
|
// src/constants.ts
|
|
8
8
|
var CLI_NAME = "wensity";
|
|
9
|
-
var CLI_VERSION = "0.1.
|
|
9
|
+
var CLI_VERSION = "0.1.12";
|
|
10
10
|
var CONFIG_FILE_NAME = "wensity.json";
|
|
11
11
|
var DEFAULT_API_ORIGIN = "https://ui.wensity.com";
|
|
12
12
|
var DEFAULT_REGISTRY_PATH = "/api/registry";
|
|
@@ -25,8 +25,8 @@ Usage:
|
|
|
25
25
|
wensity logout
|
|
26
26
|
wensity whoami [--json]
|
|
27
27
|
wensity list [--type component|block] [options]
|
|
28
|
-
wensity add <slug
|
|
29
|
-
wensity update <slug
|
|
28
|
+
wensity add <slug...> [--type component|block] [options]
|
|
29
|
+
wensity update <slug...> [--type component|block] [options]
|
|
30
30
|
wensity version
|
|
31
31
|
|
|
32
32
|
Commands:
|
|
@@ -37,8 +37,8 @@ Commands:
|
|
|
37
37
|
logout Remove saved CLI credentials
|
|
38
38
|
whoami Verify the saved token and print the authenticated account
|
|
39
39
|
list List registry components or blocks
|
|
40
|
-
add Download
|
|
41
|
-
update Re-download
|
|
40
|
+
add Download one or more component/block source files into this project
|
|
41
|
+
update Re-download one or more components/blocks and overwrite the local copies
|
|
42
42
|
version Print the CLI version
|
|
43
43
|
|
|
44
44
|
Options:
|
|
@@ -49,6 +49,7 @@ Options:
|
|
|
49
49
|
--preset <code> Apply a shareable Wensity style preset (wsty1.\u2026); merges style when config exists
|
|
50
50
|
--preset-file <path> Read a preset code or JSON payload from a file
|
|
51
51
|
--template <name> Scaffold a new app before init: next, vite, astro, react-router
|
|
52
|
+
--package-manager <pm> Force npm, pnpm, yarn, or bun instead of auto-detection
|
|
52
53
|
--force Overwrite generated files when adding or initializing
|
|
53
54
|
--no-install Write files without running the package manager
|
|
54
55
|
--dry-run Print planned writes and installs without changing files
|
|
@@ -63,7 +64,7 @@ Preset notes:
|
|
|
63
64
|
Plain init refuses an existing wensity.json unless --force
|
|
64
65
|
init --preset merges style + primitive CSS; does not rewrite installed source
|
|
65
66
|
init --template writes a minimal app scaffold in the current directory before Wensity files
|
|
66
|
-
add/update read style.iconLibrary for UI primitives only (Tabler|Lucide)
|
|
67
|
+
add/update accept several slugs at once and read style.iconLibrary for UI primitives only (Tabler|Lucide)
|
|
67
68
|
Invalid/expired/unsupported codes exit non-zero with an actionable message
|
|
68
69
|
`;
|
|
69
70
|
|
|
@@ -126,7 +127,16 @@ function hasFlag(args, name) {
|
|
|
126
127
|
return args.options.get(name) === true;
|
|
127
128
|
}
|
|
128
129
|
function expectsValue(name) {
|
|
129
|
-
return [
|
|
130
|
+
return [
|
|
131
|
+
"api-url",
|
|
132
|
+
"registry",
|
|
133
|
+
"token",
|
|
134
|
+
"type",
|
|
135
|
+
"preset",
|
|
136
|
+
"preset-file",
|
|
137
|
+
"template",
|
|
138
|
+
"package-manager"
|
|
139
|
+
].includes(name);
|
|
130
140
|
}
|
|
131
141
|
function normalizeOptionName(name) {
|
|
132
142
|
if (name === "dry") return "dry-run";
|
|
@@ -2521,6 +2531,91 @@ function resolvedTokensToCssVars(tokens, mode) {
|
|
|
2521
2531
|
[RESOLVED_TOKEN_CSS_VARS.fontMono]: tokens.fontMono
|
|
2522
2532
|
};
|
|
2523
2533
|
}
|
|
2534
|
+
function resolvedNamedBaseSemanticCssVars(baseColor, mode) {
|
|
2535
|
+
if (baseColor === "inherit") return {};
|
|
2536
|
+
const seeds = BASE_COLOR_PALETTE[baseColor]?.[mode];
|
|
2537
|
+
if (!seeds) return {};
|
|
2538
|
+
if (mode === "light") {
|
|
2539
|
+
return {
|
|
2540
|
+
// Soft family wash over white — the main light-mode tell (slate vs stone).
|
|
2541
|
+
"--background": `color-mix(in oklab, ${seeds.surfaceHover} 62%, white)`,
|
|
2542
|
+
"--foreground": `color-mix(in oklab, ${seeds.textSecondary} 38%, black)`,
|
|
2543
|
+
"--surface": seeds.surfaceElevated,
|
|
2544
|
+
"--surface-muted": seeds.surfaceHover,
|
|
2545
|
+
"--muted": seeds.surfaceHover,
|
|
2546
|
+
"--muted-foreground": seeds.textSecondary,
|
|
2547
|
+
"--border": seeds.borderSubtle,
|
|
2548
|
+
"--border-strong": `color-mix(in oklab, ${seeds.textSecondary} 30%, transparent)`
|
|
2549
|
+
};
|
|
2550
|
+
}
|
|
2551
|
+
return {
|
|
2552
|
+
// Canvas sits under elevated cards (darker than surfaceElevated).
|
|
2553
|
+
"--background": `color-mix(in oklab, ${seeds.surfaceElevated} 58%, black)`,
|
|
2554
|
+
"--foreground": `color-mix(in oklab, ${seeds.textSecondary} 22%, white)`,
|
|
2555
|
+
"--surface": seeds.surfaceElevated,
|
|
2556
|
+
"--surface-muted": seeds.surfaceHover,
|
|
2557
|
+
"--muted": seeds.surfaceHover,
|
|
2558
|
+
"--muted-foreground": seeds.textSecondary,
|
|
2559
|
+
"--border": seeds.borderSubtle,
|
|
2560
|
+
"--border-strong": `color-mix(in oklab, ${seeds.textSecondary} 38%, transparent)`
|
|
2561
|
+
};
|
|
2562
|
+
}
|
|
2563
|
+
var INHERIT_BASE_SEMANTIC_CSS_VARS = {
|
|
2564
|
+
light: {
|
|
2565
|
+
"--background": "#fafafa",
|
|
2566
|
+
"--foreground": "#0a0a0a",
|
|
2567
|
+
"--surface": "#ffffff",
|
|
2568
|
+
"--surface-muted": "#f4f4f5",
|
|
2569
|
+
"--muted": "#f4f4f5",
|
|
2570
|
+
"--muted-foreground": "#52525b",
|
|
2571
|
+
"--border": "rgba(10, 10, 10, 0.08)",
|
|
2572
|
+
"--border-strong": "rgba(10, 10, 10, 0.16)"
|
|
2573
|
+
},
|
|
2574
|
+
dark: {
|
|
2575
|
+
"--background": "#0a0a0b",
|
|
2576
|
+
"--foreground": "#f5f5f6",
|
|
2577
|
+
"--surface": "#111113",
|
|
2578
|
+
"--surface-muted": "#18181b",
|
|
2579
|
+
"--muted": "#1c1c1f",
|
|
2580
|
+
"--muted-foreground": "#a1a1aa",
|
|
2581
|
+
"--border": "rgba(255, 255, 255, 0.08)",
|
|
2582
|
+
"--border-strong": "rgba(255, 255, 255, 0.14)"
|
|
2583
|
+
}
|
|
2584
|
+
};
|
|
2585
|
+
function exportedBaseSemanticCssVars(baseColor, mode) {
|
|
2586
|
+
const named = resolvedNamedBaseSemanticCssVars(baseColor, mode);
|
|
2587
|
+
return Object.keys(named).length > 0 ? named : { ...INHERIT_BASE_SEMANTIC_CSS_VARS[mode] };
|
|
2588
|
+
}
|
|
2589
|
+
var BASE_SEMANTIC_THEME_UTILITIES = {
|
|
2590
|
+
"--color-background": "var(--background)",
|
|
2591
|
+
"--color-foreground": "var(--foreground)",
|
|
2592
|
+
"--color-surface": "var(--surface)",
|
|
2593
|
+
"--color-surface-muted": "var(--surface-muted)",
|
|
2594
|
+
"--color-muted": "var(--muted)",
|
|
2595
|
+
"--color-muted-foreground": "var(--muted-foreground)",
|
|
2596
|
+
"--color-border": "var(--border)",
|
|
2597
|
+
"--color-border-strong": "var(--border-strong)"
|
|
2598
|
+
};
|
|
2599
|
+
var BASE_CSS_START = "/* \u2500\u2500 Wensity Base Tokens \u2014 generated by the Wensity CLI.\n Edit these values to retheme your app; re-running `wensity init --preset`\n replaces this block. \u2500\u2500 */";
|
|
2600
|
+
var BASE_CSS_END = "/* \u2500\u2500 End Wensity Base Tokens \u2500\u2500 */";
|
|
2601
|
+
function formatBaseDeclarations(entries, indent = " ") {
|
|
2602
|
+
return Object.entries(entries).map(([name, value]) => `${indent}${name}: ${value};`).join("\n");
|
|
2603
|
+
}
|
|
2604
|
+
function buildBaseCssBlock(baseColor = "inherit") {
|
|
2605
|
+
return [
|
|
2606
|
+
BASE_CSS_START,
|
|
2607
|
+
"@theme inline {",
|
|
2608
|
+
formatBaseDeclarations(BASE_SEMANTIC_THEME_UTILITIES),
|
|
2609
|
+
"}",
|
|
2610
|
+
":root {",
|
|
2611
|
+
formatBaseDeclarations(exportedBaseSemanticCssVars(baseColor, "light")),
|
|
2612
|
+
"}",
|
|
2613
|
+
".dark {",
|
|
2614
|
+
formatBaseDeclarations(exportedBaseSemanticCssVars(baseColor, "dark")),
|
|
2615
|
+
"}",
|
|
2616
|
+
BASE_CSS_END
|
|
2617
|
+
].join("\n");
|
|
2618
|
+
}
|
|
2524
2619
|
|
|
2525
2620
|
// src/paths.ts
|
|
2526
2621
|
import { dirname, isAbsolute, relative, resolve, sep } from "path";
|
|
@@ -2608,6 +2703,12 @@ function resolveTokensForStyle(style) {
|
|
|
2608
2703
|
}
|
|
2609
2704
|
return resolveStyleConfig(normalizeStyleConfig(style));
|
|
2610
2705
|
}
|
|
2706
|
+
function baseColorFromStyle(style) {
|
|
2707
|
+
if (isCanonicalStyleShape(style)) {
|
|
2708
|
+
return normalizeStyleConfigCanonical(style).baseColor;
|
|
2709
|
+
}
|
|
2710
|
+
return "inherit";
|
|
2711
|
+
}
|
|
2611
2712
|
function curatedFontsFromStyle(style) {
|
|
2612
2713
|
if (isCanonicalStyleShape(style)) {
|
|
2613
2714
|
const normalized = normalizeStyleConfigCanonical(style);
|
|
@@ -3475,15 +3576,34 @@ function rewriteStrokeProps(content, localBindings) {
|
|
|
3475
3576
|
}
|
|
3476
3577
|
|
|
3477
3578
|
// src/package-manager.ts
|
|
3478
|
-
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
3579
|
+
import { existsSync as existsSync2, readFileSync, writeFileSync } from "fs";
|
|
3479
3580
|
import { spawnSync } from "child_process";
|
|
3480
3581
|
import { join as join4 } from "path";
|
|
3481
|
-
|
|
3582
|
+
var PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
|
|
3583
|
+
function parsePackageManager(value) {
|
|
3584
|
+
if (!value) return void 0;
|
|
3585
|
+
const normalized = value.trim().toLowerCase();
|
|
3586
|
+
if (isPackageManager(normalized)) return normalized;
|
|
3587
|
+
throw new CliError(
|
|
3588
|
+
`Unsupported package manager "${value}". Use one of: ${PACKAGE_MANAGERS.join(", ")}.`
|
|
3589
|
+
);
|
|
3590
|
+
}
|
|
3591
|
+
function packageManagerFromUserAgent(env = process.env) {
|
|
3592
|
+
const agent = env.npm_config_user_agent;
|
|
3593
|
+
if (!agent) return void 0;
|
|
3594
|
+
const first = agent.trim().split(/\s+/)[0] ?? "";
|
|
3595
|
+
const name = first.split("/")[0]?.toLowerCase();
|
|
3596
|
+
return name && isPackageManager(name) ? name : void 0;
|
|
3597
|
+
}
|
|
3598
|
+
function detectPackageManager(projectRoot, env = process.env) {
|
|
3482
3599
|
if (existsSync2(join4(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
3483
3600
|
if (existsSync2(join4(projectRoot, "bun.lockb")) || existsSync2(join4(projectRoot, "bun.lock"))) return "bun";
|
|
3484
3601
|
if (existsSync2(join4(projectRoot, "yarn.lock"))) return "yarn";
|
|
3485
3602
|
if (existsSync2(join4(projectRoot, "package-lock.json"))) return "npm";
|
|
3486
|
-
return "npm";
|
|
3603
|
+
return packageManagerFromUserAgent(env) ?? "npm";
|
|
3604
|
+
}
|
|
3605
|
+
function isPackageManager(value) {
|
|
3606
|
+
return PACKAGE_MANAGERS.includes(value);
|
|
3487
3607
|
}
|
|
3488
3608
|
function missingDependencies(projectRoot, dependencies) {
|
|
3489
3609
|
const installed = readInstalledPackageNames(projectRoot);
|
|
@@ -3503,15 +3623,34 @@ function readInstalledPackageNames(projectRoot) {
|
|
|
3503
3623
|
})
|
|
3504
3624
|
);
|
|
3505
3625
|
}
|
|
3506
|
-
|
|
3626
|
+
var YARN_CONFIG_FILE = ".yarnrc.yml";
|
|
3627
|
+
function prepareProjectPackageManager(projectRoot, options = {}) {
|
|
3628
|
+
if (resolvePackageManager(projectRoot, options) !== "yarn") return [];
|
|
3629
|
+
const configPath = join4(projectRoot, YARN_CONFIG_FILE);
|
|
3630
|
+
if (existsSync2(configPath)) return [];
|
|
3631
|
+
if (!options.dryRun) writeFileSync(configPath, "nodeLinker: node-modules\n", "utf8");
|
|
3632
|
+
return [YARN_CONFIG_FILE];
|
|
3633
|
+
}
|
|
3634
|
+
function runProjectInstall(projectRoot, options = {}) {
|
|
3635
|
+
const packageJsonPath = join4(projectRoot, "package.json");
|
|
3636
|
+
if (!existsSync2(packageJsonPath)) return;
|
|
3637
|
+
const packageManager = resolvePackageManager(projectRoot, options);
|
|
3638
|
+
runPackageManager(projectRoot, packageManager, ["install"]);
|
|
3639
|
+
}
|
|
3640
|
+
function resolvePackageManager(projectRoot, options = {}) {
|
|
3641
|
+
return options.packageManager ?? detectPackageManager(projectRoot, options.env);
|
|
3642
|
+
}
|
|
3643
|
+
function runInstall(projectRoot, dependencies, options = {}) {
|
|
3507
3644
|
if (dependencies.length === 0) return;
|
|
3508
3645
|
const packageJsonPath = join4(projectRoot, "package.json");
|
|
3509
3646
|
if (!existsSync2(packageJsonPath)) {
|
|
3510
3647
|
warn(`No package.json found. Install these dependencies manually: ${dependencies.join(" ")}`);
|
|
3511
3648
|
return;
|
|
3512
3649
|
}
|
|
3513
|
-
const packageManager =
|
|
3514
|
-
|
|
3650
|
+
const packageManager = resolvePackageManager(projectRoot, options);
|
|
3651
|
+
runPackageManager(projectRoot, packageManager, installArgs(packageManager, dependencies));
|
|
3652
|
+
}
|
|
3653
|
+
function runPackageManager(projectRoot, packageManager, args) {
|
|
3515
3654
|
const result = spawnSync(packageManager, args, {
|
|
3516
3655
|
cwd: projectRoot,
|
|
3517
3656
|
stdio: "inherit",
|
|
@@ -3775,6 +3914,12 @@ async function commitPresetInitWrites(input) {
|
|
|
3775
3914
|
// src/project-templates.ts
|
|
3776
3915
|
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
3777
3916
|
import { dirname as dirname5, join as join5 } from "path";
|
|
3917
|
+
function templateCss() {
|
|
3918
|
+
return `@import "tailwindcss";
|
|
3919
|
+
|
|
3920
|
+
${buildBaseCssBlock()}
|
|
3921
|
+
`;
|
|
3922
|
+
}
|
|
3778
3923
|
var PROJECT_TEMPLATE_IDS = ["next", "vite", "astro", "react-router"];
|
|
3779
3924
|
function parseProjectTemplate(value) {
|
|
3780
3925
|
if (!value) return void 0;
|
|
@@ -3886,11 +4031,14 @@ var templateDefinitions = {
|
|
|
3886
4031
|
start: "next start"
|
|
3887
4032
|
},
|
|
3888
4033
|
dependencies: {
|
|
4034
|
+
"clsx": "^2.1.1",
|
|
3889
4035
|
"next": "^16.2.4",
|
|
3890
4036
|
"react": "^19.2.4",
|
|
3891
|
-
"react-dom": "^19.2.4"
|
|
4037
|
+
"react-dom": "^19.2.4",
|
|
4038
|
+
"tailwind-merge": "^3.3.1"
|
|
3892
4039
|
},
|
|
3893
4040
|
devDependencies: {
|
|
4041
|
+
"@tailwindcss/postcss": "^4.0.0",
|
|
3894
4042
|
"@types/node": "^22.10.2",
|
|
3895
4043
|
"@types/react": "^19.0.0",
|
|
3896
4044
|
"@types/react-dom": "^19.0.0",
|
|
@@ -3899,7 +4047,18 @@ var templateDefinitions = {
|
|
|
3899
4047
|
}
|
|
3900
4048
|
})
|
|
3901
4049
|
},
|
|
3902
|
-
{
|
|
4050
|
+
{
|
|
4051
|
+
path: "postcss.config.mjs",
|
|
4052
|
+
content: `const config = {
|
|
4053
|
+
plugins: {
|
|
4054
|
+
"@tailwindcss/postcss": {},
|
|
4055
|
+
},
|
|
4056
|
+
};
|
|
4057
|
+
|
|
4058
|
+
export default config;
|
|
4059
|
+
`
|
|
4060
|
+
},
|
|
4061
|
+
{ path: "src/app/globals.css", content: templateCss() },
|
|
3903
4062
|
{
|
|
3904
4063
|
path: "src/app/layout.tsx",
|
|
3905
4064
|
content: `import type { ReactNode } from "react";
|
|
@@ -3961,11 +4120,14 @@ export default function RootLayout({ children }: { children: ReactNode }) {
|
|
|
3961
4120
|
preview: "vite preview"
|
|
3962
4121
|
},
|
|
3963
4122
|
dependencies: {
|
|
4123
|
+
"clsx": "^2.1.1",
|
|
3964
4124
|
"vite": "^7.3.0",
|
|
3965
4125
|
"react": "^19.2.4",
|
|
3966
|
-
"react-dom": "^19.2.4"
|
|
4126
|
+
"react-dom": "^19.2.4",
|
|
4127
|
+
"tailwind-merge": "^3.3.1"
|
|
3967
4128
|
},
|
|
3968
4129
|
devDependencies: {
|
|
4130
|
+
"@tailwindcss/vite": "^4.0.0",
|
|
3969
4131
|
"@vitejs/plugin-react": "^5.1.2",
|
|
3970
4132
|
"@types/react": "^19.0.0",
|
|
3971
4133
|
"@types/react-dom": "^19.0.0",
|
|
@@ -3975,7 +4137,7 @@ export default function RootLayout({ children }: { children: ReactNode }) {
|
|
|
3975
4137
|
})
|
|
3976
4138
|
},
|
|
3977
4139
|
{ path: "index.html", content: '<div id="root"></div><script type="module" src="/src/main.tsx"></script>\n' },
|
|
3978
|
-
{ path: "src/index.css", content:
|
|
4140
|
+
{ path: "src/index.css", content: templateCss() },
|
|
3979
4141
|
{
|
|
3980
4142
|
path: "src/main.tsx",
|
|
3981
4143
|
content: `import { createRoot } from "react-dom/client";
|
|
@@ -3990,9 +4152,10 @@ createRoot(document.getElementById("root")!).render(
|
|
|
3990
4152
|
path: "vite.config.ts",
|
|
3991
4153
|
content: `import { defineConfig } from "vite";
|
|
3992
4154
|
import react from "@vitejs/plugin-react";
|
|
4155
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
3993
4156
|
|
|
3994
4157
|
export default defineConfig({
|
|
3995
|
-
plugins: [react()],
|
|
4158
|
+
plugins: [react(), tailwindcss()],
|
|
3996
4159
|
resolve: {
|
|
3997
4160
|
alias: {
|
|
3998
4161
|
"@": new URL("./src", import.meta.url).pathname,
|
|
@@ -4019,23 +4182,30 @@ export default defineConfig({
|
|
|
4019
4182
|
dependencies: {
|
|
4020
4183
|
"@astrojs/react": "^4.4.0",
|
|
4021
4184
|
"astro": "^5.16.0",
|
|
4185
|
+
"clsx": "^2.1.1",
|
|
4022
4186
|
"react": "^19.2.4",
|
|
4023
|
-
"react-dom": "^19.2.4"
|
|
4187
|
+
"react-dom": "^19.2.4",
|
|
4188
|
+
"tailwind-merge": "^3.3.1"
|
|
4024
4189
|
},
|
|
4025
4190
|
devDependencies: {
|
|
4191
|
+
"@tailwindcss/vite": "^4.0.0",
|
|
4026
4192
|
"tailwindcss": "^4.0.0",
|
|
4027
4193
|
"typescript": "^5.7.2"
|
|
4028
4194
|
}
|
|
4029
4195
|
})
|
|
4030
4196
|
},
|
|
4031
|
-
{ path: "src/styles/global.css", content:
|
|
4197
|
+
{ path: "src/styles/global.css", content: templateCss() },
|
|
4032
4198
|
{
|
|
4033
4199
|
path: "astro.config.mjs",
|
|
4034
4200
|
content: `import { defineConfig } from "astro/config";
|
|
4035
4201
|
import react from "@astrojs/react";
|
|
4202
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
4036
4203
|
|
|
4037
4204
|
export default defineConfig({
|
|
4038
4205
|
integrations: [react()],
|
|
4206
|
+
vite: {
|
|
4207
|
+
plugins: [tailwindcss()],
|
|
4208
|
+
},
|
|
4039
4209
|
});
|
|
4040
4210
|
`
|
|
4041
4211
|
},
|
|
@@ -4064,12 +4234,15 @@ import "../styles/global.css";
|
|
|
4064
4234
|
preview: "vite preview"
|
|
4065
4235
|
},
|
|
4066
4236
|
dependencies: {
|
|
4237
|
+
"clsx": "^2.1.1",
|
|
4067
4238
|
"vite": "^7.3.0",
|
|
4068
4239
|
"react": "^19.2.4",
|
|
4069
4240
|
"react-dom": "^19.2.4",
|
|
4070
|
-
"react-router": "^7.12.0"
|
|
4241
|
+
"react-router": "^7.12.0",
|
|
4242
|
+
"tailwind-merge": "^3.3.1"
|
|
4071
4243
|
},
|
|
4072
4244
|
devDependencies: {
|
|
4245
|
+
"@tailwindcss/vite": "^4.0.0",
|
|
4073
4246
|
"@vitejs/plugin-react": "^5.1.2",
|
|
4074
4247
|
"@types/react": "^19.0.0",
|
|
4075
4248
|
"@types/react-dom": "^19.0.0",
|
|
@@ -4079,7 +4252,7 @@ import "../styles/global.css";
|
|
|
4079
4252
|
})
|
|
4080
4253
|
},
|
|
4081
4254
|
{ path: "index.html", content: '<div id="root"></div><script type="module" src="/src/main.tsx"></script>\n' },
|
|
4082
|
-
{ path: "src/app/globals.css", content:
|
|
4255
|
+
{ path: "src/app/globals.css", content: templateCss() },
|
|
4083
4256
|
{
|
|
4084
4257
|
path: "src/main.tsx",
|
|
4085
4258
|
content: `import { createRoot } from "react-dom/client";
|
|
@@ -4099,9 +4272,10 @@ createRoot(document.getElementById("root")!).render(
|
|
|
4099
4272
|
path: "vite.config.ts",
|
|
4100
4273
|
content: `import { defineConfig } from "vite";
|
|
4101
4274
|
import react from "@vitejs/plugin-react";
|
|
4275
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
4102
4276
|
|
|
4103
4277
|
export default defineConfig({
|
|
4104
|
-
plugins: [react()],
|
|
4278
|
+
plugins: [react(), tailwindcss()],
|
|
4105
4279
|
resolve: {
|
|
4106
4280
|
alias: {
|
|
4107
4281
|
"@": new URL("./src", import.meta.url).pathname,
|
|
@@ -4363,6 +4537,8 @@ var ALWAYS_PRESENT_PACKAGES = /* @__PURE__ */ new Set(["react", "react-dom", "ne
|
|
|
4363
4537
|
var OVERLAY_HELPER_PATH = "components/wensity/overlay.tsx";
|
|
4364
4538
|
var PRIMITIVE_CSS_START_MARKER = "/* \u2500\u2500 Wensity Primitive Tokens";
|
|
4365
4539
|
var PRIMITIVE_CSS_END_MARKER = "/* \u2500\u2500 End Wensity Primitive Tokens \u2500\u2500 */";
|
|
4540
|
+
var BASE_CSS_START_MARKER = "/* \u2500\u2500 Wensity Base Tokens";
|
|
4541
|
+
var BASE_CSS_END_MARKER = "/* \u2500\u2500 End Wensity Base Tokens \u2500\u2500 */";
|
|
4366
4542
|
async function prepareInstall(projectRoot, config, payload, options = {}) {
|
|
4367
4543
|
const fallbackFileName = componentFileName(payload.slug);
|
|
4368
4544
|
const files = [];
|
|
@@ -4453,7 +4629,8 @@ async function planPrimitiveCssFile(projectRoot, config, payload) {
|
|
|
4453
4629
|
if (!cssRelative) return void 0;
|
|
4454
4630
|
const absolutePath = resolveInside(projectRoot, cssRelative);
|
|
4455
4631
|
const existing = await fileExists(absolutePath) ? await readFile5(absolutePath, "utf8") : "";
|
|
4456
|
-
const
|
|
4632
|
+
const withBase = upsertBaseCssBlock(existing, buildBaseCssBlock(baseColorFromStyle(style)));
|
|
4633
|
+
const withTokens = upsertPrimitiveCssBlock(withBase, primitiveCss);
|
|
4457
4634
|
const fonts = curatedFontsFromStyle(style);
|
|
4458
4635
|
const fontPlan = planFontInstall(fonts.headingFont, fonts.bodyFont);
|
|
4459
4636
|
const next = upsertFontImportBlock(withTokens, fontPlan.block);
|
|
@@ -4466,19 +4643,44 @@ async function planPrimitiveCssFile(projectRoot, config, payload) {
|
|
|
4466
4643
|
};
|
|
4467
4644
|
}
|
|
4468
4645
|
function upsertPrimitiveCssBlock(existingCss, block) {
|
|
4646
|
+
return upsertDelimitedBlock(existingCss, block, {
|
|
4647
|
+
startMarker: PRIMITIVE_CSS_START_MARKER,
|
|
4648
|
+
endMarker: PRIMITIVE_CSS_END_MARKER,
|
|
4649
|
+
label: "Wensity Primitive Tokens"
|
|
4650
|
+
});
|
|
4651
|
+
}
|
|
4652
|
+
function upsertBaseCssBlock(existingCss, block) {
|
|
4653
|
+
return upsertDelimitedBlock(existingCss, block, {
|
|
4654
|
+
startMarker: BASE_CSS_START_MARKER,
|
|
4655
|
+
endMarker: BASE_CSS_END_MARKER,
|
|
4656
|
+
label: "Wensity Base Tokens",
|
|
4657
|
+
insertBeforeMarker: PRIMITIVE_CSS_START_MARKER
|
|
4658
|
+
});
|
|
4659
|
+
}
|
|
4660
|
+
function upsertDelimitedBlock(existingCss, block, options) {
|
|
4469
4661
|
const normalizedBlock = block.trim();
|
|
4470
|
-
const start = existingCss.indexOf(
|
|
4471
|
-
const end = existingCss.indexOf(
|
|
4662
|
+
const start = existingCss.indexOf(options.startMarker);
|
|
4663
|
+
const end = existingCss.indexOf(options.endMarker);
|
|
4472
4664
|
if (start !== -1 && end !== -1 && end > start) {
|
|
4473
4665
|
const before = existingCss.slice(0, start).replace(/\s*$/, "\n\n");
|
|
4474
|
-
const after = existingCss.slice(end +
|
|
4666
|
+
const after = existingCss.slice(end + options.endMarker.length).replace(/^\s*/, "\n");
|
|
4475
4667
|
return `${before}${normalizedBlock}${after}`.replace(/\n{3,}/g, "\n\n");
|
|
4476
4668
|
}
|
|
4477
4669
|
if (start !== -1 || end !== -1) {
|
|
4478
4670
|
throw new CliError(
|
|
4479
|
-
|
|
4671
|
+
`Configured Tailwind CSS has a partial ${options.label} block. Remove the broken markers and re-run install.`
|
|
4480
4672
|
);
|
|
4481
4673
|
}
|
|
4674
|
+
if (options.insertBeforeMarker) {
|
|
4675
|
+
const anchor = existingCss.indexOf(options.insertBeforeMarker);
|
|
4676
|
+
if (anchor !== -1) {
|
|
4677
|
+
const before = existingCss.slice(0, anchor).replace(/\s*$/, "\n\n");
|
|
4678
|
+
const after = existingCss.slice(anchor);
|
|
4679
|
+
return `${before}${normalizedBlock}
|
|
4680
|
+
|
|
4681
|
+
${after}`.replace(/\n{3,}/g, "\n\n");
|
|
4682
|
+
}
|
|
4683
|
+
}
|
|
4482
4684
|
const trimmed = existingCss.replace(/\s*$/, "");
|
|
4483
4685
|
if (!trimmed) return `${normalizedBlock}
|
|
4484
4686
|
`;
|
|
@@ -4722,6 +4924,7 @@ async function initCommand(context, parsed) {
|
|
|
4722
4924
|
const template = templateId ? getProjectTemplateDefinition(templateId) : void 0;
|
|
4723
4925
|
const hasPreset = Boolean(presetCode || presetFile);
|
|
4724
4926
|
const dependencies = ["clsx", "tailwind-merge"];
|
|
4927
|
+
const installOptions = installOptionsFrom(context, parsed);
|
|
4725
4928
|
if (hasPreset) {
|
|
4726
4929
|
await initWithPreset(context, {
|
|
4727
4930
|
dryRun,
|
|
@@ -4731,11 +4934,13 @@ async function initCommand(context, parsed) {
|
|
|
4731
4934
|
presetCode,
|
|
4732
4935
|
presetFile,
|
|
4733
4936
|
dependencies,
|
|
4734
|
-
template
|
|
4937
|
+
template,
|
|
4938
|
+
installOptions
|
|
4735
4939
|
});
|
|
4736
4940
|
return;
|
|
4737
4941
|
}
|
|
4738
4942
|
const templateFiles = template ? await scaffoldProjectTemplate(context.cwd, template, { dryRun, force }) : [];
|
|
4943
|
+
const packageManagerFiles = template ? prepareProjectPackageManager(context.cwd, { ...installOptions, dryRun }) : [];
|
|
4739
4944
|
const config = template ? applyTemplateToConfig(createDefaultConfig(context.cwd, apiUrl), template) : createDefaultConfig(context.cwd, apiUrl);
|
|
4740
4945
|
const configPath = join7(context.cwd, CONFIG_FILE_NAME);
|
|
4741
4946
|
const utilityPath = resolveConfiguredFilePath(context.cwd, config.utils);
|
|
@@ -4743,7 +4948,12 @@ async function initCommand(context, parsed) {
|
|
|
4743
4948
|
if (dryRun) {
|
|
4744
4949
|
printTitle("Project initialization preview");
|
|
4745
4950
|
print(action("Would write:"));
|
|
4746
|
-
print(formatList([
|
|
4951
|
+
print(formatList([
|
|
4952
|
+
...formatTemplateFiles(templateFiles),
|
|
4953
|
+
...packageManagerFiles,
|
|
4954
|
+
CONFIG_FILE_NAME,
|
|
4955
|
+
utilityRelativePath
|
|
4956
|
+
]));
|
|
4747
4957
|
if (template) print(action(`Template: ${template.label}`));
|
|
4748
4958
|
if (install) print(action(`Would install: ${dependencies.join(" ")}`));
|
|
4749
4959
|
return;
|
|
@@ -4753,14 +4963,24 @@ async function initCommand(context, parsed) {
|
|
|
4753
4963
|
await writeTextFile(utilityPath, cnUtilityTemplate());
|
|
4754
4964
|
}
|
|
4755
4965
|
if (install) {
|
|
4756
|
-
|
|
4757
|
-
|
|
4966
|
+
if (template) runProjectInstall(context.cwd, installOptions);
|
|
4967
|
+
runInstall(context.cwd, missingDependencies(context.cwd, dependencies), installOptions);
|
|
4968
|
+
}
|
|
4969
|
+
await assertInitPostconditions(context.cwd, [
|
|
4970
|
+
...templateFiles,
|
|
4971
|
+
...packageManagerFiles,
|
|
4972
|
+
CONFIG_FILE_NAME,
|
|
4973
|
+
utilityRelativePath
|
|
4974
|
+
]);
|
|
4758
4975
|
printTitle("Project initialized");
|
|
4759
4976
|
print(panel("Created", [
|
|
4977
|
+
...formatTemplateFiles(templateFiles).map((file) => success(file)),
|
|
4978
|
+
...packageManagerFiles.map((file) => success(file)),
|
|
4760
4979
|
success(CONFIG_FILE_NAME),
|
|
4761
4980
|
success(utilityRelativePath),
|
|
4762
4981
|
success(`registry: ${apiUrl}`)
|
|
4763
4982
|
]));
|
|
4983
|
+
print(action(`Location: ${context.cwd}`));
|
|
4764
4984
|
print(nextSteps([
|
|
4765
4985
|
"Browse available components: npx wensity list",
|
|
4766
4986
|
"Browse premium blocks: npx wensity list --type block",
|
|
@@ -4778,6 +4998,10 @@ async function initWithPreset(context, options) {
|
|
|
4778
4998
|
dryRun: options.dryRun,
|
|
4779
4999
|
force: options.force
|
|
4780
5000
|
}) : [];
|
|
5001
|
+
const packageManagerFiles = options.template ? prepareProjectPackageManager(context.cwd, {
|
|
5002
|
+
...options.installOptions,
|
|
5003
|
+
dryRun: options.dryRun
|
|
5004
|
+
}) : [];
|
|
4781
5005
|
let config;
|
|
4782
5006
|
if (configExists && !options.force) {
|
|
4783
5007
|
if (options.template) {
|
|
@@ -4801,7 +5025,12 @@ async function initWithPreset(context, options) {
|
|
|
4801
5025
|
if (options.dryRun) {
|
|
4802
5026
|
printTitle("Project initialization preview (preset)");
|
|
4803
5027
|
print(action("Would write:"));
|
|
4804
|
-
const planned = [
|
|
5028
|
+
const planned = [
|
|
5029
|
+
...formatTemplateFiles(templateFiles),
|
|
5030
|
+
...packageManagerFiles,
|
|
5031
|
+
CONFIG_FILE_NAME,
|
|
5032
|
+
utilityRelativePath
|
|
5033
|
+
];
|
|
4805
5034
|
if (prepared.cssChanged) planned.push(toPosixPath(prepared.cssRelative));
|
|
4806
5035
|
print(formatList(planned));
|
|
4807
5036
|
if (options.template) print(action(`Template: ${options.template.label}`));
|
|
@@ -4832,11 +5061,25 @@ async function initWithPreset(context, options) {
|
|
|
4832
5061
|
}
|
|
4833
5062
|
});
|
|
4834
5063
|
if (options.install) {
|
|
4835
|
-
|
|
5064
|
+
if (options.template) runProjectInstall(context.cwd, options.installOptions);
|
|
5065
|
+
runInstall(
|
|
5066
|
+
context.cwd,
|
|
5067
|
+
missingDependencies(context.cwd, installDependencies),
|
|
5068
|
+
options.installOptions
|
|
5069
|
+
);
|
|
4836
5070
|
}
|
|
5071
|
+
await assertInitPostconditions(context.cwd, [
|
|
5072
|
+
...templateFiles,
|
|
5073
|
+
...packageManagerFiles,
|
|
5074
|
+
CONFIG_FILE_NAME,
|
|
5075
|
+
utilityRelativePath,
|
|
5076
|
+
prepared.cssRelative
|
|
5077
|
+
]);
|
|
4837
5078
|
printTitle("Project initialized");
|
|
4838
5079
|
const created = [
|
|
4839
5080
|
...options.template ? [success(`template: ${options.template.label}`)] : [],
|
|
5081
|
+
...formatTemplateFiles(templateFiles).map((file) => success(file)),
|
|
5082
|
+
...packageManagerFiles.map((file) => success(file)),
|
|
4840
5083
|
success(CONFIG_FILE_NAME),
|
|
4841
5084
|
success(utilityRelativePath),
|
|
4842
5085
|
success(`registry: ${options.apiUrl}`),
|
|
@@ -4851,6 +5094,7 @@ async function initWithPreset(context, options) {
|
|
|
4851
5094
|
created.push(success(`fonts: ${prepared.fontPlan.packages.join(", ")}`));
|
|
4852
5095
|
}
|
|
4853
5096
|
print(panel("Created", created));
|
|
5097
|
+
print(action(`Location: ${context.cwd}`));
|
|
4854
5098
|
if (prepared.unusedFontWarning) warn(prepared.unusedFontWarning);
|
|
4855
5099
|
print(nextSteps([
|
|
4856
5100
|
"Browse available components: npx wensity list",
|
|
@@ -4915,7 +5159,11 @@ async function applyCommand(context, parsed) {
|
|
|
4915
5159
|
}
|
|
4916
5160
|
});
|
|
4917
5161
|
if (install && installDependencies.length > 0) {
|
|
4918
|
-
runInstall(
|
|
5162
|
+
runInstall(
|
|
5163
|
+
loaded.projectRoot,
|
|
5164
|
+
missingDependencies(loaded.projectRoot, installDependencies),
|
|
5165
|
+
installOptionsFrom(context, parsed)
|
|
5166
|
+
);
|
|
4919
5167
|
}
|
|
4920
5168
|
printTitle("Preset applied");
|
|
4921
5169
|
const applied = [
|
|
@@ -4965,7 +5213,8 @@ async function preparePresetStyleApplication(projectRoot, config, style, operati
|
|
|
4965
5213
|
}
|
|
4966
5214
|
const cssAbsolute = resolveInside(projectRoot, cssRelative);
|
|
4967
5215
|
const existingCss = await fileExists(cssAbsolute) ? await readFile6(cssAbsolute, "utf8") : "";
|
|
4968
|
-
const
|
|
5216
|
+
const withBase = upsertBaseCssBlock(existingCss, buildBaseCssBlock(baseColorFromStyle(style)));
|
|
5217
|
+
const withTokens = upsertPrimitiveCssBlock(withBase, primitiveCss);
|
|
4969
5218
|
const nextCss = upsertFontImportBlock(withTokens, fontPlan.block);
|
|
4970
5219
|
const cssChanged = nextCss !== existingCss;
|
|
4971
5220
|
const installedPackages = readInstalledPackageNames(projectRoot);
|
|
@@ -5095,13 +5344,13 @@ function formatBlockGroup(blocks) {
|
|
|
5095
5344
|
}
|
|
5096
5345
|
async function addCommand(context, parsed, positionals, updateMode) {
|
|
5097
5346
|
const resourceType = getResourceType(parsed);
|
|
5098
|
-
const
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5347
|
+
const command = updateMode ? "update" : "add";
|
|
5348
|
+
const usage = `Usage: wensity ${command} <slug...>${resourceType === "block" ? " --type block" : ""}`;
|
|
5349
|
+
const slugs = dedupe(positionals);
|
|
5350
|
+
if (slugs.length === 0) {
|
|
5351
|
+
throw new CliError(`Missing ${resourceType} slug.
|
|
5102
5352
|
|
|
5103
|
-
|
|
5104
|
-
);
|
|
5353
|
+
${usage}`);
|
|
5105
5354
|
}
|
|
5106
5355
|
const apiUrl = getStringOption(parsed, "api-url") ?? context.env.WENSITY_API_URL;
|
|
5107
5356
|
const registryOverride = getStringOption(parsed, "registry");
|
|
@@ -5109,40 +5358,67 @@ Usage: wensity ${updateMode ? "update" : "add"} <slug>${resourceType === "block"
|
|
|
5109
5358
|
const credentials = await readCredentials(context.env);
|
|
5110
5359
|
const registry = registryUrlForType(loaded.config.registry, resourceType, registryOverride);
|
|
5111
5360
|
const force = updateMode || hasFlag(parsed, "force");
|
|
5112
|
-
const payload = resourceType === "block" ? await fetchBlockPayload({ slug, registry, credentials }) : await fetchComponentPayload({ slug, registry, credentials });
|
|
5113
|
-
const prepared = await prepareInstall(loaded.projectRoot, loaded.config, payload, {
|
|
5114
|
-
overwriteExistingDependencyFiles: force
|
|
5115
|
-
});
|
|
5116
5361
|
const dryRun = hasFlag(parsed, "dry-run");
|
|
5117
5362
|
const install = !hasFlag(parsed, "no-install");
|
|
5118
|
-
const
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5363
|
+
const payloads = [];
|
|
5364
|
+
for (const slug of slugs) {
|
|
5365
|
+
payloads.push(
|
|
5366
|
+
resourceType === "block" ? await fetchBlockPayload({ slug, registry, credentials }) : await fetchComponentPayload({ slug, registry, credentials })
|
|
5367
|
+
);
|
|
5368
|
+
}
|
|
5122
5369
|
const style = normalizeProjectStyle(loaded.config.style);
|
|
5123
5370
|
const fonts = curatedFontsFromStyle(style);
|
|
5124
5371
|
const installedPackages = readInstalledPackageNames(loaded.projectRoot);
|
|
5125
|
-
const oppositeIconWarning = installedPackages && prepared.iconDependencySwap ? warnOppositeIconPackageMessage(style.iconLibrary, installedPackages) : void 0;
|
|
5126
5372
|
const unusedFontWarning = installedPackages ? warnUnusedFontPackageMessage(fonts.headingFont, fonts.bodyFont, installedPackages) : void 0;
|
|
5373
|
+
const writtenFiles = [];
|
|
5374
|
+
const cssTargets = [];
|
|
5375
|
+
const dependencies = [];
|
|
5376
|
+
const installPlans = [];
|
|
5377
|
+
let iconDependencySwapped = false;
|
|
5378
|
+
for (const payload of payloads) {
|
|
5379
|
+
const prepared = await prepareInstall(loaded.projectRoot, loaded.config, payload, {
|
|
5380
|
+
overwriteExistingDependencyFiles: force
|
|
5381
|
+
});
|
|
5382
|
+
const plannedInstallFiles = prepared.cssFile ? [...prepared.files, prepared.cssFile] : prepared.files;
|
|
5383
|
+
installPlans.push(prepared);
|
|
5384
|
+
writtenFiles.push(...summarizeFiles(loaded.projectRoot, plannedInstallFiles));
|
|
5385
|
+
if (prepared.cssFile) cssTargets.push(prepared.cssFile.relativePath);
|
|
5386
|
+
dependencies.push(...prepared.dependencies);
|
|
5387
|
+
if (prepared.iconDependencySwap) iconDependencySwapped = true;
|
|
5388
|
+
}
|
|
5389
|
+
const allPlannedInstallFiles = installPlans.flatMap(
|
|
5390
|
+
(prepared) => prepared.cssFile ? [...prepared.files, prepared.cssFile] : prepared.files
|
|
5391
|
+
);
|
|
5392
|
+
await assertNoConflicts(allPlannedInstallFiles, force || dryRun);
|
|
5393
|
+
const plannedFiles = dedupe(writtenFiles);
|
|
5394
|
+
const missing = missingDependencies(loaded.projectRoot, dedupe(dependencies));
|
|
5395
|
+
const oppositeIconWarning = installedPackages && iconDependencySwapped ? warnOppositeIconPackageMessage(style.iconLibrary, installedPackages) : void 0;
|
|
5127
5396
|
if (dryRun) {
|
|
5128
5397
|
printTitle(updateMode ? "Update preview" : "Install preview");
|
|
5129
5398
|
print(action("Would write:"));
|
|
5130
5399
|
print(formatList(plannedFiles));
|
|
5131
|
-
|
|
5132
|
-
print(action(`Would update CSS tokens in: ${
|
|
5400
|
+
for (const target of dedupe(cssTargets)) {
|
|
5401
|
+
print(action(`Would update CSS tokens in: ${target}`));
|
|
5133
5402
|
}
|
|
5134
5403
|
if (install && missing.length > 0) print(action(`Would install: ${missing.join(" ")}`));
|
|
5135
5404
|
if (oppositeIconWarning) warn(oppositeIconWarning);
|
|
5136
5405
|
if (unusedFontWarning) warn(unusedFontWarning);
|
|
5137
5406
|
return;
|
|
5138
5407
|
}
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5408
|
+
for (const prepared of installPlans) {
|
|
5409
|
+
await writePlannedFiles(
|
|
5410
|
+
prepared.cssFile ? [...prepared.files, prepared.cssFile] : prepared.files
|
|
5411
|
+
);
|
|
5142
5412
|
}
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5413
|
+
if (install) {
|
|
5414
|
+
runInstall(loaded.projectRoot, missing, installOptionsFrom(context, parsed));
|
|
5415
|
+
}
|
|
5416
|
+
const noun = payloads.length === 1 ? "Component" : "Components";
|
|
5417
|
+
printTitle(updateMode ? `${noun} updated` : `${noun} installed`);
|
|
5418
|
+
print(panel(payloads.map((payload) => payload.name).join(", "), [
|
|
5419
|
+
...payloads.map(
|
|
5420
|
+
(payload) => success(`${updateMode ? "Updated" : "Added"} ${payload.slug}`)
|
|
5421
|
+
),
|
|
5146
5422
|
...plannedFiles.map((file) => success(file))
|
|
5147
5423
|
]));
|
|
5148
5424
|
if (oppositeIconWarning) warn(oppositeIconWarning);
|
|
@@ -5156,10 +5432,13 @@ Usage: wensity ${updateMode ? "update" : "add"} <slug>${resourceType === "block"
|
|
|
5156
5432
|
return;
|
|
5157
5433
|
}
|
|
5158
5434
|
print(nextSteps([
|
|
5159
|
-
"Import the installed file from your Wensity components folder",
|
|
5435
|
+
payloads.length === 1 ? "Import the installed file from your Wensity components folder" : "Import the installed files from your Wensity components folder",
|
|
5160
5436
|
resourceType === "block" ? "Customize the block copy, colors, and sections" : "Customize props and styling for your app"
|
|
5161
5437
|
]));
|
|
5162
5438
|
}
|
|
5439
|
+
function dedupe(values) {
|
|
5440
|
+
return [...new Set(values)];
|
|
5441
|
+
}
|
|
5163
5442
|
async function fetchComponentPayload(input) {
|
|
5164
5443
|
const metadata = await withSpinner(
|
|
5165
5444
|
`Finding ${input.slug}`,
|
|
@@ -5272,6 +5551,32 @@ async function writeTextFile(path, content) {
|
|
|
5272
5551
|
await mkdir6(dirname7(path), { recursive: true });
|
|
5273
5552
|
await writeFile6(path, content, "utf8");
|
|
5274
5553
|
}
|
|
5554
|
+
function installOptionsFrom(context, parsed) {
|
|
5555
|
+
return {
|
|
5556
|
+
packageManager: parsePackageManager(getStringOption(parsed, "package-manager")),
|
|
5557
|
+
env: context.env
|
|
5558
|
+
};
|
|
5559
|
+
}
|
|
5560
|
+
async function assertInitPostconditions(projectRoot, relativePaths) {
|
|
5561
|
+
const missing = [];
|
|
5562
|
+
for (const relativePath of new Set(relativePaths)) {
|
|
5563
|
+
const absolute = join7(projectRoot, relativePath);
|
|
5564
|
+
let size = 0;
|
|
5565
|
+
try {
|
|
5566
|
+
size = (await stat(absolute)).size;
|
|
5567
|
+
} catch {
|
|
5568
|
+
missing.push(relativePath);
|
|
5569
|
+
continue;
|
|
5570
|
+
}
|
|
5571
|
+
if (size === 0) missing.push(relativePath);
|
|
5572
|
+
}
|
|
5573
|
+
if (missing.length > 0) {
|
|
5574
|
+
throw new CliError(
|
|
5575
|
+
`Initialization did not complete. These files are missing or empty in ${projectRoot}:
|
|
5576
|
+
` + missing.map((file) => ` - ${toPosixPath(file)}`).join("\n")
|
|
5577
|
+
);
|
|
5578
|
+
}
|
|
5579
|
+
}
|
|
5275
5580
|
|
|
5276
5581
|
// src/index.ts
|
|
5277
5582
|
run({
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wensity",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.12",
|
|
4
|
+
"description": "Install React components, UI primitives, and shareable Tailwind CSS presets into your Next.js project as source code you own.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Wensity <hey@wensity.com> (https://wensity.com)",
|
|
7
|
-
"homepage": "https://ui.wensity.com",
|
|
7
|
+
"homepage": "https://ui.wensity.com/docs/cli",
|
|
8
8
|
"bugs": {
|
|
9
|
+
"url": "https://github.com/wensity/cli/issues",
|
|
9
10
|
"email": "hey@wensity.com"
|
|
10
11
|
},
|
|
11
12
|
"keywords": [
|
|
@@ -13,10 +14,17 @@
|
|
|
13
14
|
"react",
|
|
14
15
|
"nextjs",
|
|
15
16
|
"tailwindcss",
|
|
17
|
+
"tailwind-v4",
|
|
16
18
|
"components",
|
|
17
19
|
"ui",
|
|
20
|
+
"ui-components",
|
|
18
21
|
"design-system",
|
|
19
|
-
"
|
|
22
|
+
"design-tokens",
|
|
23
|
+
"theme",
|
|
24
|
+
"shadcn",
|
|
25
|
+
"shadcn-ui",
|
|
26
|
+
"base-ui",
|
|
27
|
+
"cli"
|
|
20
28
|
],
|
|
21
29
|
"type": "module",
|
|
22
30
|
"bin": {
|
|
@@ -29,16 +37,22 @@
|
|
|
29
37
|
"dist",
|
|
30
38
|
"README.md"
|
|
31
39
|
],
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsup --config tsup.config.ts",
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"test": "vitest run --config vitest.config.ts",
|
|
44
|
+
"lint": "echo 'no lint'"
|
|
45
|
+
},
|
|
32
46
|
"engines": {
|
|
33
47
|
"node": ">=20.0.0"
|
|
34
48
|
},
|
|
35
49
|
"devDependencies": {
|
|
36
50
|
"@types/node": "^22.10.2",
|
|
51
|
+
"@wensity/preset": "workspace:*",
|
|
37
52
|
"lucide-react": "^1.24.0",
|
|
38
53
|
"tsup": "^8.3.5",
|
|
39
54
|
"typescript": "^5.7.2",
|
|
40
|
-
"vitest": "^4.1.5"
|
|
41
|
-
"@wensity/preset": "0.1.0"
|
|
55
|
+
"vitest": "^4.1.5"
|
|
42
56
|
},
|
|
43
57
|
"publishConfig": {
|
|
44
58
|
"access": "public"
|
|
@@ -49,10 +63,8 @@
|
|
|
49
63
|
"ora": "^9.4.0",
|
|
50
64
|
"picocolors": "^1.1.1"
|
|
51
65
|
},
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"test": "vitest run --config vitest.config.ts",
|
|
56
|
-
"lint": "echo 'no lint'"
|
|
66
|
+
"repository": {
|
|
67
|
+
"type": "git",
|
|
68
|
+
"url": "git+https://github.com/wensity/cli.git"
|
|
57
69
|
}
|
|
58
|
-
}
|
|
70
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Wensity Private Limited
|
|
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.
|