@moku-labs/web 1.12.3 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import { t as __exportAll } from "./chunk-D7D4PA-g.mjs";
2
2
  import { n as relativeDataFile, t as dataSuffix } from "./convention-Dp650o3y.mjs";
3
- import { createCoreConfig, createCorePlugin } from "@moku-labs/core";
3
+ import { browserEnv, cloudflareBindings, dotenv, envPlugin, envPlugin as envPlugin$1, logPlugin, logPlugin as logPlugin$1, processEnv } from "@moku-labs/common";
4
+ import { createCoreConfig } from "@moku-labs/core";
4
5
  import { appendFileSync, existsSync, readFileSync, readdirSync, realpathSync, statSync, watch } from "node:fs";
5
6
  import { createHash, randomUUID } from "node:crypto";
6
7
  import { cp, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
@@ -12,6 +13,7 @@ import { renderToString } from "preact-render-to-string";
12
13
  import { execFileSync } from "node:child_process";
13
14
  import { createInterface } from "node:readline";
14
15
  import { fileURLToPath, fileURLToPath as urlToPath } from "node:url";
16
+ import { CLEAR_BELOW, CLEAR_LINE, box, cursorUp, makePalette, spinnerFrameAt, supportsColor, supportsTruecolor, visibleWidth } from "@moku-labs/common/cli";
15
17
  import minproc from "node:process";
16
18
  import matter from "gray-matter";
17
19
  import rehypeShiki from "@shikijs/rehype";
@@ -28,831 +30,6 @@ import { visit } from "unist-util-visit";
28
30
  import { jsx } from "preact/jsx-runtime";
29
31
  import { defaultSchema } from "hast-util-sanitize";
30
32
  import readingTime from "reading-time";
31
- //#region src/plugins/env/api.ts
32
- /** Error prefix for all env API failures. */
33
- const ERROR_PREFIX$16 = "[web]";
34
- /**
35
- * Creates the env plugin API surface mounted at `ctx.env`. Closes over
36
- * `ctx.state` ({@link EnvState}) and reads the frozen `resolved` / `publicMap`
37
- * maps; closures never return a raw `ctx.state` reference.
38
- *
39
- * @param ctx - Core plugin context carrying the frozen env state.
40
- * @param ctx.state - The resolved + public {@link EnvState} maps.
41
- * @returns The {@link EnvApi} accessor surface mounted at `ctx.env`.
42
- * @example
43
- * ```ts
44
- * const api = createEnvApi(ctx);
45
- * api.get("PUBLIC_API_URL");
46
- * ```
47
- */
48
- function createEnvApi(ctx) {
49
- const { resolved, publicMap } = ctx.state;
50
- return {
51
- /**
52
- * Reads a resolved variable.
53
- *
54
- * @param key - Variable name.
55
- * @returns The value, or `undefined` if not present.
56
- * @example
57
- * ```ts
58
- * api.get("PUBLIC_API_URL");
59
- * ```
60
- */
61
- get(key) {
62
- return resolved.get(key);
63
- },
64
- /**
65
- * Reads a variable that must exist.
66
- *
67
- * @param key - Variable name.
68
- * @returns The value.
69
- * @throws {Error} If the variable is undefined.
70
- * @example
71
- * ```ts
72
- * api.require("DEPLOY_TOKEN");
73
- * ```
74
- */
75
- require(key) {
76
- const value = resolved.get(key);
77
- if (value === void 0) throw new Error(`${ERROR_PREFIX$16} env: required variable "${key}" is not defined.`);
78
- return value;
79
- },
80
- /**
81
- * Tests presence of a resolved variable.
82
- *
83
- * @param key - Variable name.
84
- * @returns `true` if a value is present.
85
- * @example
86
- * ```ts
87
- * api.has("PUBLIC_API_URL");
88
- * ```
89
- */
90
- has(key) {
91
- return resolved.has(key);
92
- },
93
- /**
94
- * Returns all public variables as a frozen plain object — a fresh copy,
95
- * never the raw state map.
96
- *
97
- * @returns A frozen `Record` of public variable names to values.
98
- * @example
99
- * ```ts
100
- * const payload = { ...api.getPublic() };
101
- * ```
102
- */
103
- getPublic() {
104
- return Object.freeze(Object.fromEntries(publicMap));
105
- },
106
- /**
107
- * Returns the already-frozen map of public variables.
108
- *
109
- * @returns The frozen public map.
110
- * @example
111
- * ```ts
112
- * [...api.getPublicMap()];
113
- * ```
114
- */
115
- getPublicMap() {
116
- return publicMap;
117
- }
118
- };
119
- }
120
- //#endregion
121
- //#region src/plugins/env/state.ts
122
- /**
123
- * Creates initial env plugin state: two empty, mutable maps that are populated
124
- * and frozen by `validateSchema` (the `onInit`) at `createApp` time.
125
- *
126
- * @returns A fresh `EnvState` with empty `resolved` and `publicMap` maps.
127
- * @example
128
- * ```ts
129
- * const state = createEnvState();
130
- * state.resolved.size; // 0
131
- * ```
132
- */
133
- function createEnvState() {
134
- return {
135
- resolved: /* @__PURE__ */ new Map(),
136
- publicMap: /* @__PURE__ */ new Map()
137
- };
138
- }
139
- //#endregion
140
- //#region src/plugins/env/validate.ts
141
- /** Error message thrown by every frozen-map mutator. */
142
- const FROZEN_MESSAGE = "env: map is frozen and cannot be mutated";
143
- /** Error prefix for all resolution-pipeline failures. */
144
- const ERROR_PREFIX$15 = "[web]";
145
- /** The `Map` mutators redefined as throwers when a map is frozen. */
146
- const FROZEN_METHODS = [
147
- "set",
148
- "clear",
149
- "delete"
150
- ];
151
- /**
152
- * Throws the canonical frozen-map error; installed as a map's `set`/`clear`/`delete`.
153
- *
154
- * @throws {TypeError} Always, signalling the map is frozen.
155
- * @example
156
- * ```ts
157
- * frozenThrower(); // throws TypeError
158
- * ```
159
- */
160
- function frozenThrower() {
161
- throw new TypeError(FROZEN_MESSAGE);
162
- }
163
- /**
164
- * Coerces a raw provider value to its effective presence: an empty string counts
165
- * as "absent" so a `KEY=""` falls through to later providers.
166
- *
167
- * @param raw - The raw value a provider supplied for a key (possibly `undefined`).
168
- * @returns The value, or `undefined` when it is missing or an empty string.
169
- * @example
170
- * ```ts
171
- * coerceEmpty(""); // => undefined
172
- * coerceEmpty("3000"); // => "3000"
173
- * ```
174
- */
175
- function coerceEmpty(raw) {
176
- return raw === "" ? void 0 : raw;
177
- }
178
- /**
179
- * Merges providers in array order, coercing empty strings to `undefined` before
180
- * precedence so a `KEY=""` falls through to later providers. First non-empty
181
- * value wins.
182
- *
183
- * @param config - The resolved env config carrying the ordered providers.
184
- * @returns A flat record of the first defined value found per key.
185
- * @example
186
- * ```ts
187
- * mergeProviders({ providers: [a, b], schema: {}, publicPrefix: "PUBLIC_" });
188
- * ```
189
- */
190
- function mergeProviders$1(config) {
191
- const merged = {};
192
- for (const provider of config.providers) for (const [key, raw] of Object.entries(provider.load())) {
193
- const value = coerceEmpty(raw);
194
- if (value !== void 0 && merged[key] === void 0) merged[key] = value;
195
- }
196
- return merged;
197
- }
198
- /**
199
- * Bidirectionally enforces the `PUBLIC_` naming convention against each schema
200
- * entry's `public` flag. Throws on either violation direction.
201
- *
202
- * @param config - The resolved env config carrying `schema` + `publicPrefix`.
203
- * @throws {Error} If a public var lacks the prefix, or a prefixed var is not public.
204
- * @example
205
- * ```ts
206
- * crossCheckPublicPrefix(config); // throws if PUBLIC_X is not public:true
207
- * ```
208
- */
209
- function crossCheckPublicPrefix(config) {
210
- const { schema, publicPrefix } = config;
211
- for (const [key, spec] of Object.entries(schema)) {
212
- const hasPrefix = key.startsWith(publicPrefix);
213
- if (spec.public === true && !hasPrefix) throw new Error(`${ERROR_PREFIX$15} env: "${key}" is marked public but does not start with "${publicPrefix}".`);
214
- if (hasPrefix && spec.public !== true) throw new Error(`${ERROR_PREFIX$15} env: "${key}" starts with "${publicPrefix}" but is not marked public:true.`);
215
- }
216
- }
217
- /**
218
- * Seals a map so `set`, `clear`, and `delete` throw, then `Object.freeze`s it
219
- * for defense in depth. Closes the `Object.freeze`-on-`Map` mutability hole by
220
- * redefining the mutators as non-writable, non-configurable throwers.
221
- *
222
- * @param map - The map to freeze in place.
223
- * @example
224
- * ```ts
225
- * freezeMap(state.resolved); // resolved.set(...) now throws
226
- * ```
227
- */
228
- function freezeMap(map) {
229
- for (const method of FROZEN_METHODS) Object.defineProperty(map, method, {
230
- value: frozenThrower,
231
- writable: false,
232
- configurable: false,
233
- enumerable: false
234
- });
235
- Object.freeze(map);
236
- }
237
- /**
238
- * Populates `state.publicMap` with the schema-driven public subset: every
239
- * `public:true` schema key that resolved to a defined value. This map is the only
240
- * sanctioned input to a browser-facing `define`, so it stays schema-scoped (never
241
- * includes non-schema provider keys).
242
- *
243
- * @param schema - The per-variable schema from {@link EnvConfig}.
244
- * @param merged - The merged provider values keyed by variable name.
245
- * @param publicMap - The mutable public map to fill in place.
246
- * @example
247
- * ```ts
248
- * populatePublicMap(config.schema, merged, state.publicMap);
249
- * ```
250
- */
251
- function populatePublicMap(schema, merged, publicMap) {
252
- for (const [key, spec] of Object.entries(schema)) {
253
- const value = merged[key];
254
- if (spec.public === true && value !== void 0) publicMap.set(key, value);
255
- }
256
- }
257
- /**
258
- * Populates `state.resolved` with EVERY merged key that carries a defined value
259
- * (spec/02 Lifecycle §5), including non-schema provider keys so
260
- * `ctx.env.require()` works for dynamic keys.
261
- *
262
- * @param merged - The merged provider values keyed by variable name.
263
- * @param resolved - The mutable resolved map to fill in place.
264
- * @example
265
- * ```ts
266
- * populateResolved(merged, state.resolved);
267
- * ```
268
- */
269
- function populateResolved(merged, resolved) {
270
- for (const [key, value] of Object.entries(merged)) resolved.set(key, value);
271
- }
272
- /**
273
- * Resolves, validates, and freezes the environment table at `onInit`.
274
- *
275
- * Pipeline order: merge providers (with empty-string → undefined coercion) →
276
- * `PUBLIC_` bidirectional cross-check → apply defaults → assert required →
277
- * populate `state.resolved` / `state.publicMap` → freeze both via
278
- * {@link freezeMap}. Fail-fast: any violation throws at `createApp` time.
279
- *
280
- * @param ctx - Core plugin context (`{ config, state }`).
281
- * @param ctx.config - The resolved {@link EnvConfig}.
282
- * @param ctx.state - The mutable {@link EnvState} to populate and freeze.
283
- * @throws {Error} On a `PUBLIC_` cross-check violation or a missing required variable.
284
- * @example
285
- * ```ts
286
- * validateSchema(ctx); // throws on missing required / PUBLIC_ violation
287
- * ```
288
- */
289
- function validateSchema(ctx) {
290
- const { config, state } = ctx;
291
- const { schema } = config;
292
- const merged = mergeProviders$1(config);
293
- crossCheckPublicPrefix(config);
294
- for (const [key, spec] of Object.entries(schema)) {
295
- if (merged[key] === void 0 && spec.default !== void 0) merged[key] = spec.default;
296
- if (merged[key] === void 0 && spec.required === true) throw new Error(`${ERROR_PREFIX$15} env: required variable "${key}" is not defined by any provider or default.`);
297
- }
298
- populatePublicMap(schema, merged, state.publicMap);
299
- populateResolved(merged, state.resolved);
300
- freezeMap(state.resolved);
301
- freezeMap(state.publicMap);
302
- }
303
- //#endregion
304
- //#region src/plugins/env/providers.browser.ts
305
- /** Default `globalThis` property holding a runtime-injected public-env snapshot. */
306
- const DEFAULT_GLOBAL_KEY = "__ENV__";
307
- /**
308
- * A browser-safe {@link EnvProvider} that reads `import.meta.env` and an optional
309
- * `globalThis[globalKey]` snapshot, merging them with the runtime global winning.
310
- * Contains zero `node:*` imports, so it is safe to include in the client bundle.
311
- * Never throws on missing sources — each absent source resolves to `{}`.
312
- *
313
- * @param options - Optional settings.
314
- * @param options.globalKey - `globalThis` key to read a public-env snapshot from. Defaults to `"__ENV__"`.
315
- * @returns An {@link EnvProvider} named `browser-env`.
316
- * @example
317
- * ```ts
318
- * const provider = browserEnv();
319
- * provider.load(); // { PUBLIC_API_URL: "/api", ... }
320
- * ```
321
- */
322
- function browserEnv(options) {
323
- const globalKey = options?.globalKey ?? DEFAULT_GLOBAL_KEY;
324
- return {
325
- name: "browser-env",
326
- /**
327
- * Merges `import.meta.env` with `globalThis[globalKey]`, the runtime global
328
- * winning. Each absent source resolves to `{}`; never throws.
329
- *
330
- * @returns The merged environment record.
331
- * @example
332
- * ```ts
333
- * browserEnv().load();
334
- * ```
335
- */
336
- load() {
337
- const importEnv = import.meta.env ?? {};
338
- const globalObject = globalThis[globalKey] ?? {};
339
- return {
340
- ...importEnv,
341
- ...globalObject
342
- };
343
- }
344
- };
345
- }
346
- /**
347
- * Core plugin that resolves, validates, and freezes the environment at `onInit`,
348
- * exposing a read-only accessor at `ctx.env`. No `onStart`/`onStop` — holds no resource.
349
- *
350
- * @example
351
- * ```ts
352
- * createApp({ pluginConfigs: { env: { schema: { PUBLIC_API_URL: { public: true } } } } });
353
- * ```
354
- */
355
- const envPlugin = createCorePlugin("env", {
356
- config: {
357
- schema: {},
358
- providers: [],
359
- publicPrefix: "PUBLIC_"
360
- },
361
- createState: createEnvState,
362
- api: createEnvApi,
363
- onInit: validateSchema
364
- });
365
- //#endregion
366
- //#region src/plugins/log/expect.ts
367
- /**
368
- * Named error thrown by `expect()` assertions when a trace condition fails.
369
- *
370
- * @example
371
- * ```ts
372
- * throw new LogExpectAssertionError("missing event build:complete");
373
- * ```
374
- */
375
- var LogExpectAssertionError = class extends Error {
376
- /**
377
- * Construct a new assertion error with a descriptive failure message.
378
- *
379
- * @param message - Descriptive failure message (event name, partial, index).
380
- * @example
381
- * ```ts
382
- * throw new LogExpectAssertionError("missing event build:complete");
383
- * ```
384
- */
385
- constructor(message) {
386
- super(message);
387
- this.name = "LogExpectAssertionError";
388
- }
389
- };
390
- /**
391
- * Tests whether a value is a non-null, non-array plain object.
392
- *
393
- * @param value - The value to test.
394
- * @returns `true` when `value` is a non-null object that is not an array.
395
- * @example
396
- * ```ts
397
- * isPlainObject({ a: 1 }); // true
398
- * isPlainObject([1]); // false
399
- * ```
400
- */
401
- function isPlainObject$1(value) {
402
- return typeof value === "object" && value !== null && !Array.isArray(value);
403
- }
404
- /**
405
- * Tests whether `actual` is an array that recursively matches every element of
406
- * the `partial` array (element-wise, with equal length).
407
- *
408
- * @param actual - The value to test against (must be an array of equal length).
409
- * @param partial - The expected partial array shape.
410
- * @returns `true` when `actual` is an equal-length array matching `partial` element-wise.
411
- * @example
412
- * ```ts
413
- * matchesPartialArray([1, 2], [1, 2]); // true
414
- * matchesPartialArray([1], [1, 2]); // false (length mismatch)
415
- * ```
416
- */
417
- function matchesPartialArray(actual, partial) {
418
- if (!Array.isArray(actual) || actual.length !== partial.length) return false;
419
- return partial.every((value, index) => matchesPartial(actual[index], value));
420
- }
421
- /**
422
- * Tests whether `actual` is a plain object in which every `partial` key
423
- * recursively matches (extra `actual` keys are ignored).
424
- *
425
- * @param actual - The value to test against (must be a plain object).
426
- * @param partial - The expected partial object shape.
427
- * @returns `true` when every `partial` key exists in `actual` and matches recursively.
428
- * @example
429
- * ```ts
430
- * matchesPartialObject({ a: 1, b: 2 }, { a: 1 }); // true
431
- * matchesPartialObject({ a: 1 }, { b: 1 }); // false (missing key)
432
- * ```
433
- */
434
- function matchesPartialObject(actual, partial) {
435
- if (!isPlainObject$1(actual)) return false;
436
- return Object.keys(partial).every((key) => key in actual && matchesPartial(actual[key], partial[key]));
437
- }
438
- /**
439
- * Subset-equality matcher: is `partial` a recursive subset of `actual`?
440
- *
441
- * Fast path via `Object.is` (covers identical primitives/references and
442
- * `null`/`NaN`); primitives compare with `Object.is`; arrays match element-wise
443
- * with equal length; plain objects require every `partial` key to recursively
444
- * match (extra `actual` keys ignored).
445
- *
446
- * @param actual - The value to test against (typically `entry.data`).
447
- * @param partial - The expected partial shape.
448
- * @returns `true` when `partial` is a recursive subset of `actual`.
449
- * @example
450
- * ```ts
451
- * matchesPartial({ a: 1, b: 2 }, { a: 1 }); // true
452
- * matchesPartial([1, 2], [1]); // false (length mismatch)
453
- * ```
454
- */
455
- function matchesPartial(actual, partial) {
456
- if (Object.is(actual, partial)) return true;
457
- if (Array.isArray(partial)) return matchesPartialArray(actual, partial);
458
- if (isPlainObject$1(partial)) return matchesPartialObject(actual, partial);
459
- return false;
460
- }
461
- /**
462
- * Tests whether an entry matches `event` and (when provided) `partial`.
463
- *
464
- * @param entry - The candidate trace entry.
465
- * @param event - Required event name.
466
- * @param partial - Optional partial data shape (subset-matched against `entry.data`).
467
- * @returns `true` when the entry matches the event and optional partial.
468
- * @example
469
- * ```ts
470
- * entryMatches({ level: "info", event: "a", data: { x: 1 }, ts: 0 }, "a", { x: 1 }); // true
471
- * ```
472
- */
473
- function entryMatches(entry, event, partial) {
474
- if (entry.event !== event) return false;
475
- return partial === void 0 ? true : matchesPartial(entry.data, partial);
476
- }
477
- /**
478
- * Render a `partial` for an error message, prefixed with a space when present.
479
- *
480
- * @param partial - Optional partial data shape.
481
- * @returns A ` matching <json>` suffix, or an empty string when absent.
482
- * @example
483
- * ```ts
484
- * describePartial({ ok: true }); // ' matching {"ok":true}'
485
- * ```
486
- */
487
- function describePartial(partial) {
488
- return partial === void 0 ? "" : ` matching ${JSON.stringify(partial)}`;
489
- }
490
- /**
491
- * Find the first entry with `event` at or after `startIndex`, scanning forward.
492
- *
493
- * @param entries - The trace array to scan.
494
- * @param event - Event name to find.
495
- * @param startIndex - Index to begin scanning from (inclusive).
496
- * @returns The index of the first match, or `-1` when none exists from `startIndex` on.
497
- * @example
498
- * ```ts
499
- * findEventAtOrAfter([{ event: "a" }, { event: "b" }] as LogEntry[], "b", 0); // 1
500
- * ```
501
- */
502
- function findEventAtOrAfter(entries, event, startIndex) {
503
- for (let index = startIndex; index < entries.length; index++) if (entries[index]?.event === event) return index;
504
- return -1;
505
- }
506
- /**
507
- * Create a fluent assertion chain bound to the live `entries` array. Each method
508
- * reads `entries` at call time, so assertions reflect later logging.
509
- *
510
- * @param entries - The live trace array (read on each assertion call).
511
- * @returns A fresh {@link ExpectChain} backed by `entries`.
512
- * @example
513
- * ```ts
514
- * createExpectChain(state.entries).toHaveEvent("build:complete");
515
- * ```
516
- */
517
- function createExpectChain(entries) {
518
- const chain = {
519
- /**
520
- * Assert at least one entry has `event`, optionally matching `partial`.
521
- *
522
- * @param event - Event name to find.
523
- * @param partial - Optional partial data shape (subset-matched).
524
- * @returns The same chain for chaining.
525
- * @throws {LogExpectAssertionError} When no matching entry exists.
526
- * @example
527
- * ```ts
528
- * chain.toHaveEvent("build:phase", { status: "start" });
529
- * ```
530
- */
531
- toHaveEvent(event, partial) {
532
- if (!entries.some((entry) => entryMatches(entry, event, partial))) throw new LogExpectAssertionError(`Expected trace to contain event "${event}"${describePartial(partial)}, but none was found.`);
533
- return chain;
534
- },
535
- /**
536
- * Assert all of `events` appear in the trace in the given relative order.
537
- *
538
- * @param events - Ordered list of event names (gaps allowed).
539
- * @returns The same chain for chaining.
540
- * @throws {LogExpectAssertionError} When the ordering cannot be satisfied.
541
- * @example
542
- * ```ts
543
- * chain.toHaveEventInOrder(["build:phase", "build:complete"]);
544
- * ```
545
- */
546
- toHaveEventInOrder(events) {
547
- let cursor = 0;
548
- for (const [position, event] of events.entries()) {
549
- const matchIndex = findEventAtOrAfter(entries, event, cursor);
550
- if (matchIndex === -1) throw new LogExpectAssertionError(`Expected events in order ${JSON.stringify(events)}, but "${event}" (index ${position}) was not found at or after position ${cursor}.`);
551
- cursor = matchIndex + 1;
552
- }
553
- return chain;
554
- },
555
- /**
556
- * Assert NO entry has `event` (optionally narrowed by `partial`).
557
- *
558
- * @param event - Event name that must be absent.
559
- * @param partial - Optional partial data shape; only matching entries violate.
560
- * @returns The same chain for chaining.
561
- * @throws {LogExpectAssertionError} When a matching entry exists.
562
- * @example
563
- * ```ts
564
- * chain.toNotHaveEvent("deploy:failed");
565
- * ```
566
- */
567
- toNotHaveEvent(event, partial) {
568
- const offending = entries.findIndex((entry) => entryMatches(entry, event, partial));
569
- if (offending !== -1) throw new LogExpectAssertionError(`Expected trace to NOT contain event "${event}"${describePartial(partial)}, but found one at index ${offending}.`);
570
- return chain;
571
- }
572
- };
573
- return chain;
574
- }
575
- //#endregion
576
- //#region src/plugins/log/api.ts
577
- /**
578
- * @file log plugin — API factory.
579
- *
580
- * Builds the `LogApi` over the plugin's `{ config, state }` core context:
581
- * the leveled loggers (via a shared `append`), the frozen `trace()` snapshot,
582
- * the live `expect()` chain, `addSink`, and `reset`.
583
- */
584
- /**
585
- * Append a new entry to the trace and fan it out to every sink in order.
586
- *
587
- * @param state - The mutable log state to append to.
588
- * @param level - Severity level for the entry.
589
- * @param event - Event identifier.
590
- * @param data - Optional structured payload.
591
- * @example
592
- * ```ts
593
- * append(state, "info", "content:ready", { count: 12 });
594
- * ```
595
- */
596
- function append(state, level, event, data) {
597
- const entry = {
598
- level,
599
- event,
600
- data,
601
- ts: Date.now()
602
- };
603
- state.entries.push(entry);
604
- for (const sink of state.sinks) sink.write(entry);
605
- }
606
- /**
607
- * Tests whether a value is a non-null, non-array plain object.
608
- *
609
- * @param value - The value to test.
610
- * @returns `true` when `value` is a non-null object that is not an array.
611
- * @example
612
- * ```ts
613
- * isPlainObject({ a: 1 }); // true
614
- * isPlainObject([1]); // false
615
- * ```
616
- */
617
- function isPlainObject(value) {
618
- return typeof value === "object" && value !== null && !Array.isArray(value);
619
- }
620
- /**
621
- * Merge an `Error`'s `message`/`stack` into `data` under an `error` key. The
622
- * `error` field is always preserved; only a plain object `data` contributes its
623
- * keys. Non-plain-object `data` (arrays and primitives) is replaced by `{}` —
624
- * its original value is not retained — so the merge target is always a record.
625
- *
626
- * @param data - Original payload (any shape).
627
- * @param error - The originating error to merge.
628
- * @returns A new object carrying any plain-object keys plus the `error` field.
629
- * @example
630
- * ```ts
631
- * mergeError({ target: "cf" }, new Error("boom"));
632
- * // { target: "cf", error: { message: "boom", stack: "..." } }
633
- * ```
634
- */
635
- function mergeError(data, error) {
636
- return {
637
- ...isPlainObject(data) ? data : {},
638
- error: {
639
- message: error.message,
640
- stack: error.stack
641
- }
642
- };
643
- }
644
- /**
645
- * Create the log plugin API surface injected as `ctx.log` / `app.log`.
646
- *
647
- * @param ctx - Core plugin context (`{ config, state }`).
648
- * @returns The {@link LogApi} bound to `ctx.state`.
649
- * @example
650
- * ```ts
651
- * const log = createLogApi(ctx);
652
- * log.info("content:ready", { articleCount: 12 });
653
- * ```
654
- */
655
- function createLogApi(ctx) {
656
- const { state } = ctx;
657
- return {
658
- /**
659
- * Append an `info` entry and fan it out to every sink.
660
- *
661
- * @param event - Event identifier (convention: `domain:action`).
662
- * @param data - Optional structured payload.
663
- * @example
664
- * ```ts
665
- * log.info("content:ready", { count: 12 });
666
- * ```
667
- */
668
- info(event, data) {
669
- append(state, "info", event, data);
670
- },
671
- /**
672
- * Append a `debug` entry and fan it out to every sink.
673
- *
674
- * @param event - Event identifier (convention: `domain:action`).
675
- * @param data - Optional structured payload.
676
- * @example
677
- * ```ts
678
- * log.debug("router:match", { path: "/blog/" });
679
- * ```
680
- */
681
- debug(event, data) {
682
- append(state, "debug", event, data);
683
- },
684
- /**
685
- * Append a `warn` entry and fan it out to every sink.
686
- *
687
- * @param event - Event identifier (convention: `domain:action`).
688
- * @param data - Optional structured payload.
689
- * @example
690
- * ```ts
691
- * log.warn("build:skip", { reason: "no sitemap" });
692
- * ```
693
- */
694
- warn(event, data) {
695
- append(state, "warn", event, data);
696
- },
697
- /**
698
- * Append an `error` entry. When `error` is provided, its `message`/`stack`
699
- * are merged into `data` under an `error` key (existing keys preserved);
700
- * otherwise `data` is recorded as-is.
701
- *
702
- * @param event - Event identifier (convention: `domain:action`).
703
- * @param data - Optional structured payload.
704
- * @param error - Optional originating Error to merge into `data`.
705
- * @example
706
- * ```ts
707
- * log.error("deploy:failed", { target: "cf" }, err);
708
- * ```
709
- */
710
- error(event, data, error) {
711
- append(state, "error", event, error === void 0 ? data : mergeError(data, error));
712
- },
713
- /**
714
- * Return a frozen snapshot (fresh copy) of the entries recorded so far.
715
- *
716
- * @returns A readonly, frozen copy of the recorded entries.
717
- * @example
718
- * ```ts
719
- * const entries = log.trace();
720
- * ```
721
- */
722
- trace() {
723
- return Object.freeze([...state.entries]);
724
- },
725
- /**
726
- * Return a fluent assertion chain bound to the live entries array.
727
- *
728
- * @returns A fresh {@link ExpectChain} reading `state.entries` live.
729
- * @example
730
- * ```ts
731
- * log.expect().toHaveEvent("build:complete");
732
- * ```
733
- */
734
- expect() {
735
- return createExpectChain(state.entries);
736
- },
737
- /**
738
- * Register an additional output sink at runtime.
739
- *
740
- * @param sink - The sink to add to the fan-out list.
741
- * @example
742
- * ```ts
743
- * log.addSink({ write: (e) => stream.write(JSON.stringify(e)) });
744
- * ```
745
- */
746
- addSink(sink) {
747
- state.sinks.push(sink);
748
- },
749
- /**
750
- * Clear all recorded entries while keeping registered sinks.
751
- *
752
- * @example
753
- * ```ts
754
- * log.reset();
755
- * ```
756
- */
757
- reset() {
758
- state.entries.length = 0;
759
- }
760
- };
761
- }
762
- //#endregion
763
- //#region src/plugins/log/sinks.ts
764
- /** Severity rank for threshold comparison (higher = more severe). */
765
- const LEVEL_RANK = {
766
- debug: 10,
767
- info: 20,
768
- warn: 30,
769
- error: 40
770
- };
771
- /**
772
- * Build the console sink: routes entries by channel — `error` → `console.error`,
773
- * `warn` → `console.warn`, and `debug`/`info` → `console.log`. The full entry
774
- * object is forwarded so the console serializes its `event` and `data`. Entries
775
- * below `minLevel` are dropped (the in-memory trace still records everything).
776
- *
777
- * @param minLevel - Lowest severity to print. Defaults to `"debug"` (print all).
778
- * @returns A {@link LogSink} that writes to the matching `console` channel.
779
- * @example
780
- * ```ts
781
- * state.sinks.push(consoleSink("info")); // suppress debug spam
782
- * ```
783
- */
784
- function consoleSink(minLevel = "debug") {
785
- const threshold = LEVEL_RANK[minLevel];
786
- return {
787
- /**
788
- * Route a single entry to the console channel matching its level.
789
- *
790
- * @param entry - The entry to emit.
791
- * @example
792
- * ```ts
793
- * sink.write({ level: "warn", event: "build:skip", ts: Date.now() });
794
- * ```
795
- */
796
- write(entry) {
797
- if (LEVEL_RANK[entry.level] < threshold) return;
798
- if (entry.level === "error") console.error(entry);
799
- else if (entry.level === "warn") console.warn(entry);
800
- else console.log(entry);
801
- } };
802
- }
803
- /**
804
- * Install mode-selected default sinks at onInit. The in-memory trace is always
805
- * on (`state.entries`); the console sink is added only in dev/production. `dev`
806
- * prints everything (debug+); `production` prints `info`+ only, so the per-phase
807
- * `debug` events (build:bundle, build:pages, …) don't spam a prod build. Both
808
- * modes still record all levels in the in-memory trace.
809
- *
810
- * @param ctx - Core plugin context (`{ config, state }`).
811
- * @param ctx.config - Resolved log config (`{ mode }`).
812
- * @param ctx.state - Mutable log state (`{ entries, sinks }`).
813
- * @example
814
- * ```ts
815
- * // "dev" -> [consoleSink("debug")]; "production" -> [consoleSink("info")]; "test"/"silent" -> []
816
- * ```
817
- */
818
- function installDefaultSinks(ctx) {
819
- if (ctx.config.mode === "dev") ctx.state.sinks.push(consoleSink("debug"));
820
- else if (ctx.config.mode === "production") ctx.state.sinks.push(consoleSink("info"));
821
- }
822
- //#endregion
823
- //#region src/plugins/log/state.ts
824
- /**
825
- * Create fresh log state: an empty append-only trace and an empty sink list.
826
- * No module-level singletons — guarantees per-`createApp` isolation (two
827
- * `createApp` calls never share `entries` or `sinks`).
828
- *
829
- * @param _ctx - Core plugin context (`{ config }`); unused at construction.
830
- * @returns A fresh `LogState` with empty `entries` and `sinks` arrays.
831
- * @example
832
- * ```ts
833
- * const state = createLogState({ config: { mode: "test" } }); // { entries: [], sinks: [] }
834
- * ```
835
- */
836
- function createLogState(_ctx) {
837
- return {
838
- entries: [],
839
- sinks: []
840
- };
841
- }
842
- /**
843
- * Core logging plugin — always-on in-memory trace + `expect()` event-trace DSL.
844
- * API injected as `ctx.log` on every regular plugin and surfaced as `app.log`.
845
- * No depends / events / hooks (core plugin per spec/03 §5).
846
- *
847
- * @see README.md
848
- */
849
- const logPlugin = createCorePlugin("log", {
850
- config: { mode: "production" },
851
- createState: createLogState,
852
- api: createLogApi,
853
- onInit: installDefaultSinks
854
- });
855
- //#endregion
856
33
  //#region src/config.ts
857
34
  /**
858
35
  * @file Framework configuration — Config + Events types, core plugin registration.
@@ -874,7 +51,7 @@ const coreConfig = createCoreConfig("web", {
874
51
  stage: "production",
875
52
  mode: "hybrid"
876
53
  },
877
- plugins: [logPlugin, envPlugin],
54
+ plugins: [logPlugin$1, envPlugin$1],
878
55
  pluginConfigs: { log: { mode: "production" } }
879
56
  });
880
57
  /**
@@ -8846,312 +8023,17 @@ function networkUrl(port, source = networkInterfaces) {
8846
8023
  return ip === null ? null : `http://${ip}:${port}`;
8847
8024
  }
8848
8025
  //#endregion
8849
- //#region src/plugins/cli/render/ansi.ts
8850
- /**
8851
- * @file cli plugin — TTY/NO_COLOR-aware ANSI color + box-drawing helpers shared by
8852
- * the Panel renderer. Modeled on the legacy `scripts/_log.ts`: color and box glyphs
8853
- * are emitted only on a real TTY with `NO_COLOR` unset; otherwise plain ASCII so
8854
- * CI logs and pipes stay readable.
8855
- */
8856
- /** The ANSI escape byte (ESC, `0x1b`), built so no literal control char is in source. */
8857
- const ESC = String.fromCodePoint(27);
8858
- /** ANSI SGR codes used by the Panel renderer (each prefixed with the ESC byte). */
8859
- const ANSI = {
8860
- reset: `${ESC}[0m`,
8861
- bold: `${ESC}[1m`,
8862
- dim: `${ESC}[2m`,
8863
- red: `${ESC}[31m`,
8864
- green: `${ESC}[32m`,
8865
- yellow: `${ESC}[33m`,
8866
- blue: `${ESC}[34m`,
8867
- magenta: `${ESC}[35m`,
8868
- cyan: `${ESC}[36m`,
8869
- gray: `${ESC}[90m`
8870
- };
8871
- /**
8872
- * The Moku brand pink (`#FF1E6F`) as an RGB triple, used for 24-bit truecolor output.
8873
- * Degrades to {@link ANSI.magenta} on a 16-color TTY and to plain text off a TTY.
8874
- */
8875
- const BRAND_PINK = {
8876
- r: 255,
8877
- g: 30,
8878
- b: 111
8879
- };
8880
- /**
8881
- * Build a 24-bit (truecolor) SGR foreground escape for the given RGB triple.
8882
- *
8883
- * @param r - Red channel (0–255).
8884
- * @param g - Green channel (0–255).
8885
- * @param b - Blue channel (0–255).
8886
- * @returns The `ESC[38;2;r;g;bm` foreground sequence.
8887
- * @example
8888
- * fg24(255, 30, 111); // "\x1b[38;2;255;30;111m"
8889
- */
8890
- function fg24(r, g, b) {
8891
- return `${ESC}[38;2;${r};${g};${b}m`;
8892
- }
8893
- /** ANSI: erase the entire current line, leaving the cursor where it is. */
8894
- const CLEAR_LINE = `${ESC}[2K`;
8895
- /** ANSI: erase from the cursor to the end of the screen (drops stale trailing rows). */
8896
- const CLEAR_BELOW = `${ESC}[0J`;
8897
- /**
8898
- * Braille spinner frames for live "working…" indicators on a TTY (advance one per tick).
8899
- * Off a TTY the Panel never animates, so this is unused in plain/CI output.
8900
- */
8901
- const SPINNER_FRAMES = [
8902
- "⠋",
8903
- "⠙",
8904
- "⠹",
8905
- "⠸",
8906
- "⠼",
8907
- "⠴",
8908
- "⠦",
8909
- "⠧",
8910
- "⠇",
8911
- "⠏"
8912
- ];
8913
- /**
8914
- * The ANSI sequence to move the cursor up `n` lines (empty string for `n <= 0`). The
8915
- * Panel uses it to repaint a live block in place — move up over the previous draw, then
8916
- * rewrite each row — so progress updates a fixed region instead of scrolling new lines.
8917
- *
8918
- * @param n - Number of lines to move the cursor up.
8919
- * @returns The cursor-up escape sequence, or `""` when `n <= 0`.
8920
- * @example
8921
- * cursorUp(3); // "\x1b[3A"
8922
- */
8923
- function cursorUp(n) {
8924
- return n > 0 ? `${ESC}[${n}A` : "";
8925
- }
8926
- /** Unicode rounded box glyphs used when output is a color-capable TTY. */
8927
- const UNICODE_BOX = {
8928
- topLeft: "╭",
8929
- topRight: "╮",
8930
- bottomLeft: "╰",
8931
- bottomRight: "╯",
8932
- horizontal: "─",
8933
- vertical: "│"
8934
- };
8935
- /** ASCII box glyphs used when output is piped/CI (plain mode). */
8936
- const ASCII_BOX = {
8937
- topLeft: "+",
8938
- topRight: "+",
8939
- bottomLeft: "+",
8940
- bottomRight: "+",
8941
- horizontal: "-",
8942
- vertical: "|"
8943
- };
8944
- /**
8945
- * Matches every ANSI SGR escape sequence (used to measure visible width). Built from
8946
- * the {@link ESC} byte so no literal control character appears in the source regex.
8947
- */
8948
- const ANSI_PATTERN = new RegExp(String.raw`${ESC}\[[0-9;]*m`, "g");
8949
- /**
8950
- * Whether ANSI color/box glyphs should be emitted: a TTY stream with `NO_COLOR`
8951
- * unset. Reads `process.stdout.isTTY` and `process.env.NO_COLOR` by default so the
8952
- * renderer auto-degrades in CI and pipes, exactly like the legacy logger.
8953
- *
8954
- * @param stream - Stream to probe for `isTTY` (defaults to `process.stdout`).
8955
- * @param noColor - The `NO_COLOR` value (defaults to `process.env.NO_COLOR`).
8956
- * @returns `true` when color should be used.
8957
- * @example
8958
- * supportsColor(); // true in an interactive terminal
8959
- */
8960
- function supportsColor(stream = process.stdout, noColor = process.env.NO_COLOR) {
8961
- return stream.isTTY === true && noColor === void 0;
8962
- }
8963
- /**
8964
- * Whether the terminal advertises 24-bit (truecolor) support via `COLORTERM`, so the
8965
- * renderer may emit the exact brand pink ({@link BRAND_PINK}) instead of the 16-color
8966
- * `magenta` approximation. Always layered on top of {@link supportsColor} — truecolor
8967
- * is never used when color itself is disabled.
8968
- *
8969
- * @param colorTerm - The `COLORTERM` value (defaults to `process.env.COLORTERM`).
8970
- * @returns `true` when `COLORTERM` is `truecolor` or `24bit`.
8971
- * @example
8972
- * supportsTruecolor("truecolor"); // true
8973
- */
8974
- function supportsTruecolor(colorTerm = process.env.COLORTERM) {
8975
- return colorTerm === "truecolor" || colorTerm === "24bit";
8976
- }
8977
- /**
8978
- * The braille spinner glyph for a given elapsed time, advancing one frame per
8979
- * `frameMs`. Deriving the frame from wall-clock elapsed (rather than a tick counter)
8980
- * keeps the spinner correct even when the animation ticker is briefly starved by a
8981
- * synchronous build phase and several ticks coalesce — the glyph still reflects real
8982
- * elapsed time instead of freezing on a stale frame.
8983
- *
8984
- * @param elapsedMs - Milliseconds since the live region opened.
8985
- * @param frameMs - Milliseconds per frame (defaults to `80`).
8986
- * @returns The active spinner glyph.
8987
- * @example
8988
- * spinnerFrameAt(240); // "⠹" (the 4th frame at 80ms/frame)
8989
- */
8990
- function spinnerFrameAt(elapsedMs, frameMs = 80) {
8991
- return SPINNER_FRAMES[Math.floor(Math.max(0, elapsedMs) / frameMs) % SPINNER_FRAMES.length] ?? "⠋";
8992
- }
8993
- /**
8994
- * Select the box glyph set for the given color mode (Unicode on a TTY, ASCII off it).
8995
- *
8996
- * @param color - Whether color/Unicode output is enabled.
8997
- * @returns The matching {@link BoxGlyphs} set.
8998
- * @example
8999
- * const glyphs = boxGlyphs(supportsColor());
9000
- */
9001
- function boxGlyphs(color) {
9002
- return color ? UNICODE_BOX : ASCII_BOX;
9003
- }
9004
- /**
9005
- * The visible width of a string, ignoring any ANSI escape sequences it contains.
9006
- *
9007
- * @param text - The (possibly colorized) text to measure.
9008
- * @returns The number of visible characters.
9009
- * @example
9010
- * visibleWidth(`${ANSI.red}hi${ANSI.reset}`); // 2
9011
- */
9012
- function visibleWidth(text) {
9013
- return text.replaceAll(ANSI_PATTERN, "").length;
9014
- }
9015
- /**
9016
- * Build a {@link Palette} bound to a fixed color mode. When `color` is `false` every
9017
- * helper returns its input unchanged, so the same render code path produces plain
9018
- * output in CI/pipes.
9019
- *
9020
- * @param color - Whether color is enabled (typically `supportsColor()`).
9021
- * @param truecolor - Whether 24-bit output is enabled (typically `supportsTruecolor()`);
9022
- * only consulted by {@link Palette.pink}. Defaults to `false` (16-color magenta).
9023
- * @returns The bound color palette.
9024
- * @example
9025
- * const palette = makePalette(supportsColor(), supportsTruecolor());
9026
- * const line = palette.green("done");
9027
- */
9028
- function makePalette(color, truecolor = false) {
9029
- return {
9030
- enabled: color,
9031
- /**
9032
- * Wrap text in the given ANSI code (returns it unchanged when color is off).
9033
- *
9034
- * @param code - The ANSI SGR code to apply.
9035
- * @param text - The text to colorize.
9036
- * @returns The colorized (or unchanged) text.
9037
- * @example
9038
- * palette.paint(ANSI.green, "ok");
9039
- */
9040
- paint(code, text) {
9041
- return color ? `${code}${text}${ANSI.reset}` : text;
9042
- },
9043
- /**
9044
- * Bold the given text (no-op in plain mode).
9045
- *
9046
- * @param text - The text to embolden.
9047
- * @returns The bold (or unchanged) text.
9048
- * @example
9049
- * palette.bold("title");
9050
- */
9051
- bold(text) {
9052
- return this.paint(ANSI.bold, text);
9053
- },
9054
- /**
9055
- * Dim the given text (no-op in plain mode).
9056
- *
9057
- * @param text - The text to dim.
9058
- * @returns The dim (or unchanged) text.
9059
- * @example
9060
- * palette.dim("· 84ms");
9061
- */
9062
- dim(text) {
9063
- return this.paint(ANSI.dim, text);
9064
- },
9065
- /**
9066
- * Color the given text green (no-op in plain mode).
9067
- *
9068
- * @param text - The text to colorize.
9069
- * @returns The green (or unchanged) text.
9070
- * @example
9071
- * palette.green("✓");
9072
- */
9073
- green(text) {
9074
- return this.paint(ANSI.green, text);
9075
- },
9076
- /**
9077
- * Color the given text yellow (no-op in plain mode).
9078
- *
9079
- * @param text - The text to colorize.
9080
- * @returns The yellow (or unchanged) text.
9081
- * @example
9082
- * palette.yellow("~");
9083
- */
9084
- yellow(text) {
9085
- return this.paint(ANSI.yellow, text);
9086
- },
9087
- /**
9088
- * Color the given text red (no-op in plain mode).
9089
- *
9090
- * @param text - The text to colorize.
9091
- * @returns The red (or unchanged) text.
9092
- * @example
9093
- * palette.red("✗");
9094
- */
9095
- red(text) {
9096
- return this.paint(ANSI.red, text);
9097
- },
9098
- /**
9099
- * Color the given text cyan (no-op in plain mode).
9100
- *
9101
- * @param text - The text to colorize.
9102
- * @returns The cyan (or unchanged) text.
9103
- * @example
9104
- * palette.cyan("http://localhost:4173");
9105
- */
9106
- cyan(text) {
9107
- return this.paint(ANSI.cyan, text);
9108
- },
9109
- /**
9110
- * Color the given text the Moku brand pink: exact `#FF1E6F` (24-bit) when truecolor
9111
- * is enabled, the 16-color `magenta` approximation otherwise, unchanged in plain mode.
9112
- *
9113
- * @param text - The text to colorize.
9114
- * @returns The pink (or unchanged) text.
9115
- * @example
9116
- * palette.pink("▟▙ moku web");
9117
- */
9118
- pink(text) {
9119
- if (!color) return text;
9120
- if (truecolor) return `${fg24(BRAND_PINK.r, BRAND_PINK.g, BRAND_PINK.b)}${text}${ANSI.reset}`;
9121
- return this.paint(ANSI.magenta, text);
9122
- }
9123
- };
9124
- }
8026
+ //#region src/plugins/cli/render/panel.ts
9125
8027
  /**
9126
- * Frame a list of already-rendered content lines in a box, padding each line to the
9127
- * widest visible line (or `minInnerWidth`, whichever is larger so several boxes can be
9128
- * forced to a shared width). Uses Unicode borders when `color` is enabled and ASCII
9129
- * otherwise. Visible width ignores embedded ANSI so colored lines align.
9130
- *
9131
- * @param lines - The content lines (may contain ANSI color codes).
9132
- * @param color - Whether to use Unicode borders (and assume color-capable output).
9133
- * @param minInnerWidth - Minimum inner (content) width to pad every row to. Defaults to `0`.
9134
- * @returns The boxed lines (top border, content rows, bottom border).
9135
- * @example
9136
- * box(["Local: http://localhost:4173"], true, 62);
8028
+ * @file cli plugin the Panel renderer (the "Velocity Lockup" CLI identity). Produces
8029
+ * the `▟▙ moku web` lockup + version/runtime banner, the live phase tree with an
8030
+ * animated indeterminate build bar, the BUILD summary + throughput sparkline, the
8031
+ * server-ready rail with a persistent breathing `◍ live` idle pulse, the compact
8032
+ * rebuild line, the deploy result, and diagnostic heading/check rows. TTY/`NO_COLOR`-
8033
+ * aware via {@link makePalette} (24-bit brand pink when truecolor is available, the
8034
+ * 16-color magenta approximation otherwise, plain text off a TTY); every line is
8035
+ * written through an injectable sink so tests can capture it.
9137
8036
  */
9138
- function box(lines, color, minInnerWidth = 0) {
9139
- const glyphs = boxGlyphs(color);
9140
- const inner = Math.max(0, minInnerWidth, ...lines.map((line) => visibleWidth(line)));
9141
- const horizontal = glyphs.horizontal.repeat(inner + 2);
9142
- const top = `${glyphs.topLeft}${horizontal}${glyphs.topRight}`;
9143
- const bottom = `${glyphs.bottomLeft}${horizontal}${glyphs.bottomRight}`;
9144
- return [
9145
- top,
9146
- ...lines.map((line) => {
9147
- const pad = " ".repeat(inner - visibleWidth(line));
9148
- return `${glyphs.vertical} ${line}${pad} ${glyphs.vertical}`;
9149
- }),
9150
- bottom
9151
- ];
9152
- }
9153
- //#endregion
9154
- //#region src/plugins/cli/render/panel.ts
9155
8037
  /** Per-command label shown beside the lockup wordmark. */
9156
8038
  const COMMAND_LABEL = {
9157
8039
  build: "build",
@@ -10135,7 +9017,7 @@ function spaEvents(register) {
10135
9017
  }
10136
9018
  //#endregion
10137
9019
  //#region src/plugins/spa/types.ts
10138
- var types_exports$9 = /* @__PURE__ */ __exportAll({ COMPONENT_HOOK_NAMES: () => COMPONENT_HOOK_NAMES });
9020
+ var types_exports$7 = /* @__PURE__ */ __exportAll({ COMPONENT_HOOK_NAMES: () => COMPONENT_HOOK_NAMES });
10139
9021
  /** Allowed hook names — single source of truth for fail-fast validation. */
10140
9022
  const COMPONENT_HOOK_NAMES = [
10141
9023
  "onCreate",
@@ -11471,176 +10353,11 @@ var types_exports$3 = /* @__PURE__ */ __exportAll({});
11471
10353
  //#region src/plugins/deploy/types.ts
11472
10354
  var types_exports$4 = /* @__PURE__ */ __exportAll({});
11473
10355
  //#endregion
11474
- //#region src/plugins/env/types.ts
11475
- var types_exports$5 = /* @__PURE__ */ __exportAll({});
11476
- //#endregion
11477
10356
  //#region src/plugins/head/types.ts
11478
- var types_exports$6 = /* @__PURE__ */ __exportAll({});
11479
- //#endregion
11480
- //#region src/plugins/log/types.ts
11481
- var types_exports$7 = /* @__PURE__ */ __exportAll({});
10357
+ var types_exports$5 = /* @__PURE__ */ __exportAll({});
11482
10358
  //#endregion
11483
10359
  //#region src/plugins/router/types.ts
11484
- var types_exports$8 = /* @__PURE__ */ __exportAll({});
11485
- //#endregion
11486
- //#region src/plugins/env/providers.ts
11487
- /**
11488
- * @file env plugin — built-in providers: dotenv, processEnv, cloudflareBindings.
11489
- */
11490
- /** Default dotenv file path: optional local overrides. */
11491
- const DEFAULT_DOTENV_PATH = ".env.local";
11492
- /** Property on `globalThis` that the consumer sets per Cloudflare request. */
11493
- const CLOUDFLARE_GLOBAL = "__CLOUDFLARE_ENV__";
11494
- /** `String.indexOf` sentinel meaning "no `=` separator on this line". */
11495
- const NO_SEPARATOR = -1;
11496
- /**
11497
- * Strips a single matching pair of surrounding double or single quotes from a
11498
- * value. Leaves unquoted values (and trailing inline comments) untouched.
11499
- *
11500
- * @param value - The already-trimmed raw value.
11501
- * @returns The value with one outer quote pair removed, if present.
11502
- * @example
11503
- * ```ts
11504
- * stripQuotes('"a"'); // "a"
11505
- * stripQuotes("plain # c"); // "plain # c"
11506
- * ```
11507
- */
11508
- function stripQuotes(value) {
11509
- if (value.length < 2) return value;
11510
- const first = value[0];
11511
- const last = value.at(-1);
11512
- if ((first === "\"" || first === "'") && first === last) return value.slice(1, -1);
11513
- return value;
11514
- }
11515
- /**
11516
- * Reports whether a trimmed line carries no assignment — a blank line or a
11517
- * full-line `#` comment — and should be skipped by the parser.
11518
- *
11519
- * @param trimmed - A whitespace-trimmed line from the dotenv text.
11520
- * @returns `true` when the line is empty or a comment.
11521
- * @example
11522
- * ```ts
11523
- * isIgnoredLine(""); // true
11524
- * isIgnoredLine("# note"); // true
11525
- * isIgnoredLine("A=1"); // false
11526
- * ```
11527
- */
11528
- function isIgnoredLine(trimmed) {
11529
- return trimmed === "" || trimmed.startsWith("#");
11530
- }
11531
- /**
11532
- * Parses `.env`-style text into a flat record. Handles CRLF/LF, blank lines,
11533
- * full-line `#` comments, first-`=` splitting, key/value trimming, and a single
11534
- * outer quote pair. Does not strip trailing inline comments on unquoted values.
11535
- *
11536
- * @param text - The raw file contents.
11537
- * @returns A flat record of parsed key/value pairs.
11538
- * @example
11539
- * ```ts
11540
- * parseDotenv('A=1\nB="two"'); // { A: "1", B: "two" }
11541
- * ```
11542
- */
11543
- function parseDotenv(text) {
11544
- const out = {};
11545
- for (const line of text.split(/\r?\n/)) {
11546
- const trimmed = line.trim();
11547
- if (isIgnoredLine(trimmed)) continue;
11548
- const eq = trimmed.indexOf("=");
11549
- if (eq === NO_SEPARATOR) continue;
11550
- const key = trimmed.slice(0, eq).trim();
11551
- out[key] = stripQuotes(trimmed.slice(eq + 1).trim());
11552
- }
11553
- return out;
11554
- }
11555
- /**
11556
- * A zero-dependency `.env`-style provider that re-reads and re-parses the file
11557
- * from disk on every `load()`. Missing file resolves to `{}` (optional
11558
- * overrides). Strips a single outer quote pair; does not strip trailing inline
11559
- * comments on unquoted values.
11560
- *
11561
- * @param path - Path to the dotenv file. Defaults to `.env.local`.
11562
- * @returns An {@link EnvProvider} named `dotenv:<path>` that reads fresh per call.
11563
- * @example
11564
- * ```ts
11565
- * const provider = dotenv(".env.local");
11566
- * provider.load(); // { PUBLIC_API_URL: "/api", ... }
11567
- * ```
11568
- */
11569
- function dotenv(path = DEFAULT_DOTENV_PATH) {
11570
- return {
11571
- name: `dotenv:${path}`,
11572
- /**
11573
- * Reads and parses the dotenv file fresh from disk; `{}` if it is missing.
11574
- *
11575
- * @returns The parsed environment record, or `{}` when the file is absent.
11576
- * @example
11577
- * ```ts
11578
- * dotenv(".env.local").load();
11579
- * ```
11580
- */
11581
- load() {
11582
- if (!existsSync(path)) return {};
11583
- return parseDotenv(readFileSync(path, "utf8"));
11584
- }
11585
- };
11586
- }
11587
- /**
11588
- * A provider that returns a shallow copy of `process.env` at `load()` time.
11589
- *
11590
- * @returns An {@link EnvProvider} named `process-env`.
11591
- * @example
11592
- * ```ts
11593
- * const provider = processEnv();
11594
- * provider.load().HOME; // current process value
11595
- * ```
11596
- */
11597
- function processEnv() {
11598
- return {
11599
- name: "process-env",
11600
- /**
11601
- * Returns a shallow copy of `process.env` at call time.
11602
- *
11603
- * @returns A fresh shallow copy of `process.env`.
11604
- * @example
11605
- * ```ts
11606
- * processEnv().load();
11607
- * ```
11608
- */
11609
- load() {
11610
- return { ...process.env };
11611
- }
11612
- };
11613
- }
11614
- /**
11615
- * A provider that reads live, per-request Cloudflare bindings from
11616
- * `globalThis.__CLOUDFLARE_ENV__` at `load()` time (`?? {}` when absent). Never
11617
- * caches the binding object; the consumer owns the global's request lifecycle.
11618
- *
11619
- * @returns An {@link EnvProvider} named `cloudflare`.
11620
- * @example
11621
- * ```ts
11622
- * globalThis.__CLOUDFLARE_ENV__ = env; // set by the request handler
11623
- * const provider = cloudflareBindings();
11624
- * provider.load(); // reads the current request's bindings
11625
- * ```
11626
- */
11627
- function cloudflareBindings() {
11628
- return {
11629
- name: "cloudflare",
11630
- /**
11631
- * Reads `globalThis.__CLOUDFLARE_ENV__` fresh, never caching the bindings.
11632
- *
11633
- * @returns The current Cloudflare bindings, or `{}` when the global is unset.
11634
- * @example
11635
- * ```ts
11636
- * cloudflareBindings().load();
11637
- * ```
11638
- */
11639
- load() {
11640
- return globalThis[CLOUDFLARE_GLOBAL] ?? {};
11641
- }
11642
- };
11643
- }
10360
+ var types_exports$6 = /* @__PURE__ */ __exportAll({});
11644
10361
  //#endregion
11645
10362
  //#region node_modules/unist-util-stringify-position/lib/index.js
11646
10363
  /**
@@ -13840,4 +12557,4 @@ const createApp = core.createApp;
13840
12557
  */
13841
12558
  const createPlugin = core.createPlugin;
13842
12559
  //#endregion
13843
- export { types_exports as Build, types_exports$1 as Cli, types_exports$2 as Content, types_exports$3 as Data, types_exports$4 as Deploy, EmbedFacadeButton, types_exports$5 as Env, GalleryTrack, types_exports$6 as Head, types_exports$7 as Log, types_exports$8 as Router, types_exports$9 as Spa, browserEnv, buildArticleHead, buildPlugin, canonical, cliPlugin, cloudflareBindings, contentPlugin, createApp, createComponent, createPlugin, createUrls, dataPlugin, defineRoutes, deployPlugin, dotenv, envPlugin, feedLink, fileSystemContent, headPlugin, hreflang, i18nPlugin, jsonLd, lazyEmbed, logPlugin, meta, og, processEnv, route, routerPlugin, sitePlugin, spaPlugin, twitter };
12560
+ export { types_exports as Build, types_exports$1 as Cli, types_exports$2 as Content, types_exports$3 as Data, types_exports$4 as Deploy, EmbedFacadeButton, GalleryTrack, types_exports$5 as Head, types_exports$6 as Router, types_exports$7 as Spa, browserEnv, buildArticleHead, buildPlugin, canonical, cliPlugin, cloudflareBindings, contentPlugin, createApp, createComponent, createPlugin, createUrls, dataPlugin, defineRoutes, deployPlugin, dotenv, envPlugin, feedLink, fileSystemContent, headPlugin, hreflang, i18nPlugin, jsonLd, lazyEmbed, logPlugin, meta, og, processEnv, route, routerPlugin, sitePlugin, spaPlugin, twitter };