wensity 0.1.12 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +6 -1
- package/dist/index.js +2006 -647
- package/package.json +2 -1
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.12";
|
|
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.14";
|
|
17
21
|
var INIT_PRIMITIVE_CSS_SLUG = "button";
|
|
18
22
|
var HELP_TEXT = `Wensity CLI ${CLI_VERSION}
|
|
19
23
|
|
|
@@ -68,85 +72,950 @@ Preset notes:
|
|
|
68
72
|
Invalid/expired/unsupported codes exit non-zero with an actionable message
|
|
69
73
|
`;
|
|
70
74
|
|
|
71
|
-
// src/errors.ts
|
|
72
|
-
var
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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;
|
|
78
98
|
}
|
|
79
99
|
};
|
|
100
|
+
function isWensityError(error) {
|
|
101
|
+
return error instanceof WensityError;
|
|
102
|
+
}
|
|
80
103
|
function isNodeError(error) {
|
|
81
104
|
return error instanceof Error && "code" in error;
|
|
82
105
|
}
|
|
83
106
|
|
|
84
|
-
// src/
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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);
|
|
93
209
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
210
|
+
let text;
|
|
211
|
+
try {
|
|
212
|
+
text = await response.text();
|
|
213
|
+
} catch (error) {
|
|
214
|
+
return transportFailure("Could not read the response from", error);
|
|
97
215
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
+
};
|
|
103
226
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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;
|
|
107
418
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
+
);
|
|
113
432
|
}
|
|
114
|
-
options.set(name, true);
|
|
115
433
|
}
|
|
116
|
-
return { positionals, options };
|
|
117
434
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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);
|
|
123
460
|
}
|
|
124
|
-
|
|
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
|
+
};
|
|
125
513
|
}
|
|
126
|
-
function
|
|
127
|
-
return
|
|
514
|
+
function withoutAuthorization(headers) {
|
|
515
|
+
return Object.fromEntries(Object.entries(headers).filter(([name]) => name.toLowerCase() !== "authorization"));
|
|
128
516
|
}
|
|
129
|
-
function
|
|
517
|
+
function isAuthFailure(error) {
|
|
518
|
+
const status = isWensityError(error) ? error.upstream?.status : void 0;
|
|
519
|
+
return status === 401 || status === 403;
|
|
520
|
+
}
|
|
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}`;
|
|
546
|
+
}
|
|
547
|
+
|
|
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) {
|
|
130
669
|
return [
|
|
131
|
-
|
|
132
|
-
"
|
|
133
|
-
"
|
|
134
|
-
|
|
135
|
-
"
|
|
136
|
-
|
|
137
|
-
"template",
|
|
138
|
-
"package-manager"
|
|
139
|
-
].includes(name);
|
|
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
|
+
];
|
|
140
676
|
}
|
|
141
|
-
function
|
|
142
|
-
|
|
143
|
-
|
|
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
|
+
}
|
|
144
1006
|
}
|
|
145
1007
|
|
|
146
|
-
// src/
|
|
147
|
-
import {
|
|
148
|
-
import {
|
|
149
|
-
import {
|
|
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";
|
|
150
1019
|
|
|
151
1020
|
// ../wensity-preset/src/constants.ts
|
|
152
1021
|
var ICON_LIBRARIES = ["tabler", "lucide"];
|
|
@@ -164,7 +1033,9 @@ var BASE_COLOR_IDS = [
|
|
|
164
1033
|
"mauve",
|
|
165
1034
|
"olive",
|
|
166
1035
|
"mist",
|
|
167
|
-
"black"
|
|
1036
|
+
"black",
|
|
1037
|
+
// Appended, never inserted: preset codes store the list index.
|
|
1038
|
+
"linen"
|
|
168
1039
|
];
|
|
169
1040
|
var THEME_COLOR_IDS = [
|
|
170
1041
|
"ink",
|
|
@@ -236,9 +1107,9 @@ var BODY_FONT_IDS = [
|
|
|
236
1107
|
];
|
|
237
1108
|
var RADIUS_PRESET_CSS = {
|
|
238
1109
|
none: "0px",
|
|
239
|
-
small: "0.
|
|
240
|
-
medium: "0.
|
|
241
|
-
large: "
|
|
1110
|
+
small: "0.25rem",
|
|
1111
|
+
medium: "0.375rem",
|
|
1112
|
+
large: "0.75rem"
|
|
242
1113
|
};
|
|
243
1114
|
var FONT_ID_EXPANSIONS = {
|
|
244
1115
|
"site:sans": "var(--font-sans)",
|
|
@@ -328,7 +1199,7 @@ var DEFAULT_CONTROL_SOLID_DARK = {
|
|
|
328
1199
|
};
|
|
329
1200
|
var DEFAULT_STYLE_CONFIG = {
|
|
330
1201
|
iconLibrary: "tabler",
|
|
331
|
-
radius: "
|
|
1202
|
+
radius: "large",
|
|
332
1203
|
fonts: { ...DEFAULT_STYLE_FONTS },
|
|
333
1204
|
colors: {
|
|
334
1205
|
light: { ...DEFAULT_CONTROL_SOLID_LIGHT },
|
|
@@ -353,7 +1224,7 @@ var DEFAULT_STYLE_CONFIG_CANONICAL = {
|
|
|
353
1224
|
headingFont: "inter",
|
|
354
1225
|
bodyFont: "inter",
|
|
355
1226
|
iconLibrary: "tabler",
|
|
356
|
-
radius: "
|
|
1227
|
+
radius: "large"
|
|
357
1228
|
};
|
|
358
1229
|
function createDefaultStyleConfigCanonical() {
|
|
359
1230
|
return {
|
|
@@ -1498,6 +2369,7 @@ var TABLER_TO_LUCIDE = {
|
|
|
1498
2369
|
IconChevronsRight: "ChevronsRight",
|
|
1499
2370
|
IconCircleX: "CircleX",
|
|
1500
2371
|
IconClock: "Clock",
|
|
2372
|
+
IconColorPicker: "Pipette",
|
|
1501
2373
|
IconCornerDownLeft: "CornerDownLeft",
|
|
1502
2374
|
IconDots: "Ellipsis",
|
|
1503
2375
|
IconDownload: "Download",
|
|
@@ -1767,6 +2639,26 @@ var BASE_COLOR_PALETTE = {
|
|
|
1767
2639
|
textSecondary: "oklch(0.68 0 0)",
|
|
1768
2640
|
textPlaceholder: "color-mix(in oklab, oklch(0.68 0 0) 78%, transparent)"
|
|
1769
2641
|
}
|
|
2642
|
+
},
|
|
2643
|
+
linen: {
|
|
2644
|
+
light: {
|
|
2645
|
+
surfaceElevated: "oklch(1 0 0)",
|
|
2646
|
+
surfaceOverlay: "oklch(1 0 0)",
|
|
2647
|
+
surfaceHover: "oklch(0.962 0.004 95)",
|
|
2648
|
+
surfaceActive: "color-mix(in oklab, oklch(0.962 0.004 95) 92%, oklch(0.345 0.01 115) 8%)",
|
|
2649
|
+
borderSubtle: "oklch(0.915 0.006 95)",
|
|
2650
|
+
textSecondary: "oklch(0.55 0.014 105)",
|
|
2651
|
+
textPlaceholder: "color-mix(in oklab, oklch(0.55 0.014 105) 78%, transparent)"
|
|
2652
|
+
},
|
|
2653
|
+
dark: {
|
|
2654
|
+
surfaceElevated: "oklch(0.215 0.003 95)",
|
|
2655
|
+
surfaceOverlay: "oklch(0.25 0.003 95)",
|
|
2656
|
+
surfaceHover: "oklch(0.275 0.004 95)",
|
|
2657
|
+
surfaceActive: "color-mix(in oklab, oklch(0.275 0.004 95) 88%, oklch(0.894 0.01 94) 12%)",
|
|
2658
|
+
borderSubtle: "oklch(0.894 0.01 94 / 9%)",
|
|
2659
|
+
textSecondary: "oklch(0.74 0.016 94)",
|
|
2660
|
+
textPlaceholder: "color-mix(in oklab, oklch(0.74 0.016 94) 78%, transparent)"
|
|
2661
|
+
}
|
|
1770
2662
|
}
|
|
1771
2663
|
};
|
|
1772
2664
|
var THEME_COLOR_PALETTE = {
|
|
@@ -2593,99 +3485,31 @@ var BASE_SEMANTIC_THEME_UTILITIES = {
|
|
|
2593
3485
|
"--color-surface-muted": "var(--surface-muted)",
|
|
2594
3486
|
"--color-muted": "var(--muted)",
|
|
2595
3487
|
"--color-muted-foreground": "var(--muted-foreground)",
|
|
2596
|
-
"--color-border": "var(--border)",
|
|
2597
|
-
"--color-border-strong": "var(--border-strong)"
|
|
2598
|
-
};
|
|
2599
|
-
var BASE_CSS_START = "/* \u2500\u2500 Wensity Base Tokens \u2014 generated by the Wensity CLI.\n Edit these values to retheme your app; re-running `wensity init --preset`\n replaces this block. \u2500\u2500 */";
|
|
2600
|
-
var BASE_CSS_END = "/* \u2500\u2500 End Wensity Base Tokens \u2500\u2500 */";
|
|
2601
|
-
function formatBaseDeclarations(entries, indent = " ") {
|
|
2602
|
-
return Object.entries(entries).map(([name, value]) => `${indent}${name}: ${value};`).join("\n");
|
|
2603
|
-
}
|
|
2604
|
-
function buildBaseCssBlock(baseColor = "inherit") {
|
|
2605
|
-
return [
|
|
2606
|
-
BASE_CSS_START,
|
|
2607
|
-
"@theme inline {",
|
|
2608
|
-
formatBaseDeclarations(BASE_SEMANTIC_THEME_UTILITIES),
|
|
2609
|
-
"}",
|
|
2610
|
-
":root {",
|
|
2611
|
-
formatBaseDeclarations(exportedBaseSemanticCssVars(baseColor, "light")),
|
|
2612
|
-
"}",
|
|
2613
|
-
".dark {",
|
|
2614
|
-
formatBaseDeclarations(exportedBaseSemanticCssVars(baseColor, "dark")),
|
|
2615
|
-
"}",
|
|
2616
|
-
BASE_CSS_END
|
|
2617
|
-
].join("\n");
|
|
2618
|
-
}
|
|
2619
|
-
|
|
2620
|
-
// src/paths.ts
|
|
2621
|
-
import { dirname, isAbsolute, relative, resolve, sep } from "path";
|
|
2622
|
-
function normalizeUrlOrigin(input) {
|
|
2623
|
-
const trimmed = input.trim().replace(/\/+$/, "");
|
|
2624
|
-
const withProtocol = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
2625
|
-
const url = new URL(withProtocol);
|
|
2626
|
-
if (url.protocol !== "https:" && !isLocalHttpUrl(url)) {
|
|
2627
|
-
throw new CliError("API URL must use HTTPS, except for localhost development URLs.");
|
|
2628
|
-
}
|
|
2629
|
-
return url.origin;
|
|
2630
|
-
}
|
|
2631
|
-
function isLocalHttpUrl(url) {
|
|
2632
|
-
if (url.protocol !== "http:") return false;
|
|
2633
|
-
return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
|
|
2634
|
-
}
|
|
2635
|
-
function joinUrl(originOrUrl, path) {
|
|
2636
|
-
const base = originOrUrl.endsWith("/") ? originOrUrl : `${originOrUrl}/`;
|
|
2637
|
-
const normalizedPath = path.startsWith("/") ? path.slice(1) : path;
|
|
2638
|
-
return new URL(normalizedPath, base).toString();
|
|
2639
|
-
}
|
|
2640
|
-
function resolveInside(root, candidate) {
|
|
2641
|
-
if (isAbsolute(candidate)) {
|
|
2642
|
-
throw new Error(`Path must be project-relative: ${candidate}`);
|
|
2643
|
-
}
|
|
2644
|
-
const absolutePath = resolve(root, candidate);
|
|
2645
|
-
const relativePath = relative(root, absolutePath);
|
|
2646
|
-
if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
|
|
2647
|
-
throw new Error(`Path escapes the project: ${candidate}`);
|
|
2648
|
-
}
|
|
2649
|
-
return absolutePath;
|
|
2650
|
-
}
|
|
2651
|
-
function toPosixPath(input) {
|
|
2652
|
-
return input.split(sep).join("/");
|
|
2653
|
-
}
|
|
2654
|
-
function parentDirectory(input) {
|
|
2655
|
-
return dirname(input);
|
|
2656
|
-
}
|
|
2657
|
-
function stripKnownComponentPrefix(path) {
|
|
2658
|
-
const normalized = path.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
2659
|
-
const prefixes = ["components/wensity/", "src/components/wensity/"];
|
|
2660
|
-
const prefix = prefixes.find((candidate) => normalized.startsWith(candidate));
|
|
2661
|
-
return prefix ? normalized.slice(prefix.length) : normalized;
|
|
2662
|
-
}
|
|
2663
|
-
function sanitizePayloadPath(path, fallbackFileName) {
|
|
2664
|
-
const normalized = stripKnownComponentPrefix(path);
|
|
2665
|
-
const parts = normalized.split("/").filter(Boolean);
|
|
2666
|
-
if (parts.length === 0) return fallbackFileName;
|
|
2667
|
-
for (const part of parts) {
|
|
2668
|
-
if (part === "." || part === ".." || part.includes("\0")) {
|
|
2669
|
-
throw new Error(`Unsafe registry file path: ${path}`);
|
|
2670
|
-
}
|
|
2671
|
-
}
|
|
2672
|
-
const last = parts.at(-1) ?? fallbackFileName;
|
|
2673
|
-
if (!/\.(tsx|ts|jsx|js)$/.test(last)) {
|
|
2674
|
-
parts[parts.length - 1] = fallbackFileName;
|
|
2675
|
-
}
|
|
2676
|
-
return parts.join("/");
|
|
2677
|
-
}
|
|
2678
|
-
function componentFileName(slug) {
|
|
2679
|
-
return `${slugToPascalCase(slug)}.tsx`;
|
|
2680
|
-
}
|
|
2681
|
-
function slugToPascalCase(slug) {
|
|
2682
|
-
return slug.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join("");
|
|
3488
|
+
"--color-border": "var(--border)",
|
|
3489
|
+
"--color-border-strong": "var(--border-strong)"
|
|
3490
|
+
};
|
|
3491
|
+
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 */";
|
|
3492
|
+
var BASE_CSS_END = "/* \u2500\u2500 End Wensity Base Tokens \u2500\u2500 */";
|
|
3493
|
+
function formatBaseDeclarations(entries, indent = " ") {
|
|
3494
|
+
return Object.entries(entries).map(([name, value]) => `${indent}${name}: ${value};`).join("\n");
|
|
2683
3495
|
}
|
|
2684
|
-
function
|
|
2685
|
-
return
|
|
3496
|
+
function buildBaseCssBlock(baseColor = "inherit") {
|
|
3497
|
+
return [
|
|
3498
|
+
BASE_CSS_START,
|
|
3499
|
+
"@theme inline {",
|
|
3500
|
+
formatBaseDeclarations(BASE_SEMANTIC_THEME_UTILITIES),
|
|
3501
|
+
"}",
|
|
3502
|
+
":root {",
|
|
3503
|
+
formatBaseDeclarations(exportedBaseSemanticCssVars(baseColor, "light")),
|
|
3504
|
+
"}",
|
|
3505
|
+
".dark {",
|
|
3506
|
+
formatBaseDeclarations(exportedBaseSemanticCssVars(baseColor, "dark")),
|
|
3507
|
+
"}",
|
|
3508
|
+
BASE_CSS_END
|
|
3509
|
+
].join("\n");
|
|
2686
3510
|
}
|
|
2687
3511
|
|
|
2688
|
-
// src/style-shape.ts
|
|
3512
|
+
// ../install-core/src/style-shape.ts
|
|
2689
3513
|
function isCanonicalStyleShape(value) {
|
|
2690
3514
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
2691
3515
|
const record = value;
|
|
@@ -2720,11 +3544,159 @@ function curatedFontsFromStyle(style) {
|
|
|
2720
3544
|
return { headingFont: "system", bodyFont: "system" };
|
|
2721
3545
|
}
|
|
2722
3546
|
|
|
2723
|
-
// src/
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
3547
|
+
// ../install-core/src/tsconfig-paths.ts
|
|
3548
|
+
import { existsSync, readFileSync as readFileSync2, statSync } from "fs";
|
|
3549
|
+
import { join as join4, posix, relative as relative4 } from "path";
|
|
3550
|
+
var MAX_CONFIG_HOPS = 8;
|
|
3551
|
+
var CONFIG_DIR = "${configDir}";
|
|
3552
|
+
function containedFile(projectRoot, relativePath) {
|
|
3553
|
+
let absolute;
|
|
3554
|
+
try {
|
|
3555
|
+
absolute = resolveInside(projectRoot, relativePath);
|
|
3556
|
+
assertNoSymlinkEscape(projectRoot, absolute);
|
|
3557
|
+
} catch {
|
|
3558
|
+
return void 0;
|
|
3559
|
+
}
|
|
3560
|
+
return existsSync(absolute) ? absolute : void 0;
|
|
3561
|
+
}
|
|
3562
|
+
function readPathMapping(projectRoot) {
|
|
3563
|
+
for (const file of ["tsconfig.json", "jsconfig.json"]) {
|
|
3564
|
+
if (!containedFile(projectRoot, file)) continue;
|
|
3565
|
+
const root = parseConfig(projectRoot, file);
|
|
3566
|
+
const own = resolveConfig(projectRoot, file, 0);
|
|
3567
|
+
if (!root || own === UNKNOWN) return void 0;
|
|
3568
|
+
if (own.paths) return toMapping(own, posix.dirname(file));
|
|
3569
|
+
let unresolvedReference = false;
|
|
3570
|
+
for (const reference of root.references ?? []) {
|
|
3571
|
+
if (typeof reference.path !== "string") continue;
|
|
3572
|
+
const referenced = referencePath(projectRoot, posix.join(posix.dirname(file), reference.path));
|
|
3573
|
+
const resolved = referenced ? resolveConfig(projectRoot, referenced, 0) : UNKNOWN;
|
|
3574
|
+
if (resolved === UNKNOWN) unresolvedReference = true;
|
|
3575
|
+
else if (resolved.paths) return toMapping(resolved, posix.dirname(referenced));
|
|
3576
|
+
}
|
|
3577
|
+
return unresolvedReference ? void 0 : toMapping(own, posix.dirname(file));
|
|
3578
|
+
}
|
|
3579
|
+
return void 0;
|
|
3580
|
+
}
|
|
3581
|
+
function aliasResolvesTo(mapping, alias, target) {
|
|
3582
|
+
const wanted = posix.normalize(stripExtension(target));
|
|
3583
|
+
for (const [key, values] of Object.entries(mapping.paths)) {
|
|
3584
|
+
const wildcard = key.endsWith("/*");
|
|
3585
|
+
const prefix = wildcard ? key.slice(0, -1) : key;
|
|
3586
|
+
if (wildcard ? !alias.startsWith(prefix) : alias !== key) continue;
|
|
3587
|
+
const rest = wildcard ? alias.slice(prefix.length) : "";
|
|
3588
|
+
for (const value of values) {
|
|
3589
|
+
const targetBase = value.endsWith("/*") ? value.slice(0, -1) : value;
|
|
3590
|
+
const candidate = posix.normalize(posix.join(mapping.base, targetBase, rest));
|
|
3591
|
+
if (stripExtension(candidate) === wanted) return true;
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
return false;
|
|
3595
|
+
}
|
|
3596
|
+
function atAliasRoot(mapping) {
|
|
3597
|
+
const target = mapping?.paths["@/*"]?.find((value) => value.endsWith("/*"));
|
|
3598
|
+
if (target === void 0) return void 0;
|
|
3599
|
+
const dir = posix.normalize(posix.join(mapping.base, target.slice(0, -2)));
|
|
3600
|
+
if (dir === ".." || dir.startsWith("../") || posix.isAbsolute(dir)) return void 0;
|
|
3601
|
+
if (dir === "node_modules" || dir.startsWith("node_modules/")) return void 0;
|
|
3602
|
+
return dir === "." ? "" : dir;
|
|
3603
|
+
}
|
|
3604
|
+
var UNKNOWN = /* @__PURE__ */ Symbol("unknown");
|
|
3605
|
+
function resolveConfig(projectRoot, file, hops) {
|
|
3606
|
+
const parsed = parseConfig(projectRoot, file);
|
|
3607
|
+
if (!parsed || hops > MAX_CONFIG_HOPS) return UNKNOWN;
|
|
3608
|
+
const dir = posix.dirname(file);
|
|
3609
|
+
const options = parsed.compilerOptions ?? {};
|
|
3610
|
+
let resolved = {};
|
|
3611
|
+
const parents = typeof parsed.extends === "string" ? [parsed.extends] : parsed.extends ?? [];
|
|
3612
|
+
for (const parent of parents) {
|
|
3613
|
+
if (typeof parent !== "string") return UNKNOWN;
|
|
3614
|
+
const parentFile = extendsTarget(projectRoot, dir, parent);
|
|
3615
|
+
const inherited = parentFile ? resolveConfig(projectRoot, parentFile, hops + 1) : UNKNOWN;
|
|
3616
|
+
if (inherited === UNKNOWN) return UNKNOWN;
|
|
3617
|
+
resolved = {
|
|
3618
|
+
baseUrl: inherited.baseUrl ?? resolved.baseUrl,
|
|
3619
|
+
paths: inherited.paths ?? resolved.paths
|
|
3620
|
+
};
|
|
3621
|
+
}
|
|
3622
|
+
if (options.baseUrl !== void 0) {
|
|
3623
|
+
resolved.baseUrl = options.baseUrl.startsWith(CONFIG_DIR) ? options.baseUrl : posix.normalize(posix.join(dir, options.baseUrl));
|
|
3624
|
+
}
|
|
3625
|
+
if (options.paths) resolved.paths = { map: options.paths, dir };
|
|
3626
|
+
return resolved;
|
|
3627
|
+
}
|
|
3628
|
+
function toMapping(resolved, configDir) {
|
|
3629
|
+
const expand = (value) => posix.normalize(posix.join(configDir, value.slice(CONFIG_DIR.length)));
|
|
3630
|
+
const baseUrl = resolved.baseUrl?.startsWith(CONFIG_DIR) ? expand(resolved.baseUrl) : resolved.baseUrl;
|
|
3631
|
+
if (!resolved.paths) return { base: baseUrl ?? ".", paths: {} };
|
|
3632
|
+
const base = baseUrl ?? resolved.paths.dir;
|
|
3633
|
+
const rebase = (value) => {
|
|
3634
|
+
if (!value.startsWith(CONFIG_DIR)) return value;
|
|
3635
|
+
const wildcard = value.endsWith("/*");
|
|
3636
|
+
const target = expand(wildcard ? value.slice(0, -2) : value);
|
|
3637
|
+
const rel = posix.relative(base, target) || ".";
|
|
3638
|
+
return wildcard ? `${rel}/*` : rel;
|
|
3639
|
+
};
|
|
3640
|
+
const paths = Object.fromEntries(Object.entries(resolved.paths.map).map(([key, values]) => [key, values.map(rebase)]));
|
|
3641
|
+
return { base, paths };
|
|
3642
|
+
}
|
|
3643
|
+
function extendsTarget(projectRoot, fromDir, specifier) {
|
|
3644
|
+
if (specifier.startsWith("./") || specifier.startsWith("../") || specifier === "." || specifier === "..") {
|
|
3645
|
+
return configFile(projectRoot, posix.join(fromDir, specifier));
|
|
3646
|
+
}
|
|
3647
|
+
if (posix.isAbsolute(specifier) || /^[A-Za-z]:[\\/]/.test(specifier)) return void 0;
|
|
3648
|
+
const inPackage = posix.join("node_modules", specifier);
|
|
3649
|
+
const direct = configFile(projectRoot, inPackage);
|
|
3650
|
+
if (direct) return direct;
|
|
3651
|
+
const manifest = containedFile(projectRoot, posix.join(inPackage, "package.json"));
|
|
3652
|
+
if (manifest) {
|
|
3653
|
+
try {
|
|
3654
|
+
const field = JSON.parse(readFileSync2(manifest, "utf8")).tsconfig;
|
|
3655
|
+
if (typeof field === "string") {
|
|
3656
|
+
const fromField = configFile(projectRoot, posix.join(inPackage, field));
|
|
3657
|
+
if (fromField) return fromField;
|
|
3658
|
+
}
|
|
3659
|
+
} catch {
|
|
3660
|
+
}
|
|
3661
|
+
}
|
|
3662
|
+
return configFile(projectRoot, posix.join(inPackage, "tsconfig.json"));
|
|
3663
|
+
}
|
|
3664
|
+
function parseConfig(projectRoot, file) {
|
|
3665
|
+
const path = containedFile(projectRoot, file);
|
|
3666
|
+
if (!path) return void 0;
|
|
3667
|
+
try {
|
|
3668
|
+
return JSON.parse(stripJsonComments(readFileSync2(path, "utf8")));
|
|
3669
|
+
} catch {
|
|
3670
|
+
return void 0;
|
|
3671
|
+
}
|
|
3672
|
+
}
|
|
3673
|
+
function configFile(projectRoot, path) {
|
|
3674
|
+
const normalized = posix.normalize(path);
|
|
3675
|
+
for (const candidate of [normalized, `${normalized}.json`]) {
|
|
3676
|
+
const absolute = containedFile(projectRoot, candidate);
|
|
3677
|
+
if (absolute && statSync(absolute).isFile()) return toPosixPath(relative4(projectRoot, absolute));
|
|
3678
|
+
}
|
|
3679
|
+
return void 0;
|
|
3680
|
+
}
|
|
3681
|
+
function referencePath(projectRoot, path) {
|
|
3682
|
+
const absolute = containedFile(projectRoot, path);
|
|
3683
|
+
if (!absolute) return void 0;
|
|
3684
|
+
const file = statSync(absolute).isDirectory() ? join4(absolute, "tsconfig.json") : absolute;
|
|
3685
|
+
const relativeFile = toPosixPath(relative4(projectRoot, file));
|
|
3686
|
+
return containedFile(projectRoot, relativeFile) ? relativeFile : void 0;
|
|
3687
|
+
}
|
|
3688
|
+
function stripJsonComments(text) {
|
|
3689
|
+
return text.replace(/("(?:\\.|[^"\\])*")|\/\*[\s\S]*?\*\/|\/\/[^\n]*/g, (match, string) => string ?? "").replace(/,(\s*[}\]])/g, "$1");
|
|
3690
|
+
}
|
|
3691
|
+
function stripExtension(path) {
|
|
3692
|
+
return path.replace(/\.(tsx|ts|jsx|js)$/, "");
|
|
3693
|
+
}
|
|
3694
|
+
|
|
3695
|
+
// ../install-core/src/config.ts
|
|
3696
|
+
async function loadConfig(cwd, overrides = {}, options = {}) {
|
|
3697
|
+
const { projectRoot, configPath: foundPath } = await locateProject(cwd, options.home);
|
|
3698
|
+
const configPath = foundPath ?? join5(projectRoot, CONFIG_FILE_NAME);
|
|
3699
|
+
if (foundPath && options.rejectEscapingSymlink) assertNoSymlinkEscape(projectRoot, foundPath);
|
|
2728
3700
|
const defaults = createDefaultConfig(projectRoot, overrides.apiUrl ?? DEFAULT_API_ORIGIN);
|
|
2729
3701
|
if (!foundPath) {
|
|
2730
3702
|
return {
|
|
@@ -2735,7 +3707,7 @@ async function loadConfig(cwd, overrides = {}) {
|
|
|
2735
3707
|
};
|
|
2736
3708
|
}
|
|
2737
3709
|
try {
|
|
2738
|
-
const parsed = JSON.parse(await
|
|
3710
|
+
const parsed = JSON.parse(await readFile2(foundPath, "utf8"));
|
|
2739
3711
|
return {
|
|
2740
3712
|
config: normalizeConfig(
|
|
2741
3713
|
{
|
|
@@ -2757,22 +3729,23 @@ async function loadConfig(cwd, overrides = {}) {
|
|
|
2757
3729
|
};
|
|
2758
3730
|
} catch (error) {
|
|
2759
3731
|
if (error instanceof SyntaxError) {
|
|
2760
|
-
throw new
|
|
3732
|
+
throw new WensityError("config_invalid", `${CONFIG_FILE_NAME} contains invalid JSON.`);
|
|
2761
3733
|
}
|
|
2762
3734
|
if (error instanceof PresetValidationError) {
|
|
2763
|
-
throw new
|
|
3735
|
+
throw new WensityError("config_invalid", `${CONFIG_FILE_NAME}: ${error.message}`);
|
|
2764
3736
|
}
|
|
2765
3737
|
throw error;
|
|
2766
3738
|
}
|
|
2767
3739
|
}
|
|
2768
3740
|
function createDefaultConfig(projectRoot, apiUrl = DEFAULT_API_ORIGIN) {
|
|
2769
3741
|
const apiOrigin = normalizeUrlOrigin(apiUrl);
|
|
3742
|
+
const sourceRoot = defaultSourceRoot(projectRoot);
|
|
2770
3743
|
return {
|
|
2771
3744
|
$schema: joinUrl(apiOrigin, "/schema/wensity.json"),
|
|
2772
3745
|
version: CONFIG_SCHEMA_VERSION,
|
|
2773
3746
|
registry: joinUrl(apiOrigin, DEFAULT_REGISTRY_PATH),
|
|
2774
|
-
components:
|
|
2775
|
-
utils:
|
|
3747
|
+
components: posix2.join(sourceRoot, "components/wensity"),
|
|
3748
|
+
utils: posix2.join(sourceRoot, "lib/utils"),
|
|
2776
3749
|
tailwind: {
|
|
2777
3750
|
config: detectTailwindConfig(projectRoot),
|
|
2778
3751
|
css: detectGlobalCss(projectRoot)
|
|
@@ -2786,20 +3759,21 @@ function createDefaultConfig(projectRoot, apiUrl = DEFAULT_API_ORIGIN) {
|
|
|
2786
3759
|
}
|
|
2787
3760
|
async function writeConfigFile(path, config, force) {
|
|
2788
3761
|
if (!force && await fileExists(path)) {
|
|
2789
|
-
throw new
|
|
3762
|
+
throw new WensityError("file_conflict", `${toPosixPath(relative5(process.cwd(), path))} already exists. Use --force to overwrite it.`);
|
|
2790
3763
|
}
|
|
2791
|
-
await
|
|
3764
|
+
await mkdir2(dirname3(path), { recursive: true });
|
|
2792
3765
|
await writeFile(path, `${JSON.stringify(config, null, 2)}
|
|
2793
3766
|
`, "utf8");
|
|
2794
3767
|
}
|
|
2795
3768
|
async function mergeStyleIntoConfigFile(configPath, style) {
|
|
2796
|
-
const raw = JSON.parse(await
|
|
3769
|
+
const raw = JSON.parse(await readFile2(configPath, "utf8"));
|
|
3770
|
+
const defaults = raw.components && raw.utils ? void 0 : createDefaultConfig(dirname3(configPath));
|
|
2797
3771
|
const merged = {
|
|
2798
3772
|
...raw,
|
|
2799
3773
|
version: raw.version ?? CONFIG_SCHEMA_VERSION,
|
|
2800
3774
|
registry: raw.registry ?? "",
|
|
2801
|
-
components: raw.components ?? DEFAULT_COMPONENTS_DIR,
|
|
2802
|
-
utils: raw.utils ?? DEFAULT_UTILS_PATH,
|
|
3775
|
+
components: raw.components ?? defaults?.components ?? DEFAULT_COMPONENTS_DIR,
|
|
3776
|
+
utils: raw.utils ?? defaults?.utils ?? DEFAULT_UTILS_PATH,
|
|
2803
3777
|
aliases: {
|
|
2804
3778
|
components: raw.aliases?.components ?? DEFAULT_COMPONENTS_ALIAS,
|
|
2805
3779
|
utils: raw.aliases?.utils ?? DEFAULT_UTILS_ALIAS
|
|
@@ -2817,7 +3791,7 @@ function resolveConfiguredFilePath(projectRoot, relativePath, extension = "ts")
|
|
|
2817
3791
|
return resolveInside(projectRoot, withExtension(relativePath, extension));
|
|
2818
3792
|
} catch (error) {
|
|
2819
3793
|
const message = error instanceof Error ? error.message : String(error);
|
|
2820
|
-
throw new
|
|
3794
|
+
throw new WensityError("path_unsafe", message);
|
|
2821
3795
|
}
|
|
2822
3796
|
}
|
|
2823
3797
|
async function fileExists(path) {
|
|
@@ -2829,16 +3803,52 @@ async function fileExists(path) {
|
|
|
2829
3803
|
throw error;
|
|
2830
3804
|
}
|
|
2831
3805
|
}
|
|
2832
|
-
|
|
2833
|
-
|
|
3806
|
+
function assertProjectRootCandidate(path, home = homedir()) {
|
|
3807
|
+
const resolved = resolve2(path);
|
|
3808
|
+
if (isFilesystemRoot(resolved)) {
|
|
3809
|
+
throw new WensityError(
|
|
3810
|
+
"project_ambiguous",
|
|
3811
|
+
`${resolved} is a filesystem root, not a project. Run Wensity from your project directory (the one with package.json).`
|
|
3812
|
+
);
|
|
3813
|
+
}
|
|
3814
|
+
if (isHomeDirectory(resolved, home)) {
|
|
3815
|
+
throw new WensityError(
|
|
3816
|
+
"project_ambiguous",
|
|
3817
|
+
"Your home directory is not a project. Run Wensity from your project directory (the one with package.json)."
|
|
3818
|
+
);
|
|
3819
|
+
}
|
|
3820
|
+
}
|
|
3821
|
+
async function locateProject(start, home = homedir()) {
|
|
3822
|
+
assertProjectRootCandidate(start, home);
|
|
3823
|
+
const origin = resolve2(start);
|
|
3824
|
+
let current = origin;
|
|
2834
3825
|
while (true) {
|
|
2835
|
-
const candidate =
|
|
2836
|
-
if (await fileExists(candidate)) return candidate;
|
|
2837
|
-
|
|
2838
|
-
|
|
3826
|
+
const candidate = join5(current, CONFIG_FILE_NAME);
|
|
3827
|
+
if (await fileExists(candidate)) return { projectRoot: current, configPath: candidate };
|
|
3828
|
+
if (await fileExists(join5(current, "package.json"))) return { projectRoot: current, configPath: void 0 };
|
|
3829
|
+
const parent = dirname3(current);
|
|
3830
|
+
if (isFilesystemRoot(parent) || isHomeDirectory(parent, home)) return { projectRoot: origin, configPath: void 0 };
|
|
2839
3831
|
current = parent;
|
|
2840
3832
|
}
|
|
2841
3833
|
}
|
|
3834
|
+
function isFilesystemRoot(path) {
|
|
3835
|
+
return dirname3(path) === path;
|
|
3836
|
+
}
|
|
3837
|
+
function isHomeDirectory(path, home) {
|
|
3838
|
+
if (!home) return false;
|
|
3839
|
+
if (resolve2(path) === resolve2(home)) return true;
|
|
3840
|
+
try {
|
|
3841
|
+
return realpathSync3(path) === realpathSync3(home);
|
|
3842
|
+
} catch {
|
|
3843
|
+
return false;
|
|
3844
|
+
}
|
|
3845
|
+
}
|
|
3846
|
+
function defaultSourceRoot(projectRoot) {
|
|
3847
|
+
const fromAlias = atAliasRoot(readPathMapping(projectRoot));
|
|
3848
|
+
if (fromAlias !== void 0) return fromAlias;
|
|
3849
|
+
const src = join5(projectRoot, "src");
|
|
3850
|
+
return existsSync2(src) && statSync2(src).isDirectory() ? "src" : "";
|
|
3851
|
+
}
|
|
2842
3852
|
function normalizeConfig(config, overrides) {
|
|
2843
3853
|
const apiOrigin = overrides.apiUrl ? normalizeUrlOrigin(overrides.apiUrl) : void 0;
|
|
2844
3854
|
try {
|
|
@@ -2858,14 +3868,14 @@ function normalizeConfig(config, overrides) {
|
|
|
2858
3868
|
};
|
|
2859
3869
|
} catch (error) {
|
|
2860
3870
|
if (error instanceof PresetValidationError) {
|
|
2861
|
-
throw new
|
|
3871
|
+
throw new WensityError("config_invalid", `${CONFIG_FILE_NAME}: ${error.message}`);
|
|
2862
3872
|
}
|
|
2863
3873
|
throw error;
|
|
2864
3874
|
}
|
|
2865
3875
|
}
|
|
2866
3876
|
function detectTailwindConfig(projectRoot) {
|
|
2867
3877
|
const candidates = ["tailwind.config.ts", "tailwind.config.js", "tailwind.config.mjs"];
|
|
2868
|
-
return candidates.find((candidate) =>
|
|
3878
|
+
return candidates.find((candidate) => existsSync2(join5(projectRoot, candidate))) ?? "tailwind.config.ts";
|
|
2869
3879
|
}
|
|
2870
3880
|
function detectGlobalCss(projectRoot) {
|
|
2871
3881
|
const candidates = [
|
|
@@ -2875,230 +3885,356 @@ function detectGlobalCss(projectRoot) {
|
|
|
2875
3885
|
"src/index.css",
|
|
2876
3886
|
"styles/globals.css"
|
|
2877
3887
|
];
|
|
2878
|
-
return candidates.find((candidate) =>
|
|
3888
|
+
return candidates.find((candidate) => existsSync2(join5(projectRoot, candidate))) ?? "src/app/globals.css";
|
|
2879
3889
|
}
|
|
2880
3890
|
function withExtension(path, extension) {
|
|
2881
3891
|
if (/\.(ts|tsx|js|jsx)$/.test(path)) return path;
|
|
2882
3892
|
return `${path}.${extension}`;
|
|
2883
3893
|
}
|
|
2884
3894
|
|
|
2885
|
-
// src/
|
|
2886
|
-
import {
|
|
2887
|
-
import {
|
|
2888
|
-
import {
|
|
2889
|
-
|
|
2890
|
-
function
|
|
2891
|
-
if (
|
|
2892
|
-
const
|
|
2893
|
-
|
|
3895
|
+
// ../install-core/src/package-manager.ts
|
|
3896
|
+
import { accessSync, constants as fsConstants2, existsSync as existsSync3, readFileSync as readFileSync3, statSync as statSync3, writeFileSync } from "fs";
|
|
3897
|
+
import { spawn, spawnSync } from "child_process";
|
|
3898
|
+
import { join as join6, posix as posix3, win32 } from "path";
|
|
3899
|
+
var PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
|
|
3900
|
+
function parsePackageManager(value) {
|
|
3901
|
+
if (!value) return void 0;
|
|
3902
|
+
const normalized = value.trim().toLowerCase();
|
|
3903
|
+
if (isPackageManager(normalized)) return normalized;
|
|
3904
|
+
throw new WensityError(
|
|
3905
|
+
"unsupported_package_manager",
|
|
3906
|
+
`Unsupported package manager "${value}". Use one of: ${PACKAGE_MANAGERS.join(", ")}.`
|
|
3907
|
+
);
|
|
2894
3908
|
}
|
|
2895
|
-
|
|
2896
|
-
const
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
throw new CliError(`Credentials at ${path} are not valid. Run wensity login again.`);
|
|
2902
|
-
}
|
|
2903
|
-
return parsed;
|
|
2904
|
-
} catch (error) {
|
|
2905
|
-
if (isNodeError(error) && error.code === "ENOENT") return void 0;
|
|
2906
|
-
if (error instanceof SyntaxError) {
|
|
2907
|
-
throw new CliError(`Credentials at ${path} are not valid JSON. Run wensity login again.`);
|
|
2908
|
-
}
|
|
2909
|
-
throw error;
|
|
2910
|
-
}
|
|
3909
|
+
function packageManagerFromUserAgent(env = process.env) {
|
|
3910
|
+
const agent = env.npm_config_user_agent;
|
|
3911
|
+
if (!agent) return void 0;
|
|
3912
|
+
const first = agent.trim().split(/\s+/)[0] ?? "";
|
|
3913
|
+
const name = first.split("/")[0]?.toLowerCase();
|
|
3914
|
+
return name && isPackageManager(name) ? name : void 0;
|
|
2911
3915
|
}
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
`, {
|
|
2919
|
-
encoding: "utf8",
|
|
2920
|
-
mode: 384
|
|
2921
|
-
});
|
|
2922
|
-
await rename(temporaryPath, path);
|
|
2923
|
-
await chmod(path, 384);
|
|
2924
|
-
return path;
|
|
3916
|
+
function detectPackageManager(projectRoot, env = process.env) {
|
|
3917
|
+
if (existsSync3(join6(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
3918
|
+
if (existsSync3(join6(projectRoot, "bun.lockb")) || existsSync3(join6(projectRoot, "bun.lock"))) return "bun";
|
|
3919
|
+
if (existsSync3(join6(projectRoot, "yarn.lock"))) return "yarn";
|
|
3920
|
+
if (existsSync3(join6(projectRoot, "package-lock.json"))) return "npm";
|
|
3921
|
+
return packageManagerFromUserAgent(env) ?? "npm";
|
|
2925
3922
|
}
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
try {
|
|
2929
|
-
await rm(path, { force: false });
|
|
2930
|
-
return true;
|
|
2931
|
-
} catch (error) {
|
|
2932
|
-
if (isNodeError(error) && error.code === "ENOENT") return false;
|
|
2933
|
-
throw error;
|
|
2934
|
-
}
|
|
3923
|
+
function isPackageManager(value) {
|
|
3924
|
+
return PACKAGE_MANAGERS.includes(value);
|
|
2935
3925
|
}
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
3926
|
+
function missingDependencies(projectRoot, dependencies) {
|
|
3927
|
+
const installed = readInstalledPackageNames(projectRoot);
|
|
3928
|
+
if (!installed) return dependencies;
|
|
3929
|
+
return dependencies.filter((dependency) => !installed.has(dependency));
|
|
3930
|
+
}
|
|
3931
|
+
function readInstalledPackageNames(projectRoot) {
|
|
3932
|
+
const packageJsonPath = join6(projectRoot, "package.json");
|
|
3933
|
+
if (!existsSync3(packageJsonPath)) return void 0;
|
|
3934
|
+
const packageJson2 = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
|
|
3935
|
+
return new Set(
|
|
3936
|
+
Object.keys({
|
|
3937
|
+
...packageJson2.dependencies,
|
|
3938
|
+
...packageJson2.devDependencies,
|
|
3939
|
+
...packageJson2.peerDependencies,
|
|
3940
|
+
...packageJson2.optionalDependencies
|
|
3941
|
+
})
|
|
3942
|
+
);
|
|
3943
|
+
}
|
|
3944
|
+
var YARN_CONFIG_FILE = ".yarnrc.yml";
|
|
3945
|
+
function prepareProjectPackageManager(projectRoot, options = {}) {
|
|
3946
|
+
if (resolvePackageManager(projectRoot, options) !== "yarn") return [];
|
|
3947
|
+
const configPath = join6(projectRoot, YARN_CONFIG_FILE);
|
|
3948
|
+
if (existsSync3(configPath)) return [];
|
|
3949
|
+
if (!options.dryRun) writeFileSync(configPath, "nodeLinker: node-modules\n", "utf8");
|
|
3950
|
+
return [YARN_CONFIG_FILE];
|
|
3951
|
+
}
|
|
3952
|
+
async function runProjectInstall(projectRoot, options = {}) {
|
|
3953
|
+
const packageJsonPath = join6(projectRoot, "package.json");
|
|
3954
|
+
if (!existsSync3(packageJsonPath)) return;
|
|
3955
|
+
const packageManager = resolvePackageManager(projectRoot, options);
|
|
3956
|
+
await runPackageManager(projectRoot, packageManager, ["install"], options);
|
|
3957
|
+
}
|
|
3958
|
+
function resolvePackageManager(projectRoot, options = {}) {
|
|
3959
|
+
return options.packageManager ?? detectPackageManager(projectRoot, options.env);
|
|
3960
|
+
}
|
|
3961
|
+
async function runInstall(projectRoot, dependencies, options = {}) {
|
|
3962
|
+
if (dependencies.length === 0) return;
|
|
3963
|
+
const packageJsonPath = join6(projectRoot, "package.json");
|
|
3964
|
+
if (!existsSync3(packageJsonPath)) {
|
|
3965
|
+
(options.onWarning ?? writeWarning)(
|
|
3966
|
+
`No package.json found. Install these dependencies manually: ${dependencies.join(" ")}`
|
|
3967
|
+
);
|
|
3968
|
+
return;
|
|
2943
3969
|
}
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
const previousRawMode = input.isRaw;
|
|
2947
|
-
let token = "";
|
|
2948
|
-
function cleanup() {
|
|
2949
|
-
input.off("data", onData);
|
|
2950
|
-
if (typeof previousRawMode === "boolean") {
|
|
2951
|
-
input.setRawMode(previousRawMode);
|
|
2952
|
-
}
|
|
2953
|
-
input.pause();
|
|
2954
|
-
}
|
|
2955
|
-
function onData(chunk) {
|
|
2956
|
-
const value = chunk.toString("utf8");
|
|
2957
|
-
if (value === "") {
|
|
2958
|
-
cleanup();
|
|
2959
|
-
stdout.write("\n");
|
|
2960
|
-
rejectToken(new CliError("Login cancelled.", 130));
|
|
2961
|
-
return;
|
|
2962
|
-
}
|
|
2963
|
-
if (value === "\r" || value === "\n" || value === "\r\n") {
|
|
2964
|
-
cleanup();
|
|
2965
|
-
stdout.write("\n");
|
|
2966
|
-
resolveToken(token.trim());
|
|
2967
|
-
return;
|
|
2968
|
-
}
|
|
2969
|
-
if (value === "\x7F") {
|
|
2970
|
-
token = token.slice(0, -1);
|
|
2971
|
-
return;
|
|
2972
|
-
}
|
|
2973
|
-
token += value;
|
|
2974
|
-
}
|
|
2975
|
-
stdout.write(prompt);
|
|
2976
|
-
input.setRawMode(true);
|
|
2977
|
-
input.resume();
|
|
2978
|
-
input.on("data", onData);
|
|
2979
|
-
});
|
|
3970
|
+
const packageManager = resolvePackageManager(projectRoot, options);
|
|
3971
|
+
await runPackageManager(projectRoot, packageManager, installArgs(packageManager, dependencies), options);
|
|
2980
3972
|
}
|
|
2981
|
-
function
|
|
2982
|
-
|
|
2983
|
-
|
|
3973
|
+
async function runPackageManager(projectRoot, packageManager, args, options) {
|
|
3974
|
+
const outcome = await runPackageManagerCommand(projectRoot, packageManager, args, {
|
|
3975
|
+
output: "inherit",
|
|
3976
|
+
forwardSignals: true,
|
|
3977
|
+
...options.spawnImpl ? { spawnImpl: options.spawnImpl } : {}
|
|
3978
|
+
});
|
|
3979
|
+
throwForFailure(packageManager, args, outcome);
|
|
2984
3980
|
}
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
var FONT_CSS_END_MARKER = "/* \u2500\u2500 End Wensity Fonts \u2500\u2500 */";
|
|
2989
|
-
function planFontInstall(headingFont, bodyFont) {
|
|
2990
|
-
const packages = [];
|
|
2991
|
-
const importLines = [];
|
|
2992
|
-
const seenPackages = /* @__PURE__ */ new Set();
|
|
2993
|
-
const seenImports = /* @__PURE__ */ new Set();
|
|
2994
|
-
const pushFont = (meta) => {
|
|
2995
|
-
if (!meta) return;
|
|
2996
|
-
if (!seenPackages.has(meta.packageName)) {
|
|
2997
|
-
seenPackages.add(meta.packageName);
|
|
2998
|
-
packages.push(meta.packageName);
|
|
2999
|
-
}
|
|
3000
|
-
for (const specifier of curatedFontCssImportSpecifiers(meta)) {
|
|
3001
|
-
const line = `@import "${specifier}";`;
|
|
3002
|
-
if (!seenImports.has(line)) {
|
|
3003
|
-
seenImports.add(line);
|
|
3004
|
-
importLines.push(line);
|
|
3005
|
-
}
|
|
3006
|
-
}
|
|
3007
|
-
};
|
|
3008
|
-
if (headingFont !== "system") {
|
|
3009
|
-
pushFont(HEADING_FONT_PACKAGES[headingFont]);
|
|
3981
|
+
function throwForFailure(packageManager, args, outcome) {
|
|
3982
|
+
if (outcome.spawnError) {
|
|
3983
|
+
throw new WensityError("dependency_install_failed", `Could not run ${packageManager}: ${outcome.spawnError}`);
|
|
3010
3984
|
}
|
|
3011
|
-
if (
|
|
3012
|
-
|
|
3985
|
+
if (outcome.timedOut) {
|
|
3986
|
+
throw new WensityError(
|
|
3987
|
+
"dependency_install_failed",
|
|
3988
|
+
`${packageManager} ${args.join(" ")} did not finish within ${PACKAGE_MANAGER_TIMEOUT_MS / 1e3} s and was stopped.`
|
|
3989
|
+
);
|
|
3013
3990
|
}
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
return { packages: [], importLines: [], block: null };
|
|
3991
|
+
if (outcome.exitCode !== 0) {
|
|
3992
|
+
throw new WensityError("dependency_install_failed", `${packageManager} ${args.join(" ")} failed with exit code ${outcome.exitCode ?? 1}.`);
|
|
3017
3993
|
}
|
|
3018
|
-
const block = [
|
|
3019
|
-
`${FONT_CSS_START_MARKER} \u2500\u2500 */`,
|
|
3020
|
-
...importLines,
|
|
3021
|
-
FONT_CSS_END_MARKER
|
|
3022
|
-
].join("\n");
|
|
3023
|
-
return { packages, importLines, block };
|
|
3024
3994
|
}
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3995
|
+
var PACKAGE_MANAGER_TIMEOUT_MS = 18e4;
|
|
3996
|
+
var PACKAGE_NAME = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
|
|
3997
|
+
var SUBCOMMANDS = /* @__PURE__ */ new Set(["add", "install"]);
|
|
3998
|
+
var WINDOWS_UNSAFE = /["%!^&|<>()\r\n\t\u0000-\u001f\u007f]/;
|
|
3999
|
+
function assertValidPackageName(name) {
|
|
4000
|
+
if (typeof name !== "string" || name.length > 214 || !PACKAGE_NAME.test(name) || /(^|\/|^@)[-._]/.test(name)) {
|
|
4001
|
+
throw new WensityError("dependency_install_failed", `Refusing to install an invalid package name: ${JSON.stringify(String(name).slice(0, 80))}`);
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
4004
|
+
var WINDOWS_JS_ENTRIES = {
|
|
4005
|
+
npm: ["node_modules/npm/bin/npm-cli.js"],
|
|
4006
|
+
pnpm: ["node_modules/pnpm/bin/pnpm.cjs", "node_modules/pnpm/bin/pnpm.mjs"],
|
|
4007
|
+
yarn: ["node_modules/yarn/bin/yarn.js"],
|
|
4008
|
+
bun: []
|
|
4009
|
+
};
|
|
4010
|
+
function buildPackageManagerInvocation(packageManager, args, options = {}) {
|
|
4011
|
+
if (!PACKAGE_MANAGERS.includes(packageManager)) parsePackageManager(packageManager);
|
|
4012
|
+
const [subcommand, ...names] = args;
|
|
4013
|
+
if (!subcommand || !SUBCOMMANDS.has(subcommand)) {
|
|
4014
|
+
throw new WensityError("dependency_install_failed", `Refusing to run ${packageManager} with an unexpected subcommand.`);
|
|
4015
|
+
}
|
|
4016
|
+
for (const name of names) assertValidPackageName(name);
|
|
4017
|
+
const platform = options.platform ?? process.platform;
|
|
4018
|
+
const env = options.env ?? process.env;
|
|
4019
|
+
const display = [packageManager, ...args].join(" ");
|
|
4020
|
+
const isFile = options.isFile ?? defaultIsFile;
|
|
4021
|
+
if (platform !== "win32") {
|
|
4022
|
+
const isExecutable = options.isExecutable ?? defaultIsExecutable;
|
|
4023
|
+
const dirs = (env.PATH ?? "").split(posix3.delimiter).filter(Boolean);
|
|
4024
|
+
const resolved = dirs.map((dir) => posix3.join(dir, packageManager)).find(isExecutable);
|
|
4025
|
+
if (!resolved) throw notFound(packageManager);
|
|
4026
|
+
return { shape: "posix", command: resolved, args, windowsVerbatimArguments: false, display };
|
|
4027
|
+
}
|
|
4028
|
+
const pathValue = env.PATH ?? env.Path ?? Object.entries(env).find(([key]) => key.toLowerCase() === "path")?.[1] ?? "";
|
|
4029
|
+
const extensions = (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").map((ext) => ext.toLowerCase()).filter(Boolean);
|
|
4030
|
+
for (const dir of pathValue.split(win32.delimiter).filter(Boolean)) {
|
|
4031
|
+
for (const extension of extensions) {
|
|
4032
|
+
const candidate = win32.join(dir, `${packageManager}${extension}`);
|
|
4033
|
+
if (!isFile(candidate)) continue;
|
|
4034
|
+
if (extension === ".exe" || extension === ".com") {
|
|
4035
|
+
return { shape: "windows-exe", command: candidate, args, windowsVerbatimArguments: false, display };
|
|
4036
|
+
}
|
|
4037
|
+
if (extension !== ".cmd" && extension !== ".bat") continue;
|
|
4038
|
+
const entry = WINDOWS_JS_ENTRIES[packageManager].map((relative9) => win32.join(dir, relative9)).find(isFile);
|
|
4039
|
+
if (entry) {
|
|
4040
|
+
return { shape: "windows-node", command: options.nodePath ?? process.execPath, args: [entry, ...args], windowsVerbatimArguments: false, display };
|
|
4041
|
+
}
|
|
4042
|
+
const cmd = resolveCmdExe(env);
|
|
4043
|
+
return {
|
|
4044
|
+
shape: "windows-cmd",
|
|
4045
|
+
command: cmd,
|
|
4046
|
+
args: ["/d", "/s", "/c", `"${quoteWindowsCommandLine([candidate, ...args])}"`],
|
|
4047
|
+
windowsVerbatimArguments: true,
|
|
4048
|
+
display
|
|
4049
|
+
};
|
|
3040
4050
|
}
|
|
3041
|
-
const normalizedBlock2 = block.trim();
|
|
3042
|
-
const prefix2 = before2 ? `${before2}
|
|
3043
|
-
|
|
3044
|
-
` : "";
|
|
3045
|
-
const suffix2 = after2 ? `
|
|
3046
|
-
|
|
3047
|
-
${after2}` : "\n";
|
|
3048
|
-
return `${prefix2}${normalizedBlock2}${suffix2}`.replace(/\n{3,}/g, "\n\n");
|
|
3049
4051
|
}
|
|
3050
|
-
|
|
3051
|
-
const normalizedBlock = block.trim();
|
|
3052
|
-
const insertAt = findFontBlockInsertIndex(existingCss);
|
|
3053
|
-
const before = existingCss.slice(0, insertAt).replace(/\s*$/, "");
|
|
3054
|
-
const after = existingCss.slice(insertAt).replace(/^\s*/, "");
|
|
3055
|
-
const prefix = before ? `${before}
|
|
3056
|
-
|
|
3057
|
-
` : "";
|
|
3058
|
-
const suffix = after ? `
|
|
3059
|
-
|
|
3060
|
-
${after}` : "\n";
|
|
3061
|
-
return `${prefix}${normalizedBlock}${suffix}`.replace(/\n{3,}/g, "\n\n");
|
|
4052
|
+
throw notFound(packageManager);
|
|
3062
4053
|
}
|
|
3063
|
-
function
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
(pkg) => installedPackages.has(pkg) && !needed.has(pkg)
|
|
3071
|
-
);
|
|
3072
|
-
if (leftover.length === 0) return void 0;
|
|
3073
|
-
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 quoteWindowsCommandLine(values) {
|
|
4055
|
+
return values.map((value) => {
|
|
4056
|
+
if (value.length === 0 || WINDOWS_UNSAFE.test(value)) {
|
|
4057
|
+
throw new WensityError("dependency_install_failed", `Refusing to build a command line from an unsafe value: ${JSON.stringify(value.slice(0, 80))}`);
|
|
4058
|
+
}
|
|
4059
|
+
return /\s/.test(value) ? `"${value}"` : value;
|
|
4060
|
+
}).join(" ");
|
|
3074
4061
|
}
|
|
3075
|
-
function
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
4062
|
+
function resolveCmdExe(env) {
|
|
4063
|
+
const fromEnv = env.ComSpec ?? env.COMSPEC;
|
|
4064
|
+
if (fromEnv && win32.isAbsolute(fromEnv) && win32.basename(fromEnv).toLowerCase() === "cmd.exe" && !WINDOWS_UNSAFE.test(fromEnv)) {
|
|
4065
|
+
return fromEnv;
|
|
4066
|
+
}
|
|
4067
|
+
const systemRoot = env.SystemRoot ?? env.SYSTEMROOT ?? "C:\\Windows";
|
|
4068
|
+
if (!win32.isAbsolute(systemRoot) || WINDOWS_UNSAFE.test(systemRoot)) {
|
|
4069
|
+
throw new WensityError("dependency_install_failed", "Could not locate cmd.exe safely.");
|
|
4070
|
+
}
|
|
4071
|
+
return win32.join(systemRoot, "System32", "cmd.exe");
|
|
4072
|
+
}
|
|
4073
|
+
function notFound(packageManager) {
|
|
4074
|
+
return new WensityError("dependency_install_failed", `Could not run ${packageManager}: it was not found on PATH.`);
|
|
4075
|
+
}
|
|
4076
|
+
function defaultIsExecutable(path) {
|
|
4077
|
+
try {
|
|
4078
|
+
accessSync(path, fsConstants2.X_OK);
|
|
4079
|
+
return statSync3(path).isFile();
|
|
4080
|
+
} catch {
|
|
4081
|
+
return false;
|
|
4082
|
+
}
|
|
4083
|
+
}
|
|
4084
|
+
function defaultIsFile(path) {
|
|
4085
|
+
try {
|
|
4086
|
+
return statSync3(path).isFile();
|
|
4087
|
+
} catch {
|
|
4088
|
+
return false;
|
|
4089
|
+
}
|
|
4090
|
+
}
|
|
4091
|
+
var OUTPUT_KEEP = 256 * 1024;
|
|
4092
|
+
var TREE_EXIT_WAIT_MS = 5e3;
|
|
4093
|
+
function runPackageManagerCommand(projectRoot, packageManager, args, options = {}) {
|
|
4094
|
+
const invocation = buildPackageManagerInvocation(packageManager, args, options);
|
|
4095
|
+
const platform = options.platform ?? process.platform;
|
|
4096
|
+
const inherit = options.output === "inherit";
|
|
4097
|
+
const ownGroup = !inherit && platform !== "win32";
|
|
4098
|
+
const timeoutMs = options.timeoutMs ?? (inherit ? void 0 : PACKAGE_MANAGER_TIMEOUT_MS);
|
|
4099
|
+
const started = Date.now();
|
|
4100
|
+
return new Promise((resolve3) => {
|
|
4101
|
+
let stdout2 = "";
|
|
4102
|
+
let stderr = "";
|
|
4103
|
+
let timedOut = false;
|
|
4104
|
+
const child = (options.spawnImpl ?? spawn)(invocation.command, invocation.args, {
|
|
4105
|
+
cwd: projectRoot,
|
|
4106
|
+
env: options.spawnEnv ?? process.env,
|
|
4107
|
+
shell: false,
|
|
4108
|
+
stdio: inherit ? "inherit" : ["ignore", "pipe", "pipe"],
|
|
4109
|
+
windowsHide: true,
|
|
4110
|
+
windowsVerbatimArguments: invocation.windowsVerbatimArguments,
|
|
4111
|
+
detached: ownGroup
|
|
4112
|
+
});
|
|
4113
|
+
const killTree = () => killProcessTree(child.pid, platform, options.env ?? process.env, spawnSync, ownGroup);
|
|
4114
|
+
const signalHandlers = [];
|
|
4115
|
+
if (options.forwardSignals) {
|
|
4116
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
4117
|
+
const handler = () => {
|
|
4118
|
+
killTree();
|
|
4119
|
+
removeSignalHandlers();
|
|
4120
|
+
process.kill(process.pid, signal);
|
|
4121
|
+
};
|
|
4122
|
+
signalHandlers.push([signal, handler]);
|
|
4123
|
+
process.once(signal, handler);
|
|
4124
|
+
}
|
|
3086
4125
|
}
|
|
3087
|
-
|
|
3088
|
-
const
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
4126
|
+
const removeSignalHandlers = () => {
|
|
4127
|
+
for (const [signal, handler] of signalHandlers.splice(0)) process.removeListener(signal, handler);
|
|
4128
|
+
};
|
|
4129
|
+
const timer = timeoutMs === void 0 ? void 0 : setTimeout(() => {
|
|
4130
|
+
timedOut = true;
|
|
4131
|
+
killTree();
|
|
4132
|
+
}, timeoutMs);
|
|
4133
|
+
child.stdout?.on("data", (chunk) => {
|
|
4134
|
+
stdout2 = (stdout2 + chunk.toString("utf8")).slice(-OUTPUT_KEEP);
|
|
4135
|
+
});
|
|
4136
|
+
child.stderr?.on("data", (chunk) => {
|
|
4137
|
+
stderr = (stderr + chunk.toString("utf8")).slice(-OUTPUT_KEEP);
|
|
4138
|
+
});
|
|
4139
|
+
let settled = false;
|
|
4140
|
+
const finish = async (exitCode, spawnError) => {
|
|
4141
|
+
if (settled) return;
|
|
4142
|
+
settled = true;
|
|
4143
|
+
clearTimeout(timer);
|
|
4144
|
+
if (ownGroup && child.pid !== void 0 && groupAlive(child.pid)) {
|
|
4145
|
+
killTree();
|
|
4146
|
+
await waitForGroupExit(child.pid);
|
|
4147
|
+
}
|
|
4148
|
+
removeSignalHandlers();
|
|
4149
|
+
child.stdout?.destroy();
|
|
4150
|
+
child.stderr?.destroy();
|
|
4151
|
+
resolve3({ invocation, exitCode, stdout: stdout2, stderr, durationMs: Date.now() - started, timedOut, ...spawnError ? { spawnError } : {} });
|
|
4152
|
+
};
|
|
4153
|
+
child.on("error", (error) => void finish(null, error.message));
|
|
4154
|
+
child.on("exit", (code) => void finish(code));
|
|
4155
|
+
});
|
|
4156
|
+
}
|
|
4157
|
+
function killProcessTree(pid, platform, env, run2 = spawnSync, ownGroup = true) {
|
|
4158
|
+
if (pid === void 0) return;
|
|
4159
|
+
try {
|
|
4160
|
+
if (platform !== "win32" && !ownGroup) {
|
|
4161
|
+
for (const member of [...descendantsOf(pid, run2).reverse(), pid]) {
|
|
4162
|
+
try {
|
|
4163
|
+
process.kill(member, "SIGKILL");
|
|
4164
|
+
} catch {
|
|
4165
|
+
}
|
|
4166
|
+
}
|
|
4167
|
+
return;
|
|
3092
4168
|
}
|
|
3093
|
-
if (
|
|
3094
|
-
const
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
4169
|
+
if (platform === "win32") {
|
|
4170
|
+
const systemRoot = env.SystemRoot ?? env.SYSTEMROOT ?? "C:\\Windows";
|
|
4171
|
+
run2(win32.join(systemRoot, "System32", "taskkill.exe"), ["/PID", String(pid), "/T", "/F"], { shell: false, stdio: "ignore", windowsHide: true });
|
|
4172
|
+
} else {
|
|
4173
|
+
process.kill(-pid, "SIGKILL");
|
|
3098
4174
|
}
|
|
3099
|
-
|
|
4175
|
+
} catch {
|
|
3100
4176
|
}
|
|
3101
|
-
|
|
4177
|
+
}
|
|
4178
|
+
function descendantsOf(pid, run2) {
|
|
4179
|
+
const listing = run2("ps", ["-A", "-o", "pid=,ppid="], { shell: false, encoding: "utf8" });
|
|
4180
|
+
const children = /* @__PURE__ */ new Map();
|
|
4181
|
+
for (const line of String(listing.stdout ?? "").split("\n")) {
|
|
4182
|
+
const [child, parent] = line.trim().split(/\s+/).map(Number);
|
|
4183
|
+
if (!child || !parent) continue;
|
|
4184
|
+
children.set(parent, [...children.get(parent) ?? [], child]);
|
|
4185
|
+
}
|
|
4186
|
+
const found = [];
|
|
4187
|
+
const queue = [...children.get(pid) ?? []];
|
|
4188
|
+
while (queue.length > 0) {
|
|
4189
|
+
const next = queue.shift();
|
|
4190
|
+
found.push(next);
|
|
4191
|
+
queue.push(...children.get(next) ?? []);
|
|
4192
|
+
}
|
|
4193
|
+
return found;
|
|
4194
|
+
}
|
|
4195
|
+
function groupAlive(pid) {
|
|
4196
|
+
try {
|
|
4197
|
+
process.kill(-pid, 0);
|
|
4198
|
+
return true;
|
|
4199
|
+
} catch (error) {
|
|
4200
|
+
return error.code === "EPERM";
|
|
4201
|
+
}
|
|
4202
|
+
}
|
|
4203
|
+
async function waitForGroupExit(pid) {
|
|
4204
|
+
const deadline = Date.now() + TREE_EXIT_WAIT_MS;
|
|
4205
|
+
while (groupAlive(pid) && Date.now() < deadline) {
|
|
4206
|
+
await new Promise((resolve3) => setTimeout(resolve3, 20));
|
|
4207
|
+
}
|
|
4208
|
+
}
|
|
4209
|
+
function installArgs(packageManager, dependencies) {
|
|
4210
|
+
switch (packageManager) {
|
|
4211
|
+
case "pnpm":
|
|
4212
|
+
return ["add", ...dependencies];
|
|
4213
|
+
case "yarn":
|
|
4214
|
+
return ["add", ...dependencies];
|
|
4215
|
+
case "bun":
|
|
4216
|
+
return ["add", ...dependencies];
|
|
4217
|
+
case "npm":
|
|
4218
|
+
return ["install", ...dependencies];
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
4221
|
+
function writeWarning(message) {
|
|
4222
|
+
process.stderr.write(`Warning: ${message}
|
|
4223
|
+
`);
|
|
4224
|
+
}
|
|
4225
|
+
|
|
4226
|
+
// ../install-core/src/project.ts
|
|
4227
|
+
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.';
|
|
4228
|
+
function importAliasesResolve(projectRoot, config) {
|
|
4229
|
+
const mapping = readPathMapping(projectRoot);
|
|
4230
|
+
if (!mapping) return null;
|
|
4231
|
+
return aliasResolvesTo(mapping, config.aliases.components, config.components) && aliasResolvesTo(mapping, config.aliases.utils, config.utils);
|
|
4232
|
+
}
|
|
4233
|
+
function importAliasMismatchMessage(config) {
|
|
4234
|
+
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.`;
|
|
4235
|
+
}
|
|
4236
|
+
function importAliasBlockMessage(config) {
|
|
4237
|
+
return `${importAliasMismatchMessage(config)} ${IMPORT_ALIAS_REMEDY}`;
|
|
3102
4238
|
}
|
|
3103
4239
|
|
|
3104
4240
|
// src/output.ts
|
|
@@ -3272,54 +4408,334 @@ function centerLine(line) {
|
|
|
3272
4408
|
return `${" ".repeat(padding)}${line}`;
|
|
3273
4409
|
}
|
|
3274
4410
|
|
|
3275
|
-
// src/
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
4411
|
+
// src/errors.ts
|
|
4412
|
+
var PRICING_URL = "https://ui.wensity.com/pricing";
|
|
4413
|
+
var CliError = class _CliError extends Error {
|
|
4414
|
+
exitCode;
|
|
4415
|
+
/** The install-core error this one renders, when there is one. */
|
|
4416
|
+
wensityError;
|
|
4417
|
+
constructor(message, exitCode = 1, options = {}) {
|
|
4418
|
+
super(message, options.cause ? { cause: options.cause } : void 0);
|
|
4419
|
+
this.name = "CliError";
|
|
4420
|
+
this.exitCode = exitCode;
|
|
4421
|
+
if (options.cause) this.wensityError = options.cause;
|
|
4422
|
+
}
|
|
4423
|
+
/**
|
|
4424
|
+
* install-core throws WensityError wherever this code used to throw CliError,
|
|
4425
|
+
* so `instanceof CliError` keeps meaning "a user-facing CLI failure" for both.
|
|
4426
|
+
* Render one with `toCliError` to get the exit code and CLI wording.
|
|
4427
|
+
*/
|
|
4428
|
+
static [Symbol.hasInstance](value) {
|
|
4429
|
+
return isWensityError(value) || Function.prototype[Symbol.hasInstance].call(_CliError, value);
|
|
4430
|
+
}
|
|
4431
|
+
static fromWensityError(error) {
|
|
4432
|
+
return new _CliError(renderWensityErrorMessage(error), 1, { cause: error });
|
|
4433
|
+
}
|
|
4434
|
+
};
|
|
4435
|
+
function renderWensityErrorMessage(error) {
|
|
4436
|
+
if (error.code === "entitlement_required") {
|
|
4437
|
+
return `${error.message}
|
|
4438
|
+
Upgrade here: ${pricingBlue(PRICING_URL)}`;
|
|
4439
|
+
}
|
|
4440
|
+
return error.message;
|
|
4441
|
+
}
|
|
4442
|
+
function toCliError(error) {
|
|
4443
|
+
if (isWensityError(error)) return CliError.fromWensityError(error);
|
|
4444
|
+
return error instanceof CliError ? error : void 0;
|
|
4445
|
+
}
|
|
4446
|
+
|
|
4447
|
+
// src/args.ts
|
|
4448
|
+
function parseArgs(argv) {
|
|
4449
|
+
const positionals = [];
|
|
4450
|
+
const options = /* @__PURE__ */ new Map();
|
|
4451
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
4452
|
+
const token = argv[index];
|
|
4453
|
+
if (!token.startsWith("--")) {
|
|
4454
|
+
positionals.push(token);
|
|
4455
|
+
continue;
|
|
4456
|
+
}
|
|
4457
|
+
if (token === "--") {
|
|
4458
|
+
positionals.push(...argv.slice(index + 1));
|
|
4459
|
+
break;
|
|
4460
|
+
}
|
|
4461
|
+
const inlineValueIndex = token.indexOf("=");
|
|
4462
|
+
const rawName = inlineValueIndex > -1 ? token.slice(2, inlineValueIndex) : token.slice(2);
|
|
4463
|
+
const name = normalizeOptionName(rawName);
|
|
4464
|
+
if (!name) {
|
|
4465
|
+
throw new CliError(`Invalid option: ${token}`);
|
|
4466
|
+
}
|
|
4467
|
+
if (inlineValueIndex > -1) {
|
|
4468
|
+
options.set(name, token.slice(inlineValueIndex + 1));
|
|
4469
|
+
continue;
|
|
4470
|
+
}
|
|
4471
|
+
const nextToken = argv[index + 1];
|
|
4472
|
+
if (nextToken && !nextToken.startsWith("--") && expectsValue(name)) {
|
|
4473
|
+
options.set(name, nextToken);
|
|
4474
|
+
index += 1;
|
|
4475
|
+
continue;
|
|
4476
|
+
}
|
|
4477
|
+
options.set(name, true);
|
|
4478
|
+
}
|
|
4479
|
+
return { positionals, options };
|
|
4480
|
+
}
|
|
4481
|
+
function getStringOption(args, name) {
|
|
4482
|
+
const value = args.options.get(name);
|
|
4483
|
+
if (value === void 0) return void 0;
|
|
4484
|
+
if (typeof value === "boolean") {
|
|
4485
|
+
throw new CliError(`--${name} expects a value.`);
|
|
4486
|
+
}
|
|
4487
|
+
return value;
|
|
4488
|
+
}
|
|
4489
|
+
function hasFlag(args, name) {
|
|
4490
|
+
return args.options.get(name) === true;
|
|
4491
|
+
}
|
|
4492
|
+
function expectsValue(name) {
|
|
4493
|
+
return [
|
|
4494
|
+
"api-url",
|
|
4495
|
+
"registry",
|
|
4496
|
+
"token",
|
|
4497
|
+
"type",
|
|
4498
|
+
"preset",
|
|
4499
|
+
"preset-file",
|
|
4500
|
+
"template",
|
|
4501
|
+
"package-manager"
|
|
4502
|
+
].includes(name);
|
|
4503
|
+
}
|
|
4504
|
+
function normalizeOptionName(name) {
|
|
4505
|
+
if (name === "dry") return "dry-run";
|
|
4506
|
+
return name;
|
|
4507
|
+
}
|
|
4508
|
+
|
|
4509
|
+
// src/credentials.ts
|
|
4510
|
+
import { stdin, stdout } from "process";
|
|
4511
|
+
|
|
4512
|
+
// ../install-core/src/credentials.ts
|
|
4513
|
+
import { mkdir as mkdir3, readFile as readFile4, rename as rename2, rm as rm2, writeFile as writeFile2, chmod as chmod2 } from "fs/promises";
|
|
4514
|
+
import { dirname as dirname4, join as join8 } from "path";
|
|
4515
|
+
import { randomUUID } from "crypto";
|
|
4516
|
+
function credentialsPath(env) {
|
|
4517
|
+
if (env.WENSITY_CREDENTIALS_FILE) return env.WENSITY_CREDENTIALS_FILE;
|
|
4518
|
+
const configHome = env.WENSITY_CONFIG_HOME ?? env.XDG_CONFIG_HOME ?? join8(requiredHome(env), ".config");
|
|
4519
|
+
return join8(configHome, "wensity", "credentials.json");
|
|
4520
|
+
}
|
|
4521
|
+
async function readCredentials(env) {
|
|
4522
|
+
const path = credentialsPath(env);
|
|
4523
|
+
try {
|
|
4524
|
+
const content = await readFile4(path, "utf8");
|
|
4525
|
+
const parsed = JSON.parse(content);
|
|
4526
|
+
if (parsed.version !== 1 || !parsed.token || !parsed.apiBaseUrl) {
|
|
4527
|
+
throw new WensityError("auth_invalid", `Credentials at ${path} are not valid. Run wensity login again.`);
|
|
4528
|
+
}
|
|
4529
|
+
return parsed;
|
|
4530
|
+
} catch (error) {
|
|
4531
|
+
if (isNodeError(error) && error.code === "ENOENT") return void 0;
|
|
4532
|
+
if (error instanceof SyntaxError) {
|
|
4533
|
+
throw new WensityError("auth_invalid", `Credentials at ${path} are not valid JSON. Run wensity login again.`);
|
|
4534
|
+
}
|
|
4535
|
+
throw error;
|
|
4536
|
+
}
|
|
4537
|
+
}
|
|
4538
|
+
async function writeCredentials(env, credentials) {
|
|
4539
|
+
const path = credentialsPath(env);
|
|
4540
|
+
const directory = dirname4(path);
|
|
4541
|
+
const temporaryPath = join8(directory, `.credentials.${randomUUID()}.tmp`);
|
|
4542
|
+
await mkdir3(directory, { recursive: true, mode: 448 });
|
|
4543
|
+
await writeFile2(temporaryPath, `${JSON.stringify(credentials, null, 2)}
|
|
4544
|
+
`, {
|
|
4545
|
+
encoding: "utf8",
|
|
4546
|
+
mode: 384
|
|
4547
|
+
});
|
|
4548
|
+
await rename2(temporaryPath, path);
|
|
4549
|
+
await chmod2(path, 384);
|
|
4550
|
+
return path;
|
|
4551
|
+
}
|
|
4552
|
+
async function deleteCredentials(env) {
|
|
4553
|
+
const path = credentialsPath(env);
|
|
4554
|
+
try {
|
|
4555
|
+
await rm2(path, { force: false });
|
|
4556
|
+
return true;
|
|
4557
|
+
} catch (error) {
|
|
4558
|
+
if (isNodeError(error) && error.code === "ENOENT") return false;
|
|
4559
|
+
throw error;
|
|
4560
|
+
}
|
|
4561
|
+
}
|
|
4562
|
+
function requiredHome(env) {
|
|
4563
|
+
if (env.HOME) return env.HOME;
|
|
4564
|
+
throw new WensityError("config_missing", "HOME is not set, so the CLI cannot locate ~/.config/wensity.");
|
|
4565
|
+
}
|
|
4566
|
+
|
|
4567
|
+
// src/credentials.ts
|
|
4568
|
+
async function readTokenInteractively(prompt = "Wensity API token: ") {
|
|
4569
|
+
if (!stdin.isTTY) {
|
|
4570
|
+
const chunks = [];
|
|
4571
|
+
for await (const chunk of stdin) {
|
|
4572
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
4573
|
+
}
|
|
4574
|
+
return Buffer.concat(chunks).toString("utf8").trim();
|
|
4575
|
+
}
|
|
4576
|
+
return new Promise((resolveToken, rejectToken) => {
|
|
4577
|
+
const input = stdin;
|
|
4578
|
+
const previousRawMode = input.isRaw;
|
|
4579
|
+
let token = "";
|
|
4580
|
+
function cleanup() {
|
|
4581
|
+
input.off("data", onData);
|
|
4582
|
+
if (typeof previousRawMode === "boolean") {
|
|
4583
|
+
input.setRawMode(previousRawMode);
|
|
4584
|
+
}
|
|
4585
|
+
input.pause();
|
|
4586
|
+
}
|
|
4587
|
+
function onData(chunk) {
|
|
4588
|
+
const value = chunk.toString("utf8");
|
|
4589
|
+
if (value === "") {
|
|
4590
|
+
cleanup();
|
|
4591
|
+
stdout.write("\n");
|
|
4592
|
+
rejectToken(new CliError("Login cancelled.", 130));
|
|
4593
|
+
return;
|
|
4594
|
+
}
|
|
4595
|
+
if (value === "\r" || value === "\n" || value === "\r\n") {
|
|
4596
|
+
cleanup();
|
|
4597
|
+
stdout.write("\n");
|
|
4598
|
+
resolveToken(token.trim());
|
|
4599
|
+
return;
|
|
4600
|
+
}
|
|
4601
|
+
if (value === "\x7F") {
|
|
4602
|
+
token = token.slice(0, -1);
|
|
4603
|
+
return;
|
|
4604
|
+
}
|
|
4605
|
+
token += value;
|
|
4606
|
+
}
|
|
4607
|
+
stdout.write(prompt);
|
|
4608
|
+
input.setRawMode(true);
|
|
4609
|
+
input.resume();
|
|
4610
|
+
input.on("data", onData);
|
|
4611
|
+
});
|
|
4612
|
+
}
|
|
4613
|
+
|
|
4614
|
+
// ../install-core/src/font-install.ts
|
|
4615
|
+
var FONT_CSS_START_MARKER = "/* \u2500\u2500 Wensity Fonts";
|
|
4616
|
+
var FONT_CSS_END_MARKER = "/* \u2500\u2500 End Wensity Fonts \u2500\u2500 */";
|
|
4617
|
+
function planFontInstall(headingFont, bodyFont) {
|
|
4618
|
+
const packages = [];
|
|
4619
|
+
const importLines = [];
|
|
4620
|
+
const seenPackages = /* @__PURE__ */ new Set();
|
|
4621
|
+
const seenImports = /* @__PURE__ */ new Set();
|
|
4622
|
+
const pushFont = (meta) => {
|
|
4623
|
+
if (!meta) return;
|
|
4624
|
+
if (!seenPackages.has(meta.packageName)) {
|
|
4625
|
+
seenPackages.add(meta.packageName);
|
|
4626
|
+
packages.push(meta.packageName);
|
|
4627
|
+
}
|
|
4628
|
+
for (const specifier of curatedFontCssImportSpecifiers(meta)) {
|
|
4629
|
+
const line = `@import "${specifier}";`;
|
|
4630
|
+
if (!seenImports.has(line)) {
|
|
4631
|
+
seenImports.add(line);
|
|
4632
|
+
importLines.push(line);
|
|
4633
|
+
}
|
|
4634
|
+
}
|
|
4635
|
+
};
|
|
4636
|
+
if (headingFont !== "system") {
|
|
4637
|
+
pushFont(HEADING_FONT_PACKAGES[headingFont]);
|
|
3285
4638
|
}
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
response = await fetch(url, { ...options, headers });
|
|
3289
|
-
} catch (error) {
|
|
3290
|
-
const message = error instanceof Error ? error.message : "Unknown network error";
|
|
3291
|
-
throw new CliError(`Could not reach ${url}: ${message}`);
|
|
4639
|
+
if (bodyFont !== "system") {
|
|
4640
|
+
pushFont(BODY_FONT_PACKAGES[bodyFont]);
|
|
3292
4641
|
}
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
const message = formatErrorMessage(payload) ?? response.statusText;
|
|
3297
|
-
throw new CliError(`${response.status} ${message}`);
|
|
4642
|
+
packages.sort();
|
|
4643
|
+
if (importLines.length === 0) {
|
|
4644
|
+
return { packages: [], importLines: [], block: null };
|
|
3298
4645
|
}
|
|
3299
|
-
|
|
4646
|
+
const block = [
|
|
4647
|
+
`${FONT_CSS_START_MARKER} \u2500\u2500 */`,
|
|
4648
|
+
...importLines,
|
|
4649
|
+
FONT_CSS_END_MARKER
|
|
4650
|
+
].join("\n");
|
|
4651
|
+
return { packages, importLines, block };
|
|
3300
4652
|
}
|
|
3301
|
-
function
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
4653
|
+
function upsertFontImportBlock(existingCss, block) {
|
|
4654
|
+
const start = existingCss.indexOf(FONT_CSS_START_MARKER);
|
|
4655
|
+
const end = existingCss.indexOf(FONT_CSS_END_MARKER);
|
|
4656
|
+
if (start !== -1 || end !== -1) {
|
|
4657
|
+
if (start === -1 || end === -1 || end <= start) {
|
|
4658
|
+
throw new WensityError(
|
|
4659
|
+
"config_invalid",
|
|
4660
|
+
"Configured Tailwind CSS has a partial Wensity Fonts block. Remove the broken markers and re-run install."
|
|
4661
|
+
);
|
|
4662
|
+
}
|
|
4663
|
+
const before2 = existingCss.slice(0, start).replace(/\s*$/, "");
|
|
4664
|
+
const after2 = existingCss.slice(end + FONT_CSS_END_MARKER.length).replace(/^\s*/, "");
|
|
4665
|
+
if (!block) {
|
|
4666
|
+
const joined = [before2, after2].filter((part) => part.length > 0).join("\n\n");
|
|
4667
|
+
return joined ? `${joined.replace(/\n{3,}/g, "\n\n")}
|
|
4668
|
+
` : "";
|
|
4669
|
+
}
|
|
4670
|
+
const normalizedBlock2 = block.trim();
|
|
4671
|
+
const prefix2 = before2 ? `${before2}
|
|
4672
|
+
|
|
4673
|
+
` : "";
|
|
4674
|
+
const suffix2 = after2 ? `
|
|
4675
|
+
|
|
4676
|
+
${after2}` : "\n";
|
|
4677
|
+
return `${prefix2}${normalizedBlock2}${suffix2}`.replace(/\n{3,}/g, "\n\n");
|
|
3307
4678
|
}
|
|
4679
|
+
if (!block) return existingCss;
|
|
4680
|
+
const normalizedBlock = block.trim();
|
|
4681
|
+
const insertAt = findFontBlockInsertIndex(existingCss);
|
|
4682
|
+
const before = existingCss.slice(0, insertAt).replace(/\s*$/, "");
|
|
4683
|
+
const after = existingCss.slice(insertAt).replace(/^\s*/, "");
|
|
4684
|
+
const prefix = before ? `${before}
|
|
4685
|
+
|
|
4686
|
+
` : "";
|
|
4687
|
+
const suffix = after ? `
|
|
4688
|
+
|
|
4689
|
+
${after}` : "\n";
|
|
4690
|
+
return `${prefix}${normalizedBlock}${suffix}`.replace(/\n{3,}/g, "\n\n");
|
|
4691
|
+
}
|
|
4692
|
+
function warnUnusedFontPackageMessage(headingFont, bodyFont, installedPackages) {
|
|
4693
|
+
const needed = new Set(planFontInstall(headingFont, bodyFont).packages);
|
|
4694
|
+
const curated = [
|
|
4695
|
+
...Object.values(HEADING_FONT_PACKAGES).map((entry) => entry.packageName),
|
|
4696
|
+
...Object.values(BODY_FONT_PACKAGES).map((entry) => entry.packageName)
|
|
4697
|
+
];
|
|
4698
|
+
const leftover = curated.filter(
|
|
4699
|
+
(pkg) => installedPackages.has(pkg) && !needed.has(pkg)
|
|
4700
|
+
);
|
|
4701
|
+
if (leftover.length === 0) return void 0;
|
|
4702
|
+
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).`;
|
|
3308
4703
|
}
|
|
3309
|
-
function
|
|
3310
|
-
|
|
3311
|
-
const
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
4704
|
+
function findFontBlockInsertIndex(css) {
|
|
4705
|
+
let index = 0;
|
|
4706
|
+
const length = css.length;
|
|
4707
|
+
while (index < length) {
|
|
4708
|
+
while (index < length && /\s/.test(css[index])) index += 1;
|
|
4709
|
+
if (index >= length) break;
|
|
4710
|
+
if (css.startsWith("/*", index)) {
|
|
4711
|
+
const end = css.indexOf("*/", index + 2);
|
|
4712
|
+
if (end === -1) break;
|
|
4713
|
+
index = end + 2;
|
|
4714
|
+
continue;
|
|
4715
|
+
}
|
|
4716
|
+
if (css.startsWith("//", index)) {
|
|
4717
|
+
const end = css.indexOf("\n", index);
|
|
4718
|
+
if (end === -1) return length;
|
|
4719
|
+
index = end + 1;
|
|
4720
|
+
continue;
|
|
4721
|
+
}
|
|
4722
|
+
if (css.startsWith("@charset", index) || css.startsWith("@import", index)) {
|
|
4723
|
+
const end = css.indexOf(";", index);
|
|
4724
|
+
if (end === -1) break;
|
|
4725
|
+
index = end + 1;
|
|
4726
|
+
continue;
|
|
4727
|
+
}
|
|
4728
|
+
break;
|
|
3318
4729
|
}
|
|
3319
|
-
return
|
|
4730
|
+
return index;
|
|
4731
|
+
}
|
|
4732
|
+
|
|
4733
|
+
// src/http.ts
|
|
4734
|
+
function requestJson2(url, options = {}) {
|
|
4735
|
+
return requestJson(url, { userAgent: `${CLI_NAME}/${CLI_VERSION}`, ...options });
|
|
3320
4736
|
}
|
|
3321
4737
|
|
|
3322
|
-
// src/icon-transform.ts
|
|
4738
|
+
// ../install-core/src/icon-transform.ts
|
|
3323
4739
|
var UI_PRIMITIVES_CATEGORY = "UI Primitives";
|
|
3324
4740
|
var TABLER_ICONS_PACKAGE = "@tabler/icons-react";
|
|
3325
4741
|
var LUCIDE_ICONS_PACKAGE = "lucide-react";
|
|
@@ -3363,7 +4779,7 @@ function createLucideAdapter() {
|
|
|
3363
4779
|
return resolveLucideExport(tablerName);
|
|
3364
4780
|
} catch (error) {
|
|
3365
4781
|
const message = error instanceof Error ? error.message : String(error);
|
|
3366
|
-
throw new
|
|
4782
|
+
throw new WensityError("source_integrity_failed", `${context}: ${message}`);
|
|
3367
4783
|
}
|
|
3368
4784
|
},
|
|
3369
4785
|
strokePropTo: "strokeWidth"
|
|
@@ -3477,13 +4893,15 @@ function rewriteTablerImportsToTarget(content, adapter, context) {
|
|
|
3477
4893
|
for (const spec of specs) {
|
|
3478
4894
|
if (spec.isTypeOnly || typePrefix.includes("type")) {
|
|
3479
4895
|
if (spec.imported === "Icon" || isIconTransformExclusion(spec.imported)) {
|
|
3480
|
-
throw new
|
|
4896
|
+
throw new WensityError(
|
|
4897
|
+
"source_integrity_failed",
|
|
3481
4898
|
`${context}: unexpected type-only icon import "${spec.imported}" from ${TABLER_ICONS_PACKAGE}.`
|
|
3482
4899
|
);
|
|
3483
4900
|
}
|
|
3484
4901
|
}
|
|
3485
4902
|
if (isIconTransformExclusion(spec.imported)) {
|
|
3486
|
-
throw new
|
|
4903
|
+
throw new WensityError(
|
|
4904
|
+
"source_integrity_failed",
|
|
3487
4905
|
`${context}: "${spec.imported}" is excluded from the icon map (Phase 0 internal geometry). It must not appear as a library import.`
|
|
3488
4906
|
);
|
|
3489
4907
|
}
|
|
@@ -3506,12 +4924,14 @@ function rewriteTablerImportsToTarget(content, adapter, context) {
|
|
|
3506
4924
|
}
|
|
3507
4925
|
);
|
|
3508
4926
|
if (hasPackageImport(next, TABLER_ICONS_PACKAGE)) {
|
|
3509
|
-
throw new
|
|
4927
|
+
throw new WensityError(
|
|
4928
|
+
"source_integrity_failed",
|
|
3510
4929
|
`${context}: failed to rewrite all ${TABLER_ICONS_PACKAGE} imports for ${adapter.id}.`
|
|
3511
4930
|
);
|
|
3512
4931
|
}
|
|
3513
4932
|
if (/(?:import|export)\s+\*\s+as\s+\w+\s+from\s+["']@tabler\/icons-react["']/.test(content)) {
|
|
3514
|
-
throw new
|
|
4933
|
+
throw new WensityError(
|
|
4934
|
+
"source_integrity_failed",
|
|
3515
4935
|
`${context}: namespace imports from ${TABLER_ICONS_PACKAGE} are not supported. Use named imports.`
|
|
3516
4936
|
);
|
|
3517
4937
|
}
|
|
@@ -3523,7 +4943,8 @@ function assertNoUnmappedTablerImports(content, context) {
|
|
|
3523
4943
|
const specs = parseNamedImportClause(match[1] ?? "");
|
|
3524
4944
|
for (const spec of specs) {
|
|
3525
4945
|
if (isIconTransformExclusion(spec.imported)) {
|
|
3526
|
-
throw new
|
|
4946
|
+
throw new WensityError(
|
|
4947
|
+
"source_integrity_failed",
|
|
3527
4948
|
`${context}: "${spec.imported}" must not appear as a library import (Phase 0 exclusion).`
|
|
3528
4949
|
);
|
|
3529
4950
|
}
|
|
@@ -3576,108 +4997,12 @@ function rewriteStrokeProps(content, localBindings) {
|
|
|
3576
4997
|
}
|
|
3577
4998
|
|
|
3578
4999
|
// src/package-manager.ts
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
import { join as join4 } from "path";
|
|
3582
|
-
var PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
|
|
3583
|
-
function parsePackageManager(value) {
|
|
3584
|
-
if (!value) return void 0;
|
|
3585
|
-
const normalized = value.trim().toLowerCase();
|
|
3586
|
-
if (isPackageManager(normalized)) return normalized;
|
|
3587
|
-
throw new CliError(
|
|
3588
|
-
`Unsupported package manager "${value}". Use one of: ${PACKAGE_MANAGERS.join(", ")}.`
|
|
3589
|
-
);
|
|
3590
|
-
}
|
|
3591
|
-
function packageManagerFromUserAgent(env = process.env) {
|
|
3592
|
-
const agent = env.npm_config_user_agent;
|
|
3593
|
-
if (!agent) return void 0;
|
|
3594
|
-
const first = agent.trim().split(/\s+/)[0] ?? "";
|
|
3595
|
-
const name = first.split("/")[0]?.toLowerCase();
|
|
3596
|
-
return name && isPackageManager(name) ? name : void 0;
|
|
3597
|
-
}
|
|
3598
|
-
function detectPackageManager(projectRoot, env = process.env) {
|
|
3599
|
-
if (existsSync2(join4(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
3600
|
-
if (existsSync2(join4(projectRoot, "bun.lockb")) || existsSync2(join4(projectRoot, "bun.lock"))) return "bun";
|
|
3601
|
-
if (existsSync2(join4(projectRoot, "yarn.lock"))) return "yarn";
|
|
3602
|
-
if (existsSync2(join4(projectRoot, "package-lock.json"))) return "npm";
|
|
3603
|
-
return packageManagerFromUserAgent(env) ?? "npm";
|
|
3604
|
-
}
|
|
3605
|
-
function isPackageManager(value) {
|
|
3606
|
-
return PACKAGE_MANAGERS.includes(value);
|
|
3607
|
-
}
|
|
3608
|
-
function missingDependencies(projectRoot, dependencies) {
|
|
3609
|
-
const installed = readInstalledPackageNames(projectRoot);
|
|
3610
|
-
if (!installed) return dependencies;
|
|
3611
|
-
return dependencies.filter((dependency) => !installed.has(dependency));
|
|
3612
|
-
}
|
|
3613
|
-
function readInstalledPackageNames(projectRoot) {
|
|
3614
|
-
const packageJsonPath = join4(projectRoot, "package.json");
|
|
3615
|
-
if (!existsSync2(packageJsonPath)) return void 0;
|
|
3616
|
-
const packageJson2 = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
3617
|
-
return new Set(
|
|
3618
|
-
Object.keys({
|
|
3619
|
-
...packageJson2.dependencies,
|
|
3620
|
-
...packageJson2.devDependencies,
|
|
3621
|
-
...packageJson2.peerDependencies,
|
|
3622
|
-
...packageJson2.optionalDependencies
|
|
3623
|
-
})
|
|
3624
|
-
);
|
|
3625
|
-
}
|
|
3626
|
-
var YARN_CONFIG_FILE = ".yarnrc.yml";
|
|
3627
|
-
function prepareProjectPackageManager(projectRoot, options = {}) {
|
|
3628
|
-
if (resolvePackageManager(projectRoot, options) !== "yarn") return [];
|
|
3629
|
-
const configPath = join4(projectRoot, YARN_CONFIG_FILE);
|
|
3630
|
-
if (existsSync2(configPath)) return [];
|
|
3631
|
-
if (!options.dryRun) writeFileSync(configPath, "nodeLinker: node-modules\n", "utf8");
|
|
3632
|
-
return [YARN_CONFIG_FILE];
|
|
3633
|
-
}
|
|
3634
|
-
function runProjectInstall(projectRoot, options = {}) {
|
|
3635
|
-
const packageJsonPath = join4(projectRoot, "package.json");
|
|
3636
|
-
if (!existsSync2(packageJsonPath)) return;
|
|
3637
|
-
const packageManager = resolvePackageManager(projectRoot, options);
|
|
3638
|
-
runPackageManager(projectRoot, packageManager, ["install"]);
|
|
3639
|
-
}
|
|
3640
|
-
function resolvePackageManager(projectRoot, options = {}) {
|
|
3641
|
-
return options.packageManager ?? detectPackageManager(projectRoot, options.env);
|
|
3642
|
-
}
|
|
3643
|
-
function runInstall(projectRoot, dependencies, options = {}) {
|
|
3644
|
-
if (dependencies.length === 0) return;
|
|
3645
|
-
const packageJsonPath = join4(projectRoot, "package.json");
|
|
3646
|
-
if (!existsSync2(packageJsonPath)) {
|
|
3647
|
-
warn(`No package.json found. Install these dependencies manually: ${dependencies.join(" ")}`);
|
|
3648
|
-
return;
|
|
3649
|
-
}
|
|
3650
|
-
const packageManager = resolvePackageManager(projectRoot, options);
|
|
3651
|
-
runPackageManager(projectRoot, packageManager, installArgs(packageManager, dependencies));
|
|
3652
|
-
}
|
|
3653
|
-
function runPackageManager(projectRoot, packageManager, args) {
|
|
3654
|
-
const result = spawnSync(packageManager, args, {
|
|
3655
|
-
cwd: projectRoot,
|
|
3656
|
-
stdio: "inherit",
|
|
3657
|
-
shell: process.platform === "win32"
|
|
3658
|
-
});
|
|
3659
|
-
if (result.error) {
|
|
3660
|
-
throw new CliError(`Could not run ${packageManager}: ${result.error.message}`);
|
|
3661
|
-
}
|
|
3662
|
-
if (result.status !== 0) {
|
|
3663
|
-
throw new CliError(`${packageManager} ${args.join(" ")} failed with exit code ${result.status ?? 1}.`);
|
|
3664
|
-
}
|
|
3665
|
-
}
|
|
3666
|
-
function installArgs(packageManager, dependencies) {
|
|
3667
|
-
switch (packageManager) {
|
|
3668
|
-
case "pnpm":
|
|
3669
|
-
return ["add", ...dependencies];
|
|
3670
|
-
case "yarn":
|
|
3671
|
-
return ["add", ...dependencies];
|
|
3672
|
-
case "bun":
|
|
3673
|
-
return ["add", ...dependencies];
|
|
3674
|
-
case "npm":
|
|
3675
|
-
return ["install", ...dependencies];
|
|
3676
|
-
}
|
|
5000
|
+
function runInstall2(projectRoot, dependencies, options = {}) {
|
|
5001
|
+
return runInstall(projectRoot, dependencies, { onWarning: warn, ...options });
|
|
3677
5002
|
}
|
|
3678
5003
|
|
|
3679
5004
|
// src/preset.ts
|
|
3680
|
-
import { readFile as
|
|
5005
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
3681
5006
|
async function resolvePresetStyle(input) {
|
|
3682
5007
|
if (input.presetCode && input.presetFile) {
|
|
3683
5008
|
throw new CliError("Use either --preset or --preset-file, not both.");
|
|
@@ -3685,7 +5010,7 @@ async function resolvePresetStyle(input) {
|
|
|
3685
5010
|
let code = input.presetCode;
|
|
3686
5011
|
if (input.presetFile) {
|
|
3687
5012
|
try {
|
|
3688
|
-
const raw = await
|
|
5013
|
+
const raw = await readFile5(input.presetFile, "utf8");
|
|
3689
5014
|
const trimmed = raw.trim();
|
|
3690
5015
|
if (trimmed.startsWith("{")) {
|
|
3691
5016
|
const parsed = JSON.parse(trimmed);
|
|
@@ -3885,18 +5210,18 @@ function formatTokenPanel(summary) {
|
|
|
3885
5210
|
}
|
|
3886
5211
|
|
|
3887
5212
|
// src/preset-init-writes.ts
|
|
3888
|
-
import { mkdir as
|
|
3889
|
-
import { dirname as
|
|
5213
|
+
import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
|
|
5214
|
+
import { dirname as dirname5 } from "path";
|
|
3890
5215
|
async function commitPresetInitWrites(input) {
|
|
3891
5216
|
let cssWritten = false;
|
|
3892
5217
|
try {
|
|
3893
5218
|
if (input.cssChanged) {
|
|
3894
|
-
await
|
|
5219
|
+
await mkdir4(dirname5(input.cssAbsolute), { recursive: true });
|
|
3895
5220
|
await writeFile3(input.cssAbsolute, input.nextCss, "utf8");
|
|
3896
5221
|
cssWritten = true;
|
|
3897
5222
|
}
|
|
3898
5223
|
if (input.writeUtility) {
|
|
3899
|
-
await
|
|
5224
|
+
await mkdir4(dirname5(input.utilityAbsolutePath), { recursive: true });
|
|
3900
5225
|
await writeFile3(input.utilityAbsolutePath, input.utilityContent, "utf8");
|
|
3901
5226
|
}
|
|
3902
5227
|
await input.commitConfig();
|
|
@@ -3912,8 +5237,8 @@ async function commitPresetInitWrites(input) {
|
|
|
3912
5237
|
}
|
|
3913
5238
|
|
|
3914
5239
|
// src/project-templates.ts
|
|
3915
|
-
import { mkdir as
|
|
3916
|
-
import { dirname as
|
|
5240
|
+
import { mkdir as mkdir5, readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
|
|
5241
|
+
import { dirname as dirname6, join as join9 } from "path";
|
|
3917
5242
|
function templateCss() {
|
|
3918
5243
|
return `@import "tailwindcss";
|
|
3919
5244
|
|
|
@@ -3937,6 +5262,8 @@ function getProjectTemplateDefinition(id) {
|
|
|
3937
5262
|
function applyTemplateToConfig(config, template) {
|
|
3938
5263
|
return {
|
|
3939
5264
|
...config,
|
|
5265
|
+
components: "src/components/wensity",
|
|
5266
|
+
utils: "src/lib/utils",
|
|
3940
5267
|
tailwind: {
|
|
3941
5268
|
...config.tailwind,
|
|
3942
5269
|
css: template.cssPath
|
|
@@ -3948,7 +5275,7 @@ async function scaffoldProjectTemplate(projectRoot, template, options) {
|
|
|
3948
5275
|
if (!options.force) {
|
|
3949
5276
|
const conflicts = [];
|
|
3950
5277
|
for (const file of template.files) {
|
|
3951
|
-
if (await fileExists(
|
|
5278
|
+
if (await fileExists(join9(projectRoot, file.path))) {
|
|
3952
5279
|
conflicts.push(file.path);
|
|
3953
5280
|
}
|
|
3954
5281
|
}
|
|
@@ -3965,10 +5292,10 @@ async function scaffoldProjectTemplate(projectRoot, template, options) {
|
|
|
3965
5292
|
return planned;
|
|
3966
5293
|
}
|
|
3967
5294
|
async function writeTemplateFile(projectRoot, file) {
|
|
3968
|
-
const absolute =
|
|
3969
|
-
await
|
|
5295
|
+
const absolute = join9(projectRoot, file.path);
|
|
5296
|
+
await mkdir5(dirname6(absolute), { recursive: true });
|
|
3970
5297
|
if (file.path === "package.json" && await fileExists(absolute)) {
|
|
3971
|
-
const existing = JSON.parse(await
|
|
5298
|
+
const existing = JSON.parse(await readFile6(absolute, "utf8"));
|
|
3972
5299
|
const next = JSON.parse(file.content);
|
|
3973
5300
|
await writeFile4(
|
|
3974
5301
|
absolute,
|
|
@@ -4291,7 +5618,7 @@ function formatTemplateFiles(files) {
|
|
|
4291
5618
|
return files.map(toPosixPath);
|
|
4292
5619
|
}
|
|
4293
5620
|
|
|
4294
|
-
// src/style-css.ts
|
|
5621
|
+
// ../install-core/src/style-css.ts
|
|
4295
5622
|
var ROOT_SELECTOR = ":root";
|
|
4296
5623
|
var DARK_SELECTOR = ".dark";
|
|
4297
5624
|
var SHARED_TOKEN_KEYS = ["radius", "fontSans", "fontDisplay", "fontMono"];
|
|
@@ -4375,11 +5702,11 @@ function escapeRegExp(value) {
|
|
|
4375
5702
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4376
5703
|
}
|
|
4377
5704
|
|
|
4378
|
-
// src/component-install.ts
|
|
4379
|
-
import { mkdir as
|
|
4380
|
-
import { dirname as
|
|
5705
|
+
// ../install-core/src/component-install.ts
|
|
5706
|
+
import { mkdir as mkdir6, readFile as readFile7, writeFile as writeFile5 } from "fs/promises";
|
|
5707
|
+
import { dirname as dirname7, join as join10, relative as relative7 } from "path";
|
|
4381
5708
|
|
|
4382
|
-
// src/templates.ts
|
|
5709
|
+
// ../install-core/src/templates.ts
|
|
4383
5710
|
function cnUtilityTemplate() {
|
|
4384
5711
|
return `import { clsx, type ClassValue } from "clsx";
|
|
4385
5712
|
import { twMerge } from "tailwind-merge";
|
|
@@ -4532,7 +5859,13 @@ export function WensitySecondaryButton({
|
|
|
4532
5859
|
`;
|
|
4533
5860
|
}
|
|
4534
5861
|
|
|
4535
|
-
// src/component-install.ts
|
|
5862
|
+
// ../install-core/src/component-install.ts
|
|
5863
|
+
function safeTarget(projectRoot, relativePath) {
|
|
5864
|
+
assertTargetPathLength(relativePath);
|
|
5865
|
+
const absolutePath = resolveInside(projectRoot, relativePath);
|
|
5866
|
+
assertNoSymlinkEscape(projectRoot, absolutePath);
|
|
5867
|
+
return absolutePath;
|
|
5868
|
+
}
|
|
4536
5869
|
var ALWAYS_PRESENT_PACKAGES = /* @__PURE__ */ new Set(["react", "react-dom", "next"]);
|
|
4537
5870
|
var OVERLAY_HELPER_PATH = "components/wensity/overlay.tsx";
|
|
4538
5871
|
var PRIMITIVE_CSS_START_MARKER = "/* \u2500\u2500 Wensity Primitive Tokens";
|
|
@@ -4554,7 +5887,7 @@ async function prepareInstall(projectRoot, config, payload, options = {}) {
|
|
|
4554
5887
|
for (const [index, payloadFile] of payload.files.entries()) {
|
|
4555
5888
|
const payloadFileKind = getPayloadFileKind(payloadFile);
|
|
4556
5889
|
const relativeOutputPath = getPayloadOutputPath(payloadFile, fallbackFileName, config);
|
|
4557
|
-
const absoluteOutputPath =
|
|
5890
|
+
const absoluteOutputPath = safeTarget(projectRoot, relativeOutputPath);
|
|
4558
5891
|
if (!payloadHelperNeeds) mergeHelperNeeds(inferredHelperNeeds, inferHelperNeeds(payloadFile.content));
|
|
4559
5892
|
const transformed = transformComponentSource(payloadFile.content, config, {
|
|
4560
5893
|
isPrimitiveCategory,
|
|
@@ -4577,9 +5910,9 @@ async function prepareInstall(projectRoot, config, payload, options = {}) {
|
|
|
4577
5910
|
const helperNeeds = payloadHelperNeeds ?? inferredHelperNeeds;
|
|
4578
5911
|
if (!hasGraphHelperFiles && helperNeeds.utils) {
|
|
4579
5912
|
const utilityPath = withSourceExtension(config.utils, "ts");
|
|
4580
|
-
if (!await fileExists(
|
|
5913
|
+
if (!await fileExists(safeTarget(projectRoot, utilityPath))) {
|
|
4581
5914
|
files.push({
|
|
4582
|
-
absolutePath:
|
|
5915
|
+
absolutePath: safeTarget(projectRoot, utilityPath),
|
|
4583
5916
|
relativePath: utilityPath,
|
|
4584
5917
|
content: cnUtilityTemplate(),
|
|
4585
5918
|
kind: "helper"
|
|
@@ -4587,11 +5920,11 @@ async function prepareInstall(projectRoot, config, payload, options = {}) {
|
|
|
4587
5920
|
}
|
|
4588
5921
|
}
|
|
4589
5922
|
if (!hasGraphHelperFiles && helperNeeds.primitives) {
|
|
4590
|
-
const primitivesPath = toPosixPath(
|
|
4591
|
-
if (!await fileExists(
|
|
5923
|
+
const primitivesPath = toPosixPath(join10(config.components, "primitives.tsx"));
|
|
5924
|
+
if (!await fileExists(safeTarget(projectRoot, primitivesPath))) {
|
|
4592
5925
|
const primitives = primitivesTemplate(config.aliases.utils);
|
|
4593
5926
|
files.push({
|
|
4594
|
-
absolutePath:
|
|
5927
|
+
absolutePath: safeTarget(projectRoot, primitivesPath),
|
|
4595
5928
|
relativePath: primitivesPath,
|
|
4596
5929
|
content: primitives,
|
|
4597
5930
|
kind: "helper"
|
|
@@ -4627,8 +5960,8 @@ async function planPrimitiveCssFile(projectRoot, config, payload) {
|
|
|
4627
5960
|
primitiveCss = applyStyleToPrimitiveCss(primitiveCss, style).trim();
|
|
4628
5961
|
const cssRelative = config.tailwind?.css;
|
|
4629
5962
|
if (!cssRelative) return void 0;
|
|
4630
|
-
const absolutePath =
|
|
4631
|
-
const existing = await fileExists(absolutePath) ? await
|
|
5963
|
+
const absolutePath = safeTarget(projectRoot, cssRelative);
|
|
5964
|
+
const existing = await fileExists(absolutePath) ? await readFile7(absolutePath, "utf8") : "";
|
|
4632
5965
|
const withBase = upsertBaseCssBlock(existing, buildBaseCssBlock(baseColorFromStyle(style)));
|
|
4633
5966
|
const withTokens = upsertPrimitiveCssBlock(withBase, primitiveCss);
|
|
4634
5967
|
const fonts = curatedFontsFromStyle(style);
|
|
@@ -4667,7 +6000,8 @@ function upsertDelimitedBlock(existingCss, block, options) {
|
|
|
4667
6000
|
return `${before}${normalizedBlock}${after}`.replace(/\n{3,}/g, "\n\n");
|
|
4668
6001
|
}
|
|
4669
6002
|
if (start !== -1 || end !== -1) {
|
|
4670
|
-
throw new
|
|
6003
|
+
throw new WensityError(
|
|
6004
|
+
"config_invalid",
|
|
4671
6005
|
`Configured Tailwind CSS has a partial ${options.label} block. Remove the broken markers and re-run install.`
|
|
4672
6006
|
);
|
|
4673
6007
|
}
|
|
@@ -4697,18 +6031,12 @@ async function assertNoConflicts(files, force) {
|
|
|
4697
6031
|
if (await fileExists(file.absolutePath)) conflicts.push(file.relativePath);
|
|
4698
6032
|
}
|
|
4699
6033
|
if (conflicts.length > 0) {
|
|
4700
|
-
throw new
|
|
6034
|
+
throw new WensityError("file_conflict", `Refusing to overwrite existing files. Use --force to replace:
|
|
4701
6035
|
${conflicts.map((path) => ` - ${path}`).join("\n")}`);
|
|
4702
6036
|
}
|
|
4703
6037
|
}
|
|
4704
|
-
async function writePlannedFiles(files) {
|
|
4705
|
-
for (const file of files) {
|
|
4706
|
-
await mkdir5(dirname6(file.absolutePath), { recursive: true });
|
|
4707
|
-
await writeFile5(file.absolutePath, file.content, "utf8");
|
|
4708
|
-
}
|
|
4709
|
-
}
|
|
4710
6038
|
function summarizeFiles(projectRoot, files) {
|
|
4711
|
-
return files.map((file) => toPosixPath(
|
|
6039
|
+
return files.map((file) => toPosixPath(relative7(projectRoot, file.absolutePath)));
|
|
4712
6040
|
}
|
|
4713
6041
|
function transformComponentSource(content, config, iconOptions) {
|
|
4714
6042
|
let transformed = content;
|
|
@@ -4729,13 +6057,13 @@ function getPayloadFileKind(payloadFile) {
|
|
|
4729
6057
|
}
|
|
4730
6058
|
function getPayloadOutputPath(payloadFile, fallbackFileName, config) {
|
|
4731
6059
|
if (isUtilsHelperFile(payloadFile)) return withSourceExtension(config.utils, "ts");
|
|
4732
|
-
if (isOverlayHelperFile(payloadFile)) return toPosixPath(
|
|
4733
|
-
if (isPrimitivesHelperFile(payloadFile)) return toPosixPath(
|
|
6060
|
+
if (isOverlayHelperFile(payloadFile)) return toPosixPath(join10(config.components, "overlay.tsx"));
|
|
6061
|
+
if (isPrimitivesHelperFile(payloadFile)) return toPosixPath(join10(config.components, "primitives.tsx"));
|
|
4734
6062
|
if (payloadFile.kind === "helper") {
|
|
4735
|
-
throw new
|
|
6063
|
+
throw new WensityError("path_unsafe", `Unsupported registry helper file path: ${payloadFile.path}`);
|
|
4736
6064
|
}
|
|
4737
6065
|
const safePayloadPath = sanitizePayloadPath(payloadFile.path, fallbackFileName);
|
|
4738
|
-
return toPosixPath(
|
|
6066
|
+
return toPosixPath(join10(config.components, safePayloadPath));
|
|
4739
6067
|
}
|
|
4740
6068
|
function isGraphHelperFile(payloadFile) {
|
|
4741
6069
|
return isUtilsHelperFile(payloadFile) || isOverlayHelperFile(payloadFile) || isPrimitivesHelperFile(payloadFile);
|
|
@@ -4809,7 +6137,7 @@ function collectDependencies(input) {
|
|
|
4809
6137
|
function parseImports(source) {
|
|
4810
6138
|
const imports = [];
|
|
4811
6139
|
const patterns = [
|
|
4812
|
-
/(?:import|export)\s+(?:type\s+)?(?:[
|
|
6140
|
+
/(?:import|export)\s+(?:type\s+)?(?:[^;'"`]*?\s+from\s*)?["']([^"']+)["']/g,
|
|
4813
6141
|
/import\(\s*["']([^"']+)["']\s*\)/g
|
|
4814
6142
|
];
|
|
4815
6143
|
for (const pattern of patterns) {
|
|
@@ -4832,7 +6160,7 @@ function normalizePackageName(specifier) {
|
|
|
4832
6160
|
function withSourceExtension(path, extension) {
|
|
4833
6161
|
const directory = parentDirectory(path);
|
|
4834
6162
|
const fileName = extensionlessPath(path.split("/").at(-1) ?? path);
|
|
4835
|
-
return toPosixPath(
|
|
6163
|
+
return toPosixPath(join10(directory === "." ? "" : directory, `${fileName}.${extension}`));
|
|
4836
6164
|
}
|
|
4837
6165
|
|
|
4838
6166
|
// src/commands.ts
|
|
@@ -4925,6 +6253,7 @@ async function initCommand(context, parsed) {
|
|
|
4925
6253
|
const hasPreset = Boolean(presetCode || presetFile);
|
|
4926
6254
|
const dependencies = ["clsx", "tailwind-merge"];
|
|
4927
6255
|
const installOptions = installOptionsFrom(context, parsed);
|
|
6256
|
+
assertProjectRootCandidate(context.cwd, context.env.HOME);
|
|
4928
6257
|
if (hasPreset) {
|
|
4929
6258
|
await initWithPreset(context, {
|
|
4930
6259
|
dryRun,
|
|
@@ -4942,7 +6271,7 @@ async function initCommand(context, parsed) {
|
|
|
4942
6271
|
const templateFiles = template ? await scaffoldProjectTemplate(context.cwd, template, { dryRun, force }) : [];
|
|
4943
6272
|
const packageManagerFiles = template ? prepareProjectPackageManager(context.cwd, { ...installOptions, dryRun }) : [];
|
|
4944
6273
|
const config = template ? applyTemplateToConfig(createDefaultConfig(context.cwd, apiUrl), template) : createDefaultConfig(context.cwd, apiUrl);
|
|
4945
|
-
const configPath =
|
|
6274
|
+
const configPath = join11(context.cwd, CONFIG_FILE_NAME);
|
|
4946
6275
|
const utilityPath = resolveConfiguredFilePath(context.cwd, config.utils);
|
|
4947
6276
|
const utilityRelativePath = toPosixPath(config.utils.endsWith(".ts") ? config.utils : `${config.utils}.ts`);
|
|
4948
6277
|
if (dryRun) {
|
|
@@ -4963,8 +6292,8 @@ async function initCommand(context, parsed) {
|
|
|
4963
6292
|
await writeTextFile(utilityPath, cnUtilityTemplate());
|
|
4964
6293
|
}
|
|
4965
6294
|
if (install) {
|
|
4966
|
-
if (template) runProjectInstall(context.cwd, installOptions);
|
|
4967
|
-
|
|
6295
|
+
if (template) await runProjectInstall(context.cwd, installOptions);
|
|
6296
|
+
await runInstall2(context.cwd, missingDependencies(context.cwd, dependencies), installOptions);
|
|
4968
6297
|
}
|
|
4969
6298
|
await assertInitPostconditions(context.cwd, [
|
|
4970
6299
|
...templateFiles,
|
|
@@ -4992,7 +6321,7 @@ async function initWithPreset(context, options) {
|
|
|
4992
6321
|
presetCode: options.presetCode,
|
|
4993
6322
|
presetFile: options.presetFile
|
|
4994
6323
|
});
|
|
4995
|
-
const configPath =
|
|
6324
|
+
const configPath = join11(context.cwd, CONFIG_FILE_NAME);
|
|
4996
6325
|
const configExists = await fileExists(configPath);
|
|
4997
6326
|
const templateFiles = options.template ? await scaffoldProjectTemplate(context.cwd, options.template, {
|
|
4998
6327
|
dryRun: options.dryRun,
|
|
@@ -5007,7 +6336,7 @@ async function initWithPreset(context, options) {
|
|
|
5007
6336
|
if (options.template) {
|
|
5008
6337
|
throw new CliError(`${CONFIG_FILE_NAME} already exists. Omit --template for existing projects or use --force.`);
|
|
5009
6338
|
}
|
|
5010
|
-
const loaded = await loadConfig(context.cwd, { apiUrl: options.apiUrl });
|
|
6339
|
+
const loaded = await loadConfig(context.cwd, { apiUrl: options.apiUrl }, { home: context.env.HOME });
|
|
5011
6340
|
config = {
|
|
5012
6341
|
...loaded.config,
|
|
5013
6342
|
style
|
|
@@ -5061,8 +6390,8 @@ async function initWithPreset(context, options) {
|
|
|
5061
6390
|
}
|
|
5062
6391
|
});
|
|
5063
6392
|
if (options.install) {
|
|
5064
|
-
if (options.template) runProjectInstall(context.cwd, options.installOptions);
|
|
5065
|
-
|
|
6393
|
+
if (options.template) await runProjectInstall(context.cwd, options.installOptions);
|
|
6394
|
+
await runInstall2(
|
|
5066
6395
|
context.cwd,
|
|
5067
6396
|
missingDependencies(context.cwd, installDependencies),
|
|
5068
6397
|
options.installOptions
|
|
@@ -5111,7 +6440,7 @@ async function applyCommand(context, parsed) {
|
|
|
5111
6440
|
if (!presetCodeInput && !presetFile) {
|
|
5112
6441
|
throw new CliError("Missing preset. Pass --preset <code> or --preset-file <path>.");
|
|
5113
6442
|
}
|
|
5114
|
-
const loaded = await loadConfig(context.cwd, { registry: registryOverride, apiUrl });
|
|
6443
|
+
const loaded = await loadConfig(context.cwd, { registry: registryOverride, apiUrl }, { home: context.env.HOME });
|
|
5115
6444
|
if (!loaded.exists) {
|
|
5116
6445
|
throw new CliError(`${CONFIG_FILE_NAME} not found. Run "wensity init" first, then "wensity apply --preset <code>".`);
|
|
5117
6446
|
}
|
|
@@ -5159,7 +6488,7 @@ async function applyCommand(context, parsed) {
|
|
|
5159
6488
|
}
|
|
5160
6489
|
});
|
|
5161
6490
|
if (install && installDependencies.length > 0) {
|
|
5162
|
-
|
|
6491
|
+
await runInstall2(
|
|
5163
6492
|
loaded.projectRoot,
|
|
5164
6493
|
missingDependencies(loaded.projectRoot, installDependencies),
|
|
5165
6494
|
installOptionsFrom(context, parsed)
|
|
@@ -5212,7 +6541,7 @@ async function preparePresetStyleApplication(projectRoot, config, style, operati
|
|
|
5212
6541
|
throw new CliError("No Tailwind CSS path configured. Set tailwind.css in wensity.json.");
|
|
5213
6542
|
}
|
|
5214
6543
|
const cssAbsolute = resolveInside(projectRoot, cssRelative);
|
|
5215
|
-
const existingCss = await fileExists(cssAbsolute) ? await
|
|
6544
|
+
const existingCss = await fileExists(cssAbsolute) ? await readFile8(cssAbsolute, "utf8") : "";
|
|
5216
6545
|
const withBase = upsertBaseCssBlock(existingCss, buildBaseCssBlock(baseColorFromStyle(style)));
|
|
5217
6546
|
const withTokens = upsertPrimitiveCssBlock(withBase, primitiveCss);
|
|
5218
6547
|
const nextCss = upsertFontImportBlock(withTokens, fontPlan.block);
|
|
@@ -5237,7 +6566,7 @@ async function loginCommand(context, parsed) {
|
|
|
5237
6566
|
if (!token) {
|
|
5238
6567
|
throw new CliError("A token is required. Create one in the Wensity dashboard and run wensity login again.");
|
|
5239
6568
|
}
|
|
5240
|
-
const whoami = await
|
|
6569
|
+
const whoami = await requestJson2(joinUrl(apiBaseUrl, "/api/cli/whoami"), { token });
|
|
5241
6570
|
const path = await writeCredentials(context.env, {
|
|
5242
6571
|
version: 1,
|
|
5243
6572
|
apiBaseUrl,
|
|
@@ -5266,7 +6595,7 @@ async function logoutCommand(context) {
|
|
|
5266
6595
|
}
|
|
5267
6596
|
async function whoamiCommand(context, parsed) {
|
|
5268
6597
|
const credentials = await requireCredentials(context.env);
|
|
5269
|
-
const whoami = await
|
|
6598
|
+
const whoami = await requestJson2(joinUrl(credentials.apiBaseUrl, "/api/cli/whoami"), {
|
|
5270
6599
|
token: credentials.token
|
|
5271
6600
|
});
|
|
5272
6601
|
if (hasFlag(parsed, "json")) {
|
|
@@ -5280,10 +6609,15 @@ async function listCommand(context, parsed) {
|
|
|
5280
6609
|
const resourceType = getResourceType(parsed);
|
|
5281
6610
|
const apiUrl = getStringOption(parsed, "api-url") ?? context.env.WENSITY_API_URL;
|
|
5282
6611
|
const registryOverride = getStringOption(parsed, "registry");
|
|
5283
|
-
const
|
|
5284
|
-
|
|
6612
|
+
const configuredRegistry = await loadConfig(context.cwd, { registry: registryOverride, apiUrl }, { home: context.env.HOME }).then((loaded) => loaded.config.registry).catch((error) => {
|
|
6613
|
+
if (isWensityError(error) && error.code === "project_ambiguous") {
|
|
6614
|
+
return registryOverride ?? createDefaultConfig(context.cwd, apiUrl).registry;
|
|
6615
|
+
}
|
|
6616
|
+
throw error;
|
|
6617
|
+
});
|
|
6618
|
+
const registry = registryUrlForType(configuredRegistry, resourceType, registryOverride);
|
|
5285
6619
|
if (resourceType === "block") {
|
|
5286
|
-
const response2 = await
|
|
6620
|
+
const response2 = await requestJson2(registry);
|
|
5287
6621
|
if (hasFlag(parsed, "json")) {
|
|
5288
6622
|
printJson(response2);
|
|
5289
6623
|
return;
|
|
@@ -5302,7 +6636,7 @@ async function listCommand(context, parsed) {
|
|
|
5302
6636
|
]));
|
|
5303
6637
|
return;
|
|
5304
6638
|
}
|
|
5305
|
-
const response = await
|
|
6639
|
+
const response = await requestJson2(registry);
|
|
5306
6640
|
if (hasFlag(parsed, "json")) {
|
|
5307
6641
|
printJson(response);
|
|
5308
6642
|
return;
|
|
@@ -5354,7 +6688,10 @@ ${usage}`);
|
|
|
5354
6688
|
}
|
|
5355
6689
|
const apiUrl = getStringOption(parsed, "api-url") ?? context.env.WENSITY_API_URL;
|
|
5356
6690
|
const registryOverride = getStringOption(parsed, "registry");
|
|
5357
|
-
const loaded = await loadConfig(context.cwd, { registry: registryOverride, apiUrl });
|
|
6691
|
+
const loaded = await loadConfig(context.cwd, { registry: registryOverride, apiUrl }, { home: context.env.HOME });
|
|
6692
|
+
if (importAliasesResolve(loaded.projectRoot, loaded.config) === false) {
|
|
6693
|
+
throw new CliError(importAliasBlockMessage(loaded.config));
|
|
6694
|
+
}
|
|
5358
6695
|
const credentials = await readCredentials(context.env);
|
|
5359
6696
|
const registry = registryUrlForType(loaded.config.registry, resourceType, registryOverride);
|
|
5360
6697
|
const force = updateMode || hasFlag(parsed, "force");
|
|
@@ -5405,13 +6742,12 @@ ${usage}`);
|
|
|
5405
6742
|
if (unusedFontWarning) warn(unusedFontWarning);
|
|
5406
6743
|
return;
|
|
5407
6744
|
}
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
}
|
|
6745
|
+
await writeWithRollback(
|
|
6746
|
+
loaded.projectRoot,
|
|
6747
|
+
installPlans.flatMap((prepared) => prepared.cssFile ? [...prepared.files, prepared.cssFile] : prepared.files)
|
|
6748
|
+
);
|
|
5413
6749
|
if (install) {
|
|
5414
|
-
|
|
6750
|
+
await runInstall2(loaded.projectRoot, missing, installOptionsFrom(context, parsed));
|
|
5415
6751
|
}
|
|
5416
6752
|
const noun = payloads.length === 1 ? "Component" : "Components";
|
|
5417
6753
|
printTitle(updateMode ? `${noun} updated` : `${noun} installed`);
|
|
@@ -5436,61 +6772,95 @@ ${usage}`);
|
|
|
5436
6772
|
resourceType === "block" ? "Customize the block copy, colors, and sections" : "Customize props and styling for your app"
|
|
5437
6773
|
]));
|
|
5438
6774
|
}
|
|
6775
|
+
async function writeWithRollback(projectRoot, files) {
|
|
6776
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
6777
|
+
for (const file of files) {
|
|
6778
|
+
byPath.delete(file.absolutePath);
|
|
6779
|
+
byPath.set(file.absolutePath, file);
|
|
6780
|
+
}
|
|
6781
|
+
const result = await apply({
|
|
6782
|
+
projectRoot,
|
|
6783
|
+
planId: `cli_${randomBytes(8).toString("hex")}`,
|
|
6784
|
+
files: [...byPath.values()].map((file) => ({
|
|
6785
|
+
relativePath: toPosixPath(relative8(projectRoot, file.absolutePath)),
|
|
6786
|
+
content: file.content,
|
|
6787
|
+
action: existsSync5(file.absolutePath) ? file.kind === "config" ? "modify" : "overwrite" : "create"
|
|
6788
|
+
}))
|
|
6789
|
+
});
|
|
6790
|
+
if (result.status === "committed") {
|
|
6791
|
+
if (result.cleanupWarning) warn(result.cleanupWarning);
|
|
6792
|
+
return;
|
|
6793
|
+
}
|
|
6794
|
+
const where = result.failedPath ? ` while writing ${result.failedPath}` : "";
|
|
6795
|
+
if (result.status === "rolled_back") {
|
|
6796
|
+
throw new WensityError(
|
|
6797
|
+
result.error.code === "write_failed" ? "write_failed" : result.error.code,
|
|
6798
|
+
`Installation failed${where}; all project-file changes were reverted.
|
|
6799
|
+
${result.error.message}` + (result.cleanupWarning ? `
|
|
6800
|
+
${result.cleanupWarning}` : ""),
|
|
6801
|
+
{ cause: result.error }
|
|
6802
|
+
);
|
|
6803
|
+
}
|
|
6804
|
+
throw new WensityError(
|
|
6805
|
+
"rollback_failed",
|
|
6806
|
+
[
|
|
6807
|
+
`Installation failed${where}, and some files could not be restored. Nothing was deleted: the backups are in ${result.journalPath}.`,
|
|
6808
|
+
...result.unrecovered.map((item) => ` - ${item.path}: ${item.recovery}`),
|
|
6809
|
+
"Verify with git diff before removing that directory yourself."
|
|
6810
|
+
].join("\n"),
|
|
6811
|
+
{ cause: result.error }
|
|
6812
|
+
);
|
|
6813
|
+
}
|
|
5439
6814
|
function dedupe(values) {
|
|
5440
6815
|
return [...new Set(values)];
|
|
5441
6816
|
}
|
|
5442
6817
|
async function fetchComponentPayload(input) {
|
|
6818
|
+
const client = registryClient(input.registry, input.credentials);
|
|
5443
6819
|
const metadata = await withSpinner(
|
|
5444
6820
|
`Finding ${input.slug}`,
|
|
5445
|
-
() =>
|
|
6821
|
+
() => client.fetchItemMetadata(input.slug, "component"),
|
|
5446
6822
|
`Found ${input.slug}`
|
|
5447
6823
|
);
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
`Downloaded ${metadata.name}`
|
|
5457
|
-
);
|
|
5458
|
-
} catch (error) {
|
|
5459
|
-
if (metadata.access === "pro" || !isAuthFailure(error)) throw error;
|
|
5460
|
-
warn("Saved Wensity credentials were rejected; falling back to the public free registry payload.");
|
|
5461
|
-
}
|
|
5462
|
-
}
|
|
5463
|
-
if (metadata.access === "pro") {
|
|
5464
|
-
throw new CliError(formatProRequiredError(metadata.name, "component"));
|
|
6824
|
+
try {
|
|
6825
|
+
return await client.fetchSourcePayload(input.slug, "component", {
|
|
6826
|
+
metadata,
|
|
6827
|
+
download: (task) => withSpinner(`Downloading ${metadata.name}`, task, `Downloaded ${metadata.name}`),
|
|
6828
|
+
onCredentialsRejected: () => warn("Saved Wensity credentials were rejected; falling back to the public free registry payload.")
|
|
6829
|
+
});
|
|
6830
|
+
} catch (error) {
|
|
6831
|
+
throw proRequiredError(error, metadata.name, "component");
|
|
5465
6832
|
}
|
|
5466
|
-
const publicSourceEndpoint = metadata.endpoints?.registry ?? registrySlugUrl(input.registry, input.slug);
|
|
5467
|
-
const endpoint = publicSourceEndpoint.startsWith("http") ? publicSourceEndpoint : joinUrl(registryOrigin(input.registry), publicSourceEndpoint);
|
|
5468
|
-
return withSpinner(
|
|
5469
|
-
`Downloading ${metadata.name}`,
|
|
5470
|
-
() => requestJson(endpoint),
|
|
5471
|
-
`Downloaded ${metadata.name}`
|
|
5472
|
-
);
|
|
5473
|
-
}
|
|
5474
|
-
function isAuthFailure(error) {
|
|
5475
|
-
return error instanceof CliError && /^(401|403)\b/.test(error.message);
|
|
5476
6833
|
}
|
|
5477
6834
|
async function fetchBlockPayload(input) {
|
|
6835
|
+
const client = registryClient(input.registry, input.credentials);
|
|
5478
6836
|
const metadata = await withSpinner(
|
|
5479
6837
|
`Finding ${input.slug}`,
|
|
5480
|
-
() =>
|
|
6838
|
+
() => client.fetchItemMetadata(input.slug, "block"),
|
|
5481
6839
|
`Found ${input.slug}`
|
|
5482
6840
|
);
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
6841
|
+
try {
|
|
6842
|
+
return await client.fetchSourcePayload(input.slug, "block", {
|
|
6843
|
+
metadata,
|
|
6844
|
+
download: (task) => withSpinner(`Downloading ${metadata.name}`, task, `Downloaded ${metadata.name}`)
|
|
6845
|
+
});
|
|
6846
|
+
} catch (error) {
|
|
6847
|
+
throw proRequiredError(error, metadata.name, "block");
|
|
6848
|
+
}
|
|
6849
|
+
}
|
|
6850
|
+
function registryClient(registry, credentials) {
|
|
6851
|
+
return createWensityClient({
|
|
6852
|
+
origin: registryOrigin(registry),
|
|
6853
|
+
registryUrl: registry,
|
|
6854
|
+
blocksRegistryUrl: registry,
|
|
6855
|
+
...credentials ? { token: credentials.token, apiBaseUrl: credentials.apiBaseUrl } : {},
|
|
6856
|
+
userAgent: `${CLI_NAME}/${CLI_VERSION}`
|
|
6857
|
+
});
|
|
6858
|
+
}
|
|
6859
|
+
function proRequiredError(error, resourceName, resourceType) {
|
|
6860
|
+
if (isWensityError(error) && error.code === "auth_required") {
|
|
6861
|
+
return new CliError(formatProRequiredError(resourceName, resourceType), 1, { cause: error });
|
|
6862
|
+
}
|
|
6863
|
+
return error;
|
|
5494
6864
|
}
|
|
5495
6865
|
function getResourceType(parsed) {
|
|
5496
6866
|
const value = getStringOption(parsed, "type") ?? "component";
|
|
@@ -5509,18 +6879,6 @@ async function requireCredentials(env) {
|
|
|
5509
6879
|
}
|
|
5510
6880
|
return credentials;
|
|
5511
6881
|
}
|
|
5512
|
-
function registryMetadataUrl(registry, slug) {
|
|
5513
|
-
const url = new URL(registry);
|
|
5514
|
-
url.searchParams.set("slug", slug);
|
|
5515
|
-
return url.toString();
|
|
5516
|
-
}
|
|
5517
|
-
function registrySlugUrl(registry, slug) {
|
|
5518
|
-
return joinUrl(registry.endsWith("/") ? registry : `${registry}/`, slug);
|
|
5519
|
-
}
|
|
5520
|
-
function registryOrigin(registry) {
|
|
5521
|
-
const url = new URL(registry);
|
|
5522
|
-
return `${url.protocol}//${url.host}`;
|
|
5523
|
-
}
|
|
5524
6882
|
function formatHelp() {
|
|
5525
6883
|
return [
|
|
5526
6884
|
brandBanner("Own your component source."),
|
|
@@ -5548,7 +6906,7 @@ function formatAccountPanel(whoami, savedPath) {
|
|
|
5548
6906
|
return panel(isPro ? "Wensity Pro" : "Wensity Free", lines);
|
|
5549
6907
|
}
|
|
5550
6908
|
async function writeTextFile(path, content) {
|
|
5551
|
-
await
|
|
6909
|
+
await mkdir7(dirname8(path), { recursive: true });
|
|
5552
6910
|
await writeFile6(path, content, "utf8");
|
|
5553
6911
|
}
|
|
5554
6912
|
function installOptionsFrom(context, parsed) {
|
|
@@ -5560,7 +6918,7 @@ function installOptionsFrom(context, parsed) {
|
|
|
5560
6918
|
async function assertInitPostconditions(projectRoot, relativePaths) {
|
|
5561
6919
|
const missing = [];
|
|
5562
6920
|
for (const relativePath of new Set(relativePaths)) {
|
|
5563
|
-
const absolute =
|
|
6921
|
+
const absolute = join11(projectRoot, relativePath);
|
|
5564
6922
|
let size = 0;
|
|
5565
6923
|
try {
|
|
5566
6924
|
size = (await stat(absolute)).size;
|
|
@@ -5584,10 +6942,11 @@ run({
|
|
|
5584
6942
|
cwd: process.cwd(),
|
|
5585
6943
|
env: process.env
|
|
5586
6944
|
}).catch((error) => {
|
|
5587
|
-
|
|
5588
|
-
|
|
6945
|
+
const cliError = toCliError(error);
|
|
6946
|
+
if (cliError) {
|
|
6947
|
+
process.stderr.write(`${formatCliError(cliError.message)}
|
|
5589
6948
|
`);
|
|
5590
|
-
process.exitCode =
|
|
6949
|
+
process.exitCode = cliError.exitCode;
|
|
5591
6950
|
return;
|
|
5592
6951
|
}
|
|
5593
6952
|
const message = error instanceof Error ? error.stack ?? error.message : String(error);
|