wensity 0.1.11 → 0.1.13
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 +169 -40
- package/dist/index.js +2289 -647
- package/package.json +25 -12
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/commands.ts
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { randomBytes } from "crypto";
|
|
5
|
+
import { existsSync as existsSync5 } from "fs";
|
|
6
|
+
import { mkdir as mkdir7, readFile as readFile8, stat, writeFile as writeFile6 } from "fs/promises";
|
|
7
|
+
import { dirname as dirname8, join as join11, relative as relative8 } from "path";
|
|
6
8
|
|
|
7
|
-
// src/constants.ts
|
|
8
|
-
var CLI_NAME = "wensity";
|
|
9
|
-
var CLI_VERSION = "0.1.11";
|
|
9
|
+
// ../install-core/src/constants.ts
|
|
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";
|
|
@@ -14,6 +14,10 @@ var DEFAULT_COMPONENTS_DIR = "src/components/wensity";
|
|
|
14
14
|
var DEFAULT_UTILS_PATH = "src/lib/utils";
|
|
15
15
|
var DEFAULT_COMPONENTS_ALIAS = "@/components/wensity";
|
|
16
16
|
var DEFAULT_UTILS_ALIAS = "@/lib/utils";
|
|
17
|
+
|
|
18
|
+
// src/constants.ts
|
|
19
|
+
var CLI_NAME = "wensity";
|
|
20
|
+
var CLI_VERSION = "0.1.13";
|
|
17
21
|
var INIT_PRIMITIVE_CSS_SLUG = "button";
|
|
18
22
|
var HELP_TEXT = `Wensity CLI ${CLI_VERSION}
|
|
19
23
|
|
|
@@ -25,8 +29,8 @@ Usage:
|
|
|
25
29
|
wensity logout
|
|
26
30
|
wensity whoami [--json]
|
|
27
31
|
wensity list [--type component|block] [options]
|
|
28
|
-
wensity add <slug
|
|
29
|
-
wensity update <slug
|
|
32
|
+
wensity add <slug...> [--type component|block] [options]
|
|
33
|
+
wensity update <slug...> [--type component|block] [options]
|
|
30
34
|
wensity version
|
|
31
35
|
|
|
32
36
|
Commands:
|
|
@@ -37,8 +41,8 @@ Commands:
|
|
|
37
41
|
logout Remove saved CLI credentials
|
|
38
42
|
whoami Verify the saved token and print the authenticated account
|
|
39
43
|
list List registry components or blocks
|
|
40
|
-
add Download
|
|
41
|
-
update Re-download
|
|
44
|
+
add Download one or more component/block source files into this project
|
|
45
|
+
update Re-download one or more components/blocks and overwrite the local copies
|
|
42
46
|
version Print the CLI version
|
|
43
47
|
|
|
44
48
|
Options:
|
|
@@ -49,6 +53,7 @@ Options:
|
|
|
49
53
|
--preset <code> Apply a shareable Wensity style preset (wsty1.\u2026); merges style when config exists
|
|
50
54
|
--preset-file <path> Read a preset code or JSON payload from a file
|
|
51
55
|
--template <name> Scaffold a new app before init: next, vite, astro, react-router
|
|
56
|
+
--package-manager <pm> Force npm, pnpm, yarn, or bun instead of auto-detection
|
|
52
57
|
--force Overwrite generated files when adding or initializing
|
|
53
58
|
--no-install Write files without running the package manager
|
|
54
59
|
--dry-run Print planned writes and installs without changing files
|
|
@@ -63,80 +68,954 @@ Preset notes:
|
|
|
63
68
|
Plain init refuses an existing wensity.json unless --force
|
|
64
69
|
init --preset merges style + primitive CSS; does not rewrite installed source
|
|
65
70
|
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)
|
|
71
|
+
add/update accept several slugs at once and read style.iconLibrary for UI primitives only (Tabler|Lucide)
|
|
67
72
|
Invalid/expired/unsupported codes exit non-zero with an actionable message
|
|
68
73
|
`;
|
|
69
74
|
|
|
70
|
-
// src/errors.ts
|
|
71
|
-
var
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
// ../install-core/src/errors.ts
|
|
76
|
+
var RETRYABLE_CODES = /* @__PURE__ */ new Set([
|
|
77
|
+
"rate_limited",
|
|
78
|
+
"network_error",
|
|
79
|
+
"server_error",
|
|
80
|
+
"source_unavailable",
|
|
81
|
+
"source_integrity_failed",
|
|
82
|
+
"plan_in_progress",
|
|
83
|
+
"write_failed",
|
|
84
|
+
"dependency_install_failed"
|
|
85
|
+
]);
|
|
86
|
+
var WensityError = class extends Error {
|
|
87
|
+
code;
|
|
88
|
+
retryable;
|
|
89
|
+
upstream;
|
|
90
|
+
details;
|
|
91
|
+
constructor(code, message, options = {}) {
|
|
92
|
+
super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
|
|
93
|
+
this.name = "WensityError";
|
|
94
|
+
this.code = code;
|
|
95
|
+
this.retryable = RETRYABLE_CODES.has(code);
|
|
96
|
+
if (options.upstream) this.upstream = options.upstream;
|
|
97
|
+
if (options.details) this.details = options.details;
|
|
77
98
|
}
|
|
78
99
|
};
|
|
100
|
+
function isWensityError(error) {
|
|
101
|
+
return error instanceof WensityError;
|
|
102
|
+
}
|
|
79
103
|
function isNodeError(error) {
|
|
80
104
|
return error instanceof Error && "code" in error;
|
|
81
105
|
}
|
|
82
106
|
|
|
83
|
-
// src/
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
107
|
+
// ../install-core/src/http.ts
|
|
108
|
+
var DEFAULT_RETRY_POLICY = { maxAttempts: 3, baseDelayMs: 500, maxRetryAfterMs: 2e3 };
|
|
109
|
+
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
|
|
110
|
+
var CircuitBreakers = class {
|
|
111
|
+
#options;
|
|
112
|
+
#state = /* @__PURE__ */ new Map();
|
|
113
|
+
constructor(options = {}) {
|
|
114
|
+
this.#options = { threshold: 5, cooldownMs: 3e4, now: Date.now, ...options };
|
|
115
|
+
}
|
|
116
|
+
/** Milliseconds until `origin` may be tried again, or 0 when it may be tried now. */
|
|
117
|
+
remainingMs(origin) {
|
|
118
|
+
const state = this.#state.get(origin);
|
|
119
|
+
return state ? Math.max(0, state.openUntil - this.#options.now()) : 0;
|
|
120
|
+
}
|
|
121
|
+
recordFailure(origin) {
|
|
122
|
+
const state = this.#state.get(origin) ?? { failures: 0, openUntil: 0 };
|
|
123
|
+
state.failures += 1;
|
|
124
|
+
if (state.failures >= this.#options.threshold) state.openUntil = this.#options.now() + this.#options.cooldownMs;
|
|
125
|
+
this.#state.set(origin, state);
|
|
126
|
+
}
|
|
127
|
+
recordSuccess(origin) {
|
|
128
|
+
this.#state.delete(origin);
|
|
129
|
+
}
|
|
130
|
+
reset() {
|
|
131
|
+
this.#state.clear();
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
var sharedCircuitBreakers = new CircuitBreakers();
|
|
135
|
+
async function requestJson(url, options = {}) {
|
|
136
|
+
const {
|
|
137
|
+
token,
|
|
138
|
+
userAgent,
|
|
139
|
+
timeoutMs,
|
|
140
|
+
fetch: fetchImpl = fetch,
|
|
141
|
+
retry,
|
|
142
|
+
breakers = sharedCircuitBreakers,
|
|
143
|
+
sleep = defaultSleep,
|
|
144
|
+
random = Math.random,
|
|
145
|
+
...init
|
|
146
|
+
} = options;
|
|
147
|
+
const headers = new Headers(init.headers);
|
|
148
|
+
headers.set("accept", "application/json");
|
|
149
|
+
if (userAgent) headers.set("user-agent", userAgent);
|
|
150
|
+
if (init.body && !headers.has("content-type")) {
|
|
151
|
+
headers.set("content-type", "application/json");
|
|
152
|
+
}
|
|
153
|
+
if (token) {
|
|
154
|
+
headers.set("authorization", `Bearer ${token}`);
|
|
155
|
+
}
|
|
156
|
+
const policy = { ...DEFAULT_RETRY_POLICY, ...retry || {} };
|
|
157
|
+
const method = (init.method ?? "GET").toUpperCase();
|
|
158
|
+
const idempotent = (method === "GET" || method === "HEAD") && !init.body;
|
|
159
|
+
const maxAttempts = retry === false || !idempotent ? 1 : Math.max(1, Math.floor(policy.maxAttempts));
|
|
160
|
+
const origin = originOf(url);
|
|
161
|
+
let last;
|
|
162
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
163
|
+
const waitMs = breakers && origin ? breakers.remainingMs(origin) : 0;
|
|
164
|
+
if (waitMs > 0) {
|
|
165
|
+
throw last ?? circuitOpen(url, origin, waitMs);
|
|
166
|
+
}
|
|
167
|
+
const outcome = await attemptOnce(url, { ...init, headers }, fetchImpl, timeoutMs);
|
|
168
|
+
if (breakers && origin) {
|
|
169
|
+
if (outcome.kind === "failed" && outcome.countsAsFailure) breakers.recordFailure(origin);
|
|
170
|
+
else breakers.recordSuccess(origin);
|
|
171
|
+
}
|
|
172
|
+
if (outcome.kind === "ok") return outcome.value;
|
|
173
|
+
last = outcome.error;
|
|
174
|
+
if (!outcome.retryable || attempt === maxAttempts) break;
|
|
175
|
+
if (outcome.retryAfterMs !== void 0 && outcome.retryAfterMs > policy.maxRetryAfterMs) break;
|
|
176
|
+
const backoff = random() * policy.baseDelayMs * 2 ** (attempt - 1);
|
|
177
|
+
await sleep(outcome.retryAfterMs ?? backoff);
|
|
178
|
+
}
|
|
179
|
+
throw last;
|
|
180
|
+
}
|
|
181
|
+
async function attemptOnce(url, init, fetchImpl, timeoutMs) {
|
|
182
|
+
const controller = new AbortController();
|
|
183
|
+
let timedOut = false;
|
|
184
|
+
const timer = timeoutMs === void 0 ? void 0 : setTimeout(() => {
|
|
185
|
+
timedOut = true;
|
|
186
|
+
controller.abort(new Error(`timed out after ${timeoutMs} ms`));
|
|
187
|
+
}, timeoutMs);
|
|
188
|
+
const callerSignal = init.signal;
|
|
189
|
+
const forwardAbort = () => controller.abort(callerSignal?.reason);
|
|
190
|
+
if (callerSignal?.aborted) forwardAbort();
|
|
191
|
+
else callerSignal?.addEventListener("abort", forwardAbort, { once: true });
|
|
192
|
+
const transportFailure = (prefix, error) => {
|
|
193
|
+
const message = timedOut ? `timed out after ${timeoutMs} ms` : error instanceof Error ? error.message : "Unknown network error";
|
|
194
|
+
const cancelled = Boolean(callerSignal?.aborted) && !timedOut;
|
|
195
|
+
return {
|
|
196
|
+
kind: "failed",
|
|
197
|
+
error: new WensityError("network_error", `${prefix} ${url}: ${message}`, { cause: error }),
|
|
198
|
+
// A timeout already used the whole budget, and a caller's cancel is not the origin's fault.
|
|
199
|
+
retryable: !timedOut && !cancelled,
|
|
200
|
+
countsAsFailure: !cancelled
|
|
201
|
+
};
|
|
202
|
+
};
|
|
203
|
+
try {
|
|
204
|
+
let response;
|
|
205
|
+
try {
|
|
206
|
+
response = await fetchImpl(url, { ...init, signal: controller.signal });
|
|
207
|
+
} catch (error) {
|
|
208
|
+
return transportFailure("Could not reach", error);
|
|
92
209
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
210
|
+
let text;
|
|
211
|
+
try {
|
|
212
|
+
text = await response.text();
|
|
213
|
+
} catch (error) {
|
|
214
|
+
return transportFailure("Could not read the response from", error);
|
|
96
215
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
216
|
+
if (!response.ok) {
|
|
217
|
+
const upstream = readUpstreamError(response, parseJsonOrNull(text));
|
|
218
|
+
const message = upstream.message ?? upstream.error ?? response.statusText;
|
|
219
|
+
return {
|
|
220
|
+
kind: "failed",
|
|
221
|
+
error: new WensityError(errorCodeFor(upstream), `${response.status} ${message}`, { upstream }),
|
|
222
|
+
retryable: RETRYABLE_STATUSES.has(response.status),
|
|
223
|
+
countsAsFailure: response.status === 429 || response.status >= 500,
|
|
224
|
+
...upstream.retryAfterSeconds !== void 0 ? { retryAfterMs: upstream.retryAfterSeconds * 1e3 } : {}
|
|
225
|
+
};
|
|
102
226
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
227
|
+
return { kind: "ok", value: parseJson(text) };
|
|
228
|
+
} finally {
|
|
229
|
+
if (timer) clearTimeout(timer);
|
|
230
|
+
callerSignal?.removeEventListener("abort", forwardAbort);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function circuitOpen(url, origin, waitMs) {
|
|
234
|
+
const seconds = Math.ceil(waitMs / 1e3);
|
|
235
|
+
return new WensityError(
|
|
236
|
+
"network_error",
|
|
237
|
+
`Could not reach ${url}: requests to ${origin} kept failing, so they are paused for ${seconds} s.`,
|
|
238
|
+
{ details: { circuit: "open", retryAfterSeconds: seconds } }
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
function originOf(url) {
|
|
242
|
+
try {
|
|
243
|
+
return new URL(url).origin;
|
|
244
|
+
} catch {
|
|
245
|
+
return void 0;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function defaultSleep(ms) {
|
|
249
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
250
|
+
}
|
|
251
|
+
function parseJsonOrNull(text) {
|
|
252
|
+
try {
|
|
253
|
+
return parseJson(text);
|
|
254
|
+
} catch {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function parseJson(text) {
|
|
259
|
+
if (!text.trim()) return null;
|
|
260
|
+
try {
|
|
261
|
+
return JSON.parse(text);
|
|
262
|
+
} catch {
|
|
263
|
+
throw new WensityError("source_integrity_failed", "Registry returned invalid JSON.");
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function readUpstreamError(response, payload) {
|
|
267
|
+
const upstream = { status: response.status };
|
|
268
|
+
const record = payload && typeof payload === "object" ? payload : {};
|
|
269
|
+
if (typeof record.error === "string") upstream.error = record.error;
|
|
270
|
+
if (typeof record.message === "string") upstream.message = record.message;
|
|
271
|
+
if (record.plan && typeof record.plan === "object") upstream.plan = record.plan;
|
|
272
|
+
const retryAfter = readRetryAfter(response, record);
|
|
273
|
+
if (retryAfter !== void 0) upstream.retryAfterSeconds = retryAfter;
|
|
274
|
+
return upstream;
|
|
275
|
+
}
|
|
276
|
+
function readRetryAfter(response, record) {
|
|
277
|
+
const rateLimit = record.rateLimit;
|
|
278
|
+
if (rateLimit && typeof rateLimit.retryAfterSeconds === "number") return rateLimit.retryAfterSeconds;
|
|
279
|
+
const header = Number(response.headers.get("retry-after"));
|
|
280
|
+
return Number.isFinite(header) && header > 0 ? header : void 0;
|
|
281
|
+
}
|
|
282
|
+
var UPSTREAM_ERROR_CODES = {
|
|
283
|
+
missing_token: "auth_invalid",
|
|
284
|
+
invalid_token: "auth_invalid",
|
|
285
|
+
token_revoked: "auth_revoked",
|
|
286
|
+
token_user_missing: "auth_user_missing",
|
|
287
|
+
pro_license_required: "entitlement_required",
|
|
288
|
+
component_not_found: "item_not_found",
|
|
289
|
+
block_not_found: "item_not_found",
|
|
290
|
+
invalid_component_slug: "invalid_slug",
|
|
291
|
+
invalid_block_slug: "invalid_slug",
|
|
292
|
+
rate_limit_exceeded: "rate_limited",
|
|
293
|
+
component_source_unreadable: "source_unavailable",
|
|
294
|
+
block_source_unreadable: "source_unavailable",
|
|
295
|
+
component_source_graph_unreadable: "source_unavailable",
|
|
296
|
+
cli_auth_failed: "server_error"
|
|
297
|
+
};
|
|
298
|
+
function errorCodeFor(upstream) {
|
|
299
|
+
const known = upstream.error ? UPSTREAM_ERROR_CODES[upstream.error] : void 0;
|
|
300
|
+
if (known) return known;
|
|
301
|
+
switch (upstream.status) {
|
|
302
|
+
case 401:
|
|
303
|
+
return "auth_invalid";
|
|
304
|
+
// A 403 without `pro_license_required` is not a licensing answer, so it
|
|
305
|
+
// falls through to server_error rather than guessing at entitlement.
|
|
306
|
+
case 404:
|
|
307
|
+
return "item_not_found";
|
|
308
|
+
case 429:
|
|
309
|
+
return "rate_limited";
|
|
310
|
+
default:
|
|
311
|
+
return "server_error";
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ../install-core/src/paths.ts
|
|
316
|
+
import { lstatSync, realpathSync } from "fs";
|
|
317
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "path";
|
|
318
|
+
function normalizeUrlOrigin(input) {
|
|
319
|
+
const trimmed = input.trim().replace(/\/+$/, "");
|
|
320
|
+
const withProtocol = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
321
|
+
const url = new URL(withProtocol);
|
|
322
|
+
if (url.protocol !== "https:" && !isLocalHttpUrl(url)) {
|
|
323
|
+
throw new WensityError("config_invalid", "API URL must use HTTPS, except for localhost development URLs.");
|
|
324
|
+
}
|
|
325
|
+
return url.origin;
|
|
326
|
+
}
|
|
327
|
+
function isLocalHttpUrl(url) {
|
|
328
|
+
if (url.protocol !== "http:") return false;
|
|
329
|
+
return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
|
|
330
|
+
}
|
|
331
|
+
function joinUrl(originOrUrl, path) {
|
|
332
|
+
const base = originOrUrl.endsWith("/") ? originOrUrl : `${originOrUrl}/`;
|
|
333
|
+
const normalizedPath = path.startsWith("/") ? path.slice(1) : path;
|
|
334
|
+
return new URL(normalizedPath, base).toString();
|
|
335
|
+
}
|
|
336
|
+
function resolveInside(root, candidate) {
|
|
337
|
+
if (isAbsolute(candidate)) {
|
|
338
|
+
throw new WensityError("path_unsafe", `Path must be project-relative: ${candidate}`);
|
|
339
|
+
}
|
|
340
|
+
const absolutePath = resolve(root, candidate);
|
|
341
|
+
if (escapes(relative(root, absolutePath))) {
|
|
342
|
+
throw new WensityError("path_unsafe", `Path escapes the project: ${candidate}`);
|
|
343
|
+
}
|
|
344
|
+
return absolutePath;
|
|
345
|
+
}
|
|
346
|
+
function escapes(relativePath) {
|
|
347
|
+
return relativePath === ".." || relativePath.startsWith(`..${sep}`) || isAbsolute(relativePath);
|
|
348
|
+
}
|
|
349
|
+
var MAX_TARGET_PATH_LENGTH = 240;
|
|
350
|
+
var SAFE_SEGMENT = /^[A-Za-z0-9._-]+$/;
|
|
351
|
+
function assertTargetPathLength(relativePath) {
|
|
352
|
+
if (relativePath.length > MAX_TARGET_PATH_LENGTH) {
|
|
353
|
+
throw new WensityError(
|
|
354
|
+
"path_unsafe",
|
|
355
|
+
`Path is longer than ${MAX_TARGET_PATH_LENGTH} characters: ${relativePath.slice(0, 60)}\u2026`
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
function toPosixPath(input) {
|
|
360
|
+
return input.split(sep).join("/");
|
|
361
|
+
}
|
|
362
|
+
function parentDirectory(input) {
|
|
363
|
+
return dirname(input);
|
|
364
|
+
}
|
|
365
|
+
function stripKnownComponentPrefix(path) {
|
|
366
|
+
const normalized = path.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
367
|
+
const prefixes = ["components/wensity/", "src/components/wensity/"];
|
|
368
|
+
const prefix = prefixes.find((candidate) => normalized.startsWith(candidate));
|
|
369
|
+
return prefix ? normalized.slice(prefix.length) : normalized;
|
|
370
|
+
}
|
|
371
|
+
function sanitizePayloadPath(path, fallbackFileName) {
|
|
372
|
+
const unsafe3 = () => new WensityError("path_unsafe", `Unsafe registry file path: ${path}`);
|
|
373
|
+
if (/^[\\/]/.test(path) || /^[A-Za-z]:/.test(path)) throw unsafe3();
|
|
374
|
+
const normalized = stripKnownComponentPrefix(path);
|
|
375
|
+
const parts = normalized.split("/").filter(Boolean);
|
|
376
|
+
if (parts.length === 0) return fallbackFileName;
|
|
377
|
+
for (const part of parts) {
|
|
378
|
+
if (part === "." || part === ".." || !SAFE_SEGMENT.test(part)) throw unsafe3();
|
|
379
|
+
}
|
|
380
|
+
if (normalized.length > MAX_TARGET_PATH_LENGTH) throw unsafe3();
|
|
381
|
+
const last = parts.at(-1) ?? fallbackFileName;
|
|
382
|
+
if (!/\.(tsx|ts|jsx|js)$/.test(last)) {
|
|
383
|
+
parts[parts.length - 1] = fallbackFileName;
|
|
384
|
+
}
|
|
385
|
+
return parts.join("/");
|
|
386
|
+
}
|
|
387
|
+
function componentFileName(slug) {
|
|
388
|
+
return `${slugToPascalCase(slug)}.tsx`;
|
|
389
|
+
}
|
|
390
|
+
function slugToPascalCase(slug) {
|
|
391
|
+
return slug.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join("");
|
|
392
|
+
}
|
|
393
|
+
function extensionlessPath(path) {
|
|
394
|
+
return path.replace(/\.(tsx|ts|jsx|js)$/, "");
|
|
395
|
+
}
|
|
396
|
+
function assertNoSymlinkEscape(projectRoot, absolutePath) {
|
|
397
|
+
const root = resolve(projectRoot);
|
|
398
|
+
const relativePath = relative(root, resolve(absolutePath));
|
|
399
|
+
if (escapes(relativePath)) {
|
|
400
|
+
throw new WensityError("path_unsafe", `Path escapes the project: ${toPosixPath(relativePath)}`);
|
|
401
|
+
}
|
|
402
|
+
let realRoot;
|
|
403
|
+
try {
|
|
404
|
+
realRoot = realpathSync(root);
|
|
405
|
+
} catch (error) {
|
|
406
|
+
if (isNodeError(error) && error.code === "ENOENT") return;
|
|
407
|
+
throw error;
|
|
408
|
+
}
|
|
409
|
+
let current = root;
|
|
410
|
+
for (const segment of relativePath.split(sep).filter(Boolean)) {
|
|
411
|
+
current = join(current, segment);
|
|
412
|
+
let isSymlink;
|
|
413
|
+
try {
|
|
414
|
+
isSymlink = lstatSync(current).isSymbolicLink();
|
|
415
|
+
} catch (error) {
|
|
416
|
+
if (isNodeError(error) && error.code === "ENOENT") return;
|
|
417
|
+
throw error;
|
|
106
418
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
419
|
+
if (!isSymlink) continue;
|
|
420
|
+
let target;
|
|
421
|
+
try {
|
|
422
|
+
target = realpathSync(current);
|
|
423
|
+
} catch {
|
|
424
|
+
throw new WensityError("path_unsafe", `Path passes through a broken symlink: ${toPosixPath(relative(root, current))}`);
|
|
425
|
+
}
|
|
426
|
+
const fromRoot = relative(realRoot, target);
|
|
427
|
+
if (escapes(fromRoot)) {
|
|
428
|
+
throw new WensityError(
|
|
429
|
+
"path_unsafe",
|
|
430
|
+
`Path escapes the project through a symlink: ${toPosixPath(relative(root, current))}`
|
|
431
|
+
);
|
|
112
432
|
}
|
|
113
|
-
options.set(name, true);
|
|
114
433
|
}
|
|
115
|
-
return { positionals, options };
|
|
116
434
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
435
|
+
|
|
436
|
+
// ../install-core/src/client.ts
|
|
437
|
+
function createWensityClient(options) {
|
|
438
|
+
const origin = options.origin;
|
|
439
|
+
const registryUrl = options.registryUrl ?? joinUrl(origin, "/api/registry");
|
|
440
|
+
const blocksRegistryUrl = options.blocksRegistryUrl ?? joinUrl(registryOrigin(registryUrl), "/api/blocks");
|
|
441
|
+
const apiBaseUrl = options.apiBaseUrl ?? origin;
|
|
442
|
+
const tokenOrigin = new URL(apiBaseUrl).origin;
|
|
443
|
+
const { token } = options;
|
|
444
|
+
const download = (task) => task();
|
|
445
|
+
function get(url, withToken = false) {
|
|
446
|
+
const request = {
|
|
447
|
+
userAgent: options.userAgent,
|
|
448
|
+
...options.headers ? { headers: withoutAuthorization(options.headers) } : {},
|
|
449
|
+
...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {},
|
|
450
|
+
...options.fetch ? { fetch: options.fetch } : {},
|
|
451
|
+
...options.retry !== void 0 ? { retry: options.retry } : {},
|
|
452
|
+
...options.breakers !== void 0 ? { breakers: options.breakers } : {},
|
|
453
|
+
...options.sleep ? { sleep: options.sleep } : {}
|
|
454
|
+
};
|
|
455
|
+
if (withToken && token) {
|
|
456
|
+
assertTokenDestination(url, tokenOrigin);
|
|
457
|
+
request.token = token;
|
|
458
|
+
}
|
|
459
|
+
return requestJson(url, request);
|
|
122
460
|
}
|
|
123
|
-
|
|
461
|
+
function indexUrl(kind) {
|
|
462
|
+
return kind === "block" ? blocksRegistryUrl : registryUrl;
|
|
463
|
+
}
|
|
464
|
+
async function fetchItemMetadata(slug, kind) {
|
|
465
|
+
return get(registryMetadataUrl(indexUrl(kind), slug));
|
|
466
|
+
}
|
|
467
|
+
async function fetchComponentSource(slug, opts) {
|
|
468
|
+
const metadata = opts.metadata ?? await fetchItemMetadata(slug, "component");
|
|
469
|
+
const wrap = opts.download ?? download;
|
|
470
|
+
if (token) {
|
|
471
|
+
const cliEndpoint = metadata.endpoints?.cli ?? `/api/cli/${slug}`;
|
|
472
|
+
const endpoint2 = cliEndpoint.startsWith("http") ? cliEndpoint : joinUrl(apiBaseUrl, cliEndpoint);
|
|
473
|
+
try {
|
|
474
|
+
return await wrap(() => get(endpoint2, true));
|
|
475
|
+
} catch (error) {
|
|
476
|
+
if (metadata.access === "pro" || !isAuthFailure(error)) throw error;
|
|
477
|
+
opts.onCredentialsRejected?.(error);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
if (metadata.access === "pro") {
|
|
481
|
+
throw proRequired(slug, metadata.name, "component");
|
|
482
|
+
}
|
|
483
|
+
const publicSourceEndpoint = metadata.endpoints?.registry ?? registrySlugUrl(registryUrl, slug);
|
|
484
|
+
const endpoint = publicSourceEndpoint.startsWith("http") ? publicSourceEndpoint : joinUrl(registryOrigin(registryUrl), publicSourceEndpoint);
|
|
485
|
+
return wrap(() => get(endpoint));
|
|
486
|
+
}
|
|
487
|
+
async function fetchBlockSource(slug, opts) {
|
|
488
|
+
const metadata = opts.metadata ?? await fetchItemMetadata(slug, "block");
|
|
489
|
+
if (!token) {
|
|
490
|
+
throw proRequired(slug, metadata.name, "block");
|
|
491
|
+
}
|
|
492
|
+
const cliEndpoint = metadata.endpoints?.cli ?? `/api/cli/blocks/${slug}`;
|
|
493
|
+
const endpoint = cliEndpoint.startsWith("http") ? cliEndpoint : joinUrl(apiBaseUrl, cliEndpoint);
|
|
494
|
+
return (opts.download ?? download)(() => get(endpoint, true));
|
|
495
|
+
}
|
|
496
|
+
return {
|
|
497
|
+
fetchComponentIndex: () => get(registryUrl),
|
|
498
|
+
fetchBlockIndex: () => get(blocksRegistryUrl),
|
|
499
|
+
fetchItemMetadata,
|
|
500
|
+
async fetchSourcePayload(slug, kind, opts = {}) {
|
|
501
|
+
if (kind === "block") {
|
|
502
|
+
return await fetchBlockSource(slug, opts);
|
|
503
|
+
}
|
|
504
|
+
return await fetchComponentSource(slug, opts);
|
|
505
|
+
},
|
|
506
|
+
async whoami() {
|
|
507
|
+
if (!token) {
|
|
508
|
+
throw new WensityError("auth_required", "No Wensity API token is configured.");
|
|
509
|
+
}
|
|
510
|
+
return get(joinUrl(apiBaseUrl, "/api/cli/whoami"), true);
|
|
511
|
+
}
|
|
512
|
+
};
|
|
124
513
|
}
|
|
125
|
-
function
|
|
126
|
-
return
|
|
514
|
+
function withoutAuthorization(headers) {
|
|
515
|
+
return Object.fromEntries(Object.entries(headers).filter(([name]) => name.toLowerCase() !== "authorization"));
|
|
127
516
|
}
|
|
128
|
-
function
|
|
129
|
-
|
|
517
|
+
function isAuthFailure(error) {
|
|
518
|
+
const status = isWensityError(error) ? error.upstream?.status : void 0;
|
|
519
|
+
return status === 401 || status === 403;
|
|
130
520
|
}
|
|
131
|
-
function
|
|
132
|
-
|
|
133
|
-
|
|
521
|
+
function proRequired(slug, name, kind) {
|
|
522
|
+
return new WensityError("auth_required", `${name} is a Pro ${kind}.`, {
|
|
523
|
+
details: { slug, name, kind }
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
function assertTokenDestination(url, tokenOrigin) {
|
|
527
|
+
const destination = new URL(url).origin;
|
|
528
|
+
if (destination !== tokenOrigin) {
|
|
529
|
+
throw new WensityError(
|
|
530
|
+
"source_integrity_failed",
|
|
531
|
+
`Refusing to send the saved API token to ${destination}; it belongs to ${tokenOrigin}.`
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
function registryMetadataUrl(registry, slug) {
|
|
536
|
+
const url = new URL(registry);
|
|
537
|
+
url.searchParams.set("slug", slug);
|
|
538
|
+
return url.toString();
|
|
539
|
+
}
|
|
540
|
+
function registrySlugUrl(registry, slug) {
|
|
541
|
+
return joinUrl(registry.endsWith("/") ? registry : `${registry}/`, slug);
|
|
542
|
+
}
|
|
543
|
+
function registryOrigin(registry) {
|
|
544
|
+
const url = new URL(registry);
|
|
545
|
+
return `${url.protocol}//${url.host}`;
|
|
134
546
|
}
|
|
135
547
|
|
|
136
|
-
// src/
|
|
137
|
-
import {
|
|
138
|
-
import {
|
|
139
|
-
|
|
548
|
+
// ../install-core/src/apply.ts
|
|
549
|
+
import { createHash } from "crypto";
|
|
550
|
+
import {
|
|
551
|
+
closeSync,
|
|
552
|
+
constants as fsConstants,
|
|
553
|
+
fchmodSync,
|
|
554
|
+
fstatSync,
|
|
555
|
+
fsyncSync,
|
|
556
|
+
ftruncateSync,
|
|
557
|
+
lstatSync as lstatSync3,
|
|
558
|
+
mkdirSync as mkdirSync2,
|
|
559
|
+
openSync,
|
|
560
|
+
renameSync,
|
|
561
|
+
rmdirSync as rmdirSync2,
|
|
562
|
+
unlinkSync,
|
|
563
|
+
writeSync
|
|
564
|
+
} from "fs";
|
|
565
|
+
import { chmod, copyFile, mkdir, open, readFile, rename, rm } from "fs/promises";
|
|
566
|
+
import { dirname as dirname2, join as join3, relative as relative3 } from "path";
|
|
567
|
+
|
|
568
|
+
// ../install-core/src/workdir.ts
|
|
569
|
+
import { lstatSync as lstatSync2, mkdirSync, readdirSync, readFileSync, realpathSync as realpathSync2, rmdirSync } from "fs";
|
|
570
|
+
import { isAbsolute as isAbsolute2, join as join2, relative as relative2 } from "path";
|
|
571
|
+
var WORK_DIR_NAME = ".wensity-tmp";
|
|
572
|
+
var JOURNAL_FILE = "journal.json";
|
|
573
|
+
var LOCK_FILE = "install.lock";
|
|
574
|
+
function unsafe(message) {
|
|
575
|
+
return new WensityError("path_unsafe", message);
|
|
576
|
+
}
|
|
577
|
+
function lstatOrUndefined(path) {
|
|
578
|
+
try {
|
|
579
|
+
return lstatSync2(path);
|
|
580
|
+
} catch (error) {
|
|
581
|
+
if (isNodeError(error) && error.code === "ENOENT") return void 0;
|
|
582
|
+
throw error;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
function assertRealDirectoryInside(projectRoot, path) {
|
|
586
|
+
const stat2 = lstatOrUndefined(path);
|
|
587
|
+
if (!stat2) throw unsafe(`${WORK_DIR_NAME} disappeared while it was in use.`);
|
|
588
|
+
if (stat2.isSymbolicLink() || !stat2.isDirectory()) {
|
|
589
|
+
throw unsafe(`${toPosixPath(relative2(projectRoot, path))} is not a real directory (a symlink, junction, or file). Remove it and retry.`);
|
|
590
|
+
}
|
|
591
|
+
const fromRoot = relative2(realpathSync2(projectRoot), realpathSync2(path));
|
|
592
|
+
if (fromRoot === ".." || fromRoot.startsWith("../") || fromRoot.startsWith("..\\") || isAbsolute2(fromRoot)) {
|
|
593
|
+
throw unsafe(`${WORK_DIR_NAME} resolves outside the project.`);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
function ensureWorkDir(projectRoot) {
|
|
597
|
+
const dir = join2(projectRoot, WORK_DIR_NAME);
|
|
598
|
+
if (!lstatOrUndefined(dir)) {
|
|
599
|
+
try {
|
|
600
|
+
mkdirSync(dir, { mode: 448 });
|
|
601
|
+
} catch (error) {
|
|
602
|
+
if (!(isNodeError(error) && error.code === "EEXIST")) throw error;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
assertRealDirectoryInside(projectRoot, dir);
|
|
606
|
+
return dir;
|
|
607
|
+
}
|
|
608
|
+
function removeWorkDirIfEmpty(projectRoot) {
|
|
609
|
+
const dir = join2(projectRoot, WORK_DIR_NAME);
|
|
610
|
+
const stat2 = lstatOrUndefined(dir);
|
|
611
|
+
if (!stat2 || stat2.isSymbolicLink() || !stat2.isDirectory()) return;
|
|
612
|
+
try {
|
|
613
|
+
rmdirSync(dir);
|
|
614
|
+
} catch {
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
function findIncompleteJournals(projectRoot) {
|
|
618
|
+
const dir = join2(projectRoot, WORK_DIR_NAME);
|
|
619
|
+
if (!lstatOrUndefined(dir)) return [];
|
|
620
|
+
assertRealDirectoryInside(projectRoot, dir);
|
|
621
|
+
const lock = lstatOrUndefined(join2(dir, LOCK_FILE));
|
|
622
|
+
if (lock && (lock.isSymbolicLink() || !lock.isFile())) {
|
|
623
|
+
throw unsafe(`${WORK_DIR_NAME}/${LOCK_FILE} is not a regular file. It was not followed or removed; delete it yourself.`);
|
|
624
|
+
}
|
|
625
|
+
const found = [];
|
|
626
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
627
|
+
if (!entry.isDirectory()) continue;
|
|
628
|
+
const journalPath = `${WORK_DIR_NAME}/${entry.name}`;
|
|
629
|
+
const file = join2(dir, entry.name, JOURNAL_FILE);
|
|
630
|
+
const stat2 = lstatOrUndefined(file);
|
|
631
|
+
let journal;
|
|
632
|
+
if (stat2?.isFile()) {
|
|
633
|
+
try {
|
|
634
|
+
journal = JSON.parse(readFileSync(file, "utf8"));
|
|
635
|
+
} catch {
|
|
636
|
+
journal = void 0;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
const status = typeof journal?.status === "string" ? journal.status : "unknown";
|
|
640
|
+
if (status === "committed" || status === "rolled_back") continue;
|
|
641
|
+
const entries = Array.isArray(journal?.entries) ? journal.entries : [];
|
|
642
|
+
found.push({
|
|
643
|
+
journalPath,
|
|
644
|
+
status,
|
|
645
|
+
affectedPaths: entries.map((item) => item.relativePath),
|
|
646
|
+
backupsAvailable: entries.filter((item) => item.existed && item.backup && lstatOrUndefined(join2(dir, entry.name, item.backup))?.isFile()).map((item) => item.relativePath),
|
|
647
|
+
backupsMissing: entries.filter((item) => !item.existed).map((item) => item.relativePath)
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
return found.sort((a, b) => a.journalPath.localeCompare(b.journalPath));
|
|
651
|
+
}
|
|
652
|
+
function recoveryRequired(journal) {
|
|
653
|
+
return new WensityError(
|
|
654
|
+
"recovery_required",
|
|
655
|
+
"A previous Wensity installation did not finish. Recover it before installing again.",
|
|
656
|
+
{
|
|
657
|
+
details: {
|
|
658
|
+
journalPath: journal.journalPath,
|
|
659
|
+
status: journal.status,
|
|
660
|
+
affectedPaths: journal.affectedPaths,
|
|
661
|
+
backupsAvailable: journal.backupsAvailable,
|
|
662
|
+
backupsMissing: journal.backupsMissing,
|
|
663
|
+
steps: RECOVERY_STEPS(journal.journalPath)
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
function RECOVERY_STEPS(journalPath) {
|
|
669
|
+
return [
|
|
670
|
+
`Inspect ${journalPath}/${JOURNAL_FILE} (if it is missing, no project file was changed by that run)`,
|
|
671
|
+
"For each entry with existed:true, restore the file from its backup/<n> copy",
|
|
672
|
+
"For each entry with existed:false that now exists, delete it only if you did not create it yourself",
|
|
673
|
+
`Verify with git diff, then delete the ${journalPath} directory yourself`,
|
|
674
|
+
"Re-run the installation"
|
|
675
|
+
];
|
|
676
|
+
}
|
|
677
|
+
function assertNoIncompleteJournal(projectRoot) {
|
|
678
|
+
const [first] = findIncompleteJournals(projectRoot);
|
|
679
|
+
if (first) throw recoveryRequired(first);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// ../install-core/src/apply.ts
|
|
683
|
+
var WINDOWS_RETRY_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES"]);
|
|
684
|
+
var WINDOWS_PLACEMENT_ATTEMPTS = 5;
|
|
685
|
+
function sha256(data) {
|
|
686
|
+
return createHash("sha256").update(data).digest("hex");
|
|
687
|
+
}
|
|
688
|
+
async function apply(options) {
|
|
689
|
+
const { projectRoot, planId, files } = options;
|
|
690
|
+
const hooks = options.testHooks ?? {};
|
|
691
|
+
const platform = options.platform ?? process.platform;
|
|
692
|
+
const emit = (event) => hooks.onEvent?.(event);
|
|
693
|
+
const fault = (at, index = 0) => {
|
|
694
|
+
if (hooks.fault && hooks.fault.at === at && (hooks.fault.file ?? 0) === index) {
|
|
695
|
+
throw new WensityError("write_failed", `Injected fault at ${at}.`);
|
|
696
|
+
}
|
|
697
|
+
};
|
|
698
|
+
if (!/^[A-Za-z0-9_-]{1,80}$/.test(planId)) throw new WensityError("path_unsafe", `Invalid journal name: ${planId}`);
|
|
699
|
+
assertNoIncompleteJournal(projectRoot);
|
|
700
|
+
const targets = files.map((file) => {
|
|
701
|
+
assertTargetPathLength(file.relativePath);
|
|
702
|
+
const absolutePath = resolveInside(projectRoot, file.relativePath);
|
|
703
|
+
assertNoSymlinkEscape(projectRoot, absolutePath);
|
|
704
|
+
if (file.content.includes("\0")) {
|
|
705
|
+
throw new WensityError("source_integrity_failed", `${file.relativePath} contains a NUL byte; Wensity only installs text source.`);
|
|
706
|
+
}
|
|
707
|
+
const stat2 = lstatOrUndefined2(absolutePath);
|
|
708
|
+
if (stat2 && (stat2.isSymbolicLink() || !stat2.isFile())) {
|
|
709
|
+
throw new WensityError("path_unsafe", `${file.relativePath} exists but is not a regular file (a symlink or directory). Nothing was written.`);
|
|
710
|
+
}
|
|
711
|
+
if (file.action === "create" && stat2) {
|
|
712
|
+
throw new WensityError("plan_stale", `${file.relativePath} appeared after planning.`, {
|
|
713
|
+
details: { reason: "precondition_mismatch", path: file.relativePath, expected: "absent", found: "present" }
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
if (file.action !== "create" && !stat2) {
|
|
717
|
+
throw new WensityError("plan_stale", `${file.relativePath} was removed after planning.`, {
|
|
718
|
+
details: { reason: "precondition_mismatch", path: file.relativePath, expected: "present", found: "absent" }
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
return { ...file, absolutePath, existed: Boolean(stat2), mode: stat2 ? stat2.mode & 511 : void 0 };
|
|
722
|
+
});
|
|
723
|
+
const workDir = ensureWorkDir(projectRoot);
|
|
724
|
+
const journalDir = join3(workDir, planId);
|
|
725
|
+
try {
|
|
726
|
+
mkdirSync2(journalDir, { mode: 448 });
|
|
727
|
+
} catch (error) {
|
|
728
|
+
if (isNodeError(error) && error.code === "EEXIST") {
|
|
729
|
+
throw new WensityError("path_unsafe", `${WORK_DIR_NAME}/${planId} already exists; it was not reused.`);
|
|
730
|
+
}
|
|
731
|
+
throw error;
|
|
732
|
+
}
|
|
733
|
+
const journalPath = toPosixPath(relative3(projectRoot, journalDir));
|
|
734
|
+
const journalFile = join3(journalDir, JOURNAL_FILE);
|
|
735
|
+
const entries = targets.map((target) => ({
|
|
736
|
+
relativePath: target.relativePath,
|
|
737
|
+
existed: target.existed,
|
|
738
|
+
expectedSha256: sha256(target.content),
|
|
739
|
+
action: target.action
|
|
740
|
+
}));
|
|
741
|
+
const writeJournal = async (status, step) => {
|
|
742
|
+
await writeSynced(journalFile, `${JSON.stringify({ version: 1, planId, status, step, entries }, null, 2)}
|
|
743
|
+
`, 384, true);
|
|
744
|
+
await syncDirectory(journalDir);
|
|
745
|
+
emit({ type: "journal", status, step });
|
|
746
|
+
};
|
|
747
|
+
const attempted = targets.map(() => false);
|
|
748
|
+
const placed = targets.map(() => void 0);
|
|
749
|
+
const createdDirs = [];
|
|
750
|
+
let failedPath;
|
|
751
|
+
try {
|
|
752
|
+
await mkdir(join3(journalDir, "backup"), { mode: 448 });
|
|
753
|
+
await mkdir(join3(journalDir, "staged"), { mode: 448 });
|
|
754
|
+
for (const [index, target] of targets.entries()) {
|
|
755
|
+
failedPath = target.relativePath;
|
|
756
|
+
if (target.existed) {
|
|
757
|
+
const backup = `backup/${index}`;
|
|
758
|
+
await copyFile(target.absolutePath, join3(journalDir, backup), fsConstants.COPYFILE_EXCL);
|
|
759
|
+
entries[index].backup = backup;
|
|
760
|
+
entries[index].sha256Before = sha256(await readFile(join3(journalDir, backup)));
|
|
761
|
+
entries[index].mode = target.mode;
|
|
762
|
+
emit({ type: "backup", path: target.relativePath });
|
|
763
|
+
}
|
|
764
|
+
fault("backup", index);
|
|
765
|
+
}
|
|
766
|
+
failedPath = void 0;
|
|
767
|
+
await writeJournal("in_progress", "backed_up");
|
|
768
|
+
fault("journal");
|
|
769
|
+
await writeJournal("in_progress", "staging");
|
|
770
|
+
for (const [index, target] of targets.entries()) {
|
|
771
|
+
failedPath = target.relativePath;
|
|
772
|
+
await writeSynced(join3(journalDir, "staged", String(index)), target.content, target.mode ?? 420);
|
|
773
|
+
emit({ type: "stage", path: target.relativePath });
|
|
774
|
+
fault("stage", index);
|
|
775
|
+
}
|
|
776
|
+
await writeJournal("in_progress", "placing");
|
|
777
|
+
for (const [index, target] of targets.entries()) {
|
|
778
|
+
failedPath = target.relativePath;
|
|
779
|
+
await makeParents(projectRoot, dirname2(target.absolutePath), createdDirs);
|
|
780
|
+
attempted[index] = true;
|
|
781
|
+
placed[index] = await place(join3(journalDir, "staged", String(index)), target, platform, hooks, emit, projectRoot);
|
|
782
|
+
assertPlaced(projectRoot, target, placed[index]);
|
|
783
|
+
fault("place", index);
|
|
784
|
+
const onDisk = sha256(await readFile(target.absolutePath));
|
|
785
|
+
emit({ type: "verify", path: target.relativePath });
|
|
786
|
+
if (onDisk !== entries[index].expectedSha256) {
|
|
787
|
+
throw new WensityError("source_integrity_failed", `${target.relativePath} did not verify after writing (hash mismatch).`, {
|
|
788
|
+
details: { reason: "post_write_hash_mismatch", path: target.relativePath }
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
fault("verify", index);
|
|
792
|
+
}
|
|
793
|
+
} catch (cause) {
|
|
794
|
+
const error = isWensityError(cause) ? cause : new WensityError("write_failed", `Writing ${failedPath ?? "the project files"} failed: ${cause.message}`, { cause });
|
|
795
|
+
return rollback({ projectRoot, journalDir, journalPath, targets, entries, attempted, placed, createdDirs, writeJournal, error, failedPath, emit });
|
|
796
|
+
}
|
|
797
|
+
try {
|
|
798
|
+
fault("commit");
|
|
799
|
+
await writeJournal("committed", "done");
|
|
800
|
+
} catch (cause) {
|
|
801
|
+
const error = isWensityError(cause) ? cause : new WensityError("write_failed", `Recording the commit failed: ${cause.message}`, { cause });
|
|
802
|
+
return rollback({ projectRoot, journalDir, journalPath, targets, entries, attempted, placed, createdDirs, writeJournal, error, failedPath: void 0, emit });
|
|
803
|
+
}
|
|
804
|
+
let cleanupWarning;
|
|
805
|
+
try {
|
|
806
|
+
await rm(journalDir, { recursive: true, force: true });
|
|
807
|
+
removeWorkDirIfEmpty(projectRoot);
|
|
808
|
+
} catch (error) {
|
|
809
|
+
cleanupWarning = `The install committed, but ${journalPath} could not be removed (${error.message}). It is marked committed and can be deleted.`;
|
|
810
|
+
}
|
|
811
|
+
return {
|
|
812
|
+
status: "committed",
|
|
813
|
+
...cleanupWarning ? { cleanupWarning } : {},
|
|
814
|
+
changes: targets.map((target, index) => ({
|
|
815
|
+
path: target.relativePath,
|
|
816
|
+
action: target.action === "create" ? "created" : target.action === "modify" ? "modified" : "overwritten",
|
|
817
|
+
bytes: Buffer.byteLength(target.content),
|
|
818
|
+
sha256: entries[index].expectedSha256
|
|
819
|
+
}))
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
async function rollback(input) {
|
|
823
|
+
const { journalDir, targets, entries, attempted, placed } = input;
|
|
824
|
+
const restored = [];
|
|
825
|
+
const removed = [];
|
|
826
|
+
const unrecovered = [];
|
|
827
|
+
for (let index = targets.length - 1; index >= 0; index -= 1) {
|
|
828
|
+
if (!attempted[index]) continue;
|
|
829
|
+
const target = targets[index];
|
|
830
|
+
const entry = entries[index];
|
|
831
|
+
try {
|
|
832
|
+
const backupBytes = entry.existed ? await readFile(join3(journalDir, entry.backup)) : void 0;
|
|
833
|
+
input.emit({ type: "rollback", path: target.relativePath });
|
|
834
|
+
const outcome = rollbackOneSync(input.projectRoot, target, entry, placed[index], backupBytes);
|
|
835
|
+
if (outcome === "restored") restored.push(target.relativePath);
|
|
836
|
+
else removed.push(target.relativePath);
|
|
837
|
+
} catch (error) {
|
|
838
|
+
unrecovered.push({
|
|
839
|
+
path: target.relativePath,
|
|
840
|
+
recovery: entry.existed ? `Copy ${input.journalPath}/${entry.backup} back to ${target.relativePath} (${error.message})` : `Delete ${target.relativePath} if you did not create it yourself (${error.message})`
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
for (const dir of [...input.createdDirs].reverse()) {
|
|
845
|
+
try {
|
|
846
|
+
assertNoSymlinkEscape(input.projectRoot, dir);
|
|
847
|
+
const stat2 = lstatSync3(dir);
|
|
848
|
+
if (stat2.isDirectory() && !stat2.isSymbolicLink()) rmdirSync2(dir);
|
|
849
|
+
} catch {
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
if (unrecovered.length > 0) {
|
|
853
|
+
await input.writeJournal("rollback_failed", "rollback").catch(() => void 0);
|
|
854
|
+
return {
|
|
855
|
+
status: "rollback_failed",
|
|
856
|
+
...input.failedPath ? { failedPath: input.failedPath } : {},
|
|
857
|
+
journalPath: input.journalPath,
|
|
858
|
+
restored,
|
|
859
|
+
removed,
|
|
860
|
+
unrecovered,
|
|
861
|
+
error: input.error
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
let cleanupWarning;
|
|
865
|
+
try {
|
|
866
|
+
await input.writeJournal("rolled_back", "done");
|
|
867
|
+
} catch (error) {
|
|
868
|
+
cleanupWarning = `Every file was restored, but ${input.journalPath} could not be marked rolled back (${error.message}).`;
|
|
869
|
+
}
|
|
870
|
+
try {
|
|
871
|
+
await rm(journalDir, { recursive: true, force: true });
|
|
872
|
+
removeWorkDirIfEmpty(input.projectRoot);
|
|
873
|
+
} catch (error) {
|
|
874
|
+
cleanupWarning = `Every file was restored, but ${input.journalPath} could not be removed (${error.message}); it may block the next install until you delete it.`;
|
|
875
|
+
}
|
|
876
|
+
return {
|
|
877
|
+
status: "rolled_back",
|
|
878
|
+
...input.failedPath ? { failedPath: input.failedPath } : {},
|
|
879
|
+
restored,
|
|
880
|
+
removed,
|
|
881
|
+
error: input.error,
|
|
882
|
+
...cleanupWarning ? { cleanupWarning } : {}
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
function identityOf(stat2) {
|
|
886
|
+
return { dev: stat2.dev, ino: stat2.ino };
|
|
887
|
+
}
|
|
888
|
+
function sameFile(a, b) {
|
|
889
|
+
return a.dev === b.dev && a.ino === b.ino;
|
|
890
|
+
}
|
|
891
|
+
function unsafe2(relativePath, why) {
|
|
892
|
+
return new WensityError("path_unsafe", `${relativePath} ${why}; it was not touched.`);
|
|
893
|
+
}
|
|
894
|
+
function rollbackOneSync(projectRoot, target, entry, placedFile, backupBytes) {
|
|
895
|
+
const { relativePath, absolutePath } = target;
|
|
896
|
+
if (!placedFile) return entry.existed ? "restored" : "removed";
|
|
897
|
+
assertNoSymlinkEscape(projectRoot, absolutePath);
|
|
898
|
+
const current = lstatOrUndefinedBig(absolutePath);
|
|
899
|
+
if (current && (current.isSymbolicLink() || !current.isFile())) throw unsafe2(relativePath, "was replaced by a symlink or non-file");
|
|
900
|
+
if (!current) throw unsafe2(relativePath, "was deleted after it was written");
|
|
901
|
+
if (!sameFile(identityOf(current), placedFile)) throw unsafe2(relativePath, "was replaced by another file after it was written");
|
|
902
|
+
if (!entry.existed) {
|
|
903
|
+
unlinkSync(absolutePath);
|
|
904
|
+
return "removed";
|
|
905
|
+
}
|
|
906
|
+
if (sha256(backupBytes) !== entry.sha256Before) throw new Error("the backup no longer matches its recorded hash; it was not written back");
|
|
907
|
+
const fd = openSync(absolutePath, fsConstants.O_WRONLY | (fsConstants.O_NOFOLLOW ?? 0));
|
|
908
|
+
try {
|
|
909
|
+
const opened = identityOf(fstatSync(fd, { bigint: true }));
|
|
910
|
+
assertNoSymlinkEscape(projectRoot, absolutePath);
|
|
911
|
+
const again = lstatOrUndefinedBig(absolutePath);
|
|
912
|
+
if (!sameFile(opened, placedFile) || !again || !sameFile(identityOf(again), placedFile)) {
|
|
913
|
+
throw unsafe2(relativePath, "moved while it was being restored");
|
|
914
|
+
}
|
|
915
|
+
ftruncateSync(fd, 0);
|
|
916
|
+
let offset = 0;
|
|
917
|
+
while (offset < backupBytes.length) offset += writeSync(fd, backupBytes, offset, backupBytes.length - offset, offset);
|
|
918
|
+
fsyncSync(fd);
|
|
919
|
+
if (entry.mode !== void 0) fchmodSync(fd, entry.mode);
|
|
920
|
+
} finally {
|
|
921
|
+
closeSync(fd);
|
|
922
|
+
}
|
|
923
|
+
return "restored";
|
|
924
|
+
}
|
|
925
|
+
function lstatOrUndefinedBig(path) {
|
|
926
|
+
try {
|
|
927
|
+
return lstatSync3(path, { bigint: true });
|
|
928
|
+
} catch (error) {
|
|
929
|
+
if (isNodeError(error) && error.code === "ENOENT") return void 0;
|
|
930
|
+
throw error;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
async function place(staged, target, platform, hooks, emit, projectRoot) {
|
|
934
|
+
const stagedFile = identityOf(lstatSync3(staged, { bigint: true }));
|
|
935
|
+
const attempts = platform === "win32" && target.existed ? WINDOWS_PLACEMENT_ATTEMPTS : 1;
|
|
936
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
937
|
+
emit({ type: "place", path: target.relativePath, attempt });
|
|
938
|
+
try {
|
|
939
|
+
if (hooks.rename) {
|
|
940
|
+
assertNoSymlinkEscape(projectRoot, target.absolutePath);
|
|
941
|
+
await hooks.rename(staged, target.absolutePath);
|
|
942
|
+
} else {
|
|
943
|
+
assertNoSymlinkEscape(projectRoot, target.absolutePath);
|
|
944
|
+
renameSync(staged, target.absolutePath);
|
|
945
|
+
}
|
|
946
|
+
break;
|
|
947
|
+
} catch (error) {
|
|
948
|
+
const retryable = isNodeError(error) && WINDOWS_RETRY_CODES.has(error.code ?? "");
|
|
949
|
+
if (!retryable || attempt >= attempts) throw error;
|
|
950
|
+
await new Promise((resolve3) => setTimeout(resolve3, 25 * 2 ** (attempt - 1)));
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
return stagedFile;
|
|
954
|
+
}
|
|
955
|
+
function assertPlaced(projectRoot, target, placedFile) {
|
|
956
|
+
assertNoSymlinkEscape(projectRoot, target.absolutePath);
|
|
957
|
+
const now = lstatOrUndefinedBig(target.absolutePath);
|
|
958
|
+
if (!now || now.isSymbolicLink() || !sameFile(identityOf(now), placedFile)) {
|
|
959
|
+
throw new WensityError("path_unsafe", `${target.relativePath} is not the file that was just written; the install is being rolled back.`);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
async function makeParents(projectRoot, dir, created) {
|
|
963
|
+
const missing = [];
|
|
964
|
+
for (let current = dir; current !== projectRoot && !lstatOrUndefined2(current); current = dirname2(current)) {
|
|
965
|
+
missing.unshift(current);
|
|
966
|
+
}
|
|
967
|
+
for (const path of missing) {
|
|
968
|
+
await mkdir(path);
|
|
969
|
+
created.push(path);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
async function writeSynced(path, data, mode, replace = false) {
|
|
973
|
+
if (replace) {
|
|
974
|
+
const temp = `${path}.next`;
|
|
975
|
+
await writeSynced(temp, data, mode);
|
|
976
|
+
await rename(temp, path);
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
const handle = await open(path, "wx", mode);
|
|
980
|
+
try {
|
|
981
|
+
await handle.writeFile(data, "utf8");
|
|
982
|
+
await handle.sync();
|
|
983
|
+
} finally {
|
|
984
|
+
await handle.close();
|
|
985
|
+
}
|
|
986
|
+
await chmod(path, mode);
|
|
987
|
+
}
|
|
988
|
+
async function syncDirectory(dir) {
|
|
989
|
+
try {
|
|
990
|
+
const handle = await open(dir, "r");
|
|
991
|
+
try {
|
|
992
|
+
await handle.sync();
|
|
993
|
+
} finally {
|
|
994
|
+
await handle.close();
|
|
995
|
+
}
|
|
996
|
+
} catch {
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
function lstatOrUndefined2(path) {
|
|
1000
|
+
try {
|
|
1001
|
+
return lstatSync3(path);
|
|
1002
|
+
} catch (error) {
|
|
1003
|
+
if (isNodeError(error) && error.code === "ENOENT") return void 0;
|
|
1004
|
+
throw error;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// ../install-core/src/project.ts
|
|
1009
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
1010
|
+
import { existsSync as existsSync4, lstatSync as lstatSync4, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
|
|
1011
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
1012
|
+
import { join as join7, relative as relative6 } from "path";
|
|
1013
|
+
|
|
1014
|
+
// ../install-core/src/config.ts
|
|
1015
|
+
import { existsSync as existsSync2, realpathSync as realpathSync3, statSync as statSync2 } from "fs";
|
|
1016
|
+
import { access, mkdir as mkdir2, readFile as readFile2, writeFile } from "fs/promises";
|
|
1017
|
+
import { homedir } from "os";
|
|
1018
|
+
import { dirname as dirname3, join as join5, posix as posix2, relative as relative5, resolve as resolve2 } from "path";
|
|
140
1019
|
|
|
141
1020
|
// ../wensity-preset/src/constants.ts
|
|
142
1021
|
var ICON_LIBRARIES = ["tabler", "lucide"];
|
|
@@ -1488,6 +2367,7 @@ var TABLER_TO_LUCIDE = {
|
|
|
1488
2367
|
IconChevronsRight: "ChevronsRight",
|
|
1489
2368
|
IconCircleX: "CircleX",
|
|
1490
2369
|
IconClock: "Clock",
|
|
2370
|
+
IconColorPicker: "Pipette",
|
|
1491
2371
|
IconCornerDownLeft: "CornerDownLeft",
|
|
1492
2372
|
IconDots: "Ellipsis",
|
|
1493
2373
|
IconDownload: "Download",
|
|
@@ -2521,76 +3401,93 @@ function resolvedTokensToCssVars(tokens, mode) {
|
|
|
2521
3401
|
[RESOLVED_TOKEN_CSS_VARS.fontMono]: tokens.fontMono
|
|
2522
3402
|
};
|
|
2523
3403
|
}
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
}
|
|
2540
|
-
function joinUrl(originOrUrl, path) {
|
|
2541
|
-
const base = originOrUrl.endsWith("/") ? originOrUrl : `${originOrUrl}/`;
|
|
2542
|
-
const normalizedPath = path.startsWith("/") ? path.slice(1) : path;
|
|
2543
|
-
return new URL(normalizedPath, base).toString();
|
|
2544
|
-
}
|
|
2545
|
-
function resolveInside(root, candidate) {
|
|
2546
|
-
if (isAbsolute(candidate)) {
|
|
2547
|
-
throw new Error(`Path must be project-relative: ${candidate}`);
|
|
2548
|
-
}
|
|
2549
|
-
const absolutePath = resolve(root, candidate);
|
|
2550
|
-
const relativePath = relative(root, absolutePath);
|
|
2551
|
-
if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
|
|
2552
|
-
throw new Error(`Path escapes the project: ${candidate}`);
|
|
3404
|
+
function resolvedNamedBaseSemanticCssVars(baseColor, mode) {
|
|
3405
|
+
if (baseColor === "inherit") return {};
|
|
3406
|
+
const seeds = BASE_COLOR_PALETTE[baseColor]?.[mode];
|
|
3407
|
+
if (!seeds) return {};
|
|
3408
|
+
if (mode === "light") {
|
|
3409
|
+
return {
|
|
3410
|
+
// Soft family wash over white — the main light-mode tell (slate vs stone).
|
|
3411
|
+
"--background": `color-mix(in oklab, ${seeds.surfaceHover} 62%, white)`,
|
|
3412
|
+
"--foreground": `color-mix(in oklab, ${seeds.textSecondary} 38%, black)`,
|
|
3413
|
+
"--surface": seeds.surfaceElevated,
|
|
3414
|
+
"--surface-muted": seeds.surfaceHover,
|
|
3415
|
+
"--muted": seeds.surfaceHover,
|
|
3416
|
+
"--muted-foreground": seeds.textSecondary,
|
|
3417
|
+
"--border": seeds.borderSubtle,
|
|
3418
|
+
"--border-strong": `color-mix(in oklab, ${seeds.textSecondary} 30%, transparent)`
|
|
3419
|
+
};
|
|
2553
3420
|
}
|
|
2554
|
-
return
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
const prefix = prefixes.find((candidate) => normalized.startsWith(candidate));
|
|
2566
|
-
return prefix ? normalized.slice(prefix.length) : normalized;
|
|
3421
|
+
return {
|
|
3422
|
+
// Canvas sits under elevated cards (darker than surfaceElevated).
|
|
3423
|
+
"--background": `color-mix(in oklab, ${seeds.surfaceElevated} 58%, black)`,
|
|
3424
|
+
"--foreground": `color-mix(in oklab, ${seeds.textSecondary} 22%, white)`,
|
|
3425
|
+
"--surface": seeds.surfaceElevated,
|
|
3426
|
+
"--surface-muted": seeds.surfaceHover,
|
|
3427
|
+
"--muted": seeds.surfaceHover,
|
|
3428
|
+
"--muted-foreground": seeds.textSecondary,
|
|
3429
|
+
"--border": seeds.borderSubtle,
|
|
3430
|
+
"--border-strong": `color-mix(in oklab, ${seeds.textSecondary} 38%, transparent)`
|
|
3431
|
+
};
|
|
2567
3432
|
}
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
3433
|
+
var INHERIT_BASE_SEMANTIC_CSS_VARS = {
|
|
3434
|
+
light: {
|
|
3435
|
+
"--background": "#fafafa",
|
|
3436
|
+
"--foreground": "#0a0a0a",
|
|
3437
|
+
"--surface": "#ffffff",
|
|
3438
|
+
"--surface-muted": "#f4f4f5",
|
|
3439
|
+
"--muted": "#f4f4f5",
|
|
3440
|
+
"--muted-foreground": "#52525b",
|
|
3441
|
+
"--border": "rgba(10, 10, 10, 0.08)",
|
|
3442
|
+
"--border-strong": "rgba(10, 10, 10, 0.16)"
|
|
3443
|
+
},
|
|
3444
|
+
dark: {
|
|
3445
|
+
"--background": "#0a0a0b",
|
|
3446
|
+
"--foreground": "#f5f5f6",
|
|
3447
|
+
"--surface": "#111113",
|
|
3448
|
+
"--surface-muted": "#18181b",
|
|
3449
|
+
"--muted": "#1c1c1f",
|
|
3450
|
+
"--muted-foreground": "#a1a1aa",
|
|
3451
|
+
"--border": "rgba(255, 255, 255, 0.08)",
|
|
3452
|
+
"--border-strong": "rgba(255, 255, 255, 0.14)"
|
|
2580
3453
|
}
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
return
|
|
2585
|
-
}
|
|
2586
|
-
|
|
2587
|
-
|
|
3454
|
+
};
|
|
3455
|
+
function exportedBaseSemanticCssVars(baseColor, mode) {
|
|
3456
|
+
const named = resolvedNamedBaseSemanticCssVars(baseColor, mode);
|
|
3457
|
+
return Object.keys(named).length > 0 ? named : { ...INHERIT_BASE_SEMANTIC_CSS_VARS[mode] };
|
|
3458
|
+
}
|
|
3459
|
+
var BASE_SEMANTIC_THEME_UTILITIES = {
|
|
3460
|
+
"--color-background": "var(--background)",
|
|
3461
|
+
"--color-foreground": "var(--foreground)",
|
|
3462
|
+
"--color-surface": "var(--surface)",
|
|
3463
|
+
"--color-surface-muted": "var(--surface-muted)",
|
|
3464
|
+
"--color-muted": "var(--muted)",
|
|
3465
|
+
"--color-muted-foreground": "var(--muted-foreground)",
|
|
3466
|
+
"--color-border": "var(--border)",
|
|
3467
|
+
"--color-border-strong": "var(--border-strong)"
|
|
3468
|
+
};
|
|
3469
|
+
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 */";
|
|
3470
|
+
var BASE_CSS_END = "/* \u2500\u2500 End Wensity Base Tokens \u2500\u2500 */";
|
|
3471
|
+
function formatBaseDeclarations(entries, indent = " ") {
|
|
3472
|
+
return Object.entries(entries).map(([name, value]) => `${indent}${name}: ${value};`).join("\n");
|
|
2588
3473
|
}
|
|
2589
|
-
function
|
|
2590
|
-
return
|
|
3474
|
+
function buildBaseCssBlock(baseColor = "inherit") {
|
|
3475
|
+
return [
|
|
3476
|
+
BASE_CSS_START,
|
|
3477
|
+
"@theme inline {",
|
|
3478
|
+
formatBaseDeclarations(BASE_SEMANTIC_THEME_UTILITIES),
|
|
3479
|
+
"}",
|
|
3480
|
+
":root {",
|
|
3481
|
+
formatBaseDeclarations(exportedBaseSemanticCssVars(baseColor, "light")),
|
|
3482
|
+
"}",
|
|
3483
|
+
".dark {",
|
|
3484
|
+
formatBaseDeclarations(exportedBaseSemanticCssVars(baseColor, "dark")),
|
|
3485
|
+
"}",
|
|
3486
|
+
BASE_CSS_END
|
|
3487
|
+
].join("\n");
|
|
2591
3488
|
}
|
|
2592
3489
|
|
|
2593
|
-
// src/style-shape.ts
|
|
3490
|
+
// ../install-core/src/style-shape.ts
|
|
2594
3491
|
function isCanonicalStyleShape(value) {
|
|
2595
3492
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
2596
3493
|
const record = value;
|
|
@@ -2600,30 +3497,184 @@ function normalizeProjectStyle(style) {
|
|
|
2600
3497
|
if (isCanonicalStyleShape(style)) {
|
|
2601
3498
|
return normalizeStyleConfigCanonical(style);
|
|
2602
3499
|
}
|
|
2603
|
-
return normalizeStyleConfig(style);
|
|
3500
|
+
return normalizeStyleConfig(style);
|
|
3501
|
+
}
|
|
3502
|
+
function resolveTokensForStyle(style) {
|
|
3503
|
+
if (isCanonicalStyleShape(style)) {
|
|
3504
|
+
return resolveStyleConfigCanonical(normalizeStyleConfigCanonical(style));
|
|
3505
|
+
}
|
|
3506
|
+
return resolveStyleConfig(normalizeStyleConfig(style));
|
|
3507
|
+
}
|
|
3508
|
+
function baseColorFromStyle(style) {
|
|
3509
|
+
if (isCanonicalStyleShape(style)) {
|
|
3510
|
+
return normalizeStyleConfigCanonical(style).baseColor;
|
|
3511
|
+
}
|
|
3512
|
+
return "inherit";
|
|
3513
|
+
}
|
|
3514
|
+
function curatedFontsFromStyle(style) {
|
|
3515
|
+
if (isCanonicalStyleShape(style)) {
|
|
3516
|
+
const normalized = normalizeStyleConfigCanonical(style);
|
|
3517
|
+
return {
|
|
3518
|
+
headingFont: normalized.headingFont,
|
|
3519
|
+
bodyFont: normalized.bodyFont
|
|
3520
|
+
};
|
|
3521
|
+
}
|
|
3522
|
+
return { headingFont: "system", bodyFont: "system" };
|
|
3523
|
+
}
|
|
3524
|
+
|
|
3525
|
+
// ../install-core/src/tsconfig-paths.ts
|
|
3526
|
+
import { existsSync, readFileSync as readFileSync2, statSync } from "fs";
|
|
3527
|
+
import { join as join4, posix, relative as relative4 } from "path";
|
|
3528
|
+
var MAX_CONFIG_HOPS = 8;
|
|
3529
|
+
var CONFIG_DIR = "${configDir}";
|
|
3530
|
+
function containedFile(projectRoot, relativePath) {
|
|
3531
|
+
let absolute;
|
|
3532
|
+
try {
|
|
3533
|
+
absolute = resolveInside(projectRoot, relativePath);
|
|
3534
|
+
assertNoSymlinkEscape(projectRoot, absolute);
|
|
3535
|
+
} catch {
|
|
3536
|
+
return void 0;
|
|
3537
|
+
}
|
|
3538
|
+
return existsSync(absolute) ? absolute : void 0;
|
|
3539
|
+
}
|
|
3540
|
+
function readPathMapping(projectRoot) {
|
|
3541
|
+
for (const file of ["tsconfig.json", "jsconfig.json"]) {
|
|
3542
|
+
if (!containedFile(projectRoot, file)) continue;
|
|
3543
|
+
const root = parseConfig(projectRoot, file);
|
|
3544
|
+
const own = resolveConfig(projectRoot, file, 0);
|
|
3545
|
+
if (!root || own === UNKNOWN) return void 0;
|
|
3546
|
+
if (own.paths) return toMapping(own, posix.dirname(file));
|
|
3547
|
+
let unresolvedReference = false;
|
|
3548
|
+
for (const reference of root.references ?? []) {
|
|
3549
|
+
if (typeof reference.path !== "string") continue;
|
|
3550
|
+
const referenced = referencePath(projectRoot, posix.join(posix.dirname(file), reference.path));
|
|
3551
|
+
const resolved = referenced ? resolveConfig(projectRoot, referenced, 0) : UNKNOWN;
|
|
3552
|
+
if (resolved === UNKNOWN) unresolvedReference = true;
|
|
3553
|
+
else if (resolved.paths) return toMapping(resolved, posix.dirname(referenced));
|
|
3554
|
+
}
|
|
3555
|
+
return unresolvedReference ? void 0 : toMapping(own, posix.dirname(file));
|
|
3556
|
+
}
|
|
3557
|
+
return void 0;
|
|
3558
|
+
}
|
|
3559
|
+
function aliasResolvesTo(mapping, alias, target) {
|
|
3560
|
+
const wanted = posix.normalize(stripExtension(target));
|
|
3561
|
+
for (const [key, values] of Object.entries(mapping.paths)) {
|
|
3562
|
+
const wildcard = key.endsWith("/*");
|
|
3563
|
+
const prefix = wildcard ? key.slice(0, -1) : key;
|
|
3564
|
+
if (wildcard ? !alias.startsWith(prefix) : alias !== key) continue;
|
|
3565
|
+
const rest = wildcard ? alias.slice(prefix.length) : "";
|
|
3566
|
+
for (const value of values) {
|
|
3567
|
+
const targetBase = value.endsWith("/*") ? value.slice(0, -1) : value;
|
|
3568
|
+
const candidate = posix.normalize(posix.join(mapping.base, targetBase, rest));
|
|
3569
|
+
if (stripExtension(candidate) === wanted) return true;
|
|
3570
|
+
}
|
|
3571
|
+
}
|
|
3572
|
+
return false;
|
|
3573
|
+
}
|
|
3574
|
+
function atAliasRoot(mapping) {
|
|
3575
|
+
const target = mapping?.paths["@/*"]?.find((value) => value.endsWith("/*"));
|
|
3576
|
+
if (target === void 0) return void 0;
|
|
3577
|
+
const dir = posix.normalize(posix.join(mapping.base, target.slice(0, -2)));
|
|
3578
|
+
if (dir === ".." || dir.startsWith("../") || posix.isAbsolute(dir)) return void 0;
|
|
3579
|
+
if (dir === "node_modules" || dir.startsWith("node_modules/")) return void 0;
|
|
3580
|
+
return dir === "." ? "" : dir;
|
|
3581
|
+
}
|
|
3582
|
+
var UNKNOWN = /* @__PURE__ */ Symbol("unknown");
|
|
3583
|
+
function resolveConfig(projectRoot, file, hops) {
|
|
3584
|
+
const parsed = parseConfig(projectRoot, file);
|
|
3585
|
+
if (!parsed || hops > MAX_CONFIG_HOPS) return UNKNOWN;
|
|
3586
|
+
const dir = posix.dirname(file);
|
|
3587
|
+
const options = parsed.compilerOptions ?? {};
|
|
3588
|
+
let resolved = {};
|
|
3589
|
+
const parents = typeof parsed.extends === "string" ? [parsed.extends] : parsed.extends ?? [];
|
|
3590
|
+
for (const parent of parents) {
|
|
3591
|
+
if (typeof parent !== "string") return UNKNOWN;
|
|
3592
|
+
const parentFile = extendsTarget(projectRoot, dir, parent);
|
|
3593
|
+
const inherited = parentFile ? resolveConfig(projectRoot, parentFile, hops + 1) : UNKNOWN;
|
|
3594
|
+
if (inherited === UNKNOWN) return UNKNOWN;
|
|
3595
|
+
resolved = {
|
|
3596
|
+
baseUrl: inherited.baseUrl ?? resolved.baseUrl,
|
|
3597
|
+
paths: inherited.paths ?? resolved.paths
|
|
3598
|
+
};
|
|
3599
|
+
}
|
|
3600
|
+
if (options.baseUrl !== void 0) {
|
|
3601
|
+
resolved.baseUrl = options.baseUrl.startsWith(CONFIG_DIR) ? options.baseUrl : posix.normalize(posix.join(dir, options.baseUrl));
|
|
3602
|
+
}
|
|
3603
|
+
if (options.paths) resolved.paths = { map: options.paths, dir };
|
|
3604
|
+
return resolved;
|
|
3605
|
+
}
|
|
3606
|
+
function toMapping(resolved, configDir) {
|
|
3607
|
+
const expand = (value) => posix.normalize(posix.join(configDir, value.slice(CONFIG_DIR.length)));
|
|
3608
|
+
const baseUrl = resolved.baseUrl?.startsWith(CONFIG_DIR) ? expand(resolved.baseUrl) : resolved.baseUrl;
|
|
3609
|
+
if (!resolved.paths) return { base: baseUrl ?? ".", paths: {} };
|
|
3610
|
+
const base = baseUrl ?? resolved.paths.dir;
|
|
3611
|
+
const rebase = (value) => {
|
|
3612
|
+
if (!value.startsWith(CONFIG_DIR)) return value;
|
|
3613
|
+
const wildcard = value.endsWith("/*");
|
|
3614
|
+
const target = expand(wildcard ? value.slice(0, -2) : value);
|
|
3615
|
+
const rel = posix.relative(base, target) || ".";
|
|
3616
|
+
return wildcard ? `${rel}/*` : rel;
|
|
3617
|
+
};
|
|
3618
|
+
const paths = Object.fromEntries(Object.entries(resolved.paths.map).map(([key, values]) => [key, values.map(rebase)]));
|
|
3619
|
+
return { base, paths };
|
|
3620
|
+
}
|
|
3621
|
+
function extendsTarget(projectRoot, fromDir, specifier) {
|
|
3622
|
+
if (specifier.startsWith("./") || specifier.startsWith("../") || specifier === "." || specifier === "..") {
|
|
3623
|
+
return configFile(projectRoot, posix.join(fromDir, specifier));
|
|
3624
|
+
}
|
|
3625
|
+
if (posix.isAbsolute(specifier) || /^[A-Za-z]:[\\/]/.test(specifier)) return void 0;
|
|
3626
|
+
const inPackage = posix.join("node_modules", specifier);
|
|
3627
|
+
const direct = configFile(projectRoot, inPackage);
|
|
3628
|
+
if (direct) return direct;
|
|
3629
|
+
const manifest = containedFile(projectRoot, posix.join(inPackage, "package.json"));
|
|
3630
|
+
if (manifest) {
|
|
3631
|
+
try {
|
|
3632
|
+
const field = JSON.parse(readFileSync2(manifest, "utf8")).tsconfig;
|
|
3633
|
+
if (typeof field === "string") {
|
|
3634
|
+
const fromField = configFile(projectRoot, posix.join(inPackage, field));
|
|
3635
|
+
if (fromField) return fromField;
|
|
3636
|
+
}
|
|
3637
|
+
} catch {
|
|
3638
|
+
}
|
|
3639
|
+
}
|
|
3640
|
+
return configFile(projectRoot, posix.join(inPackage, "tsconfig.json"));
|
|
2604
3641
|
}
|
|
2605
|
-
function
|
|
2606
|
-
|
|
2607
|
-
|
|
3642
|
+
function parseConfig(projectRoot, file) {
|
|
3643
|
+
const path = containedFile(projectRoot, file);
|
|
3644
|
+
if (!path) return void 0;
|
|
3645
|
+
try {
|
|
3646
|
+
return JSON.parse(stripJsonComments(readFileSync2(path, "utf8")));
|
|
3647
|
+
} catch {
|
|
3648
|
+
return void 0;
|
|
2608
3649
|
}
|
|
2609
|
-
return resolveStyleConfig(normalizeStyleConfig(style));
|
|
2610
3650
|
}
|
|
2611
|
-
function
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
bodyFont: normalized.bodyFont
|
|
2617
|
-
};
|
|
3651
|
+
function configFile(projectRoot, path) {
|
|
3652
|
+
const normalized = posix.normalize(path);
|
|
3653
|
+
for (const candidate of [normalized, `${normalized}.json`]) {
|
|
3654
|
+
const absolute = containedFile(projectRoot, candidate);
|
|
3655
|
+
if (absolute && statSync(absolute).isFile()) return toPosixPath(relative4(projectRoot, absolute));
|
|
2618
3656
|
}
|
|
2619
|
-
return
|
|
3657
|
+
return void 0;
|
|
3658
|
+
}
|
|
3659
|
+
function referencePath(projectRoot, path) {
|
|
3660
|
+
const absolute = containedFile(projectRoot, path);
|
|
3661
|
+
if (!absolute) return void 0;
|
|
3662
|
+
const file = statSync(absolute).isDirectory() ? join4(absolute, "tsconfig.json") : absolute;
|
|
3663
|
+
const relativeFile = toPosixPath(relative4(projectRoot, file));
|
|
3664
|
+
return containedFile(projectRoot, relativeFile) ? relativeFile : void 0;
|
|
3665
|
+
}
|
|
3666
|
+
function stripJsonComments(text) {
|
|
3667
|
+
return text.replace(/("(?:\\.|[^"\\])*")|\/\*[\s\S]*?\*\/|\/\/[^\n]*/g, (match, string) => string ?? "").replace(/,(\s*[}\]])/g, "$1");
|
|
3668
|
+
}
|
|
3669
|
+
function stripExtension(path) {
|
|
3670
|
+
return path.replace(/\.(tsx|ts|jsx|js)$/, "");
|
|
2620
3671
|
}
|
|
2621
3672
|
|
|
2622
|
-
// src/config.ts
|
|
2623
|
-
async function loadConfig(cwd, overrides = {}) {
|
|
2624
|
-
const foundPath = await
|
|
2625
|
-
const
|
|
2626
|
-
|
|
3673
|
+
// ../install-core/src/config.ts
|
|
3674
|
+
async function loadConfig(cwd, overrides = {}, options = {}) {
|
|
3675
|
+
const { projectRoot, configPath: foundPath } = await locateProject(cwd, options.home);
|
|
3676
|
+
const configPath = foundPath ?? join5(projectRoot, CONFIG_FILE_NAME);
|
|
3677
|
+
if (foundPath && options.rejectEscapingSymlink) assertNoSymlinkEscape(projectRoot, foundPath);
|
|
2627
3678
|
const defaults = createDefaultConfig(projectRoot, overrides.apiUrl ?? DEFAULT_API_ORIGIN);
|
|
2628
3679
|
if (!foundPath) {
|
|
2629
3680
|
return {
|
|
@@ -2634,7 +3685,7 @@ async function loadConfig(cwd, overrides = {}) {
|
|
|
2634
3685
|
};
|
|
2635
3686
|
}
|
|
2636
3687
|
try {
|
|
2637
|
-
const parsed = JSON.parse(await
|
|
3688
|
+
const parsed = JSON.parse(await readFile2(foundPath, "utf8"));
|
|
2638
3689
|
return {
|
|
2639
3690
|
config: normalizeConfig(
|
|
2640
3691
|
{
|
|
@@ -2656,22 +3707,23 @@ async function loadConfig(cwd, overrides = {}) {
|
|
|
2656
3707
|
};
|
|
2657
3708
|
} catch (error) {
|
|
2658
3709
|
if (error instanceof SyntaxError) {
|
|
2659
|
-
throw new
|
|
3710
|
+
throw new WensityError("config_invalid", `${CONFIG_FILE_NAME} contains invalid JSON.`);
|
|
2660
3711
|
}
|
|
2661
3712
|
if (error instanceof PresetValidationError) {
|
|
2662
|
-
throw new
|
|
3713
|
+
throw new WensityError("config_invalid", `${CONFIG_FILE_NAME}: ${error.message}`);
|
|
2663
3714
|
}
|
|
2664
3715
|
throw error;
|
|
2665
3716
|
}
|
|
2666
3717
|
}
|
|
2667
3718
|
function createDefaultConfig(projectRoot, apiUrl = DEFAULT_API_ORIGIN) {
|
|
2668
3719
|
const apiOrigin = normalizeUrlOrigin(apiUrl);
|
|
3720
|
+
const sourceRoot = defaultSourceRoot(projectRoot);
|
|
2669
3721
|
return {
|
|
2670
3722
|
$schema: joinUrl(apiOrigin, "/schema/wensity.json"),
|
|
2671
3723
|
version: CONFIG_SCHEMA_VERSION,
|
|
2672
3724
|
registry: joinUrl(apiOrigin, DEFAULT_REGISTRY_PATH),
|
|
2673
|
-
components:
|
|
2674
|
-
utils:
|
|
3725
|
+
components: posix2.join(sourceRoot, "components/wensity"),
|
|
3726
|
+
utils: posix2.join(sourceRoot, "lib/utils"),
|
|
2675
3727
|
tailwind: {
|
|
2676
3728
|
config: detectTailwindConfig(projectRoot),
|
|
2677
3729
|
css: detectGlobalCss(projectRoot)
|
|
@@ -2685,20 +3737,21 @@ function createDefaultConfig(projectRoot, apiUrl = DEFAULT_API_ORIGIN) {
|
|
|
2685
3737
|
}
|
|
2686
3738
|
async function writeConfigFile(path, config, force) {
|
|
2687
3739
|
if (!force && await fileExists(path)) {
|
|
2688
|
-
throw new
|
|
3740
|
+
throw new WensityError("file_conflict", `${toPosixPath(relative5(process.cwd(), path))} already exists. Use --force to overwrite it.`);
|
|
2689
3741
|
}
|
|
2690
|
-
await
|
|
3742
|
+
await mkdir2(dirname3(path), { recursive: true });
|
|
2691
3743
|
await writeFile(path, `${JSON.stringify(config, null, 2)}
|
|
2692
3744
|
`, "utf8");
|
|
2693
3745
|
}
|
|
2694
3746
|
async function mergeStyleIntoConfigFile(configPath, style) {
|
|
2695
|
-
const raw = JSON.parse(await
|
|
3747
|
+
const raw = JSON.parse(await readFile2(configPath, "utf8"));
|
|
3748
|
+
const defaults = raw.components && raw.utils ? void 0 : createDefaultConfig(dirname3(configPath));
|
|
2696
3749
|
const merged = {
|
|
2697
3750
|
...raw,
|
|
2698
3751
|
version: raw.version ?? CONFIG_SCHEMA_VERSION,
|
|
2699
3752
|
registry: raw.registry ?? "",
|
|
2700
|
-
components: raw.components ?? DEFAULT_COMPONENTS_DIR,
|
|
2701
|
-
utils: raw.utils ?? DEFAULT_UTILS_PATH,
|
|
3753
|
+
components: raw.components ?? defaults?.components ?? DEFAULT_COMPONENTS_DIR,
|
|
3754
|
+
utils: raw.utils ?? defaults?.utils ?? DEFAULT_UTILS_PATH,
|
|
2702
3755
|
aliases: {
|
|
2703
3756
|
components: raw.aliases?.components ?? DEFAULT_COMPONENTS_ALIAS,
|
|
2704
3757
|
utils: raw.aliases?.utils ?? DEFAULT_UTILS_ALIAS
|
|
@@ -2716,7 +3769,7 @@ function resolveConfiguredFilePath(projectRoot, relativePath, extension = "ts")
|
|
|
2716
3769
|
return resolveInside(projectRoot, withExtension(relativePath, extension));
|
|
2717
3770
|
} catch (error) {
|
|
2718
3771
|
const message = error instanceof Error ? error.message : String(error);
|
|
2719
|
-
throw new
|
|
3772
|
+
throw new WensityError("path_unsafe", message);
|
|
2720
3773
|
}
|
|
2721
3774
|
}
|
|
2722
3775
|
async function fileExists(path) {
|
|
@@ -2728,16 +3781,52 @@ async function fileExists(path) {
|
|
|
2728
3781
|
throw error;
|
|
2729
3782
|
}
|
|
2730
3783
|
}
|
|
2731
|
-
|
|
2732
|
-
|
|
3784
|
+
function assertProjectRootCandidate(path, home = homedir()) {
|
|
3785
|
+
const resolved = resolve2(path);
|
|
3786
|
+
if (isFilesystemRoot(resolved)) {
|
|
3787
|
+
throw new WensityError(
|
|
3788
|
+
"project_ambiguous",
|
|
3789
|
+
`${resolved} is a filesystem root, not a project. Run Wensity from your project directory (the one with package.json).`
|
|
3790
|
+
);
|
|
3791
|
+
}
|
|
3792
|
+
if (isHomeDirectory(resolved, home)) {
|
|
3793
|
+
throw new WensityError(
|
|
3794
|
+
"project_ambiguous",
|
|
3795
|
+
"Your home directory is not a project. Run Wensity from your project directory (the one with package.json)."
|
|
3796
|
+
);
|
|
3797
|
+
}
|
|
3798
|
+
}
|
|
3799
|
+
async function locateProject(start, home = homedir()) {
|
|
3800
|
+
assertProjectRootCandidate(start, home);
|
|
3801
|
+
const origin = resolve2(start);
|
|
3802
|
+
let current = origin;
|
|
2733
3803
|
while (true) {
|
|
2734
|
-
const candidate =
|
|
2735
|
-
if (await fileExists(candidate)) return candidate;
|
|
2736
|
-
|
|
2737
|
-
|
|
3804
|
+
const candidate = join5(current, CONFIG_FILE_NAME);
|
|
3805
|
+
if (await fileExists(candidate)) return { projectRoot: current, configPath: candidate };
|
|
3806
|
+
if (await fileExists(join5(current, "package.json"))) return { projectRoot: current, configPath: void 0 };
|
|
3807
|
+
const parent = dirname3(current);
|
|
3808
|
+
if (isFilesystemRoot(parent) || isHomeDirectory(parent, home)) return { projectRoot: origin, configPath: void 0 };
|
|
2738
3809
|
current = parent;
|
|
2739
3810
|
}
|
|
2740
3811
|
}
|
|
3812
|
+
function isFilesystemRoot(path) {
|
|
3813
|
+
return dirname3(path) === path;
|
|
3814
|
+
}
|
|
3815
|
+
function isHomeDirectory(path, home) {
|
|
3816
|
+
if (!home) return false;
|
|
3817
|
+
if (resolve2(path) === resolve2(home)) return true;
|
|
3818
|
+
try {
|
|
3819
|
+
return realpathSync3(path) === realpathSync3(home);
|
|
3820
|
+
} catch {
|
|
3821
|
+
return false;
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
function defaultSourceRoot(projectRoot) {
|
|
3825
|
+
const fromAlias = atAliasRoot(readPathMapping(projectRoot));
|
|
3826
|
+
if (fromAlias !== void 0) return fromAlias;
|
|
3827
|
+
const src = join5(projectRoot, "src");
|
|
3828
|
+
return existsSync2(src) && statSync2(src).isDirectory() ? "src" : "";
|
|
3829
|
+
}
|
|
2741
3830
|
function normalizeConfig(config, overrides) {
|
|
2742
3831
|
const apiOrigin = overrides.apiUrl ? normalizeUrlOrigin(overrides.apiUrl) : void 0;
|
|
2743
3832
|
try {
|
|
@@ -2757,14 +3846,14 @@ function normalizeConfig(config, overrides) {
|
|
|
2757
3846
|
};
|
|
2758
3847
|
} catch (error) {
|
|
2759
3848
|
if (error instanceof PresetValidationError) {
|
|
2760
|
-
throw new
|
|
3849
|
+
throw new WensityError("config_invalid", `${CONFIG_FILE_NAME}: ${error.message}`);
|
|
2761
3850
|
}
|
|
2762
3851
|
throw error;
|
|
2763
3852
|
}
|
|
2764
3853
|
}
|
|
2765
3854
|
function detectTailwindConfig(projectRoot) {
|
|
2766
3855
|
const candidates = ["tailwind.config.ts", "tailwind.config.js", "tailwind.config.mjs"];
|
|
2767
|
-
return candidates.find((candidate) =>
|
|
3856
|
+
return candidates.find((candidate) => existsSync2(join5(projectRoot, candidate))) ?? "tailwind.config.ts";
|
|
2768
3857
|
}
|
|
2769
3858
|
function detectGlobalCss(projectRoot) {
|
|
2770
3859
|
const candidates = [
|
|
@@ -2774,230 +3863,356 @@ function detectGlobalCss(projectRoot) {
|
|
|
2774
3863
|
"src/index.css",
|
|
2775
3864
|
"styles/globals.css"
|
|
2776
3865
|
];
|
|
2777
|
-
return candidates.find((candidate) =>
|
|
3866
|
+
return candidates.find((candidate) => existsSync2(join5(projectRoot, candidate))) ?? "src/app/globals.css";
|
|
2778
3867
|
}
|
|
2779
3868
|
function withExtension(path, extension) {
|
|
2780
3869
|
if (/\.(ts|tsx|js|jsx)$/.test(path)) return path;
|
|
2781
3870
|
return `${path}.${extension}`;
|
|
2782
3871
|
}
|
|
2783
3872
|
|
|
2784
|
-
// src/
|
|
2785
|
-
import {
|
|
2786
|
-
import {
|
|
2787
|
-
import {
|
|
2788
|
-
|
|
2789
|
-
function
|
|
2790
|
-
if (
|
|
2791
|
-
const
|
|
2792
|
-
|
|
3873
|
+
// ../install-core/src/package-manager.ts
|
|
3874
|
+
import { accessSync, constants as fsConstants2, existsSync as existsSync3, readFileSync as readFileSync3, statSync as statSync3, writeFileSync } from "fs";
|
|
3875
|
+
import { spawn, spawnSync } from "child_process";
|
|
3876
|
+
import { join as join6, posix as posix3, win32 } from "path";
|
|
3877
|
+
var PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
|
|
3878
|
+
function parsePackageManager(value) {
|
|
3879
|
+
if (!value) return void 0;
|
|
3880
|
+
const normalized = value.trim().toLowerCase();
|
|
3881
|
+
if (isPackageManager(normalized)) return normalized;
|
|
3882
|
+
throw new WensityError(
|
|
3883
|
+
"unsupported_package_manager",
|
|
3884
|
+
`Unsupported package manager "${value}". Use one of: ${PACKAGE_MANAGERS.join(", ")}.`
|
|
3885
|
+
);
|
|
2793
3886
|
}
|
|
2794
|
-
|
|
2795
|
-
const
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
3887
|
+
function packageManagerFromUserAgent(env = process.env) {
|
|
3888
|
+
const agent = env.npm_config_user_agent;
|
|
3889
|
+
if (!agent) return void 0;
|
|
3890
|
+
const first = agent.trim().split(/\s+/)[0] ?? "";
|
|
3891
|
+
const name = first.split("/")[0]?.toLowerCase();
|
|
3892
|
+
return name && isPackageManager(name) ? name : void 0;
|
|
3893
|
+
}
|
|
3894
|
+
function detectPackageManager(projectRoot, env = process.env) {
|
|
3895
|
+
if (existsSync3(join6(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
3896
|
+
if (existsSync3(join6(projectRoot, "bun.lockb")) || existsSync3(join6(projectRoot, "bun.lock"))) return "bun";
|
|
3897
|
+
if (existsSync3(join6(projectRoot, "yarn.lock"))) return "yarn";
|
|
3898
|
+
if (existsSync3(join6(projectRoot, "package-lock.json"))) return "npm";
|
|
3899
|
+
return packageManagerFromUserAgent(env) ?? "npm";
|
|
3900
|
+
}
|
|
3901
|
+
function isPackageManager(value) {
|
|
3902
|
+
return PACKAGE_MANAGERS.includes(value);
|
|
3903
|
+
}
|
|
3904
|
+
function missingDependencies(projectRoot, dependencies) {
|
|
3905
|
+
const installed = readInstalledPackageNames(projectRoot);
|
|
3906
|
+
if (!installed) return dependencies;
|
|
3907
|
+
return dependencies.filter((dependency) => !installed.has(dependency));
|
|
3908
|
+
}
|
|
3909
|
+
function readInstalledPackageNames(projectRoot) {
|
|
3910
|
+
const packageJsonPath = join6(projectRoot, "package.json");
|
|
3911
|
+
if (!existsSync3(packageJsonPath)) return void 0;
|
|
3912
|
+
const packageJson2 = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
|
|
3913
|
+
return new Set(
|
|
3914
|
+
Object.keys({
|
|
3915
|
+
...packageJson2.dependencies,
|
|
3916
|
+
...packageJson2.devDependencies,
|
|
3917
|
+
...packageJson2.peerDependencies,
|
|
3918
|
+
...packageJson2.optionalDependencies
|
|
3919
|
+
})
|
|
3920
|
+
);
|
|
3921
|
+
}
|
|
3922
|
+
var YARN_CONFIG_FILE = ".yarnrc.yml";
|
|
3923
|
+
function prepareProjectPackageManager(projectRoot, options = {}) {
|
|
3924
|
+
if (resolvePackageManager(projectRoot, options) !== "yarn") return [];
|
|
3925
|
+
const configPath = join6(projectRoot, YARN_CONFIG_FILE);
|
|
3926
|
+
if (existsSync3(configPath)) return [];
|
|
3927
|
+
if (!options.dryRun) writeFileSync(configPath, "nodeLinker: node-modules\n", "utf8");
|
|
3928
|
+
return [YARN_CONFIG_FILE];
|
|
3929
|
+
}
|
|
3930
|
+
async function runProjectInstall(projectRoot, options = {}) {
|
|
3931
|
+
const packageJsonPath = join6(projectRoot, "package.json");
|
|
3932
|
+
if (!existsSync3(packageJsonPath)) return;
|
|
3933
|
+
const packageManager = resolvePackageManager(projectRoot, options);
|
|
3934
|
+
await runPackageManager(projectRoot, packageManager, ["install"], options);
|
|
3935
|
+
}
|
|
3936
|
+
function resolvePackageManager(projectRoot, options = {}) {
|
|
3937
|
+
return options.packageManager ?? detectPackageManager(projectRoot, options.env);
|
|
3938
|
+
}
|
|
3939
|
+
async function runInstall(projectRoot, dependencies, options = {}) {
|
|
3940
|
+
if (dependencies.length === 0) return;
|
|
3941
|
+
const packageJsonPath = join6(projectRoot, "package.json");
|
|
3942
|
+
if (!existsSync3(packageJsonPath)) {
|
|
3943
|
+
(options.onWarning ?? writeWarning)(
|
|
3944
|
+
`No package.json found. Install these dependencies manually: ${dependencies.join(" ")}`
|
|
3945
|
+
);
|
|
3946
|
+
return;
|
|
2809
3947
|
}
|
|
3948
|
+
const packageManager = resolvePackageManager(projectRoot, options);
|
|
3949
|
+
await runPackageManager(projectRoot, packageManager, installArgs(packageManager, dependencies), options);
|
|
2810
3950
|
}
|
|
2811
|
-
async function
|
|
2812
|
-
const
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
await writeFile2(temporaryPath, `${JSON.stringify(credentials, null, 2)}
|
|
2817
|
-
`, {
|
|
2818
|
-
encoding: "utf8",
|
|
2819
|
-
mode: 384
|
|
3951
|
+
async function runPackageManager(projectRoot, packageManager, args, options) {
|
|
3952
|
+
const outcome = await runPackageManagerCommand(projectRoot, packageManager, args, {
|
|
3953
|
+
output: "inherit",
|
|
3954
|
+
forwardSignals: true,
|
|
3955
|
+
...options.spawnImpl ? { spawnImpl: options.spawnImpl } : {}
|
|
2820
3956
|
});
|
|
2821
|
-
|
|
2822
|
-
await chmod(path, 384);
|
|
2823
|
-
return path;
|
|
3957
|
+
throwForFailure(packageManager, args, outcome);
|
|
2824
3958
|
}
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
3959
|
+
function throwForFailure(packageManager, args, outcome) {
|
|
3960
|
+
if (outcome.spawnError) {
|
|
3961
|
+
throw new WensityError("dependency_install_failed", `Could not run ${packageManager}: ${outcome.spawnError}`);
|
|
3962
|
+
}
|
|
3963
|
+
if (outcome.timedOut) {
|
|
3964
|
+
throw new WensityError(
|
|
3965
|
+
"dependency_install_failed",
|
|
3966
|
+
`${packageManager} ${args.join(" ")} did not finish within ${PACKAGE_MANAGER_TIMEOUT_MS / 1e3} s and was stopped.`
|
|
3967
|
+
);
|
|
3968
|
+
}
|
|
3969
|
+
if (outcome.exitCode !== 0) {
|
|
3970
|
+
throw new WensityError("dependency_install_failed", `${packageManager} ${args.join(" ")} failed with exit code ${outcome.exitCode ?? 1}.`);
|
|
2833
3971
|
}
|
|
2834
3972
|
}
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
3973
|
+
var PACKAGE_MANAGER_TIMEOUT_MS = 18e4;
|
|
3974
|
+
var PACKAGE_NAME = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
|
|
3975
|
+
var SUBCOMMANDS = /* @__PURE__ */ new Set(["add", "install"]);
|
|
3976
|
+
var WINDOWS_UNSAFE = /["%!^&|<>()\r\n\t\u0000-\u001f\u007f]/;
|
|
3977
|
+
function assertValidPackageName(name) {
|
|
3978
|
+
if (typeof name !== "string" || name.length > 214 || !PACKAGE_NAME.test(name) || /(^|\/|^@)[-._]/.test(name)) {
|
|
3979
|
+
throw new WensityError("dependency_install_failed", `Refusing to install an invalid package name: ${JSON.stringify(String(name).slice(0, 80))}`);
|
|
2842
3980
|
}
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
3981
|
+
}
|
|
3982
|
+
var WINDOWS_JS_ENTRIES = {
|
|
3983
|
+
npm: ["node_modules/npm/bin/npm-cli.js"],
|
|
3984
|
+
pnpm: ["node_modules/pnpm/bin/pnpm.cjs", "node_modules/pnpm/bin/pnpm.mjs"],
|
|
3985
|
+
yarn: ["node_modules/yarn/bin/yarn.js"],
|
|
3986
|
+
bun: []
|
|
3987
|
+
};
|
|
3988
|
+
function buildPackageManagerInvocation(packageManager, args, options = {}) {
|
|
3989
|
+
if (!PACKAGE_MANAGERS.includes(packageManager)) parsePackageManager(packageManager);
|
|
3990
|
+
const [subcommand, ...names] = args;
|
|
3991
|
+
if (!subcommand || !SUBCOMMANDS.has(subcommand)) {
|
|
3992
|
+
throw new WensityError("dependency_install_failed", `Refusing to run ${packageManager} with an unexpected subcommand.`);
|
|
3993
|
+
}
|
|
3994
|
+
for (const name of names) assertValidPackageName(name);
|
|
3995
|
+
const platform = options.platform ?? process.platform;
|
|
3996
|
+
const env = options.env ?? process.env;
|
|
3997
|
+
const display = [packageManager, ...args].join(" ");
|
|
3998
|
+
const isFile = options.isFile ?? defaultIsFile;
|
|
3999
|
+
if (platform !== "win32") {
|
|
4000
|
+
const isExecutable = options.isExecutable ?? defaultIsExecutable;
|
|
4001
|
+
const dirs = (env.PATH ?? "").split(posix3.delimiter).filter(Boolean);
|
|
4002
|
+
const resolved = dirs.map((dir) => posix3.join(dir, packageManager)).find(isExecutable);
|
|
4003
|
+
if (!resolved) throw notFound(packageManager);
|
|
4004
|
+
return { shape: "posix", command: resolved, args, windowsVerbatimArguments: false, display };
|
|
4005
|
+
}
|
|
4006
|
+
const pathValue = env.PATH ?? env.Path ?? Object.entries(env).find(([key]) => key.toLowerCase() === "path")?.[1] ?? "";
|
|
4007
|
+
const extensions = (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").map((ext) => ext.toLowerCase()).filter(Boolean);
|
|
4008
|
+
for (const dir of pathValue.split(win32.delimiter).filter(Boolean)) {
|
|
4009
|
+
for (const extension of extensions) {
|
|
4010
|
+
const candidate = win32.join(dir, `${packageManager}${extension}`);
|
|
4011
|
+
if (!isFile(candidate)) continue;
|
|
4012
|
+
if (extension === ".exe" || extension === ".com") {
|
|
4013
|
+
return { shape: "windows-exe", command: candidate, args, windowsVerbatimArguments: false, display };
|
|
2867
4014
|
}
|
|
2868
|
-
if (
|
|
2869
|
-
|
|
2870
|
-
|
|
4015
|
+
if (extension !== ".cmd" && extension !== ".bat") continue;
|
|
4016
|
+
const entry = WINDOWS_JS_ENTRIES[packageManager].map((relative9) => win32.join(dir, relative9)).find(isFile);
|
|
4017
|
+
if (entry) {
|
|
4018
|
+
return { shape: "windows-node", command: options.nodePath ?? process.execPath, args: [entry, ...args], windowsVerbatimArguments: false, display };
|
|
2871
4019
|
}
|
|
2872
|
-
|
|
4020
|
+
const cmd = resolveCmdExe(env);
|
|
4021
|
+
return {
|
|
4022
|
+
shape: "windows-cmd",
|
|
4023
|
+
command: cmd,
|
|
4024
|
+
args: ["/d", "/s", "/c", `"${quoteWindowsCommandLine([candidate, ...args])}"`],
|
|
4025
|
+
windowsVerbatimArguments: true,
|
|
4026
|
+
display
|
|
4027
|
+
};
|
|
2873
4028
|
}
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
input.resume();
|
|
2877
|
-
input.on("data", onData);
|
|
2878
|
-
});
|
|
2879
|
-
}
|
|
2880
|
-
function requiredHome(env) {
|
|
2881
|
-
if (env.HOME) return env.HOME;
|
|
2882
|
-
throw new CliError("HOME is not set, so the CLI cannot locate ~/.config/wensity.");
|
|
4029
|
+
}
|
|
4030
|
+
throw notFound(packageManager);
|
|
2883
4031
|
}
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
function planFontInstall(headingFont, bodyFont) {
|
|
2889
|
-
const packages = [];
|
|
2890
|
-
const importLines = [];
|
|
2891
|
-
const seenPackages = /* @__PURE__ */ new Set();
|
|
2892
|
-
const seenImports = /* @__PURE__ */ new Set();
|
|
2893
|
-
const pushFont = (meta) => {
|
|
2894
|
-
if (!meta) return;
|
|
2895
|
-
if (!seenPackages.has(meta.packageName)) {
|
|
2896
|
-
seenPackages.add(meta.packageName);
|
|
2897
|
-
packages.push(meta.packageName);
|
|
2898
|
-
}
|
|
2899
|
-
for (const specifier of curatedFontCssImportSpecifiers(meta)) {
|
|
2900
|
-
const line = `@import "${specifier}";`;
|
|
2901
|
-
if (!seenImports.has(line)) {
|
|
2902
|
-
seenImports.add(line);
|
|
2903
|
-
importLines.push(line);
|
|
2904
|
-
}
|
|
4032
|
+
function quoteWindowsCommandLine(values) {
|
|
4033
|
+
return values.map((value) => {
|
|
4034
|
+
if (value.length === 0 || WINDOWS_UNSAFE.test(value)) {
|
|
4035
|
+
throw new WensityError("dependency_install_failed", `Refusing to build a command line from an unsafe value: ${JSON.stringify(value.slice(0, 80))}`);
|
|
2905
4036
|
}
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
4037
|
+
return /\s/.test(value) ? `"${value}"` : value;
|
|
4038
|
+
}).join(" ");
|
|
4039
|
+
}
|
|
4040
|
+
function resolveCmdExe(env) {
|
|
4041
|
+
const fromEnv = env.ComSpec ?? env.COMSPEC;
|
|
4042
|
+
if (fromEnv && win32.isAbsolute(fromEnv) && win32.basename(fromEnv).toLowerCase() === "cmd.exe" && !WINDOWS_UNSAFE.test(fromEnv)) {
|
|
4043
|
+
return fromEnv;
|
|
2912
4044
|
}
|
|
2913
|
-
|
|
2914
|
-
if (
|
|
2915
|
-
|
|
4045
|
+
const systemRoot = env.SystemRoot ?? env.SYSTEMROOT ?? "C:\\Windows";
|
|
4046
|
+
if (!win32.isAbsolute(systemRoot) || WINDOWS_UNSAFE.test(systemRoot)) {
|
|
4047
|
+
throw new WensityError("dependency_install_failed", "Could not locate cmd.exe safely.");
|
|
2916
4048
|
}
|
|
2917
|
-
|
|
2918
|
-
`${FONT_CSS_START_MARKER} \u2500\u2500 */`,
|
|
2919
|
-
...importLines,
|
|
2920
|
-
FONT_CSS_END_MARKER
|
|
2921
|
-
].join("\n");
|
|
2922
|
-
return { packages, importLines, block };
|
|
4049
|
+
return win32.join(systemRoot, "System32", "cmd.exe");
|
|
2923
4050
|
}
|
|
2924
|
-
function
|
|
2925
|
-
|
|
2926
|
-
const end = existingCss.indexOf(FONT_CSS_END_MARKER);
|
|
2927
|
-
if (start !== -1 || end !== -1) {
|
|
2928
|
-
if (start === -1 || end === -1 || end <= start) {
|
|
2929
|
-
throw new CliError(
|
|
2930
|
-
"Configured Tailwind CSS has a partial Wensity Fonts block. Remove the broken markers and re-run install."
|
|
2931
|
-
);
|
|
2932
|
-
}
|
|
2933
|
-
const before2 = existingCss.slice(0, start).replace(/\s*$/, "");
|
|
2934
|
-
const after2 = existingCss.slice(end + FONT_CSS_END_MARKER.length).replace(/^\s*/, "");
|
|
2935
|
-
if (!block) {
|
|
2936
|
-
const joined = [before2, after2].filter((part) => part.length > 0).join("\n\n");
|
|
2937
|
-
return joined ? `${joined.replace(/\n{3,}/g, "\n\n")}
|
|
2938
|
-
` : "";
|
|
2939
|
-
}
|
|
2940
|
-
const normalizedBlock2 = block.trim();
|
|
2941
|
-
const prefix2 = before2 ? `${before2}
|
|
2942
|
-
|
|
2943
|
-
` : "";
|
|
2944
|
-
const suffix2 = after2 ? `
|
|
2945
|
-
|
|
2946
|
-
${after2}` : "\n";
|
|
2947
|
-
return `${prefix2}${normalizedBlock2}${suffix2}`.replace(/\n{3,}/g, "\n\n");
|
|
2948
|
-
}
|
|
2949
|
-
if (!block) return existingCss;
|
|
2950
|
-
const normalizedBlock = block.trim();
|
|
2951
|
-
const insertAt = findFontBlockInsertIndex(existingCss);
|
|
2952
|
-
const before = existingCss.slice(0, insertAt).replace(/\s*$/, "");
|
|
2953
|
-
const after = existingCss.slice(insertAt).replace(/^\s*/, "");
|
|
2954
|
-
const prefix = before ? `${before}
|
|
2955
|
-
|
|
2956
|
-
` : "";
|
|
2957
|
-
const suffix = after ? `
|
|
2958
|
-
|
|
2959
|
-
${after}` : "\n";
|
|
2960
|
-
return `${prefix}${normalizedBlock}${suffix}`.replace(/\n{3,}/g, "\n\n");
|
|
4051
|
+
function notFound(packageManager) {
|
|
4052
|
+
return new WensityError("dependency_install_failed", `Could not run ${packageManager}: it was not found on PATH.`);
|
|
2961
4053
|
}
|
|
2962
|
-
function
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
(pkg) => installedPackages.has(pkg) && !needed.has(pkg)
|
|
2970
|
-
);
|
|
2971
|
-
if (leftover.length === 0) return void 0;
|
|
2972
|
-
return `Project package.json still lists ${leftover.join(", ")} while curated fonts are set to heading="${headingFont}" body="${bodyFont}". Wensity removed the @import for unused fonts but will not uninstall ${leftover.join(", ")} (it may be used outside Wensity).`;
|
|
4054
|
+
function defaultIsExecutable(path) {
|
|
4055
|
+
try {
|
|
4056
|
+
accessSync(path, fsConstants2.X_OK);
|
|
4057
|
+
return statSync3(path).isFile();
|
|
4058
|
+
} catch {
|
|
4059
|
+
return false;
|
|
4060
|
+
}
|
|
2973
4061
|
}
|
|
2974
|
-
function
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
4062
|
+
function defaultIsFile(path) {
|
|
4063
|
+
try {
|
|
4064
|
+
return statSync3(path).isFile();
|
|
4065
|
+
} catch {
|
|
4066
|
+
return false;
|
|
4067
|
+
}
|
|
4068
|
+
}
|
|
4069
|
+
var OUTPUT_KEEP = 256 * 1024;
|
|
4070
|
+
var TREE_EXIT_WAIT_MS = 5e3;
|
|
4071
|
+
function runPackageManagerCommand(projectRoot, packageManager, args, options = {}) {
|
|
4072
|
+
const invocation = buildPackageManagerInvocation(packageManager, args, options);
|
|
4073
|
+
const platform = options.platform ?? process.platform;
|
|
4074
|
+
const inherit = options.output === "inherit";
|
|
4075
|
+
const ownGroup = !inherit && platform !== "win32";
|
|
4076
|
+
const timeoutMs = options.timeoutMs ?? (inherit ? void 0 : PACKAGE_MANAGER_TIMEOUT_MS);
|
|
4077
|
+
const started = Date.now();
|
|
4078
|
+
return new Promise((resolve3) => {
|
|
4079
|
+
let stdout2 = "";
|
|
4080
|
+
let stderr = "";
|
|
4081
|
+
let timedOut = false;
|
|
4082
|
+
const child = (options.spawnImpl ?? spawn)(invocation.command, invocation.args, {
|
|
4083
|
+
cwd: projectRoot,
|
|
4084
|
+
env: options.spawnEnv ?? process.env,
|
|
4085
|
+
shell: false,
|
|
4086
|
+
stdio: inherit ? "inherit" : ["ignore", "pipe", "pipe"],
|
|
4087
|
+
windowsHide: true,
|
|
4088
|
+
windowsVerbatimArguments: invocation.windowsVerbatimArguments,
|
|
4089
|
+
detached: ownGroup
|
|
4090
|
+
});
|
|
4091
|
+
const killTree = () => killProcessTree(child.pid, platform, options.env ?? process.env, spawnSync, ownGroup);
|
|
4092
|
+
const signalHandlers = [];
|
|
4093
|
+
if (options.forwardSignals) {
|
|
4094
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
4095
|
+
const handler = () => {
|
|
4096
|
+
killTree();
|
|
4097
|
+
removeSignalHandlers();
|
|
4098
|
+
process.kill(process.pid, signal);
|
|
4099
|
+
};
|
|
4100
|
+
signalHandlers.push([signal, handler]);
|
|
4101
|
+
process.once(signal, handler);
|
|
4102
|
+
}
|
|
2985
4103
|
}
|
|
2986
|
-
|
|
2987
|
-
const
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
4104
|
+
const removeSignalHandlers = () => {
|
|
4105
|
+
for (const [signal, handler] of signalHandlers.splice(0)) process.removeListener(signal, handler);
|
|
4106
|
+
};
|
|
4107
|
+
const timer = timeoutMs === void 0 ? void 0 : setTimeout(() => {
|
|
4108
|
+
timedOut = true;
|
|
4109
|
+
killTree();
|
|
4110
|
+
}, timeoutMs);
|
|
4111
|
+
child.stdout?.on("data", (chunk) => {
|
|
4112
|
+
stdout2 = (stdout2 + chunk.toString("utf8")).slice(-OUTPUT_KEEP);
|
|
4113
|
+
});
|
|
4114
|
+
child.stderr?.on("data", (chunk) => {
|
|
4115
|
+
stderr = (stderr + chunk.toString("utf8")).slice(-OUTPUT_KEEP);
|
|
4116
|
+
});
|
|
4117
|
+
let settled = false;
|
|
4118
|
+
const finish = async (exitCode, spawnError) => {
|
|
4119
|
+
if (settled) return;
|
|
4120
|
+
settled = true;
|
|
4121
|
+
clearTimeout(timer);
|
|
4122
|
+
if (ownGroup && child.pid !== void 0 && groupAlive(child.pid)) {
|
|
4123
|
+
killTree();
|
|
4124
|
+
await waitForGroupExit(child.pid);
|
|
4125
|
+
}
|
|
4126
|
+
removeSignalHandlers();
|
|
4127
|
+
child.stdout?.destroy();
|
|
4128
|
+
child.stderr?.destroy();
|
|
4129
|
+
resolve3({ invocation, exitCode, stdout: stdout2, stderr, durationMs: Date.now() - started, timedOut, ...spawnError ? { spawnError } : {} });
|
|
4130
|
+
};
|
|
4131
|
+
child.on("error", (error) => void finish(null, error.message));
|
|
4132
|
+
child.on("exit", (code) => void finish(code));
|
|
4133
|
+
});
|
|
4134
|
+
}
|
|
4135
|
+
function killProcessTree(pid, platform, env, run2 = spawnSync, ownGroup = true) {
|
|
4136
|
+
if (pid === void 0) return;
|
|
4137
|
+
try {
|
|
4138
|
+
if (platform !== "win32" && !ownGroup) {
|
|
4139
|
+
for (const member of [...descendantsOf(pid, run2).reverse(), pid]) {
|
|
4140
|
+
try {
|
|
4141
|
+
process.kill(member, "SIGKILL");
|
|
4142
|
+
} catch {
|
|
4143
|
+
}
|
|
4144
|
+
}
|
|
4145
|
+
return;
|
|
2991
4146
|
}
|
|
2992
|
-
if (
|
|
2993
|
-
const
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
4147
|
+
if (platform === "win32") {
|
|
4148
|
+
const systemRoot = env.SystemRoot ?? env.SYSTEMROOT ?? "C:\\Windows";
|
|
4149
|
+
run2(win32.join(systemRoot, "System32", "taskkill.exe"), ["/PID", String(pid), "/T", "/F"], { shell: false, stdio: "ignore", windowsHide: true });
|
|
4150
|
+
} else {
|
|
4151
|
+
process.kill(-pid, "SIGKILL");
|
|
2997
4152
|
}
|
|
2998
|
-
|
|
4153
|
+
} catch {
|
|
4154
|
+
}
|
|
4155
|
+
}
|
|
4156
|
+
function descendantsOf(pid, run2) {
|
|
4157
|
+
const listing = run2("ps", ["-A", "-o", "pid=,ppid="], { shell: false, encoding: "utf8" });
|
|
4158
|
+
const children = /* @__PURE__ */ new Map();
|
|
4159
|
+
for (const line of String(listing.stdout ?? "").split("\n")) {
|
|
4160
|
+
const [child, parent] = line.trim().split(/\s+/).map(Number);
|
|
4161
|
+
if (!child || !parent) continue;
|
|
4162
|
+
children.set(parent, [...children.get(parent) ?? [], child]);
|
|
4163
|
+
}
|
|
4164
|
+
const found = [];
|
|
4165
|
+
const queue = [...children.get(pid) ?? []];
|
|
4166
|
+
while (queue.length > 0) {
|
|
4167
|
+
const next = queue.shift();
|
|
4168
|
+
found.push(next);
|
|
4169
|
+
queue.push(...children.get(next) ?? []);
|
|
4170
|
+
}
|
|
4171
|
+
return found;
|
|
4172
|
+
}
|
|
4173
|
+
function groupAlive(pid) {
|
|
4174
|
+
try {
|
|
4175
|
+
process.kill(-pid, 0);
|
|
4176
|
+
return true;
|
|
4177
|
+
} catch (error) {
|
|
4178
|
+
return error.code === "EPERM";
|
|
4179
|
+
}
|
|
4180
|
+
}
|
|
4181
|
+
async function waitForGroupExit(pid) {
|
|
4182
|
+
const deadline = Date.now() + TREE_EXIT_WAIT_MS;
|
|
4183
|
+
while (groupAlive(pid) && Date.now() < deadline) {
|
|
4184
|
+
await new Promise((resolve3) => setTimeout(resolve3, 20));
|
|
4185
|
+
}
|
|
4186
|
+
}
|
|
4187
|
+
function installArgs(packageManager, dependencies) {
|
|
4188
|
+
switch (packageManager) {
|
|
4189
|
+
case "pnpm":
|
|
4190
|
+
return ["add", ...dependencies];
|
|
4191
|
+
case "yarn":
|
|
4192
|
+
return ["add", ...dependencies];
|
|
4193
|
+
case "bun":
|
|
4194
|
+
return ["add", ...dependencies];
|
|
4195
|
+
case "npm":
|
|
4196
|
+
return ["install", ...dependencies];
|
|
2999
4197
|
}
|
|
3000
|
-
|
|
4198
|
+
}
|
|
4199
|
+
function writeWarning(message) {
|
|
4200
|
+
process.stderr.write(`Warning: ${message}
|
|
4201
|
+
`);
|
|
4202
|
+
}
|
|
4203
|
+
|
|
4204
|
+
// ../install-core/src/project.ts
|
|
4205
|
+
var IMPORT_ALIAS_REMEDY = 'Add a compilerOptions.paths entry such as "@/*": ["./*"] (or ["./src/*"]) to tsconfig.json, or set components, utils and aliases in wensity.json so each alias maps to its folder.';
|
|
4206
|
+
function importAliasesResolve(projectRoot, config) {
|
|
4207
|
+
const mapping = readPathMapping(projectRoot);
|
|
4208
|
+
if (!mapping) return null;
|
|
4209
|
+
return aliasResolvesTo(mapping, config.aliases.components, config.components) && aliasResolvesTo(mapping, config.aliases.utils, config.utils);
|
|
4210
|
+
}
|
|
4211
|
+
function importAliasMismatchMessage(config) {
|
|
4212
|
+
return `Installed files would import ${config.aliases.components} and ${config.aliases.utils}, but the tsconfig/jsconfig paths do not map them to ${config.components} and ${config.utils}, so the imports would not resolve.`;
|
|
4213
|
+
}
|
|
4214
|
+
function importAliasBlockMessage(config) {
|
|
4215
|
+
return `${importAliasMismatchMessage(config)} ${IMPORT_ALIAS_REMEDY}`;
|
|
3001
4216
|
}
|
|
3002
4217
|
|
|
3003
4218
|
// src/output.ts
|
|
@@ -3143,82 +4358,362 @@ function formatProRequiredError(resourceName, resourceType) {
|
|
|
3143
4358
|
`Pricing: ${pricingBlue("https://ui.wensity.com/pricing")}`
|
|
3144
4359
|
].join("\n");
|
|
3145
4360
|
}
|
|
3146
|
-
function color(message, hex) {
|
|
3147
|
-
if (!shouldColor()) return message;
|
|
3148
|
-
const [red, green, blue] = hexToRgb(hex);
|
|
3149
|
-
return `\x1B[38;2;${red};${green};${blue}m${message}\x1B[39m`;
|
|
3150
|
-
}
|
|
3151
|
-
function bgBrandRed(message) {
|
|
3152
|
-
if (!shouldColor()) return message;
|
|
3153
|
-
const [red, green, blue] = hexToRgb(BRAND_RED);
|
|
3154
|
-
return `\x1B[48;2;${red};${green};${blue}m${message}\x1B[49m`;
|
|
3155
|
-
}
|
|
3156
|
-
function shouldColor() {
|
|
3157
|
-
if (process.env.FORCE_COLOR && process.env.FORCE_COLOR !== "0") return true;
|
|
3158
|
-
return process.env.NO_COLOR === void 0 && process.env.FORCE_COLOR !== "0";
|
|
4361
|
+
function color(message, hex) {
|
|
4362
|
+
if (!shouldColor()) return message;
|
|
4363
|
+
const [red, green, blue] = hexToRgb(hex);
|
|
4364
|
+
return `\x1B[38;2;${red};${green};${blue}m${message}\x1B[39m`;
|
|
4365
|
+
}
|
|
4366
|
+
function bgBrandRed(message) {
|
|
4367
|
+
if (!shouldColor()) return message;
|
|
4368
|
+
const [red, green, blue] = hexToRgb(BRAND_RED);
|
|
4369
|
+
return `\x1B[48;2;${red};${green};${blue}m${message}\x1B[49m`;
|
|
4370
|
+
}
|
|
4371
|
+
function shouldColor() {
|
|
4372
|
+
if (process.env.FORCE_COLOR && process.env.FORCE_COLOR !== "0") return true;
|
|
4373
|
+
return process.env.NO_COLOR === void 0 && process.env.FORCE_COLOR !== "0";
|
|
4374
|
+
}
|
|
4375
|
+
function hexToRgb(hex) {
|
|
4376
|
+
const value = hex.replace("#", "");
|
|
4377
|
+
return [
|
|
4378
|
+
Number.parseInt(value.slice(0, 2), 16),
|
|
4379
|
+
Number.parseInt(value.slice(2, 4), 16),
|
|
4380
|
+
Number.parseInt(value.slice(4, 6), 16)
|
|
4381
|
+
];
|
|
4382
|
+
}
|
|
4383
|
+
function centerLine(line) {
|
|
4384
|
+
const columns = Math.min(process.stdout.columns || 80, 100);
|
|
4385
|
+
const padding = Math.max(0, Math.floor((columns - line.length) / 2));
|
|
4386
|
+
return `${" ".repeat(padding)}${line}`;
|
|
4387
|
+
}
|
|
4388
|
+
|
|
4389
|
+
// src/errors.ts
|
|
4390
|
+
var PRICING_URL = "https://ui.wensity.com/pricing";
|
|
4391
|
+
var CliError = class _CliError extends Error {
|
|
4392
|
+
exitCode;
|
|
4393
|
+
/** The install-core error this one renders, when there is one. */
|
|
4394
|
+
wensityError;
|
|
4395
|
+
constructor(message, exitCode = 1, options = {}) {
|
|
4396
|
+
super(message, options.cause ? { cause: options.cause } : void 0);
|
|
4397
|
+
this.name = "CliError";
|
|
4398
|
+
this.exitCode = exitCode;
|
|
4399
|
+
if (options.cause) this.wensityError = options.cause;
|
|
4400
|
+
}
|
|
4401
|
+
/**
|
|
4402
|
+
* install-core throws WensityError wherever this code used to throw CliError,
|
|
4403
|
+
* so `instanceof CliError` keeps meaning "a user-facing CLI failure" for both.
|
|
4404
|
+
* Render one with `toCliError` to get the exit code and CLI wording.
|
|
4405
|
+
*/
|
|
4406
|
+
static [Symbol.hasInstance](value) {
|
|
4407
|
+
return isWensityError(value) || Function.prototype[Symbol.hasInstance].call(_CliError, value);
|
|
4408
|
+
}
|
|
4409
|
+
static fromWensityError(error) {
|
|
4410
|
+
return new _CliError(renderWensityErrorMessage(error), 1, { cause: error });
|
|
4411
|
+
}
|
|
4412
|
+
};
|
|
4413
|
+
function renderWensityErrorMessage(error) {
|
|
4414
|
+
if (error.code === "entitlement_required") {
|
|
4415
|
+
return `${error.message}
|
|
4416
|
+
Upgrade here: ${pricingBlue(PRICING_URL)}`;
|
|
4417
|
+
}
|
|
4418
|
+
return error.message;
|
|
4419
|
+
}
|
|
4420
|
+
function toCliError(error) {
|
|
4421
|
+
if (isWensityError(error)) return CliError.fromWensityError(error);
|
|
4422
|
+
return error instanceof CliError ? error : void 0;
|
|
4423
|
+
}
|
|
4424
|
+
|
|
4425
|
+
// src/args.ts
|
|
4426
|
+
function parseArgs(argv) {
|
|
4427
|
+
const positionals = [];
|
|
4428
|
+
const options = /* @__PURE__ */ new Map();
|
|
4429
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
4430
|
+
const token = argv[index];
|
|
4431
|
+
if (!token.startsWith("--")) {
|
|
4432
|
+
positionals.push(token);
|
|
4433
|
+
continue;
|
|
4434
|
+
}
|
|
4435
|
+
if (token === "--") {
|
|
4436
|
+
positionals.push(...argv.slice(index + 1));
|
|
4437
|
+
break;
|
|
4438
|
+
}
|
|
4439
|
+
const inlineValueIndex = token.indexOf("=");
|
|
4440
|
+
const rawName = inlineValueIndex > -1 ? token.slice(2, inlineValueIndex) : token.slice(2);
|
|
4441
|
+
const name = normalizeOptionName(rawName);
|
|
4442
|
+
if (!name) {
|
|
4443
|
+
throw new CliError(`Invalid option: ${token}`);
|
|
4444
|
+
}
|
|
4445
|
+
if (inlineValueIndex > -1) {
|
|
4446
|
+
options.set(name, token.slice(inlineValueIndex + 1));
|
|
4447
|
+
continue;
|
|
4448
|
+
}
|
|
4449
|
+
const nextToken = argv[index + 1];
|
|
4450
|
+
if (nextToken && !nextToken.startsWith("--") && expectsValue(name)) {
|
|
4451
|
+
options.set(name, nextToken);
|
|
4452
|
+
index += 1;
|
|
4453
|
+
continue;
|
|
4454
|
+
}
|
|
4455
|
+
options.set(name, true);
|
|
4456
|
+
}
|
|
4457
|
+
return { positionals, options };
|
|
4458
|
+
}
|
|
4459
|
+
function getStringOption(args, name) {
|
|
4460
|
+
const value = args.options.get(name);
|
|
4461
|
+
if (value === void 0) return void 0;
|
|
4462
|
+
if (typeof value === "boolean") {
|
|
4463
|
+
throw new CliError(`--${name} expects a value.`);
|
|
4464
|
+
}
|
|
4465
|
+
return value;
|
|
4466
|
+
}
|
|
4467
|
+
function hasFlag(args, name) {
|
|
4468
|
+
return args.options.get(name) === true;
|
|
4469
|
+
}
|
|
4470
|
+
function expectsValue(name) {
|
|
4471
|
+
return [
|
|
4472
|
+
"api-url",
|
|
4473
|
+
"registry",
|
|
4474
|
+
"token",
|
|
4475
|
+
"type",
|
|
4476
|
+
"preset",
|
|
4477
|
+
"preset-file",
|
|
4478
|
+
"template",
|
|
4479
|
+
"package-manager"
|
|
4480
|
+
].includes(name);
|
|
4481
|
+
}
|
|
4482
|
+
function normalizeOptionName(name) {
|
|
4483
|
+
if (name === "dry") return "dry-run";
|
|
4484
|
+
return name;
|
|
4485
|
+
}
|
|
4486
|
+
|
|
4487
|
+
// src/credentials.ts
|
|
4488
|
+
import { stdin, stdout } from "process";
|
|
4489
|
+
|
|
4490
|
+
// ../install-core/src/credentials.ts
|
|
4491
|
+
import { mkdir as mkdir3, readFile as readFile4, rename as rename2, rm as rm2, writeFile as writeFile2, chmod as chmod2 } from "fs/promises";
|
|
4492
|
+
import { dirname as dirname4, join as join8 } from "path";
|
|
4493
|
+
import { randomUUID } from "crypto";
|
|
4494
|
+
function credentialsPath(env) {
|
|
4495
|
+
if (env.WENSITY_CREDENTIALS_FILE) return env.WENSITY_CREDENTIALS_FILE;
|
|
4496
|
+
const configHome = env.WENSITY_CONFIG_HOME ?? env.XDG_CONFIG_HOME ?? join8(requiredHome(env), ".config");
|
|
4497
|
+
return join8(configHome, "wensity", "credentials.json");
|
|
4498
|
+
}
|
|
4499
|
+
async function readCredentials(env) {
|
|
4500
|
+
const path = credentialsPath(env);
|
|
4501
|
+
try {
|
|
4502
|
+
const content = await readFile4(path, "utf8");
|
|
4503
|
+
const parsed = JSON.parse(content);
|
|
4504
|
+
if (parsed.version !== 1 || !parsed.token || !parsed.apiBaseUrl) {
|
|
4505
|
+
throw new WensityError("auth_invalid", `Credentials at ${path} are not valid. Run wensity login again.`);
|
|
4506
|
+
}
|
|
4507
|
+
return parsed;
|
|
4508
|
+
} catch (error) {
|
|
4509
|
+
if (isNodeError(error) && error.code === "ENOENT") return void 0;
|
|
4510
|
+
if (error instanceof SyntaxError) {
|
|
4511
|
+
throw new WensityError("auth_invalid", `Credentials at ${path} are not valid JSON. Run wensity login again.`);
|
|
4512
|
+
}
|
|
4513
|
+
throw error;
|
|
4514
|
+
}
|
|
4515
|
+
}
|
|
4516
|
+
async function writeCredentials(env, credentials) {
|
|
4517
|
+
const path = credentialsPath(env);
|
|
4518
|
+
const directory = dirname4(path);
|
|
4519
|
+
const temporaryPath = join8(directory, `.credentials.${randomUUID()}.tmp`);
|
|
4520
|
+
await mkdir3(directory, { recursive: true, mode: 448 });
|
|
4521
|
+
await writeFile2(temporaryPath, `${JSON.stringify(credentials, null, 2)}
|
|
4522
|
+
`, {
|
|
4523
|
+
encoding: "utf8",
|
|
4524
|
+
mode: 384
|
|
4525
|
+
});
|
|
4526
|
+
await rename2(temporaryPath, path);
|
|
4527
|
+
await chmod2(path, 384);
|
|
4528
|
+
return path;
|
|
4529
|
+
}
|
|
4530
|
+
async function deleteCredentials(env) {
|
|
4531
|
+
const path = credentialsPath(env);
|
|
4532
|
+
try {
|
|
4533
|
+
await rm2(path, { force: false });
|
|
4534
|
+
return true;
|
|
4535
|
+
} catch (error) {
|
|
4536
|
+
if (isNodeError(error) && error.code === "ENOENT") return false;
|
|
4537
|
+
throw error;
|
|
4538
|
+
}
|
|
4539
|
+
}
|
|
4540
|
+
function requiredHome(env) {
|
|
4541
|
+
if (env.HOME) return env.HOME;
|
|
4542
|
+
throw new WensityError("config_missing", "HOME is not set, so the CLI cannot locate ~/.config/wensity.");
|
|
4543
|
+
}
|
|
4544
|
+
|
|
4545
|
+
// src/credentials.ts
|
|
4546
|
+
async function readTokenInteractively(prompt = "Wensity API token: ") {
|
|
4547
|
+
if (!stdin.isTTY) {
|
|
4548
|
+
const chunks = [];
|
|
4549
|
+
for await (const chunk of stdin) {
|
|
4550
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
4551
|
+
}
|
|
4552
|
+
return Buffer.concat(chunks).toString("utf8").trim();
|
|
4553
|
+
}
|
|
4554
|
+
return new Promise((resolveToken, rejectToken) => {
|
|
4555
|
+
const input = stdin;
|
|
4556
|
+
const previousRawMode = input.isRaw;
|
|
4557
|
+
let token = "";
|
|
4558
|
+
function cleanup() {
|
|
4559
|
+
input.off("data", onData);
|
|
4560
|
+
if (typeof previousRawMode === "boolean") {
|
|
4561
|
+
input.setRawMode(previousRawMode);
|
|
4562
|
+
}
|
|
4563
|
+
input.pause();
|
|
4564
|
+
}
|
|
4565
|
+
function onData(chunk) {
|
|
4566
|
+
const value = chunk.toString("utf8");
|
|
4567
|
+
if (value === "") {
|
|
4568
|
+
cleanup();
|
|
4569
|
+
stdout.write("\n");
|
|
4570
|
+
rejectToken(new CliError("Login cancelled.", 130));
|
|
4571
|
+
return;
|
|
4572
|
+
}
|
|
4573
|
+
if (value === "\r" || value === "\n" || value === "\r\n") {
|
|
4574
|
+
cleanup();
|
|
4575
|
+
stdout.write("\n");
|
|
4576
|
+
resolveToken(token.trim());
|
|
4577
|
+
return;
|
|
4578
|
+
}
|
|
4579
|
+
if (value === "\x7F") {
|
|
4580
|
+
token = token.slice(0, -1);
|
|
4581
|
+
return;
|
|
4582
|
+
}
|
|
4583
|
+
token += value;
|
|
4584
|
+
}
|
|
4585
|
+
stdout.write(prompt);
|
|
4586
|
+
input.setRawMode(true);
|
|
4587
|
+
input.resume();
|
|
4588
|
+
input.on("data", onData);
|
|
4589
|
+
});
|
|
4590
|
+
}
|
|
4591
|
+
|
|
4592
|
+
// ../install-core/src/font-install.ts
|
|
4593
|
+
var FONT_CSS_START_MARKER = "/* \u2500\u2500 Wensity Fonts";
|
|
4594
|
+
var FONT_CSS_END_MARKER = "/* \u2500\u2500 End Wensity Fonts \u2500\u2500 */";
|
|
4595
|
+
function planFontInstall(headingFont, bodyFont) {
|
|
4596
|
+
const packages = [];
|
|
4597
|
+
const importLines = [];
|
|
4598
|
+
const seenPackages = /* @__PURE__ */ new Set();
|
|
4599
|
+
const seenImports = /* @__PURE__ */ new Set();
|
|
4600
|
+
const pushFont = (meta) => {
|
|
4601
|
+
if (!meta) return;
|
|
4602
|
+
if (!seenPackages.has(meta.packageName)) {
|
|
4603
|
+
seenPackages.add(meta.packageName);
|
|
4604
|
+
packages.push(meta.packageName);
|
|
4605
|
+
}
|
|
4606
|
+
for (const specifier of curatedFontCssImportSpecifiers(meta)) {
|
|
4607
|
+
const line = `@import "${specifier}";`;
|
|
4608
|
+
if (!seenImports.has(line)) {
|
|
4609
|
+
seenImports.add(line);
|
|
4610
|
+
importLines.push(line);
|
|
4611
|
+
}
|
|
4612
|
+
}
|
|
4613
|
+
};
|
|
4614
|
+
if (headingFont !== "system") {
|
|
4615
|
+
pushFont(HEADING_FONT_PACKAGES[headingFont]);
|
|
4616
|
+
}
|
|
4617
|
+
if (bodyFont !== "system") {
|
|
4618
|
+
pushFont(BODY_FONT_PACKAGES[bodyFont]);
|
|
4619
|
+
}
|
|
4620
|
+
packages.sort();
|
|
4621
|
+
if (importLines.length === 0) {
|
|
4622
|
+
return { packages: [], importLines: [], block: null };
|
|
4623
|
+
}
|
|
4624
|
+
const block = [
|
|
4625
|
+
`${FONT_CSS_START_MARKER} \u2500\u2500 */`,
|
|
4626
|
+
...importLines,
|
|
4627
|
+
FONT_CSS_END_MARKER
|
|
4628
|
+
].join("\n");
|
|
4629
|
+
return { packages, importLines, block };
|
|
4630
|
+
}
|
|
4631
|
+
function upsertFontImportBlock(existingCss, block) {
|
|
4632
|
+
const start = existingCss.indexOf(FONT_CSS_START_MARKER);
|
|
4633
|
+
const end = existingCss.indexOf(FONT_CSS_END_MARKER);
|
|
4634
|
+
if (start !== -1 || end !== -1) {
|
|
4635
|
+
if (start === -1 || end === -1 || end <= start) {
|
|
4636
|
+
throw new WensityError(
|
|
4637
|
+
"config_invalid",
|
|
4638
|
+
"Configured Tailwind CSS has a partial Wensity Fonts block. Remove the broken markers and re-run install."
|
|
4639
|
+
);
|
|
4640
|
+
}
|
|
4641
|
+
const before2 = existingCss.slice(0, start).replace(/\s*$/, "");
|
|
4642
|
+
const after2 = existingCss.slice(end + FONT_CSS_END_MARKER.length).replace(/^\s*/, "");
|
|
4643
|
+
if (!block) {
|
|
4644
|
+
const joined = [before2, after2].filter((part) => part.length > 0).join("\n\n");
|
|
4645
|
+
return joined ? `${joined.replace(/\n{3,}/g, "\n\n")}
|
|
4646
|
+
` : "";
|
|
4647
|
+
}
|
|
4648
|
+
const normalizedBlock2 = block.trim();
|
|
4649
|
+
const prefix2 = before2 ? `${before2}
|
|
4650
|
+
|
|
4651
|
+
` : "";
|
|
4652
|
+
const suffix2 = after2 ? `
|
|
4653
|
+
|
|
4654
|
+
${after2}` : "\n";
|
|
4655
|
+
return `${prefix2}${normalizedBlock2}${suffix2}`.replace(/\n{3,}/g, "\n\n");
|
|
4656
|
+
}
|
|
4657
|
+
if (!block) return existingCss;
|
|
4658
|
+
const normalizedBlock = block.trim();
|
|
4659
|
+
const insertAt = findFontBlockInsertIndex(existingCss);
|
|
4660
|
+
const before = existingCss.slice(0, insertAt).replace(/\s*$/, "");
|
|
4661
|
+
const after = existingCss.slice(insertAt).replace(/^\s*/, "");
|
|
4662
|
+
const prefix = before ? `${before}
|
|
4663
|
+
|
|
4664
|
+
` : "";
|
|
4665
|
+
const suffix = after ? `
|
|
4666
|
+
|
|
4667
|
+
${after}` : "\n";
|
|
4668
|
+
return `${prefix}${normalizedBlock}${suffix}`.replace(/\n{3,}/g, "\n\n");
|
|
3159
4669
|
}
|
|
3160
|
-
function
|
|
3161
|
-
const
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
Number.parseInt(value.slice(4, 6), 16)
|
|
4670
|
+
function warnUnusedFontPackageMessage(headingFont, bodyFont, installedPackages) {
|
|
4671
|
+
const needed = new Set(planFontInstall(headingFont, bodyFont).packages);
|
|
4672
|
+
const curated = [
|
|
4673
|
+
...Object.values(HEADING_FONT_PACKAGES).map((entry) => entry.packageName),
|
|
4674
|
+
...Object.values(BODY_FONT_PACKAGES).map((entry) => entry.packageName)
|
|
3166
4675
|
];
|
|
4676
|
+
const leftover = curated.filter(
|
|
4677
|
+
(pkg) => installedPackages.has(pkg) && !needed.has(pkg)
|
|
4678
|
+
);
|
|
4679
|
+
if (leftover.length === 0) return void 0;
|
|
4680
|
+
return `Project package.json still lists ${leftover.join(", ")} while curated fonts are set to heading="${headingFont}" body="${bodyFont}". Wensity removed the @import for unused fonts but will not uninstall ${leftover.join(", ")} (it may be used outside Wensity).`;
|
|
3167
4681
|
}
|
|
3168
|
-
function
|
|
3169
|
-
|
|
3170
|
-
const
|
|
3171
|
-
|
|
4682
|
+
function findFontBlockInsertIndex(css) {
|
|
4683
|
+
let index = 0;
|
|
4684
|
+
const length = css.length;
|
|
4685
|
+
while (index < length) {
|
|
4686
|
+
while (index < length && /\s/.test(css[index])) index += 1;
|
|
4687
|
+
if (index >= length) break;
|
|
4688
|
+
if (css.startsWith("/*", index)) {
|
|
4689
|
+
const end = css.indexOf("*/", index + 2);
|
|
4690
|
+
if (end === -1) break;
|
|
4691
|
+
index = end + 2;
|
|
4692
|
+
continue;
|
|
4693
|
+
}
|
|
4694
|
+
if (css.startsWith("//", index)) {
|
|
4695
|
+
const end = css.indexOf("\n", index);
|
|
4696
|
+
if (end === -1) return length;
|
|
4697
|
+
index = end + 1;
|
|
4698
|
+
continue;
|
|
4699
|
+
}
|
|
4700
|
+
if (css.startsWith("@charset", index) || css.startsWith("@import", index)) {
|
|
4701
|
+
const end = css.indexOf(";", index);
|
|
4702
|
+
if (end === -1) break;
|
|
4703
|
+
index = end + 1;
|
|
4704
|
+
continue;
|
|
4705
|
+
}
|
|
4706
|
+
break;
|
|
4707
|
+
}
|
|
4708
|
+
return index;
|
|
3172
4709
|
}
|
|
3173
4710
|
|
|
3174
4711
|
// src/http.ts
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
headers.set("accept", "application/json");
|
|
3178
|
-
headers.set("user-agent", `${CLI_NAME}/${CLI_VERSION}`);
|
|
3179
|
-
if (options.body && !headers.has("content-type")) {
|
|
3180
|
-
headers.set("content-type", "application/json");
|
|
3181
|
-
}
|
|
3182
|
-
if (options.token) {
|
|
3183
|
-
headers.set("authorization", `Bearer ${options.token}`);
|
|
3184
|
-
}
|
|
3185
|
-
let response;
|
|
3186
|
-
try {
|
|
3187
|
-
response = await fetch(url, { ...options, headers });
|
|
3188
|
-
} catch (error) {
|
|
3189
|
-
const message = error instanceof Error ? error.message : "Unknown network error";
|
|
3190
|
-
throw new CliError(`Could not reach ${url}: ${message}`);
|
|
3191
|
-
}
|
|
3192
|
-
const text = await response.text();
|
|
3193
|
-
const payload = parseJson(text);
|
|
3194
|
-
if (!response.ok) {
|
|
3195
|
-
const message = formatErrorMessage(payload) ?? response.statusText;
|
|
3196
|
-
throw new CliError(`${response.status} ${message}`);
|
|
3197
|
-
}
|
|
3198
|
-
return payload;
|
|
3199
|
-
}
|
|
3200
|
-
function parseJson(text) {
|
|
3201
|
-
if (!text.trim()) return null;
|
|
3202
|
-
try {
|
|
3203
|
-
return JSON.parse(text);
|
|
3204
|
-
} catch {
|
|
3205
|
-
throw new CliError("Registry returned invalid JSON.");
|
|
3206
|
-
}
|
|
3207
|
-
}
|
|
3208
|
-
function formatErrorMessage(payload) {
|
|
3209
|
-
if (!payload || typeof payload !== "object") return void 0;
|
|
3210
|
-
const record = payload;
|
|
3211
|
-
const code = typeof record.error === "string" ? record.error : void 0;
|
|
3212
|
-
const message = typeof record.message === "string" ? record.message : code;
|
|
3213
|
-
if (code === "pro_license_required") {
|
|
3214
|
-
const base = message ?? "This component requires Wensity Pro.";
|
|
3215
|
-
return `${base}
|
|
3216
|
-
Upgrade here: ${pricingBlue("https://ui.wensity.com/pricing")}`;
|
|
3217
|
-
}
|
|
3218
|
-
return message;
|
|
4712
|
+
function requestJson2(url, options = {}) {
|
|
4713
|
+
return requestJson(url, { userAgent: `${CLI_NAME}/${CLI_VERSION}`, ...options });
|
|
3219
4714
|
}
|
|
3220
4715
|
|
|
3221
|
-
// src/icon-transform.ts
|
|
4716
|
+
// ../install-core/src/icon-transform.ts
|
|
3222
4717
|
var UI_PRIMITIVES_CATEGORY = "UI Primitives";
|
|
3223
4718
|
var TABLER_ICONS_PACKAGE = "@tabler/icons-react";
|
|
3224
4719
|
var LUCIDE_ICONS_PACKAGE = "lucide-react";
|
|
@@ -3262,7 +4757,7 @@ function createLucideAdapter() {
|
|
|
3262
4757
|
return resolveLucideExport(tablerName);
|
|
3263
4758
|
} catch (error) {
|
|
3264
4759
|
const message = error instanceof Error ? error.message : String(error);
|
|
3265
|
-
throw new
|
|
4760
|
+
throw new WensityError("source_integrity_failed", `${context}: ${message}`);
|
|
3266
4761
|
}
|
|
3267
4762
|
},
|
|
3268
4763
|
strokePropTo: "strokeWidth"
|
|
@@ -3376,13 +4871,15 @@ function rewriteTablerImportsToTarget(content, adapter, context) {
|
|
|
3376
4871
|
for (const spec of specs) {
|
|
3377
4872
|
if (spec.isTypeOnly || typePrefix.includes("type")) {
|
|
3378
4873
|
if (spec.imported === "Icon" || isIconTransformExclusion(spec.imported)) {
|
|
3379
|
-
throw new
|
|
4874
|
+
throw new WensityError(
|
|
4875
|
+
"source_integrity_failed",
|
|
3380
4876
|
`${context}: unexpected type-only icon import "${spec.imported}" from ${TABLER_ICONS_PACKAGE}.`
|
|
3381
4877
|
);
|
|
3382
4878
|
}
|
|
3383
4879
|
}
|
|
3384
4880
|
if (isIconTransformExclusion(spec.imported)) {
|
|
3385
|
-
throw new
|
|
4881
|
+
throw new WensityError(
|
|
4882
|
+
"source_integrity_failed",
|
|
3386
4883
|
`${context}: "${spec.imported}" is excluded from the icon map (Phase 0 internal geometry). It must not appear as a library import.`
|
|
3387
4884
|
);
|
|
3388
4885
|
}
|
|
@@ -3405,12 +4902,14 @@ function rewriteTablerImportsToTarget(content, adapter, context) {
|
|
|
3405
4902
|
}
|
|
3406
4903
|
);
|
|
3407
4904
|
if (hasPackageImport(next, TABLER_ICONS_PACKAGE)) {
|
|
3408
|
-
throw new
|
|
4905
|
+
throw new WensityError(
|
|
4906
|
+
"source_integrity_failed",
|
|
3409
4907
|
`${context}: failed to rewrite all ${TABLER_ICONS_PACKAGE} imports for ${adapter.id}.`
|
|
3410
4908
|
);
|
|
3411
4909
|
}
|
|
3412
4910
|
if (/(?:import|export)\s+\*\s+as\s+\w+\s+from\s+["']@tabler\/icons-react["']/.test(content)) {
|
|
3413
|
-
throw new
|
|
4911
|
+
throw new WensityError(
|
|
4912
|
+
"source_integrity_failed",
|
|
3414
4913
|
`${context}: namespace imports from ${TABLER_ICONS_PACKAGE} are not supported. Use named imports.`
|
|
3415
4914
|
);
|
|
3416
4915
|
}
|
|
@@ -3422,7 +4921,8 @@ function assertNoUnmappedTablerImports(content, context) {
|
|
|
3422
4921
|
const specs = parseNamedImportClause(match[1] ?? "");
|
|
3423
4922
|
for (const spec of specs) {
|
|
3424
4923
|
if (isIconTransformExclusion(spec.imported)) {
|
|
3425
|
-
throw new
|
|
4924
|
+
throw new WensityError(
|
|
4925
|
+
"source_integrity_failed",
|
|
3426
4926
|
`${context}: "${spec.imported}" must not appear as a library import (Phase 0 exclusion).`
|
|
3427
4927
|
);
|
|
3428
4928
|
}
|
|
@@ -3475,70 +4975,12 @@ function rewriteStrokeProps(content, localBindings) {
|
|
|
3475
4975
|
}
|
|
3476
4976
|
|
|
3477
4977
|
// src/package-manager.ts
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
import { join as join4 } from "path";
|
|
3481
|
-
function detectPackageManager(projectRoot) {
|
|
3482
|
-
if (existsSync2(join4(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
3483
|
-
if (existsSync2(join4(projectRoot, "bun.lockb")) || existsSync2(join4(projectRoot, "bun.lock"))) return "bun";
|
|
3484
|
-
if (existsSync2(join4(projectRoot, "yarn.lock"))) return "yarn";
|
|
3485
|
-
if (existsSync2(join4(projectRoot, "package-lock.json"))) return "npm";
|
|
3486
|
-
return "npm";
|
|
3487
|
-
}
|
|
3488
|
-
function missingDependencies(projectRoot, dependencies) {
|
|
3489
|
-
const installed = readInstalledPackageNames(projectRoot);
|
|
3490
|
-
if (!installed) return dependencies;
|
|
3491
|
-
return dependencies.filter((dependency) => !installed.has(dependency));
|
|
3492
|
-
}
|
|
3493
|
-
function readInstalledPackageNames(projectRoot) {
|
|
3494
|
-
const packageJsonPath = join4(projectRoot, "package.json");
|
|
3495
|
-
if (!existsSync2(packageJsonPath)) return void 0;
|
|
3496
|
-
const packageJson2 = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
3497
|
-
return new Set(
|
|
3498
|
-
Object.keys({
|
|
3499
|
-
...packageJson2.dependencies,
|
|
3500
|
-
...packageJson2.devDependencies,
|
|
3501
|
-
...packageJson2.peerDependencies,
|
|
3502
|
-
...packageJson2.optionalDependencies
|
|
3503
|
-
})
|
|
3504
|
-
);
|
|
3505
|
-
}
|
|
3506
|
-
function runInstall(projectRoot, dependencies) {
|
|
3507
|
-
if (dependencies.length === 0) return;
|
|
3508
|
-
const packageJsonPath = join4(projectRoot, "package.json");
|
|
3509
|
-
if (!existsSync2(packageJsonPath)) {
|
|
3510
|
-
warn(`No package.json found. Install these dependencies manually: ${dependencies.join(" ")}`);
|
|
3511
|
-
return;
|
|
3512
|
-
}
|
|
3513
|
-
const packageManager = detectPackageManager(projectRoot);
|
|
3514
|
-
const args = installArgs(packageManager, dependencies);
|
|
3515
|
-
const result = spawnSync(packageManager, args, {
|
|
3516
|
-
cwd: projectRoot,
|
|
3517
|
-
stdio: "inherit",
|
|
3518
|
-
shell: process.platform === "win32"
|
|
3519
|
-
});
|
|
3520
|
-
if (result.error) {
|
|
3521
|
-
throw new CliError(`Could not run ${packageManager}: ${result.error.message}`);
|
|
3522
|
-
}
|
|
3523
|
-
if (result.status !== 0) {
|
|
3524
|
-
throw new CliError(`${packageManager} ${args.join(" ")} failed with exit code ${result.status ?? 1}.`);
|
|
3525
|
-
}
|
|
3526
|
-
}
|
|
3527
|
-
function installArgs(packageManager, dependencies) {
|
|
3528
|
-
switch (packageManager) {
|
|
3529
|
-
case "pnpm":
|
|
3530
|
-
return ["add", ...dependencies];
|
|
3531
|
-
case "yarn":
|
|
3532
|
-
return ["add", ...dependencies];
|
|
3533
|
-
case "bun":
|
|
3534
|
-
return ["add", ...dependencies];
|
|
3535
|
-
case "npm":
|
|
3536
|
-
return ["install", ...dependencies];
|
|
3537
|
-
}
|
|
4978
|
+
function runInstall2(projectRoot, dependencies, options = {}) {
|
|
4979
|
+
return runInstall(projectRoot, dependencies, { onWarning: warn, ...options });
|
|
3538
4980
|
}
|
|
3539
4981
|
|
|
3540
4982
|
// src/preset.ts
|
|
3541
|
-
import { readFile as
|
|
4983
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
3542
4984
|
async function resolvePresetStyle(input) {
|
|
3543
4985
|
if (input.presetCode && input.presetFile) {
|
|
3544
4986
|
throw new CliError("Use either --preset or --preset-file, not both.");
|
|
@@ -3546,7 +4988,7 @@ async function resolvePresetStyle(input) {
|
|
|
3546
4988
|
let code = input.presetCode;
|
|
3547
4989
|
if (input.presetFile) {
|
|
3548
4990
|
try {
|
|
3549
|
-
const raw = await
|
|
4991
|
+
const raw = await readFile5(input.presetFile, "utf8");
|
|
3550
4992
|
const trimmed = raw.trim();
|
|
3551
4993
|
if (trimmed.startsWith("{")) {
|
|
3552
4994
|
const parsed = JSON.parse(trimmed);
|
|
@@ -3746,18 +5188,18 @@ function formatTokenPanel(summary) {
|
|
|
3746
5188
|
}
|
|
3747
5189
|
|
|
3748
5190
|
// src/preset-init-writes.ts
|
|
3749
|
-
import { mkdir as
|
|
3750
|
-
import { dirname as
|
|
5191
|
+
import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
|
|
5192
|
+
import { dirname as dirname5 } from "path";
|
|
3751
5193
|
async function commitPresetInitWrites(input) {
|
|
3752
5194
|
let cssWritten = false;
|
|
3753
5195
|
try {
|
|
3754
5196
|
if (input.cssChanged) {
|
|
3755
|
-
await
|
|
5197
|
+
await mkdir4(dirname5(input.cssAbsolute), { recursive: true });
|
|
3756
5198
|
await writeFile3(input.cssAbsolute, input.nextCss, "utf8");
|
|
3757
5199
|
cssWritten = true;
|
|
3758
5200
|
}
|
|
3759
5201
|
if (input.writeUtility) {
|
|
3760
|
-
await
|
|
5202
|
+
await mkdir4(dirname5(input.utilityAbsolutePath), { recursive: true });
|
|
3761
5203
|
await writeFile3(input.utilityAbsolutePath, input.utilityContent, "utf8");
|
|
3762
5204
|
}
|
|
3763
5205
|
await input.commitConfig();
|
|
@@ -3773,8 +5215,14 @@ async function commitPresetInitWrites(input) {
|
|
|
3773
5215
|
}
|
|
3774
5216
|
|
|
3775
5217
|
// src/project-templates.ts
|
|
3776
|
-
import { mkdir as
|
|
3777
|
-
import { dirname as
|
|
5218
|
+
import { mkdir as mkdir5, readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
|
|
5219
|
+
import { dirname as dirname6, join as join9 } from "path";
|
|
5220
|
+
function templateCss() {
|
|
5221
|
+
return `@import "tailwindcss";
|
|
5222
|
+
|
|
5223
|
+
${buildBaseCssBlock()}
|
|
5224
|
+
`;
|
|
5225
|
+
}
|
|
3778
5226
|
var PROJECT_TEMPLATE_IDS = ["next", "vite", "astro", "react-router"];
|
|
3779
5227
|
function parseProjectTemplate(value) {
|
|
3780
5228
|
if (!value) return void 0;
|
|
@@ -3792,6 +5240,8 @@ function getProjectTemplateDefinition(id) {
|
|
|
3792
5240
|
function applyTemplateToConfig(config, template) {
|
|
3793
5241
|
return {
|
|
3794
5242
|
...config,
|
|
5243
|
+
components: "src/components/wensity",
|
|
5244
|
+
utils: "src/lib/utils",
|
|
3795
5245
|
tailwind: {
|
|
3796
5246
|
...config.tailwind,
|
|
3797
5247
|
css: template.cssPath
|
|
@@ -3803,7 +5253,7 @@ async function scaffoldProjectTemplate(projectRoot, template, options) {
|
|
|
3803
5253
|
if (!options.force) {
|
|
3804
5254
|
const conflicts = [];
|
|
3805
5255
|
for (const file of template.files) {
|
|
3806
|
-
if (await fileExists(
|
|
5256
|
+
if (await fileExists(join9(projectRoot, file.path))) {
|
|
3807
5257
|
conflicts.push(file.path);
|
|
3808
5258
|
}
|
|
3809
5259
|
}
|
|
@@ -3820,10 +5270,10 @@ async function scaffoldProjectTemplate(projectRoot, template, options) {
|
|
|
3820
5270
|
return planned;
|
|
3821
5271
|
}
|
|
3822
5272
|
async function writeTemplateFile(projectRoot, file) {
|
|
3823
|
-
const absolute =
|
|
3824
|
-
await
|
|
5273
|
+
const absolute = join9(projectRoot, file.path);
|
|
5274
|
+
await mkdir5(dirname6(absolute), { recursive: true });
|
|
3825
5275
|
if (file.path === "package.json" && await fileExists(absolute)) {
|
|
3826
|
-
const existing = JSON.parse(await
|
|
5276
|
+
const existing = JSON.parse(await readFile6(absolute, "utf8"));
|
|
3827
5277
|
const next = JSON.parse(file.content);
|
|
3828
5278
|
await writeFile4(
|
|
3829
5279
|
absolute,
|
|
@@ -3886,11 +5336,14 @@ var templateDefinitions = {
|
|
|
3886
5336
|
start: "next start"
|
|
3887
5337
|
},
|
|
3888
5338
|
dependencies: {
|
|
5339
|
+
"clsx": "^2.1.1",
|
|
3889
5340
|
"next": "^16.2.4",
|
|
3890
5341
|
"react": "^19.2.4",
|
|
3891
|
-
"react-dom": "^19.2.4"
|
|
5342
|
+
"react-dom": "^19.2.4",
|
|
5343
|
+
"tailwind-merge": "^3.3.1"
|
|
3892
5344
|
},
|
|
3893
5345
|
devDependencies: {
|
|
5346
|
+
"@tailwindcss/postcss": "^4.0.0",
|
|
3894
5347
|
"@types/node": "^22.10.2",
|
|
3895
5348
|
"@types/react": "^19.0.0",
|
|
3896
5349
|
"@types/react-dom": "^19.0.0",
|
|
@@ -3899,7 +5352,18 @@ var templateDefinitions = {
|
|
|
3899
5352
|
}
|
|
3900
5353
|
})
|
|
3901
5354
|
},
|
|
3902
|
-
{
|
|
5355
|
+
{
|
|
5356
|
+
path: "postcss.config.mjs",
|
|
5357
|
+
content: `const config = {
|
|
5358
|
+
plugins: {
|
|
5359
|
+
"@tailwindcss/postcss": {},
|
|
5360
|
+
},
|
|
5361
|
+
};
|
|
5362
|
+
|
|
5363
|
+
export default config;
|
|
5364
|
+
`
|
|
5365
|
+
},
|
|
5366
|
+
{ path: "src/app/globals.css", content: templateCss() },
|
|
3903
5367
|
{
|
|
3904
5368
|
path: "src/app/layout.tsx",
|
|
3905
5369
|
content: `import type { ReactNode } from "react";
|
|
@@ -3961,11 +5425,14 @@ export default function RootLayout({ children }: { children: ReactNode }) {
|
|
|
3961
5425
|
preview: "vite preview"
|
|
3962
5426
|
},
|
|
3963
5427
|
dependencies: {
|
|
5428
|
+
"clsx": "^2.1.1",
|
|
3964
5429
|
"vite": "^7.3.0",
|
|
3965
5430
|
"react": "^19.2.4",
|
|
3966
|
-
"react-dom": "^19.2.4"
|
|
5431
|
+
"react-dom": "^19.2.4",
|
|
5432
|
+
"tailwind-merge": "^3.3.1"
|
|
3967
5433
|
},
|
|
3968
5434
|
devDependencies: {
|
|
5435
|
+
"@tailwindcss/vite": "^4.0.0",
|
|
3969
5436
|
"@vitejs/plugin-react": "^5.1.2",
|
|
3970
5437
|
"@types/react": "^19.0.0",
|
|
3971
5438
|
"@types/react-dom": "^19.0.0",
|
|
@@ -3975,7 +5442,7 @@ export default function RootLayout({ children }: { children: ReactNode }) {
|
|
|
3975
5442
|
})
|
|
3976
5443
|
},
|
|
3977
5444
|
{ path: "index.html", content: '<div id="root"></div><script type="module" src="/src/main.tsx"></script>\n' },
|
|
3978
|
-
{ path: "src/index.css", content:
|
|
5445
|
+
{ path: "src/index.css", content: templateCss() },
|
|
3979
5446
|
{
|
|
3980
5447
|
path: "src/main.tsx",
|
|
3981
5448
|
content: `import { createRoot } from "react-dom/client";
|
|
@@ -3990,9 +5457,10 @@ createRoot(document.getElementById("root")!).render(
|
|
|
3990
5457
|
path: "vite.config.ts",
|
|
3991
5458
|
content: `import { defineConfig } from "vite";
|
|
3992
5459
|
import react from "@vitejs/plugin-react";
|
|
5460
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
3993
5461
|
|
|
3994
5462
|
export default defineConfig({
|
|
3995
|
-
plugins: [react()],
|
|
5463
|
+
plugins: [react(), tailwindcss()],
|
|
3996
5464
|
resolve: {
|
|
3997
5465
|
alias: {
|
|
3998
5466
|
"@": new URL("./src", import.meta.url).pathname,
|
|
@@ -4019,23 +5487,30 @@ export default defineConfig({
|
|
|
4019
5487
|
dependencies: {
|
|
4020
5488
|
"@astrojs/react": "^4.4.0",
|
|
4021
5489
|
"astro": "^5.16.0",
|
|
5490
|
+
"clsx": "^2.1.1",
|
|
4022
5491
|
"react": "^19.2.4",
|
|
4023
|
-
"react-dom": "^19.2.4"
|
|
5492
|
+
"react-dom": "^19.2.4",
|
|
5493
|
+
"tailwind-merge": "^3.3.1"
|
|
4024
5494
|
},
|
|
4025
5495
|
devDependencies: {
|
|
5496
|
+
"@tailwindcss/vite": "^4.0.0",
|
|
4026
5497
|
"tailwindcss": "^4.0.0",
|
|
4027
5498
|
"typescript": "^5.7.2"
|
|
4028
5499
|
}
|
|
4029
5500
|
})
|
|
4030
5501
|
},
|
|
4031
|
-
{ path: "src/styles/global.css", content:
|
|
5502
|
+
{ path: "src/styles/global.css", content: templateCss() },
|
|
4032
5503
|
{
|
|
4033
5504
|
path: "astro.config.mjs",
|
|
4034
5505
|
content: `import { defineConfig } from "astro/config";
|
|
4035
5506
|
import react from "@astrojs/react";
|
|
5507
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
4036
5508
|
|
|
4037
5509
|
export default defineConfig({
|
|
4038
5510
|
integrations: [react()],
|
|
5511
|
+
vite: {
|
|
5512
|
+
plugins: [tailwindcss()],
|
|
5513
|
+
},
|
|
4039
5514
|
});
|
|
4040
5515
|
`
|
|
4041
5516
|
},
|
|
@@ -4064,12 +5539,15 @@ import "../styles/global.css";
|
|
|
4064
5539
|
preview: "vite preview"
|
|
4065
5540
|
},
|
|
4066
5541
|
dependencies: {
|
|
5542
|
+
"clsx": "^2.1.1",
|
|
4067
5543
|
"vite": "^7.3.0",
|
|
4068
5544
|
"react": "^19.2.4",
|
|
4069
5545
|
"react-dom": "^19.2.4",
|
|
4070
|
-
"react-router": "^7.12.0"
|
|
5546
|
+
"react-router": "^7.12.0",
|
|
5547
|
+
"tailwind-merge": "^3.3.1"
|
|
4071
5548
|
},
|
|
4072
5549
|
devDependencies: {
|
|
5550
|
+
"@tailwindcss/vite": "^4.0.0",
|
|
4073
5551
|
"@vitejs/plugin-react": "^5.1.2",
|
|
4074
5552
|
"@types/react": "^19.0.0",
|
|
4075
5553
|
"@types/react-dom": "^19.0.0",
|
|
@@ -4079,7 +5557,7 @@ import "../styles/global.css";
|
|
|
4079
5557
|
})
|
|
4080
5558
|
},
|
|
4081
5559
|
{ path: "index.html", content: '<div id="root"></div><script type="module" src="/src/main.tsx"></script>\n' },
|
|
4082
|
-
{ path: "src/app/globals.css", content:
|
|
5560
|
+
{ path: "src/app/globals.css", content: templateCss() },
|
|
4083
5561
|
{
|
|
4084
5562
|
path: "src/main.tsx",
|
|
4085
5563
|
content: `import { createRoot } from "react-dom/client";
|
|
@@ -4099,9 +5577,10 @@ createRoot(document.getElementById("root")!).render(
|
|
|
4099
5577
|
path: "vite.config.ts",
|
|
4100
5578
|
content: `import { defineConfig } from "vite";
|
|
4101
5579
|
import react from "@vitejs/plugin-react";
|
|
5580
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
4102
5581
|
|
|
4103
5582
|
export default defineConfig({
|
|
4104
|
-
plugins: [react()],
|
|
5583
|
+
plugins: [react(), tailwindcss()],
|
|
4105
5584
|
resolve: {
|
|
4106
5585
|
alias: {
|
|
4107
5586
|
"@": new URL("./src", import.meta.url).pathname,
|
|
@@ -4117,7 +5596,7 @@ function formatTemplateFiles(files) {
|
|
|
4117
5596
|
return files.map(toPosixPath);
|
|
4118
5597
|
}
|
|
4119
5598
|
|
|
4120
|
-
// src/style-css.ts
|
|
5599
|
+
// ../install-core/src/style-css.ts
|
|
4121
5600
|
var ROOT_SELECTOR = ":root";
|
|
4122
5601
|
var DARK_SELECTOR = ".dark";
|
|
4123
5602
|
var SHARED_TOKEN_KEYS = ["radius", "fontSans", "fontDisplay", "fontMono"];
|
|
@@ -4201,11 +5680,11 @@ function escapeRegExp(value) {
|
|
|
4201
5680
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4202
5681
|
}
|
|
4203
5682
|
|
|
4204
|
-
// src/component-install.ts
|
|
4205
|
-
import { mkdir as
|
|
4206
|
-
import { dirname as
|
|
5683
|
+
// ../install-core/src/component-install.ts
|
|
5684
|
+
import { mkdir as mkdir6, readFile as readFile7, writeFile as writeFile5 } from "fs/promises";
|
|
5685
|
+
import { dirname as dirname7, join as join10, relative as relative7 } from "path";
|
|
4207
5686
|
|
|
4208
|
-
// src/templates.ts
|
|
5687
|
+
// ../install-core/src/templates.ts
|
|
4209
5688
|
function cnUtilityTemplate() {
|
|
4210
5689
|
return `import { clsx, type ClassValue } from "clsx";
|
|
4211
5690
|
import { twMerge } from "tailwind-merge";
|
|
@@ -4358,11 +5837,19 @@ export function WensitySecondaryButton({
|
|
|
4358
5837
|
`;
|
|
4359
5838
|
}
|
|
4360
5839
|
|
|
4361
|
-
// src/component-install.ts
|
|
5840
|
+
// ../install-core/src/component-install.ts
|
|
5841
|
+
function safeTarget(projectRoot, relativePath) {
|
|
5842
|
+
assertTargetPathLength(relativePath);
|
|
5843
|
+
const absolutePath = resolveInside(projectRoot, relativePath);
|
|
5844
|
+
assertNoSymlinkEscape(projectRoot, absolutePath);
|
|
5845
|
+
return absolutePath;
|
|
5846
|
+
}
|
|
4362
5847
|
var ALWAYS_PRESENT_PACKAGES = /* @__PURE__ */ new Set(["react", "react-dom", "next"]);
|
|
4363
5848
|
var OVERLAY_HELPER_PATH = "components/wensity/overlay.tsx";
|
|
4364
5849
|
var PRIMITIVE_CSS_START_MARKER = "/* \u2500\u2500 Wensity Primitive Tokens";
|
|
4365
5850
|
var PRIMITIVE_CSS_END_MARKER = "/* \u2500\u2500 End Wensity Primitive Tokens \u2500\u2500 */";
|
|
5851
|
+
var BASE_CSS_START_MARKER = "/* \u2500\u2500 Wensity Base Tokens";
|
|
5852
|
+
var BASE_CSS_END_MARKER = "/* \u2500\u2500 End Wensity Base Tokens \u2500\u2500 */";
|
|
4366
5853
|
async function prepareInstall(projectRoot, config, payload, options = {}) {
|
|
4367
5854
|
const fallbackFileName = componentFileName(payload.slug);
|
|
4368
5855
|
const files = [];
|
|
@@ -4378,7 +5865,7 @@ async function prepareInstall(projectRoot, config, payload, options = {}) {
|
|
|
4378
5865
|
for (const [index, payloadFile] of payload.files.entries()) {
|
|
4379
5866
|
const payloadFileKind = getPayloadFileKind(payloadFile);
|
|
4380
5867
|
const relativeOutputPath = getPayloadOutputPath(payloadFile, fallbackFileName, config);
|
|
4381
|
-
const absoluteOutputPath =
|
|
5868
|
+
const absoluteOutputPath = safeTarget(projectRoot, relativeOutputPath);
|
|
4382
5869
|
if (!payloadHelperNeeds) mergeHelperNeeds(inferredHelperNeeds, inferHelperNeeds(payloadFile.content));
|
|
4383
5870
|
const transformed = transformComponentSource(payloadFile.content, config, {
|
|
4384
5871
|
isPrimitiveCategory,
|
|
@@ -4401,9 +5888,9 @@ async function prepareInstall(projectRoot, config, payload, options = {}) {
|
|
|
4401
5888
|
const helperNeeds = payloadHelperNeeds ?? inferredHelperNeeds;
|
|
4402
5889
|
if (!hasGraphHelperFiles && helperNeeds.utils) {
|
|
4403
5890
|
const utilityPath = withSourceExtension(config.utils, "ts");
|
|
4404
|
-
if (!await fileExists(
|
|
5891
|
+
if (!await fileExists(safeTarget(projectRoot, utilityPath))) {
|
|
4405
5892
|
files.push({
|
|
4406
|
-
absolutePath:
|
|
5893
|
+
absolutePath: safeTarget(projectRoot, utilityPath),
|
|
4407
5894
|
relativePath: utilityPath,
|
|
4408
5895
|
content: cnUtilityTemplate(),
|
|
4409
5896
|
kind: "helper"
|
|
@@ -4411,11 +5898,11 @@ async function prepareInstall(projectRoot, config, payload, options = {}) {
|
|
|
4411
5898
|
}
|
|
4412
5899
|
}
|
|
4413
5900
|
if (!hasGraphHelperFiles && helperNeeds.primitives) {
|
|
4414
|
-
const primitivesPath = toPosixPath(
|
|
4415
|
-
if (!await fileExists(
|
|
5901
|
+
const primitivesPath = toPosixPath(join10(config.components, "primitives.tsx"));
|
|
5902
|
+
if (!await fileExists(safeTarget(projectRoot, primitivesPath))) {
|
|
4416
5903
|
const primitives = primitivesTemplate(config.aliases.utils);
|
|
4417
5904
|
files.push({
|
|
4418
|
-
absolutePath:
|
|
5905
|
+
absolutePath: safeTarget(projectRoot, primitivesPath),
|
|
4419
5906
|
relativePath: primitivesPath,
|
|
4420
5907
|
content: primitives,
|
|
4421
5908
|
kind: "helper"
|
|
@@ -4451,9 +5938,10 @@ async function planPrimitiveCssFile(projectRoot, config, payload) {
|
|
|
4451
5938
|
primitiveCss = applyStyleToPrimitiveCss(primitiveCss, style).trim();
|
|
4452
5939
|
const cssRelative = config.tailwind?.css;
|
|
4453
5940
|
if (!cssRelative) return void 0;
|
|
4454
|
-
const absolutePath =
|
|
4455
|
-
const existing = await fileExists(absolutePath) ? await
|
|
4456
|
-
const
|
|
5941
|
+
const absolutePath = safeTarget(projectRoot, cssRelative);
|
|
5942
|
+
const existing = await fileExists(absolutePath) ? await readFile7(absolutePath, "utf8") : "";
|
|
5943
|
+
const withBase = upsertBaseCssBlock(existing, buildBaseCssBlock(baseColorFromStyle(style)));
|
|
5944
|
+
const withTokens = upsertPrimitiveCssBlock(withBase, primitiveCss);
|
|
4457
5945
|
const fonts = curatedFontsFromStyle(style);
|
|
4458
5946
|
const fontPlan = planFontInstall(fonts.headingFont, fonts.bodyFont);
|
|
4459
5947
|
const next = upsertFontImportBlock(withTokens, fontPlan.block);
|
|
@@ -4466,19 +5954,45 @@ async function planPrimitiveCssFile(projectRoot, config, payload) {
|
|
|
4466
5954
|
};
|
|
4467
5955
|
}
|
|
4468
5956
|
function upsertPrimitiveCssBlock(existingCss, block) {
|
|
5957
|
+
return upsertDelimitedBlock(existingCss, block, {
|
|
5958
|
+
startMarker: PRIMITIVE_CSS_START_MARKER,
|
|
5959
|
+
endMarker: PRIMITIVE_CSS_END_MARKER,
|
|
5960
|
+
label: "Wensity Primitive Tokens"
|
|
5961
|
+
});
|
|
5962
|
+
}
|
|
5963
|
+
function upsertBaseCssBlock(existingCss, block) {
|
|
5964
|
+
return upsertDelimitedBlock(existingCss, block, {
|
|
5965
|
+
startMarker: BASE_CSS_START_MARKER,
|
|
5966
|
+
endMarker: BASE_CSS_END_MARKER,
|
|
5967
|
+
label: "Wensity Base Tokens",
|
|
5968
|
+
insertBeforeMarker: PRIMITIVE_CSS_START_MARKER
|
|
5969
|
+
});
|
|
5970
|
+
}
|
|
5971
|
+
function upsertDelimitedBlock(existingCss, block, options) {
|
|
4469
5972
|
const normalizedBlock = block.trim();
|
|
4470
|
-
const start = existingCss.indexOf(
|
|
4471
|
-
const end = existingCss.indexOf(
|
|
5973
|
+
const start = existingCss.indexOf(options.startMarker);
|
|
5974
|
+
const end = existingCss.indexOf(options.endMarker);
|
|
4472
5975
|
if (start !== -1 && end !== -1 && end > start) {
|
|
4473
5976
|
const before = existingCss.slice(0, start).replace(/\s*$/, "\n\n");
|
|
4474
|
-
const after = existingCss.slice(end +
|
|
5977
|
+
const after = existingCss.slice(end + options.endMarker.length).replace(/^\s*/, "\n");
|
|
4475
5978
|
return `${before}${normalizedBlock}${after}`.replace(/\n{3,}/g, "\n\n");
|
|
4476
5979
|
}
|
|
4477
5980
|
if (start !== -1 || end !== -1) {
|
|
4478
|
-
throw new
|
|
4479
|
-
"
|
|
5981
|
+
throw new WensityError(
|
|
5982
|
+
"config_invalid",
|
|
5983
|
+
`Configured Tailwind CSS has a partial ${options.label} block. Remove the broken markers and re-run install.`
|
|
4480
5984
|
);
|
|
4481
5985
|
}
|
|
5986
|
+
if (options.insertBeforeMarker) {
|
|
5987
|
+
const anchor = existingCss.indexOf(options.insertBeforeMarker);
|
|
5988
|
+
if (anchor !== -1) {
|
|
5989
|
+
const before = existingCss.slice(0, anchor).replace(/\s*$/, "\n\n");
|
|
5990
|
+
const after = existingCss.slice(anchor);
|
|
5991
|
+
return `${before}${normalizedBlock}
|
|
5992
|
+
|
|
5993
|
+
${after}`.replace(/\n{3,}/g, "\n\n");
|
|
5994
|
+
}
|
|
5995
|
+
}
|
|
4482
5996
|
const trimmed = existingCss.replace(/\s*$/, "");
|
|
4483
5997
|
if (!trimmed) return `${normalizedBlock}
|
|
4484
5998
|
`;
|
|
@@ -4495,18 +6009,12 @@ async function assertNoConflicts(files, force) {
|
|
|
4495
6009
|
if (await fileExists(file.absolutePath)) conflicts.push(file.relativePath);
|
|
4496
6010
|
}
|
|
4497
6011
|
if (conflicts.length > 0) {
|
|
4498
|
-
throw new
|
|
6012
|
+
throw new WensityError("file_conflict", `Refusing to overwrite existing files. Use --force to replace:
|
|
4499
6013
|
${conflicts.map((path) => ` - ${path}`).join("\n")}`);
|
|
4500
6014
|
}
|
|
4501
6015
|
}
|
|
4502
|
-
async function writePlannedFiles(files) {
|
|
4503
|
-
for (const file of files) {
|
|
4504
|
-
await mkdir5(dirname6(file.absolutePath), { recursive: true });
|
|
4505
|
-
await writeFile5(file.absolutePath, file.content, "utf8");
|
|
4506
|
-
}
|
|
4507
|
-
}
|
|
4508
6016
|
function summarizeFiles(projectRoot, files) {
|
|
4509
|
-
return files.map((file) => toPosixPath(
|
|
6017
|
+
return files.map((file) => toPosixPath(relative7(projectRoot, file.absolutePath)));
|
|
4510
6018
|
}
|
|
4511
6019
|
function transformComponentSource(content, config, iconOptions) {
|
|
4512
6020
|
let transformed = content;
|
|
@@ -4527,13 +6035,13 @@ function getPayloadFileKind(payloadFile) {
|
|
|
4527
6035
|
}
|
|
4528
6036
|
function getPayloadOutputPath(payloadFile, fallbackFileName, config) {
|
|
4529
6037
|
if (isUtilsHelperFile(payloadFile)) return withSourceExtension(config.utils, "ts");
|
|
4530
|
-
if (isOverlayHelperFile(payloadFile)) return toPosixPath(
|
|
4531
|
-
if (isPrimitivesHelperFile(payloadFile)) return toPosixPath(
|
|
6038
|
+
if (isOverlayHelperFile(payloadFile)) return toPosixPath(join10(config.components, "overlay.tsx"));
|
|
6039
|
+
if (isPrimitivesHelperFile(payloadFile)) return toPosixPath(join10(config.components, "primitives.tsx"));
|
|
4532
6040
|
if (payloadFile.kind === "helper") {
|
|
4533
|
-
throw new
|
|
6041
|
+
throw new WensityError("path_unsafe", `Unsupported registry helper file path: ${payloadFile.path}`);
|
|
4534
6042
|
}
|
|
4535
6043
|
const safePayloadPath = sanitizePayloadPath(payloadFile.path, fallbackFileName);
|
|
4536
|
-
return toPosixPath(
|
|
6044
|
+
return toPosixPath(join10(config.components, safePayloadPath));
|
|
4537
6045
|
}
|
|
4538
6046
|
function isGraphHelperFile(payloadFile) {
|
|
4539
6047
|
return isUtilsHelperFile(payloadFile) || isOverlayHelperFile(payloadFile) || isPrimitivesHelperFile(payloadFile);
|
|
@@ -4630,7 +6138,7 @@ function normalizePackageName(specifier) {
|
|
|
4630
6138
|
function withSourceExtension(path, extension) {
|
|
4631
6139
|
const directory = parentDirectory(path);
|
|
4632
6140
|
const fileName = extensionlessPath(path.split("/").at(-1) ?? path);
|
|
4633
|
-
return toPosixPath(
|
|
6141
|
+
return toPosixPath(join10(directory === "." ? "" : directory, `${fileName}.${extension}`));
|
|
4634
6142
|
}
|
|
4635
6143
|
|
|
4636
6144
|
// src/commands.ts
|
|
@@ -4722,6 +6230,8 @@ async function initCommand(context, parsed) {
|
|
|
4722
6230
|
const template = templateId ? getProjectTemplateDefinition(templateId) : void 0;
|
|
4723
6231
|
const hasPreset = Boolean(presetCode || presetFile);
|
|
4724
6232
|
const dependencies = ["clsx", "tailwind-merge"];
|
|
6233
|
+
const installOptions = installOptionsFrom(context, parsed);
|
|
6234
|
+
assertProjectRootCandidate(context.cwd, context.env.HOME);
|
|
4725
6235
|
if (hasPreset) {
|
|
4726
6236
|
await initWithPreset(context, {
|
|
4727
6237
|
dryRun,
|
|
@@ -4731,19 +6241,26 @@ async function initCommand(context, parsed) {
|
|
|
4731
6241
|
presetCode,
|
|
4732
6242
|
presetFile,
|
|
4733
6243
|
dependencies,
|
|
4734
|
-
template
|
|
6244
|
+
template,
|
|
6245
|
+
installOptions
|
|
4735
6246
|
});
|
|
4736
6247
|
return;
|
|
4737
6248
|
}
|
|
4738
6249
|
const templateFiles = template ? await scaffoldProjectTemplate(context.cwd, template, { dryRun, force }) : [];
|
|
6250
|
+
const packageManagerFiles = template ? prepareProjectPackageManager(context.cwd, { ...installOptions, dryRun }) : [];
|
|
4739
6251
|
const config = template ? applyTemplateToConfig(createDefaultConfig(context.cwd, apiUrl), template) : createDefaultConfig(context.cwd, apiUrl);
|
|
4740
|
-
const configPath =
|
|
6252
|
+
const configPath = join11(context.cwd, CONFIG_FILE_NAME);
|
|
4741
6253
|
const utilityPath = resolveConfiguredFilePath(context.cwd, config.utils);
|
|
4742
6254
|
const utilityRelativePath = toPosixPath(config.utils.endsWith(".ts") ? config.utils : `${config.utils}.ts`);
|
|
4743
6255
|
if (dryRun) {
|
|
4744
6256
|
printTitle("Project initialization preview");
|
|
4745
6257
|
print(action("Would write:"));
|
|
4746
|
-
print(formatList([
|
|
6258
|
+
print(formatList([
|
|
6259
|
+
...formatTemplateFiles(templateFiles),
|
|
6260
|
+
...packageManagerFiles,
|
|
6261
|
+
CONFIG_FILE_NAME,
|
|
6262
|
+
utilityRelativePath
|
|
6263
|
+
]));
|
|
4747
6264
|
if (template) print(action(`Template: ${template.label}`));
|
|
4748
6265
|
if (install) print(action(`Would install: ${dependencies.join(" ")}`));
|
|
4749
6266
|
return;
|
|
@@ -4753,14 +6270,24 @@ async function initCommand(context, parsed) {
|
|
|
4753
6270
|
await writeTextFile(utilityPath, cnUtilityTemplate());
|
|
4754
6271
|
}
|
|
4755
6272
|
if (install) {
|
|
4756
|
-
|
|
4757
|
-
|
|
6273
|
+
if (template) await runProjectInstall(context.cwd, installOptions);
|
|
6274
|
+
await runInstall2(context.cwd, missingDependencies(context.cwd, dependencies), installOptions);
|
|
6275
|
+
}
|
|
6276
|
+
await assertInitPostconditions(context.cwd, [
|
|
6277
|
+
...templateFiles,
|
|
6278
|
+
...packageManagerFiles,
|
|
6279
|
+
CONFIG_FILE_NAME,
|
|
6280
|
+
utilityRelativePath
|
|
6281
|
+
]);
|
|
4758
6282
|
printTitle("Project initialized");
|
|
4759
6283
|
print(panel("Created", [
|
|
6284
|
+
...formatTemplateFiles(templateFiles).map((file) => success(file)),
|
|
6285
|
+
...packageManagerFiles.map((file) => success(file)),
|
|
4760
6286
|
success(CONFIG_FILE_NAME),
|
|
4761
6287
|
success(utilityRelativePath),
|
|
4762
6288
|
success(`registry: ${apiUrl}`)
|
|
4763
6289
|
]));
|
|
6290
|
+
print(action(`Location: ${context.cwd}`));
|
|
4764
6291
|
print(nextSteps([
|
|
4765
6292
|
"Browse available components: npx wensity list",
|
|
4766
6293
|
"Browse premium blocks: npx wensity list --type block",
|
|
@@ -4772,18 +6299,22 @@ async function initWithPreset(context, options) {
|
|
|
4772
6299
|
presetCode: options.presetCode,
|
|
4773
6300
|
presetFile: options.presetFile
|
|
4774
6301
|
});
|
|
4775
|
-
const configPath =
|
|
6302
|
+
const configPath = join11(context.cwd, CONFIG_FILE_NAME);
|
|
4776
6303
|
const configExists = await fileExists(configPath);
|
|
4777
6304
|
const templateFiles = options.template ? await scaffoldProjectTemplate(context.cwd, options.template, {
|
|
4778
6305
|
dryRun: options.dryRun,
|
|
4779
6306
|
force: options.force
|
|
4780
6307
|
}) : [];
|
|
6308
|
+
const packageManagerFiles = options.template ? prepareProjectPackageManager(context.cwd, {
|
|
6309
|
+
...options.installOptions,
|
|
6310
|
+
dryRun: options.dryRun
|
|
6311
|
+
}) : [];
|
|
4781
6312
|
let config;
|
|
4782
6313
|
if (configExists && !options.force) {
|
|
4783
6314
|
if (options.template) {
|
|
4784
6315
|
throw new CliError(`${CONFIG_FILE_NAME} already exists. Omit --template for existing projects or use --force.`);
|
|
4785
6316
|
}
|
|
4786
|
-
const loaded = await loadConfig(context.cwd, { apiUrl: options.apiUrl });
|
|
6317
|
+
const loaded = await loadConfig(context.cwd, { apiUrl: options.apiUrl }, { home: context.env.HOME });
|
|
4787
6318
|
config = {
|
|
4788
6319
|
...loaded.config,
|
|
4789
6320
|
style
|
|
@@ -4801,7 +6332,12 @@ async function initWithPreset(context, options) {
|
|
|
4801
6332
|
if (options.dryRun) {
|
|
4802
6333
|
printTitle("Project initialization preview (preset)");
|
|
4803
6334
|
print(action("Would write:"));
|
|
4804
|
-
const planned = [
|
|
6335
|
+
const planned = [
|
|
6336
|
+
...formatTemplateFiles(templateFiles),
|
|
6337
|
+
...packageManagerFiles,
|
|
6338
|
+
CONFIG_FILE_NAME,
|
|
6339
|
+
utilityRelativePath
|
|
6340
|
+
];
|
|
4805
6341
|
if (prepared.cssChanged) planned.push(toPosixPath(prepared.cssRelative));
|
|
4806
6342
|
print(formatList(planned));
|
|
4807
6343
|
if (options.template) print(action(`Template: ${options.template.label}`));
|
|
@@ -4832,11 +6368,25 @@ async function initWithPreset(context, options) {
|
|
|
4832
6368
|
}
|
|
4833
6369
|
});
|
|
4834
6370
|
if (options.install) {
|
|
4835
|
-
|
|
6371
|
+
if (options.template) await runProjectInstall(context.cwd, options.installOptions);
|
|
6372
|
+
await runInstall2(
|
|
6373
|
+
context.cwd,
|
|
6374
|
+
missingDependencies(context.cwd, installDependencies),
|
|
6375
|
+
options.installOptions
|
|
6376
|
+
);
|
|
4836
6377
|
}
|
|
6378
|
+
await assertInitPostconditions(context.cwd, [
|
|
6379
|
+
...templateFiles,
|
|
6380
|
+
...packageManagerFiles,
|
|
6381
|
+
CONFIG_FILE_NAME,
|
|
6382
|
+
utilityRelativePath,
|
|
6383
|
+
prepared.cssRelative
|
|
6384
|
+
]);
|
|
4837
6385
|
printTitle("Project initialized");
|
|
4838
6386
|
const created = [
|
|
4839
6387
|
...options.template ? [success(`template: ${options.template.label}`)] : [],
|
|
6388
|
+
...formatTemplateFiles(templateFiles).map((file) => success(file)),
|
|
6389
|
+
...packageManagerFiles.map((file) => success(file)),
|
|
4840
6390
|
success(CONFIG_FILE_NAME),
|
|
4841
6391
|
success(utilityRelativePath),
|
|
4842
6392
|
success(`registry: ${options.apiUrl}`),
|
|
@@ -4851,6 +6401,7 @@ async function initWithPreset(context, options) {
|
|
|
4851
6401
|
created.push(success(`fonts: ${prepared.fontPlan.packages.join(", ")}`));
|
|
4852
6402
|
}
|
|
4853
6403
|
print(panel("Created", created));
|
|
6404
|
+
print(action(`Location: ${context.cwd}`));
|
|
4854
6405
|
if (prepared.unusedFontWarning) warn(prepared.unusedFontWarning);
|
|
4855
6406
|
print(nextSteps([
|
|
4856
6407
|
"Browse available components: npx wensity list",
|
|
@@ -4867,7 +6418,7 @@ async function applyCommand(context, parsed) {
|
|
|
4867
6418
|
if (!presetCodeInput && !presetFile) {
|
|
4868
6419
|
throw new CliError("Missing preset. Pass --preset <code> or --preset-file <path>.");
|
|
4869
6420
|
}
|
|
4870
|
-
const loaded = await loadConfig(context.cwd, { registry: registryOverride, apiUrl });
|
|
6421
|
+
const loaded = await loadConfig(context.cwd, { registry: registryOverride, apiUrl }, { home: context.env.HOME });
|
|
4871
6422
|
if (!loaded.exists) {
|
|
4872
6423
|
throw new CliError(`${CONFIG_FILE_NAME} not found. Run "wensity init" first, then "wensity apply --preset <code>".`);
|
|
4873
6424
|
}
|
|
@@ -4915,7 +6466,11 @@ async function applyCommand(context, parsed) {
|
|
|
4915
6466
|
}
|
|
4916
6467
|
});
|
|
4917
6468
|
if (install && installDependencies.length > 0) {
|
|
4918
|
-
|
|
6469
|
+
await runInstall2(
|
|
6470
|
+
loaded.projectRoot,
|
|
6471
|
+
missingDependencies(loaded.projectRoot, installDependencies),
|
|
6472
|
+
installOptionsFrom(context, parsed)
|
|
6473
|
+
);
|
|
4919
6474
|
}
|
|
4920
6475
|
printTitle("Preset applied");
|
|
4921
6476
|
const applied = [
|
|
@@ -4964,8 +6519,9 @@ async function preparePresetStyleApplication(projectRoot, config, style, operati
|
|
|
4964
6519
|
throw new CliError("No Tailwind CSS path configured. Set tailwind.css in wensity.json.");
|
|
4965
6520
|
}
|
|
4966
6521
|
const cssAbsolute = resolveInside(projectRoot, cssRelative);
|
|
4967
|
-
const existingCss = await fileExists(cssAbsolute) ? await
|
|
4968
|
-
const
|
|
6522
|
+
const existingCss = await fileExists(cssAbsolute) ? await readFile8(cssAbsolute, "utf8") : "";
|
|
6523
|
+
const withBase = upsertBaseCssBlock(existingCss, buildBaseCssBlock(baseColorFromStyle(style)));
|
|
6524
|
+
const withTokens = upsertPrimitiveCssBlock(withBase, primitiveCss);
|
|
4969
6525
|
const nextCss = upsertFontImportBlock(withTokens, fontPlan.block);
|
|
4970
6526
|
const cssChanged = nextCss !== existingCss;
|
|
4971
6527
|
const installedPackages = readInstalledPackageNames(projectRoot);
|
|
@@ -4988,7 +6544,7 @@ async function loginCommand(context, parsed) {
|
|
|
4988
6544
|
if (!token) {
|
|
4989
6545
|
throw new CliError("A token is required. Create one in the Wensity dashboard and run wensity login again.");
|
|
4990
6546
|
}
|
|
4991
|
-
const whoami = await
|
|
6547
|
+
const whoami = await requestJson2(joinUrl(apiBaseUrl, "/api/cli/whoami"), { token });
|
|
4992
6548
|
const path = await writeCredentials(context.env, {
|
|
4993
6549
|
version: 1,
|
|
4994
6550
|
apiBaseUrl,
|
|
@@ -5017,7 +6573,7 @@ async function logoutCommand(context) {
|
|
|
5017
6573
|
}
|
|
5018
6574
|
async function whoamiCommand(context, parsed) {
|
|
5019
6575
|
const credentials = await requireCredentials(context.env);
|
|
5020
|
-
const whoami = await
|
|
6576
|
+
const whoami = await requestJson2(joinUrl(credentials.apiBaseUrl, "/api/cli/whoami"), {
|
|
5021
6577
|
token: credentials.token
|
|
5022
6578
|
});
|
|
5023
6579
|
if (hasFlag(parsed, "json")) {
|
|
@@ -5031,10 +6587,15 @@ async function listCommand(context, parsed) {
|
|
|
5031
6587
|
const resourceType = getResourceType(parsed);
|
|
5032
6588
|
const apiUrl = getStringOption(parsed, "api-url") ?? context.env.WENSITY_API_URL;
|
|
5033
6589
|
const registryOverride = getStringOption(parsed, "registry");
|
|
5034
|
-
const
|
|
5035
|
-
|
|
6590
|
+
const configuredRegistry = await loadConfig(context.cwd, { registry: registryOverride, apiUrl }, { home: context.env.HOME }).then((loaded) => loaded.config.registry).catch((error) => {
|
|
6591
|
+
if (isWensityError(error) && error.code === "project_ambiguous") {
|
|
6592
|
+
return registryOverride ?? createDefaultConfig(context.cwd, apiUrl).registry;
|
|
6593
|
+
}
|
|
6594
|
+
throw error;
|
|
6595
|
+
});
|
|
6596
|
+
const registry = registryUrlForType(configuredRegistry, resourceType, registryOverride);
|
|
5036
6597
|
if (resourceType === "block") {
|
|
5037
|
-
const response2 = await
|
|
6598
|
+
const response2 = await requestJson2(registry);
|
|
5038
6599
|
if (hasFlag(parsed, "json")) {
|
|
5039
6600
|
printJson(response2);
|
|
5040
6601
|
return;
|
|
@@ -5053,7 +6614,7 @@ async function listCommand(context, parsed) {
|
|
|
5053
6614
|
]));
|
|
5054
6615
|
return;
|
|
5055
6616
|
}
|
|
5056
|
-
const response = await
|
|
6617
|
+
const response = await requestJson2(registry);
|
|
5057
6618
|
if (hasFlag(parsed, "json")) {
|
|
5058
6619
|
printJson(response);
|
|
5059
6620
|
return;
|
|
@@ -5095,54 +6656,83 @@ function formatBlockGroup(blocks) {
|
|
|
5095
6656
|
}
|
|
5096
6657
|
async function addCommand(context, parsed, positionals, updateMode) {
|
|
5097
6658
|
const resourceType = getResourceType(parsed);
|
|
5098
|
-
const
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
6659
|
+
const command = updateMode ? "update" : "add";
|
|
6660
|
+
const usage = `Usage: wensity ${command} <slug...>${resourceType === "block" ? " --type block" : ""}`;
|
|
6661
|
+
const slugs = dedupe(positionals);
|
|
6662
|
+
if (slugs.length === 0) {
|
|
6663
|
+
throw new CliError(`Missing ${resourceType} slug.
|
|
5102
6664
|
|
|
5103
|
-
|
|
5104
|
-
);
|
|
6665
|
+
${usage}`);
|
|
5105
6666
|
}
|
|
5106
6667
|
const apiUrl = getStringOption(parsed, "api-url") ?? context.env.WENSITY_API_URL;
|
|
5107
6668
|
const registryOverride = getStringOption(parsed, "registry");
|
|
5108
|
-
const loaded = await loadConfig(context.cwd, { registry: registryOverride, apiUrl });
|
|
6669
|
+
const loaded = await loadConfig(context.cwd, { registry: registryOverride, apiUrl }, { home: context.env.HOME });
|
|
6670
|
+
if (importAliasesResolve(loaded.projectRoot, loaded.config) === false) {
|
|
6671
|
+
throw new CliError(importAliasBlockMessage(loaded.config));
|
|
6672
|
+
}
|
|
5109
6673
|
const credentials = await readCredentials(context.env);
|
|
5110
6674
|
const registry = registryUrlForType(loaded.config.registry, resourceType, registryOverride);
|
|
5111
6675
|
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
6676
|
const dryRun = hasFlag(parsed, "dry-run");
|
|
5117
6677
|
const install = !hasFlag(parsed, "no-install");
|
|
5118
|
-
const
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
6678
|
+
const payloads = [];
|
|
6679
|
+
for (const slug of slugs) {
|
|
6680
|
+
payloads.push(
|
|
6681
|
+
resourceType === "block" ? await fetchBlockPayload({ slug, registry, credentials }) : await fetchComponentPayload({ slug, registry, credentials })
|
|
6682
|
+
);
|
|
6683
|
+
}
|
|
5122
6684
|
const style = normalizeProjectStyle(loaded.config.style);
|
|
5123
6685
|
const fonts = curatedFontsFromStyle(style);
|
|
5124
6686
|
const installedPackages = readInstalledPackageNames(loaded.projectRoot);
|
|
5125
|
-
const oppositeIconWarning = installedPackages && prepared.iconDependencySwap ? warnOppositeIconPackageMessage(style.iconLibrary, installedPackages) : void 0;
|
|
5126
6687
|
const unusedFontWarning = installedPackages ? warnUnusedFontPackageMessage(fonts.headingFont, fonts.bodyFont, installedPackages) : void 0;
|
|
6688
|
+
const writtenFiles = [];
|
|
6689
|
+
const cssTargets = [];
|
|
6690
|
+
const dependencies = [];
|
|
6691
|
+
const installPlans = [];
|
|
6692
|
+
let iconDependencySwapped = false;
|
|
6693
|
+
for (const payload of payloads) {
|
|
6694
|
+
const prepared = await prepareInstall(loaded.projectRoot, loaded.config, payload, {
|
|
6695
|
+
overwriteExistingDependencyFiles: force
|
|
6696
|
+
});
|
|
6697
|
+
const plannedInstallFiles = prepared.cssFile ? [...prepared.files, prepared.cssFile] : prepared.files;
|
|
6698
|
+
installPlans.push(prepared);
|
|
6699
|
+
writtenFiles.push(...summarizeFiles(loaded.projectRoot, plannedInstallFiles));
|
|
6700
|
+
if (prepared.cssFile) cssTargets.push(prepared.cssFile.relativePath);
|
|
6701
|
+
dependencies.push(...prepared.dependencies);
|
|
6702
|
+
if (prepared.iconDependencySwap) iconDependencySwapped = true;
|
|
6703
|
+
}
|
|
6704
|
+
const allPlannedInstallFiles = installPlans.flatMap(
|
|
6705
|
+
(prepared) => prepared.cssFile ? [...prepared.files, prepared.cssFile] : prepared.files
|
|
6706
|
+
);
|
|
6707
|
+
await assertNoConflicts(allPlannedInstallFiles, force || dryRun);
|
|
6708
|
+
const plannedFiles = dedupe(writtenFiles);
|
|
6709
|
+
const missing = missingDependencies(loaded.projectRoot, dedupe(dependencies));
|
|
6710
|
+
const oppositeIconWarning = installedPackages && iconDependencySwapped ? warnOppositeIconPackageMessage(style.iconLibrary, installedPackages) : void 0;
|
|
5127
6711
|
if (dryRun) {
|
|
5128
6712
|
printTitle(updateMode ? "Update preview" : "Install preview");
|
|
5129
6713
|
print(action("Would write:"));
|
|
5130
6714
|
print(formatList(plannedFiles));
|
|
5131
|
-
|
|
5132
|
-
print(action(`Would update CSS tokens in: ${
|
|
6715
|
+
for (const target of dedupe(cssTargets)) {
|
|
6716
|
+
print(action(`Would update CSS tokens in: ${target}`));
|
|
5133
6717
|
}
|
|
5134
6718
|
if (install && missing.length > 0) print(action(`Would install: ${missing.join(" ")}`));
|
|
5135
6719
|
if (oppositeIconWarning) warn(oppositeIconWarning);
|
|
5136
6720
|
if (unusedFontWarning) warn(unusedFontWarning);
|
|
5137
6721
|
return;
|
|
5138
6722
|
}
|
|
5139
|
-
await
|
|
6723
|
+
await writeWithRollback(
|
|
6724
|
+
loaded.projectRoot,
|
|
6725
|
+
installPlans.flatMap((prepared) => prepared.cssFile ? [...prepared.files, prepared.cssFile] : prepared.files)
|
|
6726
|
+
);
|
|
5140
6727
|
if (install) {
|
|
5141
|
-
|
|
5142
|
-
}
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
6728
|
+
await runInstall2(loaded.projectRoot, missing, installOptionsFrom(context, parsed));
|
|
6729
|
+
}
|
|
6730
|
+
const noun = payloads.length === 1 ? "Component" : "Components";
|
|
6731
|
+
printTitle(updateMode ? `${noun} updated` : `${noun} installed`);
|
|
6732
|
+
print(panel(payloads.map((payload) => payload.name).join(", "), [
|
|
6733
|
+
...payloads.map(
|
|
6734
|
+
(payload) => success(`${updateMode ? "Updated" : "Added"} ${payload.slug}`)
|
|
6735
|
+
),
|
|
5146
6736
|
...plannedFiles.map((file) => success(file))
|
|
5147
6737
|
]));
|
|
5148
6738
|
if (oppositeIconWarning) warn(oppositeIconWarning);
|
|
@@ -5156,62 +6746,99 @@ Usage: wensity ${updateMode ? "update" : "add"} <slug>${resourceType === "block"
|
|
|
5156
6746
|
return;
|
|
5157
6747
|
}
|
|
5158
6748
|
print(nextSteps([
|
|
5159
|
-
"Import the installed file from your Wensity components folder",
|
|
6749
|
+
payloads.length === 1 ? "Import the installed file from your Wensity components folder" : "Import the installed files from your Wensity components folder",
|
|
5160
6750
|
resourceType === "block" ? "Customize the block copy, colors, and sections" : "Customize props and styling for your app"
|
|
5161
6751
|
]));
|
|
5162
6752
|
}
|
|
6753
|
+
async function writeWithRollback(projectRoot, files) {
|
|
6754
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
6755
|
+
for (const file of files) {
|
|
6756
|
+
byPath.delete(file.absolutePath);
|
|
6757
|
+
byPath.set(file.absolutePath, file);
|
|
6758
|
+
}
|
|
6759
|
+
const result = await apply({
|
|
6760
|
+
projectRoot,
|
|
6761
|
+
planId: `cli_${randomBytes(8).toString("hex")}`,
|
|
6762
|
+
files: [...byPath.values()].map((file) => ({
|
|
6763
|
+
relativePath: toPosixPath(relative8(projectRoot, file.absolutePath)),
|
|
6764
|
+
content: file.content,
|
|
6765
|
+
action: existsSync5(file.absolutePath) ? file.kind === "config" ? "modify" : "overwrite" : "create"
|
|
6766
|
+
}))
|
|
6767
|
+
});
|
|
6768
|
+
if (result.status === "committed") {
|
|
6769
|
+
if (result.cleanupWarning) warn(result.cleanupWarning);
|
|
6770
|
+
return;
|
|
6771
|
+
}
|
|
6772
|
+
const where = result.failedPath ? ` while writing ${result.failedPath}` : "";
|
|
6773
|
+
if (result.status === "rolled_back") {
|
|
6774
|
+
throw new WensityError(
|
|
6775
|
+
result.error.code === "write_failed" ? "write_failed" : result.error.code,
|
|
6776
|
+
`Installation failed${where}; all project-file changes were reverted.
|
|
6777
|
+
${result.error.message}` + (result.cleanupWarning ? `
|
|
6778
|
+
${result.cleanupWarning}` : ""),
|
|
6779
|
+
{ cause: result.error }
|
|
6780
|
+
);
|
|
6781
|
+
}
|
|
6782
|
+
throw new WensityError(
|
|
6783
|
+
"rollback_failed",
|
|
6784
|
+
[
|
|
6785
|
+
`Installation failed${where}, and some files could not be restored. Nothing was deleted: the backups are in ${result.journalPath}.`,
|
|
6786
|
+
...result.unrecovered.map((item) => ` - ${item.path}: ${item.recovery}`),
|
|
6787
|
+
"Verify with git diff before removing that directory yourself."
|
|
6788
|
+
].join("\n"),
|
|
6789
|
+
{ cause: result.error }
|
|
6790
|
+
);
|
|
6791
|
+
}
|
|
6792
|
+
function dedupe(values) {
|
|
6793
|
+
return [...new Set(values)];
|
|
6794
|
+
}
|
|
5163
6795
|
async function fetchComponentPayload(input) {
|
|
6796
|
+
const client = registryClient(input.registry, input.credentials);
|
|
5164
6797
|
const metadata = await withSpinner(
|
|
5165
6798
|
`Finding ${input.slug}`,
|
|
5166
|
-
() =>
|
|
6799
|
+
() => client.fetchItemMetadata(input.slug, "component"),
|
|
5167
6800
|
`Found ${input.slug}`
|
|
5168
6801
|
);
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
`Downloaded ${metadata.name}`
|
|
5178
|
-
);
|
|
5179
|
-
} catch (error) {
|
|
5180
|
-
if (metadata.access === "pro" || !isAuthFailure(error)) throw error;
|
|
5181
|
-
warn("Saved Wensity credentials were rejected; falling back to the public free registry payload.");
|
|
5182
|
-
}
|
|
5183
|
-
}
|
|
5184
|
-
if (metadata.access === "pro") {
|
|
5185
|
-
throw new CliError(formatProRequiredError(metadata.name, "component"));
|
|
6802
|
+
try {
|
|
6803
|
+
return await client.fetchSourcePayload(input.slug, "component", {
|
|
6804
|
+
metadata,
|
|
6805
|
+
download: (task) => withSpinner(`Downloading ${metadata.name}`, task, `Downloaded ${metadata.name}`),
|
|
6806
|
+
onCredentialsRejected: () => warn("Saved Wensity credentials were rejected; falling back to the public free registry payload.")
|
|
6807
|
+
});
|
|
6808
|
+
} catch (error) {
|
|
6809
|
+
throw proRequiredError(error, metadata.name, "component");
|
|
5186
6810
|
}
|
|
5187
|
-
const publicSourceEndpoint = metadata.endpoints?.registry ?? registrySlugUrl(input.registry, input.slug);
|
|
5188
|
-
const endpoint = publicSourceEndpoint.startsWith("http") ? publicSourceEndpoint : joinUrl(registryOrigin(input.registry), publicSourceEndpoint);
|
|
5189
|
-
return withSpinner(
|
|
5190
|
-
`Downloading ${metadata.name}`,
|
|
5191
|
-
() => requestJson(endpoint),
|
|
5192
|
-
`Downloaded ${metadata.name}`
|
|
5193
|
-
);
|
|
5194
|
-
}
|
|
5195
|
-
function isAuthFailure(error) {
|
|
5196
|
-
return error instanceof CliError && /^(401|403)\b/.test(error.message);
|
|
5197
6811
|
}
|
|
5198
6812
|
async function fetchBlockPayload(input) {
|
|
6813
|
+
const client = registryClient(input.registry, input.credentials);
|
|
5199
6814
|
const metadata = await withSpinner(
|
|
5200
6815
|
`Finding ${input.slug}`,
|
|
5201
|
-
() =>
|
|
6816
|
+
() => client.fetchItemMetadata(input.slug, "block"),
|
|
5202
6817
|
`Found ${input.slug}`
|
|
5203
6818
|
);
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
6819
|
+
try {
|
|
6820
|
+
return await client.fetchSourcePayload(input.slug, "block", {
|
|
6821
|
+
metadata,
|
|
6822
|
+
download: (task) => withSpinner(`Downloading ${metadata.name}`, task, `Downloaded ${metadata.name}`)
|
|
6823
|
+
});
|
|
6824
|
+
} catch (error) {
|
|
6825
|
+
throw proRequiredError(error, metadata.name, "block");
|
|
6826
|
+
}
|
|
6827
|
+
}
|
|
6828
|
+
function registryClient(registry, credentials) {
|
|
6829
|
+
return createWensityClient({
|
|
6830
|
+
origin: registryOrigin(registry),
|
|
6831
|
+
registryUrl: registry,
|
|
6832
|
+
blocksRegistryUrl: registry,
|
|
6833
|
+
...credentials ? { token: credentials.token, apiBaseUrl: credentials.apiBaseUrl } : {},
|
|
6834
|
+
userAgent: `${CLI_NAME}/${CLI_VERSION}`
|
|
6835
|
+
});
|
|
6836
|
+
}
|
|
6837
|
+
function proRequiredError(error, resourceName, resourceType) {
|
|
6838
|
+
if (isWensityError(error) && error.code === "auth_required") {
|
|
6839
|
+
return new CliError(formatProRequiredError(resourceName, resourceType), 1, { cause: error });
|
|
6840
|
+
}
|
|
6841
|
+
return error;
|
|
5215
6842
|
}
|
|
5216
6843
|
function getResourceType(parsed) {
|
|
5217
6844
|
const value = getStringOption(parsed, "type") ?? "component";
|
|
@@ -5230,18 +6857,6 @@ async function requireCredentials(env) {
|
|
|
5230
6857
|
}
|
|
5231
6858
|
return credentials;
|
|
5232
6859
|
}
|
|
5233
|
-
function registryMetadataUrl(registry, slug) {
|
|
5234
|
-
const url = new URL(registry);
|
|
5235
|
-
url.searchParams.set("slug", slug);
|
|
5236
|
-
return url.toString();
|
|
5237
|
-
}
|
|
5238
|
-
function registrySlugUrl(registry, slug) {
|
|
5239
|
-
return joinUrl(registry.endsWith("/") ? registry : `${registry}/`, slug);
|
|
5240
|
-
}
|
|
5241
|
-
function registryOrigin(registry) {
|
|
5242
|
-
const url = new URL(registry);
|
|
5243
|
-
return `${url.protocol}//${url.host}`;
|
|
5244
|
-
}
|
|
5245
6860
|
function formatHelp() {
|
|
5246
6861
|
return [
|
|
5247
6862
|
brandBanner("Own your component source."),
|
|
@@ -5269,9 +6884,35 @@ function formatAccountPanel(whoami, savedPath) {
|
|
|
5269
6884
|
return panel(isPro ? "Wensity Pro" : "Wensity Free", lines);
|
|
5270
6885
|
}
|
|
5271
6886
|
async function writeTextFile(path, content) {
|
|
5272
|
-
await
|
|
6887
|
+
await mkdir7(dirname8(path), { recursive: true });
|
|
5273
6888
|
await writeFile6(path, content, "utf8");
|
|
5274
6889
|
}
|
|
6890
|
+
function installOptionsFrom(context, parsed) {
|
|
6891
|
+
return {
|
|
6892
|
+
packageManager: parsePackageManager(getStringOption(parsed, "package-manager")),
|
|
6893
|
+
env: context.env
|
|
6894
|
+
};
|
|
6895
|
+
}
|
|
6896
|
+
async function assertInitPostconditions(projectRoot, relativePaths) {
|
|
6897
|
+
const missing = [];
|
|
6898
|
+
for (const relativePath of new Set(relativePaths)) {
|
|
6899
|
+
const absolute = join11(projectRoot, relativePath);
|
|
6900
|
+
let size = 0;
|
|
6901
|
+
try {
|
|
6902
|
+
size = (await stat(absolute)).size;
|
|
6903
|
+
} catch {
|
|
6904
|
+
missing.push(relativePath);
|
|
6905
|
+
continue;
|
|
6906
|
+
}
|
|
6907
|
+
if (size === 0) missing.push(relativePath);
|
|
6908
|
+
}
|
|
6909
|
+
if (missing.length > 0) {
|
|
6910
|
+
throw new CliError(
|
|
6911
|
+
`Initialization did not complete. These files are missing or empty in ${projectRoot}:
|
|
6912
|
+
` + missing.map((file) => ` - ${toPosixPath(file)}`).join("\n")
|
|
6913
|
+
);
|
|
6914
|
+
}
|
|
6915
|
+
}
|
|
5275
6916
|
|
|
5276
6917
|
// src/index.ts
|
|
5277
6918
|
run({
|
|
@@ -5279,10 +6920,11 @@ run({
|
|
|
5279
6920
|
cwd: process.cwd(),
|
|
5280
6921
|
env: process.env
|
|
5281
6922
|
}).catch((error) => {
|
|
5282
|
-
|
|
5283
|
-
|
|
6923
|
+
const cliError = toCliError(error);
|
|
6924
|
+
if (cliError) {
|
|
6925
|
+
process.stderr.write(`${formatCliError(cliError.message)}
|
|
5284
6926
|
`);
|
|
5285
|
-
process.exitCode =
|
|
6927
|
+
process.exitCode = cliError.exitCode;
|
|
5286
6928
|
return;
|
|
5287
6929
|
}
|
|
5288
6930
|
const message = error instanceof Error ? error.stack ?? error.message : String(error);
|