@bud.sh/app 0.2.0 → 0.3.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/client/assets/{index-sj6mWw9-.js → index-GPPa1XPO.js} +2 -2
- package/dist/client/assets/workspace-CznMN7F6.js +2 -0
- package/dist/client/index.html +1 -1
- package/dist/hosted/hosted.js +112 -3
- package/dist/node/index.js +95 -0
- package/dist/node-host/node-host.js +1723 -1607
- package/dist/server/assets/{branch-previews-C_TVt_HQ.js → branch-previews-BryeA_kA.js} +112 -3
- package/dist/server/assets/{listener-BVbFsnmX.js → listener-oKd5ETMj.js} +3 -3
- package/dist/server/assets/{server-DUBjmaBw.js → server-DN5uwE4k.js} +1 -1
- package/dist/server/assets/{services-Og5rcF50.js → services-Ber8opFE.js} +2 -2
- package/dist/server/capture.js +1 -1
- package/dist/server/migration.js +3 -3
- package/dist/server/runtime.js +4 -4
- package/dist/server/server.js +3 -3
- package/dist/types/assets/{config-BzzRLcL-.d.ts → config-Cuauq8xY.d.ts} +24 -1
- package/dist/types/assets/{contract-jA5pqK6h.d.ts → contract-BsCQbbxD.d.ts} +1 -1
- package/dist/types/companion.d.ts +1 -1
- package/dist/types/hosted.d.ts +2 -2
- package/dist/types/index.d.ts +1 -1
- package/dist/types/node-host.d.ts +7 -1
- package/dist/types/screen.d.ts +1 -1
- package/package.json +1 -1
- package/dist/client/assets/workspace-9KxFcfyV.js +0 -2
|
@@ -9,6 +9,7 @@ import * as Crypto from "node:crypto";
|
|
|
9
9
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
10
10
|
import { any, array, boolean, createSchema, defaulted, enum_, fail, number, object, optional, parse, parseSafe, string } from "remix/data-schema";
|
|
11
11
|
import { gunzipSync, gzipSync } from "node:zlib";
|
|
12
|
+
import { RoutePattern } from "remix/route-pattern";
|
|
12
13
|
import { staticFiles } from "remix/middleware/static";
|
|
13
14
|
import { createController, createRouter } from "remix/router";
|
|
14
15
|
import * as NodeChildProcess from "node:child_process";
|
|
@@ -18,7 +19,6 @@ import * as NFS from "node:fs";
|
|
|
18
19
|
import { readFileSync } from "node:fs";
|
|
19
20
|
import * as OS from "node:os";
|
|
20
21
|
import { tmpdir } from "node:os";
|
|
21
|
-
import { RoutePattern } from "remix/route-pattern";
|
|
22
22
|
import { clientEntry, navigate, on, ref } from "remix/component";
|
|
23
23
|
import { jsx, jsxs } from "remix/component/jsx-runtime";
|
|
24
24
|
import { createMultiMatcher } from "remix/route-pattern/match";
|
|
@@ -7646,7 +7646,7 @@ var logWithLevel$1 = (level) => (...message) => {
|
|
|
7646
7646
|
return void_$2;
|
|
7647
7647
|
});
|
|
7648
7648
|
};
|
|
7649
|
-
var colors = {
|
|
7649
|
+
var colors$1 = {
|
|
7650
7650
|
bold: "1",
|
|
7651
7651
|
red: "31",
|
|
7652
7652
|
green: "32",
|
|
@@ -7658,7 +7658,7 @@ var colors = {
|
|
|
7658
7658
|
black: "30",
|
|
7659
7659
|
bgBrightRed: "101"
|
|
7660
7660
|
};
|
|
7661
|
-
colors.gray, colors.blue, colors.green, colors.yellow, colors.red, colors.bgBrightRed, colors.black;
|
|
7661
|
+
colors$1.gray, colors$1.blue, colors$1.green, colors$1.yellow, colors$1.red, colors$1.bgBrightRed, colors$1.black;
|
|
7662
7662
|
var defaultDateFormat = (date) => `${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}:${date.getSeconds().toString().padStart(2, "0")}.${date.getMilliseconds().toString().padStart(3, "0")}`;
|
|
7663
7663
|
/** @internal */
|
|
7664
7664
|
var defaultLogger = /*#__PURE__*/ loggerMake(({ cause, date, fiber, logLevel, message }) => {
|
|
@@ -22149,658 +22149,1407 @@ function httpPreviewStorage(base, options = {}) {
|
|
|
22149
22149
|
};
|
|
22150
22150
|
}
|
|
22151
22151
|
//#endregion
|
|
22152
|
-
//#region ../
|
|
22153
|
-
var
|
|
22154
|
-
//#endregion
|
|
22155
|
-
//#region ../beads/src/lib/validation.ts
|
|
22156
|
-
/** Beads returned or was sent data of the wrong shape; `field` names where. */
|
|
22157
|
-
var BeadsValidationError = class extends Error {
|
|
22158
|
-
field;
|
|
22159
|
-
constructor(field) {
|
|
22160
|
-
super(`Invalid Beads data at ${field}.`);
|
|
22161
|
-
this.name = "BeadsValidationError";
|
|
22162
|
-
this.field = field;
|
|
22163
|
-
}
|
|
22164
|
-
};
|
|
22165
|
-
/**
|
|
22166
|
-
* Checks that a value is a JSON object.
|
|
22167
|
-
* @param value - Untrusted data.
|
|
22168
|
-
* @param field - Where the value sits, for the error.
|
|
22169
|
-
* @returns The object.
|
|
22170
|
-
*/
|
|
22171
|
-
function object$1(value, field) {
|
|
22172
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new BeadsValidationError(field);
|
|
22173
|
-
return value;
|
|
22174
|
-
}
|
|
22175
|
-
/**
|
|
22176
|
-
* Checks a string without NUL characters within a length limit.
|
|
22177
|
-
* @param value - Untrusted data.
|
|
22178
|
-
* @param field - Where the value sits, for the error.
|
|
22179
|
-
* @param max - The most characters it may have.
|
|
22180
|
-
* @returns The string.
|
|
22181
|
-
*/
|
|
22182
|
-
function text$4(value, field, max = 262144) {
|
|
22183
|
-
if (typeof value !== "string" || value.length > max || value.includes("\0")) throw new BeadsValidationError(field);
|
|
22184
|
-
return value;
|
|
22185
|
-
}
|
|
22186
|
-
/**
|
|
22187
|
-
* Checks an ID, name or commit made of letters, digits, dots, underscores and hyphens.
|
|
22188
|
-
* @param value - Untrusted data.
|
|
22189
|
-
* @param field - Where the value sits, for the error.
|
|
22190
|
-
* @returns The identifier.
|
|
22191
|
-
*/
|
|
22192
|
-
function identifier$1(value, field) {
|
|
22193
|
-
let result = text$4(value, field, 512);
|
|
22194
|
-
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(result)) throw new BeadsValidationError(field);
|
|
22195
|
-
return result;
|
|
22196
|
-
}
|
|
22197
|
-
/**
|
|
22198
|
-
* Checks an optional string.
|
|
22199
|
-
* @param value - Untrusted data.
|
|
22200
|
-
* @param field - Where the value sits, for the error.
|
|
22201
|
-
* @param max - The most characters it may have.
|
|
22202
|
-
* @returns The string, or null when it is missing.
|
|
22203
|
-
*/
|
|
22204
|
-
function nullableText(value, field, max) {
|
|
22205
|
-
return value == null ? null : text$4(value, field, max);
|
|
22206
|
-
}
|
|
22152
|
+
//#region ../project/src/lib/validate.ts
|
|
22153
|
+
var errors = /* @__PURE__ */ new WeakMap();
|
|
22207
22154
|
/**
|
|
22208
|
-
*
|
|
22209
|
-
* @param
|
|
22210
|
-
* @param
|
|
22211
|
-
* @
|
|
22212
|
-
* @returns The integer.
|
|
22155
|
+
* Validates config, throwing its first problem as the `Error` the config checks report.
|
|
22156
|
+
* @param schema - The config schema.
|
|
22157
|
+
* @param value - The project's untrusted setting.
|
|
22158
|
+
* @returns The validated, normalized setting.
|
|
22213
22159
|
*/
|
|
22214
|
-
function
|
|
22215
|
-
|
|
22216
|
-
return value;
|
|
22160
|
+
function parseConfig(schema, value) {
|
|
22161
|
+
let result = parseSafe(schema, value, { abortEarly: true });
|
|
22162
|
+
if (result.success) return result.value;
|
|
22163
|
+
let issue = result.issues[0];
|
|
22164
|
+
throw errors.get(issue) ?? new Error(issue.message);
|
|
22217
22165
|
}
|
|
22218
22166
|
/**
|
|
22219
|
-
*
|
|
22220
|
-
* @param
|
|
22221
|
-
* @param
|
|
22222
|
-
* @
|
|
22223
|
-
* @returns The integer, or null when it is missing.
|
|
22167
|
+
* Reports any failure of `schema` with one message.
|
|
22168
|
+
* @param schema - The schema to run.
|
|
22169
|
+
* @param message - The message for any of its failures.
|
|
22170
|
+
* @returns The described schema.
|
|
22224
22171
|
*/
|
|
22225
|
-
function
|
|
22226
|
-
return
|
|
22172
|
+
function described(schema, message) {
|
|
22173
|
+
return createSchema((value, context) => {
|
|
22174
|
+
let result = schema["~run"](value, context);
|
|
22175
|
+
return result.issues ? fail(message, context.path) : result;
|
|
22176
|
+
});
|
|
22227
22177
|
}
|
|
22228
22178
|
/**
|
|
22229
|
-
*
|
|
22230
|
-
* @param
|
|
22231
|
-
* @param
|
|
22232
|
-
* @returns The
|
|
22179
|
+
* Applies a rule after `schema` validates.
|
|
22180
|
+
* @param schema - The schema to run first.
|
|
22181
|
+
* @param rule - Returns a message, or undefined when the value is valid.
|
|
22182
|
+
* @returns The checked schema.
|
|
22233
22183
|
*/
|
|
22234
|
-
function
|
|
22235
|
-
|
|
22236
|
-
|
|
22184
|
+
function check(schema, rule) {
|
|
22185
|
+
return createSchema((value, context) => {
|
|
22186
|
+
let result = schema["~run"](value, context);
|
|
22187
|
+
if (result.issues) return result;
|
|
22188
|
+
let message = rule(result.value);
|
|
22189
|
+
return message === void 0 ? result : fail(message, context.path);
|
|
22190
|
+
});
|
|
22237
22191
|
}
|
|
22238
22192
|
/**
|
|
22239
|
-
*
|
|
22240
|
-
* @param
|
|
22241
|
-
* @param
|
|
22242
|
-
* @returns The
|
|
22193
|
+
* Validates with a function that throws, such as a URL or route map parser, keeping its error.
|
|
22194
|
+
* @param schema - The schema to run first.
|
|
22195
|
+
* @param parse - Turns its value into the output, or throws.
|
|
22196
|
+
* @returns The delegating schema.
|
|
22243
22197
|
*/
|
|
22244
|
-
function
|
|
22245
|
-
|
|
22246
|
-
|
|
22247
|
-
|
|
22198
|
+
function delegate(schema, parse) {
|
|
22199
|
+
return createSchema((value, context) => {
|
|
22200
|
+
let result = schema["~run"](value, context);
|
|
22201
|
+
if (result.issues) return result;
|
|
22202
|
+
try {
|
|
22203
|
+
return { value: parse(result.value) };
|
|
22204
|
+
} catch (error) {
|
|
22205
|
+
if (!(error instanceof Error)) throw error;
|
|
22206
|
+
let failure = fail(error.message, context.path);
|
|
22207
|
+
errors.set(failure.issues[0], error);
|
|
22208
|
+
return failure;
|
|
22209
|
+
}
|
|
22210
|
+
});
|
|
22248
22211
|
}
|
|
22249
22212
|
/**
|
|
22250
|
-
*
|
|
22251
|
-
* @param
|
|
22252
|
-
* @
|
|
22253
|
-
* @returns The timestamp, or null when it is missing.
|
|
22213
|
+
* Chooses a schema from the unvalidated value.
|
|
22214
|
+
* @param choose - Picks the schema for a value.
|
|
22215
|
+
* @returns The selecting schema.
|
|
22254
22216
|
*/
|
|
22255
|
-
function
|
|
22256
|
-
return value
|
|
22217
|
+
function select$1(choose) {
|
|
22218
|
+
return createSchema((value, context) => choose(value)["~run"](value, context));
|
|
22257
22219
|
}
|
|
22258
22220
|
/**
|
|
22259
|
-
*
|
|
22260
|
-
* @param
|
|
22261
|
-
* @param
|
|
22262
|
-
* @
|
|
22263
|
-
* @param max - The most entries it may have.
|
|
22264
|
-
* @returns The parsed entries.
|
|
22221
|
+
* Substitutes a default for null or undefined.
|
|
22222
|
+
* @param schema - The schema for present values.
|
|
22223
|
+
* @param value - Makes the default.
|
|
22224
|
+
* @returns The schema with its default.
|
|
22265
22225
|
*/
|
|
22266
|
-
function
|
|
22267
|
-
|
|
22268
|
-
return value.map((entry, i) => parse(entry, `${field}[${i}]`));
|
|
22226
|
+
function fallback(schema, value) {
|
|
22227
|
+
return createSchema((input, context) => input == null ? { value: value() } : schema["~run"](input, context));
|
|
22269
22228
|
}
|
|
22270
22229
|
/**
|
|
22271
|
-
*
|
|
22272
|
-
* @param
|
|
22273
|
-
* @param
|
|
22274
|
-
* @
|
|
22275
|
-
* @returns The values.
|
|
22230
|
+
* A settings object that names its unsupported keys before validating its fields.
|
|
22231
|
+
* @param name - The setting's name in messages, such as `previews`.
|
|
22232
|
+
* @param shape - The schema for each supported key.
|
|
22233
|
+
* @returns The settings schema.
|
|
22276
22234
|
*/
|
|
22277
|
-
function
|
|
22278
|
-
|
|
22279
|
-
return
|
|
22235
|
+
function settings(name, shape) {
|
|
22236
|
+
let schema = object(shape);
|
|
22237
|
+
return createSchema((value, context) => {
|
|
22238
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return fail(`${name} must be an object.`, context.path);
|
|
22239
|
+
for (let key of Object.keys(value)) if (!Object.hasOwn(shape, key)) return fail(`${name}.${key} is not a supported setting.`, [...context.path, key]);
|
|
22240
|
+
return schema["~run"](value, context);
|
|
22241
|
+
});
|
|
22280
22242
|
}
|
|
22281
22243
|
/**
|
|
22282
|
-
*
|
|
22283
|
-
* @param
|
|
22284
|
-
* @param
|
|
22285
|
-
* @param
|
|
22286
|
-
* @
|
|
22287
|
-
* @returns The metadata.
|
|
22244
|
+
* A list whose length is checked before its entries.
|
|
22245
|
+
* @param item - The schema for each entry.
|
|
22246
|
+
* @param message - The message when it is not a list or is too short.
|
|
22247
|
+
* @param minimum - The fewest entries it may have.
|
|
22248
|
+
* @returns The list schema.
|
|
22288
22249
|
*/
|
|
22289
|
-
function
|
|
22290
|
-
|
|
22291
|
-
|
|
22292
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
22293
|
-
if (typeof value === "string") return text$4(value, field);
|
|
22294
|
-
if (Array.isArray(value)) return value.map((entry, index) => metadata(entry, `${field}[${index}]`, depth + 1, budget));
|
|
22295
|
-
let input = object$1(value, field);
|
|
22296
|
-
return Object.fromEntries(Object.entries(input).map(([key, entry]) => [text$4(key, field, 512), metadata(entry, `${field}.${key}`, depth + 1, budget)]));
|
|
22250
|
+
function list(item, message, minimum = 0) {
|
|
22251
|
+
let items = array(item);
|
|
22252
|
+
return createSchema((value, context) => !Array.isArray(value) || value.length < minimum ? fail(message, context.path) : items["~run"](value, context));
|
|
22297
22253
|
}
|
|
22298
|
-
|
|
22299
|
-
|
|
22254
|
+
//#endregion
|
|
22255
|
+
//#region ../project/src/lib/files.ts
|
|
22256
|
+
/** The Files settings, with their defaults filled in. */
|
|
22257
|
+
var BudFilesSchema = defaulted(settings("files", { edit: fallback(described(boolean(), "files.edit must be true or false."), () => true) }), () => ({ edit: true }));
|
|
22258
|
+
//#endregion
|
|
22259
|
+
//#region ../project/src/lib/flows.ts
|
|
22260
|
+
var message$1 = "flows must be [{ id, title, start, script, steps: [{ id, title }], block? }] with short ids, local paths and unique steps.";
|
|
22261
|
+
var slug = (value) => typeof value === "string" && /^[a-z0-9][a-z0-9-]{0,62}$/.test(value);
|
|
22262
|
+
var text$4 = (value) => typeof value === "string" && value.trim() !== "" && value.length <= 200;
|
|
22263
|
+
function flow(value) {
|
|
22264
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
22265
|
+
let { id, title, start, script, steps, block = [] } = value;
|
|
22266
|
+
if (!slug(id) || !text$4(title) || !text$4(script) || script.includes("\0")) return null;
|
|
22267
|
+
if (typeof start !== "string" || !start.startsWith("/") || start.startsWith("//")) return null;
|
|
22268
|
+
if (new URL(start, "http://bud.invalid").origin !== "http://bud.invalid") return null;
|
|
22269
|
+
if (!Array.isArray(steps) || !steps.length || steps.length > 50) return null;
|
|
22270
|
+
let resolved = steps.map((step) => {
|
|
22271
|
+
let { id: stepId, title: stepTitle } = step ?? {};
|
|
22272
|
+
return slug(stepId) && text$4(stepTitle) ? {
|
|
22273
|
+
id: stepId,
|
|
22274
|
+
title: stepTitle.trim()
|
|
22275
|
+
} : null;
|
|
22276
|
+
});
|
|
22277
|
+
if (resolved.some((step) => !step)) return null;
|
|
22278
|
+
let ids = resolved.map((step) => step.id);
|
|
22279
|
+
if (new Set(ids).size !== ids.length) return null;
|
|
22280
|
+
if (!Array.isArray(block) || block.some((part) => !text$4(part))) return null;
|
|
22300
22281
|
return {
|
|
22301
|
-
id
|
|
22302
|
-
|
|
22303
|
-
|
|
22304
|
-
|
|
22305
|
-
|
|
22306
|
-
|
|
22282
|
+
id,
|
|
22283
|
+
title: title.trim(),
|
|
22284
|
+
start,
|
|
22285
|
+
script: script.trim(),
|
|
22286
|
+
steps: resolved,
|
|
22287
|
+
block
|
|
22307
22288
|
};
|
|
22308
22289
|
}
|
|
22290
|
+
/** The flows, none when omitted. */
|
|
22291
|
+
var BudFlowsSchema = createSchema((value, context) => {
|
|
22292
|
+
if (value === void 0) return { value: [] };
|
|
22293
|
+
if (!Array.isArray(value)) return fail(message$1, context.path);
|
|
22294
|
+
let flows = value.map(flow);
|
|
22295
|
+
if (flows.some((resolved) => !resolved)) return fail(message$1, context.path);
|
|
22296
|
+
let ids = flows.map((resolved) => resolved.id);
|
|
22297
|
+
if (new Set(ids).size !== ids.length) return fail(message$1, context.path);
|
|
22298
|
+
return { value: flows };
|
|
22299
|
+
});
|
|
22309
22300
|
/**
|
|
22310
|
-
*
|
|
22311
|
-
*
|
|
22312
|
-
* @param
|
|
22313
|
-
* @
|
|
22301
|
+
* Where a flow's step is pictured: a route of its own, named `<flow>/<step>`, in a group named
|
|
22302
|
+
* after the flow. Its path is the start page with the step as its fragment, unique among routes.
|
|
22303
|
+
* @param flow - The flow.
|
|
22304
|
+
* @param step - One of its steps.
|
|
22305
|
+
* @returns The step's route.
|
|
22314
22306
|
*/
|
|
22315
|
-
function
|
|
22316
|
-
let input = object$1(value, field);
|
|
22317
|
-
let string = (key, max) => text$4(input[key], `${field}.${key}`, max);
|
|
22318
|
-
let optional = (key, max) => nullableText(input[key], `${field}.${key}`, max);
|
|
22319
|
-
let count = (key) => nullableInteger(input[key], `${field}.${key}`);
|
|
22320
|
-
let date = (key) => nullableTimestamp(input[key], `${field}.${key}`);
|
|
22307
|
+
function flowStepRoute(flow, step) {
|
|
22321
22308
|
return {
|
|
22322
|
-
id:
|
|
22323
|
-
|
|
22324
|
-
|
|
22325
|
-
|
|
22326
|
-
|
|
22327
|
-
|
|
22328
|
-
|
|
22329
|
-
|
|
22330
|
-
notes: string("notes"),
|
|
22331
|
-
assignee: optional("assignee"),
|
|
22332
|
-
owner: optional("owner"),
|
|
22333
|
-
createdBy: optional("createdBy"),
|
|
22334
|
-
createdAt: timestamp(input.createdAt, `${field}.createdAt`),
|
|
22335
|
-
updatedAt: timestamp(input.updatedAt, `${field}.updatedAt`),
|
|
22336
|
-
startedAt: date("startedAt"),
|
|
22337
|
-
closedAt: date("closedAt"),
|
|
22338
|
-
closeReason: optional("closeReason"),
|
|
22339
|
-
dueAt: date("dueAt"),
|
|
22340
|
-
deferUntil: date("deferUntil"),
|
|
22341
|
-
estimatedMinutes: count("estimatedMinutes"),
|
|
22342
|
-
externalRef: optional("externalRef"),
|
|
22343
|
-
specId: optional("specId"),
|
|
22344
|
-
labels: unique(array$1(input.labels, `${field}.labels`, (item, path) => text$4(item, path, 512), 1e3), (item) => item, `${field}.labels`),
|
|
22345
|
-
parentId: input.parentId == null ? null : identifier$1(input.parentId, `${field}.parentId`),
|
|
22346
|
-
dependencies: array$1(input.dependencies, `${field}.dependencies`, dependency),
|
|
22347
|
-
dependents: array$1(input.dependents, `${field}.dependents`, dependency),
|
|
22348
|
-
dependencyCount: count("dependencyCount"),
|
|
22349
|
-
dependentCount: count("dependentCount"),
|
|
22350
|
-
commentCount: count("commentCount"),
|
|
22351
|
-
epicTotalChildren: count("epicTotalChildren"),
|
|
22352
|
-
epicClosedChildren: count("epicClosedChildren"),
|
|
22353
|
-
epicCloseable: input.epicCloseable == null ? null : boolean$2(input.epicCloseable, `${field}.epicCloseable`),
|
|
22354
|
-
revision: optional("revision", 256),
|
|
22355
|
-
metadata: metadata(input.metadata, `${field}.metadata`),
|
|
22356
|
-
contentLoaded: boolean$2(input.contentLoaded, `${field}.contentLoaded`)
|
|
22309
|
+
id: `${flow.id}/${step.id}`,
|
|
22310
|
+
path: `${flow.start.replace(/#.*$/, "")}#${flow.id}-${step.id}`,
|
|
22311
|
+
title: step.title,
|
|
22312
|
+
group: flow.title,
|
|
22313
|
+
flow: {
|
|
22314
|
+
id: flow.id,
|
|
22315
|
+
step: step.id
|
|
22316
|
+
}
|
|
22357
22317
|
};
|
|
22358
22318
|
}
|
|
22359
|
-
|
|
22360
|
-
|
|
22361
|
-
|
|
22362
|
-
|
|
22363
|
-
|
|
22364
|
-
|
|
22365
|
-
|
|
22366
|
-
|
|
22367
|
-
|
|
22368
|
-
|
|
22369
|
-
|
|
22370
|
-
|
|
22371
|
-
|
|
22372
|
-
|
|
22373
|
-
|
|
22374
|
-
|
|
22375
|
-
|
|
22376
|
-
|
|
22377
|
-
|
|
22378
|
-
|
|
22379
|
-
|
|
22380
|
-
|
|
22381
|
-
|
|
22319
|
+
//#endregion
|
|
22320
|
+
//#region ../project/src/lib/logo.ts
|
|
22321
|
+
/** The image types a logo may be, by extension. */
|
|
22322
|
+
var LOGO_TYPES = Object.freeze({
|
|
22323
|
+
".svg": "image/svg+xml",
|
|
22324
|
+
".png": "image/png",
|
|
22325
|
+
".webp": "image/webp",
|
|
22326
|
+
".jpg": "image/jpeg",
|
|
22327
|
+
".jpeg": "image/jpeg",
|
|
22328
|
+
".gif": "image/gif",
|
|
22329
|
+
".ico": "image/x-icon"
|
|
22330
|
+
});
|
|
22331
|
+
var logoMessage = "logo must be an image path inside the project (.svg, .png, .webp, .jpg, .gif or .ico), or { light, dark }.";
|
|
22332
|
+
function image(value) {
|
|
22333
|
+
if (typeof value !== "string" || value.length > 1024 || value.includes("\0")) return null;
|
|
22334
|
+
let path = value.trim().replaceAll("\\", "/");
|
|
22335
|
+
if (!path || path.startsWith("/") || /^[a-z]:/i.test(path)) return null;
|
|
22336
|
+
if (path.split("/").includes("..")) return null;
|
|
22337
|
+
let extension = path.slice(path.lastIndexOf(".")).toLowerCase();
|
|
22338
|
+
return Object.hasOwn(LOGO_TYPES, extension) ? path : null;
|
|
22339
|
+
}
|
|
22340
|
+
/** A logo in its one resolved form, or null for Bud's own mark. */
|
|
22341
|
+
var BudLogoSchema = createSchema((value, context) => {
|
|
22342
|
+
if (value === void 0) return { value: null };
|
|
22343
|
+
let light = image(typeof value === "object" && value ? value.light : value);
|
|
22344
|
+
let raw = typeof value === "object" && value ? value.dark : void 0;
|
|
22345
|
+
let dark = raw === void 0 ? null : image(raw);
|
|
22346
|
+
let keys = typeof value === "object" && value ? Object.keys(value) : [];
|
|
22347
|
+
if (light && (raw === void 0 || dark) && keys.every((key) => key === "light" || key === "dark")) return { value: {
|
|
22348
|
+
light,
|
|
22349
|
+
dark
|
|
22350
|
+
} };
|
|
22351
|
+
return fail(logoMessage, context.path);
|
|
22352
|
+
});
|
|
22353
|
+
//#endregion
|
|
22354
|
+
//#region ../project/src/lib/theme.ts
|
|
22355
|
+
/**
|
|
22356
|
+
* Bud's colors and shadows a theme may set, as `--bud-<name>` in its styles. Everything else
|
|
22357
|
+
* Bud derives from them.
|
|
22358
|
+
*/
|
|
22359
|
+
var THEME_TOKENS = Object.freeze([
|
|
22360
|
+
"background",
|
|
22361
|
+
"shell",
|
|
22362
|
+
"main",
|
|
22363
|
+
"canvas",
|
|
22364
|
+
"panel",
|
|
22365
|
+
"board",
|
|
22366
|
+
"editor",
|
|
22367
|
+
"editor-tabs",
|
|
22368
|
+
"foreground",
|
|
22369
|
+
"muted",
|
|
22370
|
+
"primary",
|
|
22371
|
+
"primary-hover",
|
|
22372
|
+
"primary-foreground",
|
|
22373
|
+
"border",
|
|
22374
|
+
"tab-divider",
|
|
22375
|
+
"hover",
|
|
22376
|
+
"selected",
|
|
22377
|
+
"control",
|
|
22378
|
+
"control-active",
|
|
22379
|
+
"control-selected",
|
|
22380
|
+
"card",
|
|
22381
|
+
"menu",
|
|
22382
|
+
"menu-hover",
|
|
22383
|
+
"card-frame",
|
|
22384
|
+
"card-surface",
|
|
22385
|
+
"card-border",
|
|
22386
|
+
"card-frame-border",
|
|
22387
|
+
"card-badge",
|
|
22388
|
+
"overlay-shadow",
|
|
22389
|
+
"tooltip",
|
|
22390
|
+
"board-control",
|
|
22391
|
+
"board-control-hover",
|
|
22392
|
+
"board-card",
|
|
22393
|
+
"control-elevation",
|
|
22394
|
+
"board-elevation",
|
|
22395
|
+
"board-card-elevation",
|
|
22396
|
+
"card-elevation",
|
|
22397
|
+
"board-card-lift",
|
|
22398
|
+
"frame-control",
|
|
22399
|
+
"modal-elevation",
|
|
22400
|
+
"segment-elevation",
|
|
22401
|
+
"control-open-elevation",
|
|
22402
|
+
"board-segment",
|
|
22403
|
+
"scrollbar",
|
|
22404
|
+
"focus",
|
|
22405
|
+
"scrollbar-hover",
|
|
22406
|
+
"tooltip-border",
|
|
22407
|
+
"tooltip-shadow"
|
|
22408
|
+
]);
|
|
22409
|
+
var themeMessage = `theme must be { light, dark }, each mapping Bud's tokens (${THEME_TOKENS.slice(0, 3).join(", ")}, …) to CSS colors or shadows.`;
|
|
22410
|
+
function cssValue(value) {
|
|
22411
|
+
if (typeof value !== "string") return null;
|
|
22412
|
+
let trimmed = value.trim();
|
|
22413
|
+
if (!trimmed || trimmed.length > 300) return null;
|
|
22414
|
+
if (!/^[#a-zA-Z0-9(),.%\s/+-]+$/.test(trimmed) || /url\s*\(/i.test(trimmed)) return null;
|
|
22415
|
+
return trimmed;
|
|
22416
|
+
}
|
|
22417
|
+
function colors(value) {
|
|
22418
|
+
if (value === void 0) return {};
|
|
22419
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
22420
|
+
let result = {};
|
|
22421
|
+
for (let [name, raw] of Object.entries(value)) {
|
|
22422
|
+
let checked = cssValue(raw);
|
|
22423
|
+
if (!THEME_TOKENS.includes(name) || checked === null) return null;
|
|
22424
|
+
result[name] = checked;
|
|
22382
22425
|
}
|
|
22383
|
-
|
|
22384
|
-
"unavailable",
|
|
22385
|
-
"not-initialized",
|
|
22386
|
-
"unsupported-version"
|
|
22387
|
-
].includes(input.state)) throw new BeadsValidationError("connection.state");
|
|
22388
|
-
return {
|
|
22389
|
-
state: input.state,
|
|
22390
|
-
version: nullableText(input.version, "connection.version", 128),
|
|
22391
|
-
message: text$4(input.message, "connection.message", 1e3)
|
|
22392
|
-
};
|
|
22426
|
+
return Object.freeze(result);
|
|
22393
22427
|
}
|
|
22394
|
-
/**
|
|
22395
|
-
|
|
22396
|
-
|
|
22397
|
-
|
|
22398
|
-
|
|
22399
|
-
|
|
22400
|
-
|
|
22401
|
-
let
|
|
22402
|
-
|
|
22403
|
-
|
|
22404
|
-
|
|
22405
|
-
description: text$4(input.description, `${field}.description`),
|
|
22406
|
-
custom: boolean$2(input.custom, `${field}.custom`)
|
|
22428
|
+
/** A theme in its one resolved form; Bud's own colors when absent. */
|
|
22429
|
+
var BudThemeSchema = createSchema((value, context) => {
|
|
22430
|
+
if (value === void 0) return { value: {
|
|
22431
|
+
light: {},
|
|
22432
|
+
dark: {}
|
|
22433
|
+
} };
|
|
22434
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return fail(themeMessage, context.path);
|
|
22435
|
+
let { light, dark, ...rest } = value;
|
|
22436
|
+
let resolved = {
|
|
22437
|
+
light: colors(light),
|
|
22438
|
+
dark: colors(dark)
|
|
22407
22439
|
};
|
|
22408
|
-
|
|
22440
|
+
if (Object.keys(rest).length || !resolved.light || !resolved.dark) return fail(themeMessage, context.path);
|
|
22441
|
+
return { value: {
|
|
22442
|
+
light: resolved.light,
|
|
22443
|
+
dark: resolved.dark
|
|
22444
|
+
} };
|
|
22445
|
+
});
|
|
22409
22446
|
/**
|
|
22410
|
-
*
|
|
22411
|
-
* @param
|
|
22412
|
-
* @
|
|
22413
|
-
* @returns The type.
|
|
22447
|
+
* The theme as styles that follow Bud's own, so its tokens win in each color scheme.
|
|
22448
|
+
* @param theme - The resolved theme.
|
|
22449
|
+
* @returns A `<style>` element, or an empty string for Bud's own colors.
|
|
22414
22450
|
*/
|
|
22415
|
-
function
|
|
22416
|
-
let
|
|
22417
|
-
|
|
22418
|
-
"
|
|
22419
|
-
"
|
|
22420
|
-
"custom"
|
|
22421
|
-
].includes(input.kind)) throw new BeadsValidationError(`${field}.kind`);
|
|
22422
|
-
return {
|
|
22423
|
-
name: identifier$1(input.name, `${field}.name`),
|
|
22424
|
-
description: text$4(input.description, `${field}.description`),
|
|
22425
|
-
kind: input.kind
|
|
22451
|
+
function themeStyle(theme) {
|
|
22452
|
+
let block = (selector, values) => {
|
|
22453
|
+
let entries = Object.entries(values);
|
|
22454
|
+
if (!entries.length) return "";
|
|
22455
|
+
return `${selector}{${entries.map(([name, value]) => `--bud-${name}:${value};`).join("")}}`;
|
|
22426
22456
|
};
|
|
22457
|
+
let css = block(":root:not([data-theme='dark'])", theme.light) + block(":root[data-theme='dark']", theme.dark);
|
|
22458
|
+
return css ? `<style data-bud-theme>${css}</style>` : "";
|
|
22427
22459
|
}
|
|
22428
|
-
|
|
22429
|
-
|
|
22430
|
-
|
|
22431
|
-
|
|
22432
|
-
|
|
22433
|
-
function parseBeadsSnapshot(value) {
|
|
22434
|
-
let input = object$1(value, "snapshot");
|
|
22435
|
-
let connection = parseBeadsConnection(input.connection);
|
|
22436
|
-
let issues = unique(array$1(input.issues, "snapshot.issues", parseBeadIssue, MAX_BEADS), (issue) => issue.id, "snapshot.issues");
|
|
22437
|
-
let readyIds = unique(array$1(input.readyIds, "snapshot.readyIds", identifier$1, MAX_BEADS), (id) => id, "snapshot.readyIds");
|
|
22438
|
-
let statuses = unique(array$1(input.statuses, "snapshot.statuses", parseBeadStatus, 1e3), (item) => item.name, "snapshot.statuses");
|
|
22439
|
-
let types = unique(array$1(input.types, "snapshot.types", parseBeadType, 1e3), (item) => item.name, "snapshot.types");
|
|
22440
|
-
let truncated = boolean$2(input.truncated, "snapshot.truncated");
|
|
22441
|
-
if (connection.state !== "ready" && (issues.length || readyIds.length || statuses.length || types.length || truncated)) throw new BeadsValidationError("snapshot.connection");
|
|
22442
|
-
return {
|
|
22443
|
-
connection,
|
|
22444
|
-
issues,
|
|
22445
|
-
readyIds,
|
|
22446
|
-
statuses,
|
|
22447
|
-
types,
|
|
22448
|
-
truncated
|
|
22449
|
-
};
|
|
22460
|
+
//#endregion
|
|
22461
|
+
//#region ../project/src/lib/branches.ts
|
|
22462
|
+
var message = "branches must be { main?, build?: { install?, build?, serve? } } with a branch name and nonempty shell commands.";
|
|
22463
|
+
function branch(value) {
|
|
22464
|
+
return typeof value === "string" && value.length <= 255 && /^[A-Za-z0-9._/-]+$/.test(value) && !value.startsWith("-") && !value.startsWith("/") && !value.endsWith("/") && !value.endsWith(".lock") && !value.includes("..") && !value.includes("//") ? value : null;
|
|
22450
22465
|
}
|
|
22451
|
-
|
|
22452
|
-
|
|
22453
|
-
* @param value - Untrusted data.
|
|
22454
|
-
* @param field - Where the value sits, for the error.
|
|
22455
|
-
* @returns The comment.
|
|
22456
|
-
*/
|
|
22457
|
-
function parseBeadDiscussion(value, field = "discussion") {
|
|
22458
|
-
let input = object$1(value, field);
|
|
22459
|
-
return {
|
|
22460
|
-
id: identifier$1(input.id, `${field}.id`),
|
|
22461
|
-
issueId: identifier$1(input.issueId, `${field}.issueId`),
|
|
22462
|
-
author: text$4(input.author, `${field}.author`),
|
|
22463
|
-
body: text$4(input.body, `${field}.body`),
|
|
22464
|
-
createdAt: timestamp(input.createdAt, `${field}.createdAt`)
|
|
22465
|
-
};
|
|
22466
|
+
function command(value) {
|
|
22467
|
+
return typeof value === "string" && value.trim() && value.length <= 4096 && !value.includes("\0") ? value.trim() : null;
|
|
22466
22468
|
}
|
|
22467
|
-
/**
|
|
22468
|
-
|
|
22469
|
-
|
|
22470
|
-
|
|
22471
|
-
|
|
22472
|
-
|
|
22473
|
-
|
|
22474
|
-
let input = object$1(value, field);
|
|
22475
|
-
return {
|
|
22476
|
-
commit: identifier$1(input.commit, `${field}.commit`),
|
|
22477
|
-
author: text$4(input.author, `${field}.author`),
|
|
22478
|
-
createdAt: timestamp(input.createdAt, `${field}.createdAt`),
|
|
22479
|
-
issue: input.issue == null ? null : parseBeadIssue(input.issue, `${field}.issue`)
|
|
22469
|
+
/** The branch settings, defaults filled in. */
|
|
22470
|
+
var BudBranchesSchema = createSchema((value, context) => {
|
|
22471
|
+
let empty = {
|
|
22472
|
+
main: null,
|
|
22473
|
+
install: null,
|
|
22474
|
+
build: null,
|
|
22475
|
+
serve: null
|
|
22480
22476
|
};
|
|
22481
|
-
}
|
|
22477
|
+
if (value === void 0) return { value: empty };
|
|
22478
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return fail(message, context.path);
|
|
22479
|
+
let input = value;
|
|
22480
|
+
let build = input.build ?? {};
|
|
22481
|
+
if (!build || typeof build !== "object" || Array.isArray(build) || Object.keys(input).some((key) => key !== "main" && key !== "build")) return fail(message, context.path);
|
|
22482
|
+
let steps = build;
|
|
22483
|
+
if (Object.keys(steps).some((key) => ![
|
|
22484
|
+
"install",
|
|
22485
|
+
"build",
|
|
22486
|
+
"serve"
|
|
22487
|
+
].includes(key))) return fail(message, context.path);
|
|
22488
|
+
let resolved = {
|
|
22489
|
+
main: input.main === void 0 ? null : branch(input.main),
|
|
22490
|
+
install: steps.install === void 0 ? null : command(steps.install),
|
|
22491
|
+
build: steps.build === void 0 ? null : command(steps.build),
|
|
22492
|
+
serve: steps.serve === void 0 ? null : command(steps.serve)
|
|
22493
|
+
};
|
|
22494
|
+
if (input.main !== void 0 && !resolved.main || [
|
|
22495
|
+
"install",
|
|
22496
|
+
"build",
|
|
22497
|
+
"serve"
|
|
22498
|
+
].some((key) => steps[key] !== void 0 && !resolved[key])) return fail(message, context.path);
|
|
22499
|
+
return { value: resolved };
|
|
22500
|
+
});
|
|
22501
|
+
//#endregion
|
|
22502
|
+
//#region ../project/src/lib/storage.ts
|
|
22482
22503
|
/**
|
|
22483
|
-
*
|
|
22484
|
-
* @param
|
|
22485
|
-
* @param
|
|
22486
|
-
* @
|
|
22504
|
+
* A project module named in the config: a ./ path inside the project, outside node_modules.
|
|
22505
|
+
* @param name - The config option, for the error message.
|
|
22506
|
+
* @param server - Whether it runs on the server (.server.ts) rather than in the browser.
|
|
22507
|
+
* @param message - What to say instead of the default when it is invalid.
|
|
22508
|
+
* @returns A schema for the module's path.
|
|
22487
22509
|
*/
|
|
22488
|
-
function
|
|
22489
|
-
let
|
|
22490
|
-
return {
|
|
22491
|
-
id: identifier$1(input.id, `${field}.id`),
|
|
22492
|
-
issueId: identifier$1(input.issueId, `${field}.issueId`),
|
|
22493
|
-
type: identifier$1(input.type, `${field}.type`),
|
|
22494
|
-
actor: text$4(input.actor, `${field}.actor`),
|
|
22495
|
-
oldValue: nullableText(input.oldValue, `${field}.oldValue`),
|
|
22496
|
-
newValue: nullableText(input.newValue, `${field}.newValue`),
|
|
22497
|
-
comment: nullableText(input.comment, `${field}.comment`),
|
|
22498
|
-
createdAt: timestamp(input.createdAt, `${field}.createdAt`)
|
|
22499
|
-
};
|
|
22510
|
+
function modulePath(name, server, message) {
|
|
22511
|
+
let extension = server ? /\.server\.ts$/ : /\.(?:ts|tsx|js|jsx)$/;
|
|
22512
|
+
return described(string().refine((value) => value.length <= 1024 && value.startsWith("./") && !/[\u0000-\u001f\u007f\\?#%]/.test(value) && extension.test(value) && (server || !/\.server\.[cm]?[jt]sx?$/.test(value)) && !value.slice(2).split("/").some((part) => !part || part === "." || part === ".." || part === "node_modules")), message ?? (server ? `${name} must be a built-in adapter or a ./ module ending in .server.ts inside the project.` : `${name} must be bud:beads or a ./ browser module ending in .ts, .tsx, .js or .jsx inside the project.`));
|
|
22500
22513
|
}
|
|
22501
|
-
|
|
22502
|
-
|
|
22503
|
-
|
|
22504
|
-
|
|
22505
|
-
|
|
22506
|
-
|
|
22507
|
-
|
|
22508
|
-
|
|
22509
|
-
|
|
22510
|
-
if (input.state === "unsupported" && items.length === 0 && truncated === false) return {
|
|
22511
|
-
state: "unsupported",
|
|
22512
|
-
items: [],
|
|
22513
|
-
truncated: false
|
|
22514
|
-
};
|
|
22515
|
-
throw new BeadsValidationError(`${field}.state`);
|
|
22516
|
-
}
|
|
22517
|
-
/**
|
|
22518
|
-
* Validates an issue's detail, whose sections must all belong to that issue.
|
|
22519
|
-
* @param value - Untrusted data.
|
|
22520
|
-
* @returns The detail.
|
|
22521
|
-
*/
|
|
22522
|
-
function parseBeadDetail(value) {
|
|
22523
|
-
let input = object$1(value, "detail");
|
|
22524
|
-
let connection = parseBeadsConnection(input.connection);
|
|
22525
|
-
if (connection.state !== "ready") throw new BeadsValidationError("detail.connection");
|
|
22526
|
-
let issue = parseBeadIssue(input.issue);
|
|
22527
|
-
let discussion = section$3(input.discussion, "discussion", parseBeadDiscussion, 200);
|
|
22528
|
-
let history = section$3(input.history, "history", parseBeadHistory, 50);
|
|
22529
|
-
let events = section$3(input.events, "events", parseBeadEvent, 50);
|
|
22530
|
-
if (!issue.contentLoaded || discussion.items.some((entry) => entry.issueId !== issue.id) || events.items.some((entry) => entry.issueId !== issue.id) || history.items.some((entry) => entry.issue && entry.issue.id !== issue.id)) throw new BeadsValidationError("detail.issue");
|
|
22531
|
-
return {
|
|
22532
|
-
connection,
|
|
22533
|
-
issue,
|
|
22534
|
-
discussion,
|
|
22535
|
-
history,
|
|
22536
|
-
events
|
|
22537
|
-
};
|
|
22514
|
+
var builtInStores = enum_([
|
|
22515
|
+
false,
|
|
22516
|
+
"bud:beads",
|
|
22517
|
+
"bud:memory",
|
|
22518
|
+
"bud:git-comments"
|
|
22519
|
+
]);
|
|
22520
|
+
function store(kind) {
|
|
22521
|
+
let module = modulePath(`storage.${kind}`, true);
|
|
22522
|
+
return defaulted(check(select$1((value) => value === false || value === "bud:beads" || value === "bud:memory" || value === "bud:git-comments" ? builtInStores : module), (value) => kind === "work" && value === "bud:git-comments" ? "storage.work cannot use bud:git-comments; it only stores threads." : void 0), "bud:beads");
|
|
22538
22523
|
}
|
|
22524
|
+
/** The selected work and discussion stores, with Beads as the default for both. */
|
|
22525
|
+
var BudStorageSchema = defaulted(settings("storage", {
|
|
22526
|
+
work: store("work"),
|
|
22527
|
+
threads: store("threads")
|
|
22528
|
+
}), () => ({
|
|
22529
|
+
work: "bud:beads",
|
|
22530
|
+
threads: "bud:beads"
|
|
22531
|
+
}));
|
|
22532
|
+
/** Task presentation, chosen separately from its storage. */
|
|
22533
|
+
var BudDesignsSchema = defaulted(settings("designs", {
|
|
22534
|
+
tasks: defaulted(select$1((value) => value === "bud:beads" ? enum_(["bud:beads"]) : modulePath("designs.tasks", false)), "bud:beads"),
|
|
22535
|
+
pages: defaulted(enum_(["path", "title"]), "path")
|
|
22536
|
+
}), () => ({
|
|
22537
|
+
tasks: "bud:beads",
|
|
22538
|
+
pages: "path"
|
|
22539
|
+
}));
|
|
22539
22540
|
//#endregion
|
|
22540
|
-
//#region ../
|
|
22541
|
-
var
|
|
22542
|
-
"
|
|
22543
|
-
"
|
|
22544
|
-
"
|
|
22545
|
-
|
|
22546
|
-
|
|
22547
|
-
|
|
22548
|
-
|
|
22549
|
-
|
|
22550
|
-
|
|
22551
|
-
|
|
22552
|
-
|
|
22553
|
-
"
|
|
22554
|
-
|
|
22555
|
-
|
|
22556
|
-
|
|
22557
|
-
"
|
|
22558
|
-
|
|
22559
|
-
|
|
22560
|
-
|
|
22561
|
-
|
|
22562
|
-
|
|
22563
|
-
|
|
22564
|
-
|
|
22565
|
-
|
|
22566
|
-
|
|
22567
|
-
|
|
22568
|
-
"
|
|
22569
|
-
"
|
|
22570
|
-
|
|
22571
|
-
|
|
22572
|
-
|
|
22573
|
-
|
|
22574
|
-
return
|
|
22575
|
-
}
|
|
22576
|
-
|
|
22577
|
-
|
|
22578
|
-
|
|
22579
|
-
|
|
22541
|
+
//#region ../project/src/lib/picker.ts
|
|
22542
|
+
var builtIn = /* @__PURE__ */ new Set([
|
|
22543
|
+
"bud:dom",
|
|
22544
|
+
"bud:react",
|
|
22545
|
+
"bud:react-grab"
|
|
22546
|
+
]);
|
|
22547
|
+
var projectPicker = modulePath("picker", false, "picker must be bud:dom, bud:react, bud:react-grab or a ./ browser module ending in .ts, .tsx, .js or .jsx inside the project.");
|
|
22548
|
+
/** The picker, with Bud's DOM picker as the default. */
|
|
22549
|
+
var BudPickerSchema = createSchema((value, context) => {
|
|
22550
|
+
if (value === void 0) return { value: {
|
|
22551
|
+
name: "bud:dom",
|
|
22552
|
+
module: null
|
|
22553
|
+
} };
|
|
22554
|
+
if (typeof value === "string" && builtIn.has(value)) return { value: {
|
|
22555
|
+
name: value,
|
|
22556
|
+
module: null
|
|
22557
|
+
} };
|
|
22558
|
+
let result = projectPicker["~run"](value, context);
|
|
22559
|
+
return result.issues ? result : { value: {
|
|
22560
|
+
name: result.value,
|
|
22561
|
+
module: result.value
|
|
22562
|
+
} };
|
|
22563
|
+
});
|
|
22564
|
+
//#endregion
|
|
22565
|
+
//#region ../project/src/lib/previews.ts
|
|
22566
|
+
var readyMessage = "ready must be a CSS selector, { selector } or { expression }.";
|
|
22567
|
+
/** A readiness condition; a string is a selector. */
|
|
22568
|
+
var PreviewReadySchema = createSchema((value, context) => {
|
|
22569
|
+
let ready = typeof value === "string" ? { selector: value } : value;
|
|
22570
|
+
if (ready && typeof ready === "object" && !Array.isArray(ready)) {
|
|
22571
|
+
let entries = Object.entries(ready);
|
|
22572
|
+
let [key, text] = entries[0] ?? [];
|
|
22573
|
+
if (entries.length === 1 && (key === "selector" || key === "expression") && typeof text === "string" && text.trim() && text.length <= 4096) return { value: key === "selector" ? { selector: text.trim() } : { expression: text.trim() } };
|
|
22574
|
+
}
|
|
22575
|
+
return fail(readyMessage, context.path);
|
|
22576
|
+
});
|
|
22577
|
+
var defaultThemes = () => ["light", "dark"];
|
|
22578
|
+
var defaultViewports = () => [{
|
|
22579
|
+
id: "desktop",
|
|
22580
|
+
name: "Desktop",
|
|
22581
|
+
width: 1440,
|
|
22582
|
+
height: 900,
|
|
22583
|
+
mobile: false
|
|
22584
|
+
}];
|
|
22585
|
+
var viewportsMessage = "previews.viewports must list up to 4 { name, width, height?, mobile? } with distinct names, widths from 240 to 3840 and heights from 240 to 4000.";
|
|
22586
|
+
function viewportId(name) {
|
|
22587
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32);
|
|
22580
22588
|
}
|
|
22589
|
+
var ViewportsSchema = createSchema((value, context) => {
|
|
22590
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > 4) return fail(viewportsMessage, context.path);
|
|
22591
|
+
let viewports = [];
|
|
22592
|
+
for (let entry of value) {
|
|
22593
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return fail(viewportsMessage, context.path);
|
|
22594
|
+
let { name, width, height, mobile, ...rest } = entry;
|
|
22595
|
+
let size = (pixels, from, to) => typeof pixels === "number" && Number.isInteger(pixels) && pixels >= from && pixels <= to;
|
|
22596
|
+
if (Object.keys(rest).length || typeof name !== "string" || !name.trim() || name.length > 40 || !viewportId(name) || !size(width, 240, 3840) || height !== void 0 && !size(height, 240, 4e3) || mobile !== void 0 && typeof mobile !== "boolean") return fail(viewportsMessage, context.path);
|
|
22597
|
+
let phone = mobile ?? width < 800;
|
|
22598
|
+
viewports.push({
|
|
22599
|
+
id: viewportId(name),
|
|
22600
|
+
name: name.trim(),
|
|
22601
|
+
width,
|
|
22602
|
+
height: height ?? (phone ? Math.round(width * 844 / 390) : 900),
|
|
22603
|
+
mobile: phone
|
|
22604
|
+
});
|
|
22605
|
+
}
|
|
22606
|
+
if (new Set(viewports.map((viewport) => viewport.id)).size !== viewports.length) return fail(viewportsMessage, context.path);
|
|
22607
|
+
return { value: viewports };
|
|
22608
|
+
});
|
|
22609
|
+
/** The preview settings, with their defaults filled in. */
|
|
22610
|
+
var BudPreviewsSchema = defaulted(settings("previews", {
|
|
22611
|
+
scale: fallback(described(number().refine((scale) => Number.isInteger(scale) && scale >= 1 && scale <= 3), "previews.scale must be 1, 2 or 3."), () => 2),
|
|
22612
|
+
themes: fallback(described(array(enum_(["light", "dark"])).refine((themes) => themes.length > 0 && new Set(themes).size === themes.length), "previews.themes must list light, dark or both, once each."), defaultThemes),
|
|
22613
|
+
viewports: fallback(ViewportsSchema, defaultViewports),
|
|
22614
|
+
ready: fallback(PreviewReadySchema, () => null),
|
|
22615
|
+
readyTimeout: fallback(described(number().refine((ms) => Number.isInteger(ms) && ms >= 1e3 && ms <= 6e5), "previews.readyTimeout must be whole milliseconds from 1000 to 600000."), () => 6e4),
|
|
22616
|
+
css: fallback(described(string().refine((css) => css.length <= 2e4 && !css.includes("<")), "previews.css must be CSS text without <, up to 20000 characters."), () => "")
|
|
22617
|
+
}), () => ({
|
|
22618
|
+
scale: 2,
|
|
22619
|
+
themes: defaultThemes(),
|
|
22620
|
+
viewports: defaultViewports(),
|
|
22621
|
+
ready: null,
|
|
22622
|
+
readyTimeout: 6e4,
|
|
22623
|
+
css: ""
|
|
22624
|
+
}));
|
|
22625
|
+
//#endregion
|
|
22626
|
+
//#region ../project/src/lib/screen-routes.ts
|
|
22581
22627
|
/**
|
|
22582
|
-
* Validates
|
|
22583
|
-
* @param
|
|
22584
|
-
* @
|
|
22628
|
+
* Validates a screen's route map without importing its component module.
|
|
22629
|
+
* @param routes - A Remix route map shared with the screen's definition.
|
|
22630
|
+
* @param path - The configured screen's static path.
|
|
22631
|
+
* @returns Named patterns confined to the screen's path.
|
|
22585
22632
|
*/
|
|
22586
|
-
function
|
|
22587
|
-
let
|
|
22588
|
-
let
|
|
22589
|
-
|
|
22590
|
-
|
|
22591
|
-
|
|
22592
|
-
|
|
22593
|
-
|
|
22594
|
-
|
|
22595
|
-
|
|
22596
|
-
|
|
22597
|
-
|
|
22598
|
-
|
|
22599
|
-
|
|
22600
|
-
|
|
22601
|
-
|
|
22602
|
-
|
|
22603
|
-
|
|
22604
|
-
|
|
22605
|
-
|
|
22606
|
-
|
|
22607
|
-
|
|
22608
|
-
|
|
22609
|
-
|
|
22610
|
-
if ("metadata" in input) {
|
|
22611
|
-
let source = object$1(input.metadata, "patch.metadata");
|
|
22612
|
-
if (JSON.stringify(source).length > 32e3) throw new BeadsValidationError("patch.metadata");
|
|
22613
|
-
patch.metadata = metadata(source, "patch.metadata");
|
|
22633
|
+
function resolveScreenRoutes(routes, path) {
|
|
22634
|
+
let result = [];
|
|
22635
|
+
let seen = /* @__PURE__ */ new Set();
|
|
22636
|
+
function visit(value, prefix) {
|
|
22637
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || seen.has(value)) throw new Error("Screen routes must be a Remix route map.");
|
|
22638
|
+
seen.add(value);
|
|
22639
|
+
for (let [key, entry] of Object.entries(value)) {
|
|
22640
|
+
let name = prefix ? `${prefix}.${key}` : key;
|
|
22641
|
+
if (!entry || typeof entry !== "object") throw new Error(`Screen route ${name} must be a Remix route.`);
|
|
22642
|
+
if (!("pattern" in entry)) {
|
|
22643
|
+
visit(entry, name);
|
|
22644
|
+
continue;
|
|
22645
|
+
}
|
|
22646
|
+
if (!["GET", "ANY"].includes(entry.method) || typeof entry.href !== "function" || typeof entry.pattern?.source !== "string") throw new Error(`Screen route ${name} must be a GET route.`);
|
|
22647
|
+
let pattern = RoutePattern.parse(entry.pattern.source);
|
|
22648
|
+
let parts = pattern.toJSON();
|
|
22649
|
+
let pathname = `/${parts.pathname}`;
|
|
22650
|
+
if (parts.protocol || parts.hostname || parts.port || parts.pathname.split("/").some((segment) => /^\.{1,2}$/.test(decodeURIComponent(segment))) || pathname !== path && !pathname.startsWith(`${path}/`) && !pathname.startsWith(`${path}(/`)) throw new Error(`Screen route ${name} must stay under ${path}.`);
|
|
22651
|
+
result.push({
|
|
22652
|
+
name,
|
|
22653
|
+
pattern: pattern.source
|
|
22654
|
+
});
|
|
22655
|
+
}
|
|
22656
|
+
seen.delete(value);
|
|
22614
22657
|
}
|
|
22615
|
-
|
|
22616
|
-
|
|
22658
|
+
visit(routes, "");
|
|
22659
|
+
if (!result.length) throw new Error("Screen routes must contain at least one route.");
|
|
22660
|
+
return result;
|
|
22617
22661
|
}
|
|
22662
|
+
//#endregion
|
|
22663
|
+
//#region ../project/src/lib/screens.ts
|
|
22618
22664
|
/**
|
|
22619
|
-
*
|
|
22620
|
-
* @
|
|
22621
|
-
* @returns The fields, with a title.
|
|
22665
|
+
* Bud's browser of your live pages, with their comments.
|
|
22666
|
+
* @returns The built-in screen, ready to relabel by spreading.
|
|
22622
22667
|
*/
|
|
22623
|
-
function
|
|
22624
|
-
let patch = parseBeadPatch(value);
|
|
22625
|
-
if (!patch.title) throw new BeadsValidationError("create.title");
|
|
22668
|
+
function pages() {
|
|
22626
22669
|
return {
|
|
22627
|
-
|
|
22628
|
-
|
|
22670
|
+
id: "pages",
|
|
22671
|
+
label: "Pages",
|
|
22672
|
+
icon: "browser",
|
|
22673
|
+
path: "/pages",
|
|
22674
|
+
module: "bud:pages"
|
|
22629
22675
|
};
|
|
22630
22676
|
}
|
|
22631
22677
|
/**
|
|
22632
|
-
*
|
|
22633
|
-
* @
|
|
22634
|
-
* @returns The action.
|
|
22678
|
+
* Bud's canvas of every route's screenshot.
|
|
22679
|
+
* @returns The built-in screen, ready to relabel by spreading.
|
|
22635
22680
|
*/
|
|
22636
|
-
function
|
|
22637
|
-
|
|
22638
|
-
|
|
22639
|
-
|
|
22640
|
-
|
|
22641
|
-
|
|
22642
|
-
|
|
22643
|
-
|
|
22644
|
-
"patch"
|
|
22645
|
-
]);
|
|
22646
|
-
return {
|
|
22647
|
-
kind: input.kind,
|
|
22648
|
-
expectedRevision,
|
|
22649
|
-
patch: parseBeadPatch(input.patch)
|
|
22650
|
-
};
|
|
22651
|
-
case "claim":
|
|
22652
|
-
strict$1(input, "action", ["kind", "expectedRevision"]);
|
|
22653
|
-
return {
|
|
22654
|
-
kind: input.kind,
|
|
22655
|
-
expectedRevision
|
|
22656
|
-
};
|
|
22657
|
-
case "close":
|
|
22658
|
-
case "reopen":
|
|
22659
|
-
strict$1(input, "action", [
|
|
22660
|
-
"kind",
|
|
22661
|
-
"expectedRevision",
|
|
22662
|
-
"reason"
|
|
22663
|
-
]);
|
|
22664
|
-
return {
|
|
22665
|
-
kind: input.kind,
|
|
22666
|
-
expectedRevision,
|
|
22667
|
-
reason: text$4(input.reason ?? "", "action.reason", 4e3)
|
|
22668
|
-
};
|
|
22669
|
-
case "add-dependency":
|
|
22670
|
-
strict$1(input, "action", [
|
|
22671
|
-
"kind",
|
|
22672
|
-
"expectedRevision",
|
|
22673
|
-
"targetId",
|
|
22674
|
-
"type"
|
|
22675
|
-
]);
|
|
22676
|
-
if (!dependencyTypes.includes(input.type)) throw new BeadsValidationError("action.type");
|
|
22677
|
-
return {
|
|
22678
|
-
kind: input.kind,
|
|
22679
|
-
expectedRevision,
|
|
22680
|
-
targetId: identifier$1(input.targetId, "action.targetId"),
|
|
22681
|
-
type: input.type
|
|
22682
|
-
};
|
|
22683
|
-
case "remove-dependency":
|
|
22684
|
-
strict$1(input, "action", [
|
|
22685
|
-
"kind",
|
|
22686
|
-
"expectedRevision",
|
|
22687
|
-
"targetId"
|
|
22688
|
-
]);
|
|
22689
|
-
return {
|
|
22690
|
-
kind: input.kind,
|
|
22691
|
-
expectedRevision,
|
|
22692
|
-
targetId: identifier$1(input.targetId, "action.targetId")
|
|
22693
|
-
};
|
|
22694
|
-
case "comment": {
|
|
22695
|
-
strict$1(input, "action", [
|
|
22696
|
-
"kind",
|
|
22697
|
-
"expectedRevision",
|
|
22698
|
-
"body"
|
|
22699
|
-
]);
|
|
22700
|
-
let body = nonempty(input.body, "action.body", 6e4);
|
|
22701
|
-
if (new TextEncoder().encode(body).byteLength > 122880) throw new BeadsValidationError("action.body");
|
|
22702
|
-
return {
|
|
22703
|
-
kind: input.kind,
|
|
22704
|
-
expectedRevision,
|
|
22705
|
-
body
|
|
22706
|
-
};
|
|
22707
|
-
}
|
|
22708
|
-
default: throw new BeadsValidationError("action.kind");
|
|
22709
|
-
}
|
|
22681
|
+
function canvas() {
|
|
22682
|
+
return {
|
|
22683
|
+
id: "canvas",
|
|
22684
|
+
label: "Canvas",
|
|
22685
|
+
icon: "grid",
|
|
22686
|
+
path: "/canvas",
|
|
22687
|
+
module: "bud:canvas"
|
|
22688
|
+
};
|
|
22710
22689
|
}
|
|
22711
22690
|
/**
|
|
22712
|
-
*
|
|
22713
|
-
* @
|
|
22714
|
-
* @returns The request.
|
|
22691
|
+
* Bud's Beads task board.
|
|
22692
|
+
* @returns The built-in screen, ready to relabel by spreading.
|
|
22715
22693
|
*/
|
|
22716
|
-
function
|
|
22717
|
-
|
|
22718
|
-
|
|
22719
|
-
|
|
22694
|
+
function tasks() {
|
|
22695
|
+
return {
|
|
22696
|
+
id: "tasks",
|
|
22697
|
+
label: "Tasks",
|
|
22698
|
+
icon: "tasks",
|
|
22699
|
+
path: "/tasks",
|
|
22700
|
+
module: "bud:tasks"
|
|
22701
|
+
};
|
|
22720
22702
|
}
|
|
22721
|
-
//#endregion
|
|
22722
|
-
//#region ../beads/src/lib/protocol.ts
|
|
22723
22703
|
/**
|
|
22724
|
-
*
|
|
22725
|
-
*
|
|
22726
|
-
* @param value - The record.
|
|
22727
|
-
* @param contentLoaded - Whether it holds the issue's long content.
|
|
22728
|
-
* @returns The issue.
|
|
22704
|
+
* Bud's file browser and review.
|
|
22705
|
+
* @returns The built-in screen, ready to relabel by spreading.
|
|
22729
22706
|
*/
|
|
22730
|
-
function
|
|
22731
|
-
|
|
22732
|
-
|
|
22733
|
-
|
|
22734
|
-
|
|
22735
|
-
|
|
22736
|
-
|
|
22737
|
-
|
|
22738
|
-
|
|
22739
|
-
|
|
22740
|
-
|
|
22741
|
-
|
|
22742
|
-
|
|
22743
|
-
|
|
22744
|
-
|
|
22745
|
-
|
|
22746
|
-
|
|
22747
|
-
|
|
22748
|
-
|
|
22749
|
-
|
|
22750
|
-
|
|
22751
|
-
|
|
22752
|
-
|
|
22753
|
-
|
|
22754
|
-
|
|
22755
|
-
|
|
22707
|
+
function files() {
|
|
22708
|
+
return {
|
|
22709
|
+
id: "files",
|
|
22710
|
+
label: "Files",
|
|
22711
|
+
icon: "files",
|
|
22712
|
+
path: "/files",
|
|
22713
|
+
module: "bud:files"
|
|
22714
|
+
};
|
|
22715
|
+
}
|
|
22716
|
+
/** The screens shown when the config doesn't list any. */
|
|
22717
|
+
var defaultScreens = [
|
|
22718
|
+
pages(),
|
|
22719
|
+
canvas(),
|
|
22720
|
+
tasks(),
|
|
22721
|
+
files()
|
|
22722
|
+
];
|
|
22723
|
+
var builtIns$1 = new Map(defaultScreens.map((screen) => [screen.module, screen]));
|
|
22724
|
+
/** First path segments owned by Bud's routes, its development server and its public assets. */
|
|
22725
|
+
var reservedScreenPaths = [
|
|
22726
|
+
"/comments",
|
|
22727
|
+
...defaultScreens.map((screen) => screen.path),
|
|
22728
|
+
"/api",
|
|
22729
|
+
"/health",
|
|
22730
|
+
"/__bud",
|
|
22731
|
+
"/assets",
|
|
22732
|
+
"/app",
|
|
22733
|
+
"/src",
|
|
22734
|
+
"/node_modules",
|
|
22735
|
+
"/agents",
|
|
22736
|
+
"/brand",
|
|
22737
|
+
"/fonts",
|
|
22738
|
+
"/frameworks",
|
|
22739
|
+
"/licenses"
|
|
22740
|
+
];
|
|
22741
|
+
var ScreenSchema = check(described(any().refine((value) => !!value && typeof value === "object"), "Each screen must be an object."), ({ id, label, icon = "layers", module, path }) => {
|
|
22742
|
+
if (typeof id !== "string" || !/^[a-z][a-z0-9-]{0,31}$/.test(id)) return "Screen ids are 1-32 lowercase letters, digits and hyphens.";
|
|
22743
|
+
if (typeof label !== "string" || !label.trim()) return `Screen ${id} needs a label.`;
|
|
22744
|
+
if (typeof icon !== "string" || !icon) return `Screen ${id} needs an icon name.`;
|
|
22745
|
+
if (typeof module !== "string") return `Screen ${id} needs a module.`;
|
|
22746
|
+
let builtIn = builtIns$1.get(module);
|
|
22747
|
+
path ??= builtIn?.path ?? `/${id}`;
|
|
22748
|
+
if (builtIn) {
|
|
22749
|
+
if (id !== builtIn.id || path !== builtIn.path) return `${module} keeps id "${builtIn.id}" and path "${builtIn.path}".`;
|
|
22750
|
+
return;
|
|
22756
22751
|
}
|
|
22757
|
-
return
|
|
22758
|
-
|
|
22759
|
-
|
|
22760
|
-
|
|
22761
|
-
|
|
22762
|
-
|
|
22763
|
-
|
|
22764
|
-
|
|
22765
|
-
|
|
22766
|
-
|
|
22767
|
-
|
|
22768
|
-
|
|
22769
|
-
|
|
22770
|
-
|
|
22771
|
-
|
|
22772
|
-
|
|
22773
|
-
|
|
22774
|
-
|
|
22775
|
-
|
|
22776
|
-
|
|
22777
|
-
|
|
22778
|
-
|
|
22779
|
-
|
|
22780
|
-
|
|
22781
|
-
|
|
22782
|
-
dependencies: dependencies("dependencies"),
|
|
22783
|
-
dependents: dependencies("dependents"),
|
|
22784
|
-
dependencyCount: input.dependency_count ?? null,
|
|
22785
|
-
dependentCount: input.dependent_count ?? null,
|
|
22786
|
-
commentCount: input.comment_count ?? null,
|
|
22787
|
-
epicTotalChildren: input.epic_total_children ?? null,
|
|
22788
|
-
epicClosedChildren: input.epic_closed_children ?? null,
|
|
22789
|
-
epicCloseable: input.epic_closeable ?? null,
|
|
22790
|
-
revision: input.revision ?? null,
|
|
22791
|
-
metadata: input.metadata ?? null,
|
|
22792
|
-
contentLoaded
|
|
22752
|
+
if (!/^\.\/[^\0\\]+\.(?:ts|tsx|js|jsx)$/.test(module)) return `Screen ${id} must be a ./ module ending in .ts, .tsx, .js or .jsx.`;
|
|
22753
|
+
if (typeof path !== "string" || !/^\/[a-z0-9][a-z0-9-]*$/.test(path)) return `Screen ${id} needs one static path segment, such as /${id}.`;
|
|
22754
|
+
if (defaultScreens.some((screen) => screen.id === id)) return `Screen id "${id}" is reserved for a built-in screen.`;
|
|
22755
|
+
if (reservedScreenPaths.includes(path)) return `Bud reserves the path ${path}.`;
|
|
22756
|
+
});
|
|
22757
|
+
/** Bud's navigation in order; file and icon checks need the app and happen at startup. */
|
|
22758
|
+
var BudScreensSchema = defaulted(delegate(described(array(any()).refine((screens) => screens.length > 0), "screens must list at least one screen."), (screens) => {
|
|
22759
|
+
let ids = /* @__PURE__ */ new Set();
|
|
22760
|
+
let paths = /* @__PURE__ */ new Set();
|
|
22761
|
+
return screens.map((value) => {
|
|
22762
|
+
let input = parseConfig(ScreenSchema, value);
|
|
22763
|
+
let { id, label, icon = "layers", module } = input;
|
|
22764
|
+
let path = input.path ?? builtIns$1.get(module)?.path ?? `/${id}`;
|
|
22765
|
+
if (ids.has(id) || paths.has(path)) throw new Error("Screen ids and paths must be unique.");
|
|
22766
|
+
ids.add(id);
|
|
22767
|
+
paths.add(path);
|
|
22768
|
+
if (input.routes !== void 0) resolveScreenRoutes(input.routes, path);
|
|
22769
|
+
return {
|
|
22770
|
+
id,
|
|
22771
|
+
label,
|
|
22772
|
+
icon,
|
|
22773
|
+
path,
|
|
22774
|
+
module,
|
|
22775
|
+
...input.routes === void 0 ? {} : { routes: input.routes }
|
|
22776
|
+
};
|
|
22793
22777
|
});
|
|
22778
|
+
}), () => defaultScreens.map((screen) => ({ ...screen })));
|
|
22779
|
+
//#endregion
|
|
22780
|
+
//#region ../project/src/lib/config.ts
|
|
22781
|
+
var objectLike = (value) => !!value && typeof value === "object";
|
|
22782
|
+
function section$3(message, shape) {
|
|
22783
|
+
let schema = object(shape);
|
|
22784
|
+
return createSchema((value, context) => objectLike(value) ? schema["~run"](Array.isArray(value) ? { ...value } : value, context) : fail(message, context.path));
|
|
22794
22785
|
}
|
|
22795
|
-
|
|
22796
|
-
|
|
22797
|
-
|
|
22798
|
-
|
|
22799
|
-
|
|
22800
|
-
|
|
22801
|
-
|
|
22802
|
-
|
|
22803
|
-
|
|
22786
|
+
var ConfigFields = check(section$3("Bud config must export an object.", {
|
|
22787
|
+
routes: described(array(any()).refine((routes) => routes.length > 0), "bud.config must define at least one route."),
|
|
22788
|
+
name: optional(described(string().refine((name) => !!name.trim()), "name must be a nonempty string.")),
|
|
22789
|
+
target: optional(section$3("target must be an object.", {
|
|
22790
|
+
origin: any(),
|
|
22791
|
+
port: any()
|
|
22792
|
+
})),
|
|
22793
|
+
comments: optional(section$3("comments must be an object.", { remote: any() })),
|
|
22794
|
+
port: any(),
|
|
22795
|
+
screens: any(),
|
|
22796
|
+
storage: any(),
|
|
22797
|
+
designs: any(),
|
|
22798
|
+
previews: any(),
|
|
22799
|
+
files: any(),
|
|
22800
|
+
logo: any(),
|
|
22801
|
+
theme: any(),
|
|
22802
|
+
picker: any(),
|
|
22803
|
+
branches: any(),
|
|
22804
|
+
flows: any()
|
|
22805
|
+
}), ({ target }) => {
|
|
22806
|
+
if (target?.port === 0) return "target.port must be a listening port, not zero.";
|
|
22807
|
+
if (target?.origin !== void 0 && (typeof target.origin !== "string" || !target.origin.trim())) return "target.origin must be an HTTP(S) origin.";
|
|
22808
|
+
});
|
|
22809
|
+
var RouteSchema = check(any().transform((input) => typeof input === "string" ? {
|
|
22810
|
+
path: input,
|
|
22811
|
+
title: input === "/" ? "Home" : input
|
|
22812
|
+
} : input), (route) => {
|
|
22813
|
+
if (!route || typeof route.path !== "string" || !route.path.startsWith("/") || route.path.startsWith("//")) return "Canvas routes must be local paths beginning with a single /.";
|
|
22814
|
+
if (new URL(route.path, "http://bud.invalid").origin !== "http://bud.invalid" || route.path.includes("\\")) return "Canvas routes must stay on the target origin.";
|
|
22815
|
+
let id = route.id ?? routePath(route.path);
|
|
22816
|
+
if (typeof id !== "string" || !id.trim()) return "Canvas route IDs and paths must be unique.";
|
|
22817
|
+
if (typeof route.title !== "string" || !route.title.trim()) return "Each route needs a title.";
|
|
22818
|
+
if (route.pattern !== void 0 && (typeof route.pattern !== "string" || !route.pattern.startsWith("/"))) return "A route pattern must be a path beginning with /.";
|
|
22819
|
+
if (route.group !== void 0 && (typeof route.group !== "string" || !route.group.trim() || route.group.length > 120)) return "A route group must be a short name.";
|
|
22820
|
+
if (route.ready !== void 0 && route.ready !== false && !parseSafe(PreviewReadySchema, route.ready).success) return "A route ready must be false, a CSS selector, { selector } or { expression }.";
|
|
22821
|
+
}).transform((route) => {
|
|
22822
|
+
let path = routePath(route.path);
|
|
22823
|
+
return {
|
|
22824
|
+
id: route.id ?? path,
|
|
22825
|
+
path,
|
|
22826
|
+
title: route.title,
|
|
22827
|
+
...route.pattern === void 0 ? {} : { pattern: route.pattern },
|
|
22828
|
+
...route.group === void 0 ? {} : { group: route.group.trim() },
|
|
22829
|
+
...route.ready === void 0 ? {} : { ready: route.ready === false ? null : parseConfig(PreviewReadySchema, route.ready) }
|
|
22830
|
+
};
|
|
22831
|
+
});
|
|
22832
|
+
function routePath(path) {
|
|
22833
|
+
let url = new URL(path, "http://bud.invalid");
|
|
22834
|
+
let search = url.searchParams.toString();
|
|
22835
|
+
return `${url.pathname}${search ? `?${search}` : ""}${url.hash}`;
|
|
22836
|
+
}
|
|
22837
|
+
/** Canvas routes with unique IDs and normalized paths. */
|
|
22838
|
+
var BudRoutesSchema = check(list(RouteSchema, "bud.config must define at least one route.", 1), (routes) => new Set(routes.map((route) => route.id)).size !== routes.length || new Set(routes.map((route) => route.path)).size !== routes.length ? "Canvas route IDs and paths must be unique." : void 0);
|
|
22839
|
+
var port = (value) => value === void 0 || Number.isInteger(value) && value >= 0 && value <= 65535;
|
|
22840
|
+
var remote$1 = (value) => typeof value === "string" && /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(value);
|
|
22841
|
+
/** A project's config, validated and normalized before Bud starts any listeners. */
|
|
22842
|
+
var BudConfigSchema = delegate(check(delegate(check(delegate(check(delegate(ConfigFields, (config) => ({
|
|
22843
|
+
...config,
|
|
22844
|
+
routes: parseConfig(BudRoutesSchema, config.routes)
|
|
22845
|
+
})), ({ port: companion, target }) => port(companion ?? 0) && port(target?.port) ? void 0 : "Ports must be integers between 0 and 65535."), ({ target, ...config }) => {
|
|
22846
|
+
let origin = target?.origin ?? (target?.port ? `http://127.0.0.1:${target.port}` : void 0);
|
|
22847
|
+
return {
|
|
22848
|
+
...config,
|
|
22849
|
+
targetOrigin: origin ? normalizeOrigin(origin, "target.origin must be an HTTP(S) origin without a path or credentials.") : origin
|
|
22850
|
+
};
|
|
22851
|
+
}), ({ comments }) => remote$1(comments?.remote ?? "origin") ? void 0 : "comments.remote must be a Git remote name."), (config) => {
|
|
22852
|
+
let flows = parseConfig(BudFlowsSchema, config.flows);
|
|
22853
|
+
let steps = flows.flatMap((flow) => flow.steps.map((step) => flowStepRoute(flow, step)));
|
|
22854
|
+
let home = config.routes.findIndex((route) => route.path === "/") + 1;
|
|
22855
|
+
return {
|
|
22856
|
+
...config,
|
|
22857
|
+
flows,
|
|
22858
|
+
routes: [
|
|
22859
|
+
...config.routes.slice(0, home),
|
|
22860
|
+
...steps,
|
|
22861
|
+
...config.routes.slice(home)
|
|
22862
|
+
]
|
|
22863
|
+
};
|
|
22864
|
+
}), ({ routes }) => new Set(routes.map((route) => route.id)).size !== routes.length || new Set(routes.map((route) => route.path)).size !== routes.length ? "A flow step must not reuse a route’s id or path." : void 0), (config) => ({
|
|
22865
|
+
name: config.name ?? "Your project",
|
|
22866
|
+
routes: config.routes,
|
|
22867
|
+
flows: config.flows,
|
|
22868
|
+
port: config.port ?? 0,
|
|
22869
|
+
targetOrigin: config.targetOrigin,
|
|
22870
|
+
remote: config.comments?.remote ?? "origin",
|
|
22871
|
+
screens: parseConfig(BudScreensSchema, config.screens),
|
|
22872
|
+
storage: parseConfig(BudStorageSchema, config.storage),
|
|
22873
|
+
designs: parseConfig(BudDesignsSchema, config.designs),
|
|
22874
|
+
previews: parseConfig(BudPreviewsSchema, config.previews),
|
|
22875
|
+
files: parseConfig(BudFilesSchema, config.files),
|
|
22876
|
+
logo: parseConfig(BudLogoSchema, config.logo),
|
|
22877
|
+
theme: parseConfig(BudThemeSchema, config.theme),
|
|
22878
|
+
picker: parseConfig(BudPickerSchema, config.picker),
|
|
22879
|
+
branches: parseConfig(BudBranchesSchema, config.branches)
|
|
22880
|
+
}));
|
|
22881
|
+
/**
|
|
22882
|
+
* Validates executable project config before starting any listeners.
|
|
22883
|
+
* @param value - The config module's default export.
|
|
22884
|
+
* @returns Normalized route IDs, ports, and URLs.
|
|
22885
|
+
*/
|
|
22886
|
+
function resolveBudConfig(value) {
|
|
22887
|
+
return parseConfig(BudConfigSchema, value);
|
|
22888
|
+
}
|
|
22889
|
+
/**
|
|
22890
|
+
* Returns a bare HTTP(S) origin, rejecting credentials, paths, queries and fragments.
|
|
22891
|
+
* @param origin - The URL to check.
|
|
22892
|
+
* @param message - The error's message when it is not a bare origin.
|
|
22893
|
+
* @returns The URL's origin.
|
|
22894
|
+
*/
|
|
22895
|
+
function normalizeOrigin(origin, message) {
|
|
22896
|
+
let url = new URL(origin);
|
|
22897
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.pathname !== "/" || url.search || url.hash) throw new Error(message);
|
|
22898
|
+
return url.origin;
|
|
22899
|
+
}
|
|
22900
|
+
//#endregion
|
|
22901
|
+
//#region ../beads/src/lib/contracts.ts
|
|
22902
|
+
var MAX_BEADS = 1e3;
|
|
22903
|
+
//#endregion
|
|
22904
|
+
//#region ../beads/src/lib/validation.ts
|
|
22905
|
+
/** Beads returned or was sent data of the wrong shape; `field` names where. */
|
|
22906
|
+
var BeadsValidationError = class extends Error {
|
|
22907
|
+
field;
|
|
22908
|
+
constructor(field) {
|
|
22909
|
+
super(`Invalid Beads data at ${field}.`);
|
|
22910
|
+
this.name = "BeadsValidationError";
|
|
22911
|
+
this.field = field;
|
|
22912
|
+
}
|
|
22913
|
+
};
|
|
22914
|
+
/**
|
|
22915
|
+
* Checks that a value is a JSON object.
|
|
22916
|
+
* @param value - Untrusted data.
|
|
22917
|
+
* @param field - Where the value sits, for the error.
|
|
22918
|
+
* @returns The object.
|
|
22919
|
+
*/
|
|
22920
|
+
function object$1(value, field) {
|
|
22921
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new BeadsValidationError(field);
|
|
22922
|
+
return value;
|
|
22923
|
+
}
|
|
22924
|
+
/**
|
|
22925
|
+
* Checks a string without NUL characters within a length limit.
|
|
22926
|
+
* @param value - Untrusted data.
|
|
22927
|
+
* @param field - Where the value sits, for the error.
|
|
22928
|
+
* @param max - The most characters it may have.
|
|
22929
|
+
* @returns The string.
|
|
22930
|
+
*/
|
|
22931
|
+
function text$3(value, field, max = 262144) {
|
|
22932
|
+
if (typeof value !== "string" || value.length > max || value.includes("\0")) throw new BeadsValidationError(field);
|
|
22933
|
+
return value;
|
|
22934
|
+
}
|
|
22935
|
+
/**
|
|
22936
|
+
* Checks an ID, name or commit made of letters, digits, dots, underscores and hyphens.
|
|
22937
|
+
* @param value - Untrusted data.
|
|
22938
|
+
* @param field - Where the value sits, for the error.
|
|
22939
|
+
* @returns The identifier.
|
|
22940
|
+
*/
|
|
22941
|
+
function identifier$1(value, field) {
|
|
22942
|
+
let result = text$3(value, field, 512);
|
|
22943
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(result)) throw new BeadsValidationError(field);
|
|
22944
|
+
return result;
|
|
22945
|
+
}
|
|
22946
|
+
/**
|
|
22947
|
+
* Checks an optional string.
|
|
22948
|
+
* @param value - Untrusted data.
|
|
22949
|
+
* @param field - Where the value sits, for the error.
|
|
22950
|
+
* @param max - The most characters it may have.
|
|
22951
|
+
* @returns The string, or null when it is missing.
|
|
22952
|
+
*/
|
|
22953
|
+
function nullableText(value, field, max) {
|
|
22954
|
+
return value == null ? null : text$3(value, field, max);
|
|
22955
|
+
}
|
|
22956
|
+
/**
|
|
22957
|
+
* Checks a nonnegative safe integer.
|
|
22958
|
+
* @param value - Untrusted data.
|
|
22959
|
+
* @param field - Where the value sits, for the error.
|
|
22960
|
+
* @param max - The largest value it may have.
|
|
22961
|
+
* @returns The integer.
|
|
22962
|
+
*/
|
|
22963
|
+
function integer(value, field, max = Number.MAX_SAFE_INTEGER) {
|
|
22964
|
+
if (!Number.isSafeInteger(value) || value < 0 || value > max) throw new BeadsValidationError(field);
|
|
22965
|
+
return value;
|
|
22966
|
+
}
|
|
22967
|
+
/**
|
|
22968
|
+
* Checks an optional nonnegative integer.
|
|
22969
|
+
* @param value - Untrusted data.
|
|
22970
|
+
* @param field - Where the value sits, for the error.
|
|
22971
|
+
* @param max - The largest value it may have.
|
|
22972
|
+
* @returns The integer, or null when it is missing.
|
|
22973
|
+
*/
|
|
22974
|
+
function nullableInteger(value, field, max) {
|
|
22975
|
+
return value == null ? null : integer(value, field, max);
|
|
22976
|
+
}
|
|
22977
|
+
/**
|
|
22978
|
+
* Checks a boolean.
|
|
22979
|
+
* @param value - Untrusted data.
|
|
22980
|
+
* @param field - Where the value sits, for the error.
|
|
22981
|
+
* @returns The boolean.
|
|
22982
|
+
*/
|
|
22983
|
+
function boolean$2(value, field) {
|
|
22984
|
+
if (typeof value !== "boolean") throw new BeadsValidationError(field);
|
|
22985
|
+
return value;
|
|
22986
|
+
}
|
|
22987
|
+
/**
|
|
22988
|
+
* Checks an ISO 8601 timestamp with a time zone.
|
|
22989
|
+
* @param value - Untrusted data.
|
|
22990
|
+
* @param field - Where the value sits, for the error.
|
|
22991
|
+
* @returns The timestamp as given.
|
|
22992
|
+
*/
|
|
22993
|
+
function timestamp(value, field) {
|
|
22994
|
+
let result = text$3(value, field, 64);
|
|
22995
|
+
if (!/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?(?:Z|[+-]\d\d:\d\d)$/.test(result) || !Number.isFinite(Date.parse(result))) throw new BeadsValidationError(field);
|
|
22996
|
+
return result;
|
|
22997
|
+
}
|
|
22998
|
+
/**
|
|
22999
|
+
* Checks an optional ISO 8601 timestamp.
|
|
23000
|
+
* @param value - Untrusted data.
|
|
23001
|
+
* @param field - Where the value sits, for the error.
|
|
23002
|
+
* @returns The timestamp, or null when it is missing.
|
|
23003
|
+
*/
|
|
23004
|
+
function nullableTimestamp(value, field) {
|
|
23005
|
+
return value == null ? null : timestamp(value, field);
|
|
23006
|
+
}
|
|
23007
|
+
/**
|
|
23008
|
+
* Checks a bounded array and each of its entries.
|
|
23009
|
+
* @param value - Untrusted data.
|
|
23010
|
+
* @param field - Where the value sits, for the error.
|
|
23011
|
+
* @param parse - Checks one entry.
|
|
23012
|
+
* @param max - The most entries it may have.
|
|
23013
|
+
* @returns The parsed entries.
|
|
23014
|
+
*/
|
|
23015
|
+
function array$1(value, field, parse, max = 1e4) {
|
|
23016
|
+
if (!Array.isArray(value) || value.length > max) throw new BeadsValidationError(field);
|
|
23017
|
+
return value.map((entry, i) => parse(entry, `${field}[${i}]`));
|
|
23018
|
+
}
|
|
23019
|
+
/**
|
|
23020
|
+
* Checks that no two values share a key.
|
|
23021
|
+
* @param values - The values to check.
|
|
23022
|
+
* @param key - Gives each value's key.
|
|
23023
|
+
* @param field - Where the values sit, for the error.
|
|
23024
|
+
* @returns The values.
|
|
23025
|
+
*/
|
|
23026
|
+
function unique(values, key, field) {
|
|
23027
|
+
if (new Set(values.map(key)).size !== values.length) throw new BeadsValidationError(field);
|
|
23028
|
+
return values;
|
|
23029
|
+
}
|
|
23030
|
+
/**
|
|
23031
|
+
* Checks JSON metadata, bounding its depth and its total number of values.
|
|
23032
|
+
* @param value - Untrusted data.
|
|
23033
|
+
* @param field - Where the value sits, for the error.
|
|
23034
|
+
* @param depth - How deep this value is nested.
|
|
23035
|
+
* @param budget - The values left to check, shared across the whole tree.
|
|
23036
|
+
* @returns The metadata.
|
|
23037
|
+
*/
|
|
23038
|
+
function metadata(value, field, depth = 0, budget = { remaining: 1e4 }) {
|
|
23039
|
+
if (depth > 12 || --budget.remaining < 0) throw new BeadsValidationError(field);
|
|
23040
|
+
if (value === null || typeof value === "boolean") return value;
|
|
23041
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
23042
|
+
if (typeof value === "string") return text$3(value, field);
|
|
23043
|
+
if (Array.isArray(value)) return value.map((entry, index) => metadata(entry, `${field}[${index}]`, depth + 1, budget));
|
|
23044
|
+
let input = object$1(value, field);
|
|
23045
|
+
return Object.fromEntries(Object.entries(input).map(([key, entry]) => [text$3(key, field, 512), metadata(entry, `${field}.${key}`, depth + 1, budget)]));
|
|
23046
|
+
}
|
|
23047
|
+
function dependency(value, field) {
|
|
23048
|
+
let input = object$1(value, field);
|
|
23049
|
+
return {
|
|
23050
|
+
id: identifier$1(input.id, `${field}.id`),
|
|
23051
|
+
type: identifier$1(input.type, `${field}.type`),
|
|
23052
|
+
title: nullableText(input.title, `${field}.title`),
|
|
23053
|
+
status: nullableText(input.status, `${field}.status`, 128),
|
|
23054
|
+
priority: nullableInteger(input.priority, `${field}.priority`, 4),
|
|
23055
|
+
issueType: nullableText(input.issueType, `${field}.issueType`, 128)
|
|
23056
|
+
};
|
|
23057
|
+
}
|
|
23058
|
+
/**
|
|
23059
|
+
* Validates an issue.
|
|
23060
|
+
* @param value - Untrusted data.
|
|
23061
|
+
* @param field - Where the value sits, for the error.
|
|
23062
|
+
* @returns The issue.
|
|
23063
|
+
*/
|
|
23064
|
+
function parseBeadIssue(value, field = "issue") {
|
|
23065
|
+
let input = object$1(value, field);
|
|
23066
|
+
let string = (key, max) => text$3(input[key], `${field}.${key}`, max);
|
|
23067
|
+
let optional = (key, max) => nullableText(input[key], `${field}.${key}`, max);
|
|
23068
|
+
let count = (key) => nullableInteger(input[key], `${field}.${key}`);
|
|
23069
|
+
let date = (key) => nullableTimestamp(input[key], `${field}.${key}`);
|
|
23070
|
+
return {
|
|
23071
|
+
id: identifier$1(input.id, `${field}.id`),
|
|
23072
|
+
title: string("title"),
|
|
23073
|
+
status: identifier$1(input.status, `${field}.status`),
|
|
23074
|
+
priority: integer(input.priority, `${field}.priority`, 4),
|
|
23075
|
+
type: identifier$1(input.type, `${field}.type`),
|
|
23076
|
+
description: string("description"),
|
|
23077
|
+
design: string("design"),
|
|
23078
|
+
acceptanceCriteria: string("acceptanceCriteria"),
|
|
23079
|
+
notes: string("notes"),
|
|
23080
|
+
assignee: optional("assignee"),
|
|
23081
|
+
owner: optional("owner"),
|
|
23082
|
+
createdBy: optional("createdBy"),
|
|
23083
|
+
createdAt: timestamp(input.createdAt, `${field}.createdAt`),
|
|
23084
|
+
updatedAt: timestamp(input.updatedAt, `${field}.updatedAt`),
|
|
23085
|
+
startedAt: date("startedAt"),
|
|
23086
|
+
closedAt: date("closedAt"),
|
|
23087
|
+
closeReason: optional("closeReason"),
|
|
23088
|
+
dueAt: date("dueAt"),
|
|
23089
|
+
deferUntil: date("deferUntil"),
|
|
23090
|
+
estimatedMinutes: count("estimatedMinutes"),
|
|
23091
|
+
externalRef: optional("externalRef"),
|
|
23092
|
+
specId: optional("specId"),
|
|
23093
|
+
labels: unique(array$1(input.labels, `${field}.labels`, (item, path) => text$3(item, path, 512), 1e3), (item) => item, `${field}.labels`),
|
|
23094
|
+
parentId: input.parentId == null ? null : identifier$1(input.parentId, `${field}.parentId`),
|
|
23095
|
+
dependencies: array$1(input.dependencies, `${field}.dependencies`, dependency),
|
|
23096
|
+
dependents: array$1(input.dependents, `${field}.dependents`, dependency),
|
|
23097
|
+
dependencyCount: count("dependencyCount"),
|
|
23098
|
+
dependentCount: count("dependentCount"),
|
|
23099
|
+
commentCount: count("commentCount"),
|
|
23100
|
+
epicTotalChildren: count("epicTotalChildren"),
|
|
23101
|
+
epicClosedChildren: count("epicClosedChildren"),
|
|
23102
|
+
epicCloseable: input.epicCloseable == null ? null : boolean$2(input.epicCloseable, `${field}.epicCloseable`),
|
|
23103
|
+
revision: optional("revision", 256),
|
|
23104
|
+
metadata: metadata(input.metadata, `${field}.metadata`),
|
|
23105
|
+
contentLoaded: boolean$2(input.contentLoaded, `${field}.contentLoaded`)
|
|
23106
|
+
};
|
|
23107
|
+
}
|
|
23108
|
+
/**
|
|
23109
|
+
* Validates a connection state.
|
|
23110
|
+
* @param value - Untrusted data.
|
|
23111
|
+
* @returns The connection.
|
|
23112
|
+
*/
|
|
23113
|
+
function parseBeadsConnection(value) {
|
|
23114
|
+
let input = object$1(value, "connection");
|
|
23115
|
+
if (input.state === "ready") {
|
|
23116
|
+
let project = object$1(input.project, "connection.project");
|
|
23117
|
+
if (input.schemaVersion !== 1) throw new BeadsValidationError("connection.schemaVersion");
|
|
23118
|
+
return {
|
|
23119
|
+
state: "ready",
|
|
23120
|
+
version: text$3(input.version, "connection.version", 128),
|
|
23121
|
+
schemaVersion: 1,
|
|
23122
|
+
project: {
|
|
23123
|
+
name: text$3(project.name, "project.name", 512),
|
|
23124
|
+
prefix: nullableText(project.prefix, "project.prefix", 512),
|
|
23125
|
+
backend: text$3(project.backend, "project.backend", 128),
|
|
23126
|
+
mode: nullableText(project.mode, "project.mode", 128),
|
|
23127
|
+
redirected: boolean$2(project.redirected, "project.redirected"),
|
|
23128
|
+
worktree: boolean$2(project.worktree, "project.worktree")
|
|
23129
|
+
}
|
|
23130
|
+
};
|
|
23131
|
+
}
|
|
23132
|
+
if (![
|
|
23133
|
+
"unavailable",
|
|
23134
|
+
"not-initialized",
|
|
23135
|
+
"unsupported-version"
|
|
23136
|
+
].includes(input.state)) throw new BeadsValidationError("connection.state");
|
|
23137
|
+
return {
|
|
23138
|
+
state: input.state,
|
|
23139
|
+
version: nullableText(input.version, "connection.version", 128),
|
|
23140
|
+
message: text$3(input.message, "connection.message", 1e3)
|
|
23141
|
+
};
|
|
23142
|
+
}
|
|
23143
|
+
/**
|
|
23144
|
+
* Validates a status definition.
|
|
23145
|
+
* @param value - Untrusted data.
|
|
23146
|
+
* @param field - Where the value sits, for the error.
|
|
23147
|
+
* @returns The status.
|
|
23148
|
+
*/
|
|
23149
|
+
function parseBeadStatus(value, field = "status") {
|
|
23150
|
+
let input = object$1(value, field);
|
|
23151
|
+
return {
|
|
23152
|
+
name: identifier$1(input.name, `${field}.name`),
|
|
23153
|
+
category: nullableText(input.category, `${field}.category`, 128),
|
|
23154
|
+
description: text$3(input.description, `${field}.description`),
|
|
23155
|
+
custom: boolean$2(input.custom, `${field}.custom`)
|
|
23156
|
+
};
|
|
23157
|
+
}
|
|
23158
|
+
/**
|
|
23159
|
+
* Validates an issue type definition.
|
|
23160
|
+
* @param value - Untrusted data.
|
|
23161
|
+
* @param field - Where the value sits, for the error.
|
|
23162
|
+
* @returns The type.
|
|
23163
|
+
*/
|
|
23164
|
+
function parseBeadType(value, field = "type") {
|
|
23165
|
+
let input = object$1(value, field);
|
|
23166
|
+
if (![
|
|
23167
|
+
"core",
|
|
23168
|
+
"system",
|
|
23169
|
+
"custom"
|
|
23170
|
+
].includes(input.kind)) throw new BeadsValidationError(`${field}.kind`);
|
|
23171
|
+
return {
|
|
23172
|
+
name: identifier$1(input.name, `${field}.name`),
|
|
23173
|
+
description: text$3(input.description, `${field}.description`),
|
|
23174
|
+
kind: input.kind
|
|
23175
|
+
};
|
|
23176
|
+
}
|
|
23177
|
+
/**
|
|
23178
|
+
* Validates a board snapshot, which holds no rows unless its connection is ready.
|
|
23179
|
+
* @param value - Untrusted data.
|
|
23180
|
+
* @returns The snapshot.
|
|
23181
|
+
*/
|
|
23182
|
+
function parseBeadsSnapshot(value) {
|
|
23183
|
+
let input = object$1(value, "snapshot");
|
|
23184
|
+
let connection = parseBeadsConnection(input.connection);
|
|
23185
|
+
let issues = unique(array$1(input.issues, "snapshot.issues", parseBeadIssue, MAX_BEADS), (issue) => issue.id, "snapshot.issues");
|
|
23186
|
+
let readyIds = unique(array$1(input.readyIds, "snapshot.readyIds", identifier$1, MAX_BEADS), (id) => id, "snapshot.readyIds");
|
|
23187
|
+
let statuses = unique(array$1(input.statuses, "snapshot.statuses", parseBeadStatus, 1e3), (item) => item.name, "snapshot.statuses");
|
|
23188
|
+
let types = unique(array$1(input.types, "snapshot.types", parseBeadType, 1e3), (item) => item.name, "snapshot.types");
|
|
23189
|
+
let truncated = boolean$2(input.truncated, "snapshot.truncated");
|
|
23190
|
+
if (connection.state !== "ready" && (issues.length || readyIds.length || statuses.length || types.length || truncated)) throw new BeadsValidationError("snapshot.connection");
|
|
23191
|
+
return {
|
|
23192
|
+
connection,
|
|
23193
|
+
issues,
|
|
23194
|
+
readyIds,
|
|
23195
|
+
statuses,
|
|
23196
|
+
types,
|
|
23197
|
+
truncated
|
|
23198
|
+
};
|
|
23199
|
+
}
|
|
23200
|
+
/**
|
|
23201
|
+
* Validates an issue comment.
|
|
23202
|
+
* @param value - Untrusted data.
|
|
23203
|
+
* @param field - Where the value sits, for the error.
|
|
23204
|
+
* @returns The comment.
|
|
23205
|
+
*/
|
|
23206
|
+
function parseBeadDiscussion(value, field = "discussion") {
|
|
23207
|
+
let input = object$1(value, field);
|
|
23208
|
+
return {
|
|
23209
|
+
id: identifier$1(input.id, `${field}.id`),
|
|
23210
|
+
issueId: identifier$1(input.issueId, `${field}.issueId`),
|
|
23211
|
+
author: text$3(input.author, `${field}.author`),
|
|
23212
|
+
body: text$3(input.body, `${field}.body`),
|
|
23213
|
+
createdAt: timestamp(input.createdAt, `${field}.createdAt`)
|
|
23214
|
+
};
|
|
23215
|
+
}
|
|
23216
|
+
/**
|
|
23217
|
+
* Validates an issue's version at one commit.
|
|
23218
|
+
* @param value - Untrusted data.
|
|
23219
|
+
* @param field - Where the value sits, for the error.
|
|
23220
|
+
* @returns The version.
|
|
23221
|
+
*/
|
|
23222
|
+
function parseBeadHistory(value, field = "history") {
|
|
23223
|
+
let input = object$1(value, field);
|
|
23224
|
+
return {
|
|
23225
|
+
commit: identifier$1(input.commit, `${field}.commit`),
|
|
23226
|
+
author: text$3(input.author, `${field}.author`),
|
|
23227
|
+
createdAt: timestamp(input.createdAt, `${field}.createdAt`),
|
|
23228
|
+
issue: input.issue == null ? null : parseBeadIssue(input.issue, `${field}.issue`)
|
|
23229
|
+
};
|
|
23230
|
+
}
|
|
23231
|
+
/**
|
|
23232
|
+
* Validates an event log entry.
|
|
23233
|
+
* @param value - Untrusted data.
|
|
23234
|
+
* @param field - Where the value sits, for the error.
|
|
23235
|
+
* @returns The event.
|
|
23236
|
+
*/
|
|
23237
|
+
function parseBeadEvent(value, field = "event") {
|
|
23238
|
+
let input = object$1(value, field);
|
|
23239
|
+
return {
|
|
23240
|
+
id: identifier$1(input.id, `${field}.id`),
|
|
23241
|
+
issueId: identifier$1(input.issueId, `${field}.issueId`),
|
|
23242
|
+
type: identifier$1(input.type, `${field}.type`),
|
|
23243
|
+
actor: text$3(input.actor, `${field}.actor`),
|
|
23244
|
+
oldValue: nullableText(input.oldValue, `${field}.oldValue`),
|
|
23245
|
+
newValue: nullableText(input.newValue, `${field}.newValue`),
|
|
23246
|
+
comment: nullableText(input.comment, `${field}.comment`),
|
|
23247
|
+
createdAt: timestamp(input.createdAt, `${field}.createdAt`)
|
|
23248
|
+
};
|
|
23249
|
+
}
|
|
23250
|
+
function section$2(value, field, parser, max) {
|
|
23251
|
+
let input = object$1(value, field);
|
|
23252
|
+
let items = array$1(input.items, `${field}.items`, parser, max);
|
|
23253
|
+
let truncated = boolean$2(input.truncated, `${field}.truncated`);
|
|
23254
|
+
if (input.state === "available") return {
|
|
23255
|
+
state: "available",
|
|
23256
|
+
items,
|
|
23257
|
+
truncated
|
|
23258
|
+
};
|
|
23259
|
+
if (input.state === "unsupported" && items.length === 0 && truncated === false) return {
|
|
23260
|
+
state: "unsupported",
|
|
23261
|
+
items: [],
|
|
23262
|
+
truncated: false
|
|
23263
|
+
};
|
|
23264
|
+
throw new BeadsValidationError(`${field}.state`);
|
|
23265
|
+
}
|
|
23266
|
+
/**
|
|
23267
|
+
* Validates an issue's detail, whose sections must all belong to that issue.
|
|
23268
|
+
* @param value - Untrusted data.
|
|
23269
|
+
* @returns The detail.
|
|
23270
|
+
*/
|
|
23271
|
+
function parseBeadDetail(value) {
|
|
23272
|
+
let input = object$1(value, "detail");
|
|
23273
|
+
let connection = parseBeadsConnection(input.connection);
|
|
23274
|
+
if (connection.state !== "ready") throw new BeadsValidationError("detail.connection");
|
|
23275
|
+
let issue = parseBeadIssue(input.issue);
|
|
23276
|
+
let discussion = section$2(input.discussion, "discussion", parseBeadDiscussion, 200);
|
|
23277
|
+
let history = section$2(input.history, "history", parseBeadHistory, 50);
|
|
23278
|
+
let events = section$2(input.events, "events", parseBeadEvent, 50);
|
|
23279
|
+
if (!issue.contentLoaded || discussion.items.some((entry) => entry.issueId !== issue.id) || events.items.some((entry) => entry.issueId !== issue.id) || history.items.some((entry) => entry.issue && entry.issue.id !== issue.id)) throw new BeadsValidationError("detail.issue");
|
|
23280
|
+
return {
|
|
23281
|
+
connection,
|
|
23282
|
+
issue,
|
|
23283
|
+
discussion,
|
|
23284
|
+
history,
|
|
23285
|
+
events
|
|
23286
|
+
};
|
|
23287
|
+
}
|
|
23288
|
+
//#endregion
|
|
23289
|
+
//#region ../beads/src/lib/actions.ts
|
|
23290
|
+
var dependencyTypes = [
|
|
23291
|
+
"blocks",
|
|
23292
|
+
"tracks",
|
|
23293
|
+
"related",
|
|
23294
|
+
"discovered-from",
|
|
23295
|
+
"until",
|
|
23296
|
+
"caused-by",
|
|
23297
|
+
"validates",
|
|
23298
|
+
"relates-to",
|
|
23299
|
+
"supersedes"
|
|
23300
|
+
];
|
|
23301
|
+
var fields$2 = [
|
|
23302
|
+
"title",
|
|
23303
|
+
"type",
|
|
23304
|
+
"status",
|
|
23305
|
+
"priority",
|
|
23306
|
+
"assignee",
|
|
23307
|
+
"labels",
|
|
23308
|
+
"parentId",
|
|
23309
|
+
"description",
|
|
23310
|
+
"design",
|
|
23311
|
+
"acceptanceCriteria",
|
|
23312
|
+
"notes",
|
|
23313
|
+
"estimatedMinutes",
|
|
23314
|
+
"externalRef",
|
|
23315
|
+
"specId",
|
|
23316
|
+
"dueAt",
|
|
23317
|
+
"deferUntil",
|
|
23318
|
+
"metadata"
|
|
23319
|
+
];
|
|
23320
|
+
function strict$1(value, name, allowed) {
|
|
23321
|
+
let input = object$1(value, name);
|
|
23322
|
+
for (let key of Object.keys(input)) if (!allowed.includes(key)) throw new BeadsValidationError(`${name}.${key}`);
|
|
23323
|
+
return input;
|
|
23324
|
+
}
|
|
23325
|
+
function nonempty(value, field, max) {
|
|
23326
|
+
let result = text$3(value, field, max).trim();
|
|
23327
|
+
if (!result) throw new BeadsValidationError(field);
|
|
23328
|
+
return result;
|
|
23329
|
+
}
|
|
23330
|
+
/**
|
|
23331
|
+
* Validates an update's fields, rejecting unknown and oversized ones.
|
|
23332
|
+
* @param value - Untrusted data.
|
|
23333
|
+
* @returns The patch.
|
|
23334
|
+
*/
|
|
23335
|
+
function parseBeadPatch(value) {
|
|
23336
|
+
let input = strict$1(value, "patch", fields$2);
|
|
23337
|
+
let patch = {};
|
|
23338
|
+
if (!Object.keys(input).length) throw new BeadsValidationError("patch");
|
|
23339
|
+
if ("title" in input) patch.title = nonempty(input.title, "patch.title", 1e3);
|
|
23340
|
+
if ("type" in input) patch.type = identifier$1(input.type, "patch.type");
|
|
23341
|
+
if ("status" in input) patch.status = identifier$1(input.status, "patch.status");
|
|
23342
|
+
if ("priority" in input) patch.priority = integer(input.priority, "patch.priority", 4);
|
|
23343
|
+
if ("assignee" in input) patch.assignee = text$3(input.assignee, "patch.assignee", 256).trim();
|
|
23344
|
+
if ("labels" in input) patch.labels = unique(array$1(input.labels, "patch.labels", (value, field) => {
|
|
23345
|
+
let label = nonempty(value, field, 128);
|
|
23346
|
+
if (/[\r\n,]/.test(label)) throw new BeadsValidationError(field);
|
|
23347
|
+
return label;
|
|
23348
|
+
}, 100), (label) => label, "patch.labels");
|
|
23349
|
+
if ("parentId" in input) patch.parentId = input.parentId === null ? null : identifier$1(input.parentId, "patch.parentId");
|
|
23350
|
+
for (let key of [
|
|
23351
|
+
"description",
|
|
23352
|
+
"design",
|
|
23353
|
+
"acceptanceCriteria",
|
|
23354
|
+
"notes"
|
|
23355
|
+
]) if (key in input) patch[key] = text$3(input[key], `patch.${key}`, 6e4);
|
|
23356
|
+
if ("estimatedMinutes" in input) patch.estimatedMinutes = integer(input.estimatedMinutes, "patch.estimatedMinutes", 1e6);
|
|
23357
|
+
for (let key of ["externalRef", "specId"]) if (key in input) patch[key] = text$3(input[key], `patch.${key}`, 2048);
|
|
23358
|
+
for (let key of ["dueAt", "deferUntil"]) if (key in input) patch[key] = text$3(input[key], `patch.${key}`, 128);
|
|
23359
|
+
if ("metadata" in input) {
|
|
23360
|
+
let source = object$1(input.metadata, "patch.metadata");
|
|
23361
|
+
if (JSON.stringify(source).length > 32e3) throw new BeadsValidationError("patch.metadata");
|
|
23362
|
+
patch.metadata = metadata(source, "patch.metadata");
|
|
23363
|
+
}
|
|
23364
|
+
if (new TextEncoder().encode(JSON.stringify(patch)).byteLength > 122880) throw new BeadsValidationError("patch");
|
|
23365
|
+
return patch;
|
|
23366
|
+
}
|
|
23367
|
+
/**
|
|
23368
|
+
* Validates a new issue's fields.
|
|
23369
|
+
* @param value - Untrusted data.
|
|
23370
|
+
* @returns The fields, with a title.
|
|
23371
|
+
*/
|
|
23372
|
+
function parseBeadCreate(value) {
|
|
23373
|
+
let patch = parseBeadPatch(value);
|
|
23374
|
+
if (!patch.title) throw new BeadsValidationError("create.title");
|
|
23375
|
+
return {
|
|
23376
|
+
...patch,
|
|
23377
|
+
title: patch.title
|
|
23378
|
+
};
|
|
23379
|
+
}
|
|
23380
|
+
/**
|
|
23381
|
+
* Validates a change to an issue.
|
|
23382
|
+
* @param value - Untrusted data.
|
|
23383
|
+
* @returns The action.
|
|
23384
|
+
*/
|
|
23385
|
+
function parseBeadAction(value) {
|
|
23386
|
+
let input = object$1(value, "action");
|
|
23387
|
+
let expectedRevision = nonempty(input.expectedRevision, "action.expectedRevision", 256);
|
|
23388
|
+
switch (input.kind) {
|
|
23389
|
+
case "update":
|
|
23390
|
+
strict$1(input, "action", [
|
|
23391
|
+
"kind",
|
|
23392
|
+
"expectedRevision",
|
|
23393
|
+
"patch"
|
|
23394
|
+
]);
|
|
23395
|
+
return {
|
|
23396
|
+
kind: input.kind,
|
|
23397
|
+
expectedRevision,
|
|
23398
|
+
patch: parseBeadPatch(input.patch)
|
|
23399
|
+
};
|
|
23400
|
+
case "claim":
|
|
23401
|
+
strict$1(input, "action", ["kind", "expectedRevision"]);
|
|
23402
|
+
return {
|
|
23403
|
+
kind: input.kind,
|
|
23404
|
+
expectedRevision
|
|
23405
|
+
};
|
|
23406
|
+
case "close":
|
|
23407
|
+
case "reopen":
|
|
23408
|
+
strict$1(input, "action", [
|
|
23409
|
+
"kind",
|
|
23410
|
+
"expectedRevision",
|
|
23411
|
+
"reason"
|
|
23412
|
+
]);
|
|
23413
|
+
return {
|
|
23414
|
+
kind: input.kind,
|
|
23415
|
+
expectedRevision,
|
|
23416
|
+
reason: text$3(input.reason ?? "", "action.reason", 4e3)
|
|
23417
|
+
};
|
|
23418
|
+
case "add-dependency":
|
|
23419
|
+
strict$1(input, "action", [
|
|
23420
|
+
"kind",
|
|
23421
|
+
"expectedRevision",
|
|
23422
|
+
"targetId",
|
|
23423
|
+
"type"
|
|
23424
|
+
]);
|
|
23425
|
+
if (!dependencyTypes.includes(input.type)) throw new BeadsValidationError("action.type");
|
|
23426
|
+
return {
|
|
23427
|
+
kind: input.kind,
|
|
23428
|
+
expectedRevision,
|
|
23429
|
+
targetId: identifier$1(input.targetId, "action.targetId"),
|
|
23430
|
+
type: input.type
|
|
23431
|
+
};
|
|
23432
|
+
case "remove-dependency":
|
|
23433
|
+
strict$1(input, "action", [
|
|
23434
|
+
"kind",
|
|
23435
|
+
"expectedRevision",
|
|
23436
|
+
"targetId"
|
|
23437
|
+
]);
|
|
23438
|
+
return {
|
|
23439
|
+
kind: input.kind,
|
|
23440
|
+
expectedRevision,
|
|
23441
|
+
targetId: identifier$1(input.targetId, "action.targetId")
|
|
23442
|
+
};
|
|
23443
|
+
case "comment": {
|
|
23444
|
+
strict$1(input, "action", [
|
|
23445
|
+
"kind",
|
|
23446
|
+
"expectedRevision",
|
|
23447
|
+
"body"
|
|
23448
|
+
]);
|
|
23449
|
+
let body = nonempty(input.body, "action.body", 6e4);
|
|
23450
|
+
if (new TextEncoder().encode(body).byteLength > 122880) throw new BeadsValidationError("action.body");
|
|
23451
|
+
return {
|
|
23452
|
+
kind: input.kind,
|
|
23453
|
+
expectedRevision,
|
|
23454
|
+
body
|
|
23455
|
+
};
|
|
23456
|
+
}
|
|
23457
|
+
default: throw new BeadsValidationError("action.kind");
|
|
23458
|
+
}
|
|
23459
|
+
}
|
|
23460
|
+
/**
|
|
23461
|
+
* Validates a sync request.
|
|
23462
|
+
* @param value - Untrusted data.
|
|
23463
|
+
* @returns The request.
|
|
23464
|
+
*/
|
|
23465
|
+
function parseBeadSync(value) {
|
|
23466
|
+
let input = strict$1(value, "sync", ["direction"]);
|
|
23467
|
+
if (input.direction !== "pull" && input.direction !== "push") throw new BeadsValidationError("sync.direction");
|
|
23468
|
+
return { direction: input.direction };
|
|
23469
|
+
}
|
|
23470
|
+
//#endregion
|
|
23471
|
+
//#region ../beads/src/lib/protocol.ts
|
|
23472
|
+
/**
|
|
23473
|
+
* Turns a CLI or export issue record into the browser's issue. CLI and export records share this
|
|
23474
|
+
* one boundary before reaching the browser DTOs.
|
|
23475
|
+
* @param value - The record.
|
|
23476
|
+
* @param contentLoaded - Whether it holds the issue's long content.
|
|
23477
|
+
* @returns The issue.
|
|
23478
|
+
*/
|
|
23479
|
+
function parseCliIssue(value, contentLoaded = false) {
|
|
23480
|
+
let input = object$1(value, "issue");
|
|
23481
|
+
let id = identifier$1(input.id, "issue.id");
|
|
23482
|
+
function dependencies(field) {
|
|
23483
|
+
return array$1(input[field] ?? [], `issue.${field}`, (entry, path) => {
|
|
23484
|
+
let edge = object$1(entry, path);
|
|
23485
|
+
if ("depends_on_id" in edge) {
|
|
23486
|
+
if (identifier$1(edge.issue_id, `${path}.issue_id`) !== id) throw new Error("Dependency belongs to a different issue.");
|
|
23487
|
+
return {
|
|
23488
|
+
id: identifier$1(edge.depends_on_id, `${path}.depends_on_id`),
|
|
23489
|
+
type: identifier$1(edge.type, `${path}.type`),
|
|
23490
|
+
title: null,
|
|
23491
|
+
status: null,
|
|
23492
|
+
priority: null,
|
|
23493
|
+
issueType: null
|
|
23494
|
+
};
|
|
23495
|
+
}
|
|
23496
|
+
return {
|
|
23497
|
+
id: edge.id,
|
|
23498
|
+
type: edge.dependency_type,
|
|
23499
|
+
title: edge.title ?? null,
|
|
23500
|
+
status: edge.status ?? null,
|
|
23501
|
+
priority: edge.priority ?? null,
|
|
23502
|
+
issueType: edge.issue_type ?? null
|
|
23503
|
+
};
|
|
23504
|
+
});
|
|
23505
|
+
}
|
|
23506
|
+
return parseBeadIssue({
|
|
23507
|
+
id,
|
|
23508
|
+
title: input.title,
|
|
23509
|
+
status: input.status,
|
|
23510
|
+
priority: input.priority,
|
|
23511
|
+
type: input.issue_type,
|
|
23512
|
+
description: input.description ?? "",
|
|
23513
|
+
design: input.design ?? "",
|
|
23514
|
+
acceptanceCriteria: input.acceptance_criteria ?? "",
|
|
23515
|
+
notes: input.notes ?? "",
|
|
23516
|
+
assignee: input.assignee ?? null,
|
|
23517
|
+
owner: input.owner ?? null,
|
|
23518
|
+
createdBy: input.created_by ?? null,
|
|
23519
|
+
createdAt: input.created_at,
|
|
23520
|
+
updatedAt: input.updated_at,
|
|
23521
|
+
startedAt: input.started_at ?? null,
|
|
23522
|
+
closedAt: input.closed_at ?? null,
|
|
23523
|
+
closeReason: input.close_reason ?? null,
|
|
23524
|
+
dueAt: input.due_at ?? null,
|
|
23525
|
+
deferUntil: input.defer_until ?? null,
|
|
23526
|
+
estimatedMinutes: input.estimated_minutes ?? null,
|
|
23527
|
+
externalRef: input.external_ref ?? null,
|
|
23528
|
+
specId: input.spec_id ?? null,
|
|
23529
|
+
labels: input.labels ?? [],
|
|
23530
|
+
parentId: input.parent ?? null,
|
|
23531
|
+
dependencies: dependencies("dependencies"),
|
|
23532
|
+
dependents: dependencies("dependents"),
|
|
23533
|
+
dependencyCount: input.dependency_count ?? null,
|
|
23534
|
+
dependentCount: input.dependent_count ?? null,
|
|
23535
|
+
commentCount: input.comment_count ?? null,
|
|
23536
|
+
epicTotalChildren: input.epic_total_children ?? null,
|
|
23537
|
+
epicClosedChildren: input.epic_closed_children ?? null,
|
|
23538
|
+
epicCloseable: input.epic_closeable ?? null,
|
|
23539
|
+
revision: input.revision ?? null,
|
|
23540
|
+
metadata: input.metadata ?? null,
|
|
23541
|
+
contentLoaded
|
|
23542
|
+
});
|
|
23543
|
+
}
|
|
23544
|
+
/**
|
|
23545
|
+
* Turns the CLI's issue comments into discussion entries with unique IDs.
|
|
23546
|
+
* @param value - The CLI's comment list, or nothing.
|
|
23547
|
+
* @returns The comments.
|
|
23548
|
+
*/
|
|
23549
|
+
function parseCliDiscussion(value) {
|
|
23550
|
+
return unique(array$1(value ?? [], "comments", (entry, field) => {
|
|
23551
|
+
let item = object$1(entry, field);
|
|
23552
|
+
return parseBeadDiscussion({
|
|
22804
23553
|
id: item.id,
|
|
22805
23554
|
issueId: item.issue_id,
|
|
22806
23555
|
author: item.author,
|
|
@@ -23280,7 +24029,7 @@ function payload$1(value) {
|
|
|
23280
24029
|
return /^[{[]/.test(value.trim()) ? null : value;
|
|
23281
24030
|
}
|
|
23282
24031
|
}
|
|
23283
|
-
function text$
|
|
24032
|
+
function text$2(value) {
|
|
23284
24033
|
return typeof value === "string" || typeof value === "number" ? String(value).replace(/\s+/g, " ").trim() : "";
|
|
23285
24034
|
}
|
|
23286
24035
|
function field(value, key) {
|
|
@@ -23289,7 +24038,7 @@ function field(value, key) {
|
|
|
23289
24038
|
function changed$1(key, before, after) {
|
|
23290
24039
|
let name = fields[key] ?? label$1(key).toLowerCase();
|
|
23291
24040
|
if (key === "labels") {
|
|
23292
|
-
let labels = (value) => Array.isArray(value) ? value.map(text$
|
|
24041
|
+
let labels = (value) => Array.isArray(value) ? value.map(text$2).filter(Boolean) : text$2(value).split(",").map((v) => v.trim()).filter(Boolean);
|
|
23293
24042
|
let oldLabels = labels(before);
|
|
23294
24043
|
let newLabels = labels(after);
|
|
23295
24044
|
let added = newLabels.filter((value) => !oldLabels.includes(value));
|
|
@@ -23305,7 +24054,7 @@ function changed$1(key, before, after) {
|
|
|
23305
24054
|
"metadata"
|
|
23306
24055
|
].includes(key)) return `updated ${name}`;
|
|
23307
24056
|
let display = (value) => {
|
|
23308
|
-
let result = text$
|
|
24057
|
+
let result = text$2(value);
|
|
23309
24058
|
if (!result) return key === "assignee" ? "Unassigned" : "None";
|
|
23310
24059
|
if (key === "priority" && /^\d$/.test(result)) return `P${result}`;
|
|
23311
24060
|
return key === "status" || key === "issue_type" ? label$1(result) : result;
|
|
@@ -23326,7 +24075,7 @@ function beadActivitySummary(event) {
|
|
|
23326
24075
|
case "created": return "created this task";
|
|
23327
24076
|
case "closed":
|
|
23328
24077
|
case "reopened": {
|
|
23329
|
-
let reason = text$
|
|
24078
|
+
let reason = text$2(payload$1(event.comment)) || text$2(field(after, "close_reason")) || text$2(field(after, "reason"));
|
|
23330
24079
|
if (!reason || [
|
|
23331
24080
|
"closed",
|
|
23332
24081
|
"open",
|
|
@@ -23340,12 +24089,12 @@ function beadActivitySummary(event) {
|
|
|
23340
24089
|
case "label_added":
|
|
23341
24090
|
case "label_removed": {
|
|
23342
24091
|
let verb = event.type === "label_added" ? "added" : "removed";
|
|
23343
|
-
let value = text$
|
|
24092
|
+
let value = text$2(field(after ?? before, "label")) || text$2(payload$1(event.comment)).replace(/^(?:Added|Removed) label:\s*/i, "");
|
|
23344
24093
|
return `${verb} a label${value ? `: ${value}` : ""}`;
|
|
23345
24094
|
}
|
|
23346
24095
|
case "dependency_added":
|
|
23347
24096
|
case "dependency_removed": {
|
|
23348
|
-
let value = text$
|
|
24097
|
+
let value = text$2(after ?? before);
|
|
23349
24098
|
let match = /^(?:Added|Removed) dependency: \S+ (\S+) (\S+)$/.exec(value);
|
|
23350
24099
|
let target = match?.[2] ?? (/^[\w.-]+$/.test(value) ? value : "");
|
|
23351
24100
|
let kind = match?.[1];
|
|
@@ -24436,7 +25185,7 @@ function validate$1(parse, reason = "protocol") {
|
|
|
24436
25185
|
})
|
|
24437
25186
|
});
|
|
24438
25187
|
}
|
|
24439
|
-
function section$
|
|
25188
|
+
function section$1(source, map) {
|
|
24440
25189
|
return source.state === "available" ? {
|
|
24441
25190
|
...source,
|
|
24442
25191
|
items: source.items.map(map)
|
|
@@ -24591,7 +25340,7 @@ var beadsStoreWorkAdapterLayer = effect(WorkAdapter, gen(function* () {
|
|
|
24591
25340
|
return yield* validate$1(() => parseWorkDetail({
|
|
24592
25341
|
connection: beadsWorkConnection(session.connection, schema),
|
|
24593
25342
|
item: item(issue, schema, ready.ids.includes(id) ? true : ready.truncated ? void 0 : false),
|
|
24594
|
-
discussion: section$
|
|
25343
|
+
discussion: section$1(discussion, (message) => ({
|
|
24595
25344
|
id: message.id,
|
|
24596
25345
|
body: message.body,
|
|
24597
25346
|
createdAt: message.createdAt,
|
|
@@ -24617,13 +25366,13 @@ var beadsStoreWorkAdapterLayer = effect(WorkAdapter, gen(function* () {
|
|
|
24617
25366
|
let events = yield* session.events(id);
|
|
24618
25367
|
data = yield* validate$1(() => ({
|
|
24619
25368
|
kind: "activity",
|
|
24620
|
-
data: section$
|
|
25369
|
+
data: section$1(events, beadsWorkActivity)
|
|
24621
25370
|
}));
|
|
24622
25371
|
} else if (sectionId === "history") {
|
|
24623
25372
|
let history = yield* session.history(id);
|
|
24624
25373
|
data = yield* validate$1(() => ({
|
|
24625
25374
|
kind: "history",
|
|
24626
|
-
data: section$
|
|
25375
|
+
data: section$1(history, (entry) => ({
|
|
24627
25376
|
revision: entry.commit,
|
|
24628
25377
|
author: entry.author,
|
|
24629
25378
|
at: entry.createdAt,
|
|
@@ -24958,7 +25707,7 @@ function parseThreadRecord(value) {
|
|
|
24958
25707
|
if (initial.parentId !== null) throw new WorkValidationError("thread.metadata.initial.parentId");
|
|
24959
25708
|
let bindings = unique(unique(array$1(input.bindings, "bindings", binding, MAX_BEADS_THREAD_BYTES), (entry) => entry.id, "bindings.id"), (entry) => entry.nativeId, "bindings.nativeId");
|
|
24960
25709
|
if (input.source === void 0 !== (input.sourceId === void 0)) throw new WorkValidationError("thread.metadata.source");
|
|
24961
|
-
if (input.source !== void 0 && (!text$
|
|
25710
|
+
if (input.source !== void 0 && (!text$3(input.source, "thread.metadata.source", 512).trim() || !text$3(input.sourceId, "thread.metadata.sourceId", 512).trim())) throw new WorkValidationError("thread.metadata.source");
|
|
24962
25711
|
return {
|
|
24963
25712
|
version: 1,
|
|
24964
25713
|
kind,
|
|
@@ -24969,8 +25718,8 @@ function parseThreadRecord(value) {
|
|
|
24969
25718
|
createdAt: timestamp(input.createdAt, "thread.metadata.createdAt"),
|
|
24970
25719
|
updatedAt: timestamp(input.updatedAt, "thread.metadata.updatedAt"),
|
|
24971
25720
|
...input.source === void 0 ? {} : {
|
|
24972
|
-
source: text$
|
|
24973
|
-
sourceId: text$
|
|
25721
|
+
source: text$3(input.source, "thread.metadata.source", 512),
|
|
25722
|
+
sourceId: text$3(input.sourceId, "thread.metadata.sourceId", 512)
|
|
24974
25723
|
}
|
|
24975
25724
|
};
|
|
24976
25725
|
}
|
|
@@ -26355,7 +27104,7 @@ function identifier(value) {
|
|
|
26355
27104
|
if (typeof value !== "string" || value.length > 512 || !/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(value)) throw failure$4("input", "Select a valid task ID.");
|
|
26356
27105
|
return value;
|
|
26357
27106
|
}
|
|
26358
|
-
function text$
|
|
27107
|
+
function text$1(value, field, max, required = false) {
|
|
26359
27108
|
if (typeof value !== "string" || value.length > max || value.includes("\0")) throw failure$4("input", `The ${field} is invalid.`);
|
|
26360
27109
|
if (required && !value.trim()) throw failure$4("input", `The ${field} is required.`);
|
|
26361
27110
|
return value;
|
|
@@ -26365,7 +27114,7 @@ function readFailure(cause) {
|
|
|
26365
27114
|
if (cause instanceof Error && /no such table/i.test(cause.message)) return failure$4("not-initialized", "This D1 database has no Beads schema. Run migrateBeadsD1 before using it.", { cause });
|
|
26366
27115
|
return failure$4("command", "Unable to read project tasks from the D1 store.", { cause });
|
|
26367
27116
|
}
|
|
26368
|
-
async function select
|
|
27117
|
+
async function select(core, sql, params = []) {
|
|
26369
27118
|
try {
|
|
26370
27119
|
return (await core.db.prepare(sql).bind(...params).all()).results;
|
|
26371
27120
|
} catch (cause) {
|
|
@@ -26456,10 +27205,10 @@ function edgeJson(row) {
|
|
|
26456
27205
|
* @returns Rows ordered by priority, newest first.
|
|
26457
27206
|
*/
|
|
26458
27207
|
async function listLabel(core, query) {
|
|
26459
|
-
return (await select
|
|
27208
|
+
return (await select(core, `SELECT ${columns}, ${edges} FROM beads_issues i
|
|
26460
27209
|
WHERE EXISTS (SELECT 1 FROM beads_labels l WHERE l.issue_id = i.id AND l.label = ?)
|
|
26461
27210
|
${query.includeInfra ? "" : `AND ${board$1}`}
|
|
26462
|
-
ORDER BY i.priority, i.created_at DESC, i.id LIMIT ?`, [text$
|
|
27211
|
+
ORDER BY i.priority, i.created_at DESC, i.id LIMIT ?`, [text$1(query.label, "label", 512, true), Math.max(0, Math.trunc(query.limit))])).map((row) => listJson(row, true));
|
|
26463
27212
|
}
|
|
26464
27213
|
/**
|
|
26465
27214
|
* Lists durable work items as brief board rows, like `bd list --all --flat --brief`.
|
|
@@ -26468,7 +27217,7 @@ async function listLabel(core, query) {
|
|
|
26468
27217
|
* @returns Rows ordered by priority, newest first.
|
|
26469
27218
|
*/
|
|
26470
27219
|
async function listBoard(core, limit) {
|
|
26471
|
-
return (await select
|
|
27220
|
+
return (await select(core, `SELECT ${columns}, ${edges} FROM beads_issues i WHERE ${board$1}
|
|
26472
27221
|
ORDER BY i.priority, i.created_at DESC, i.id LIMIT ?`, [limit])).map((row) => listJson(row, false));
|
|
26473
27222
|
}
|
|
26474
27223
|
/**
|
|
@@ -26478,7 +27227,7 @@ async function listBoard(core, limit) {
|
|
|
26478
27227
|
* @returns Rows ordered by priority, newest first.
|
|
26479
27228
|
*/
|
|
26480
27229
|
async function listReady(core, limit) {
|
|
26481
|
-
return (await select
|
|
27230
|
+
return (await select(core, `SELECT ${columns}, ${edges} FROM beads_issues i
|
|
26482
27231
|
WHERE ${board$1} AND i.status = 'open'
|
|
26483
27232
|
AND (i.defer_until IS NULL OR julianday(i.defer_until) <= julianday(?))
|
|
26484
27233
|
AND NOT EXISTS (
|
|
@@ -26556,7 +27305,7 @@ async function listComments(core, id, limit) {
|
|
|
26556
27305
|
* @returns Native events.
|
|
26557
27306
|
*/
|
|
26558
27307
|
async function listEvents(core, id, limit) {
|
|
26559
|
-
return (await select
|
|
27308
|
+
return (await select(core, `SELECT id, issue_id, event_type, actor, old_value, new_value, comment, created_at
|
|
26560
27309
|
FROM beads_events WHERE issue_id = ? ORDER BY seq DESC LIMIT ?`, [id, limit])).map((row) => {
|
|
26561
27310
|
let out = {
|
|
26562
27311
|
id: row.id,
|
|
@@ -26702,12 +27451,12 @@ function revision$1(issue, expected) {
|
|
|
26702
27451
|
return Number(expected);
|
|
26703
27452
|
}
|
|
26704
27453
|
async function exists$1(core, id) {
|
|
26705
|
-
return (await select
|
|
27454
|
+
return (await select(core, "SELECT 1 AS found FROM beads_issues WHERE id = ?", [id])).length > 0;
|
|
26706
27455
|
}
|
|
26707
27456
|
var alphabet$1 = "0123456789abcdefghijklmnopqrstuvwxyz";
|
|
26708
27457
|
async function allocate(core, options) {
|
|
26709
27458
|
if (options.parentId) {
|
|
26710
|
-
let children = await select
|
|
27459
|
+
let children = await select(core, "SELECT id FROM beads_issues WHERE id LIKE ? ESCAPE '\\'", [`${options.parentId.replace(/[\\%_]/g, (match) => `\\${match}`)}.%`]);
|
|
26711
27460
|
let next = 1;
|
|
26712
27461
|
for (let row of children) {
|
|
26713
27462
|
let suffix = String(row.id).slice(options.parentId.length + 1);
|
|
@@ -26751,7 +27500,7 @@ async function checkEdge(core, id, target, type) {
|
|
|
26751
27500
|
if (target === id) throw failure$4("cycle", "This relationship would create a dependency cycle. Choose a different task.");
|
|
26752
27501
|
if (!await exists$1(core, target)) throw failure$4("not-found", "This task or relationship target no longer exists. Refresh before trying again.");
|
|
26753
27502
|
if (!blockingTypes.includes(type)) return;
|
|
26754
|
-
if ((await select
|
|
27503
|
+
if ((await select(core, `WITH RECURSIVE reach(id) AS (
|
|
26755
27504
|
SELECT ? UNION
|
|
26756
27505
|
SELECT d.depends_on_id FROM beads_dependencies d JOIN reach r ON d.issue_id = r.id
|
|
26757
27506
|
WHERE d.type IN ${blocking})
|
|
@@ -26981,7 +27730,7 @@ function reopenPlan(plan, _issue, reason, expected) {
|
|
|
26981
27730
|
}
|
|
26982
27731
|
function labels(values) {
|
|
26983
27732
|
return values.map((label) => {
|
|
26984
|
-
let value = text$
|
|
27733
|
+
let value = text$1(label, "label", 512, true);
|
|
26985
27734
|
if (/[\r\n]/.test(value)) throw failure$4("input", "The label is invalid.");
|
|
26986
27735
|
return value;
|
|
26987
27736
|
});
|
|
@@ -26997,9 +27746,9 @@ function metadataObject(value) {
|
|
|
26997
27746
|
* @returns The native row, as `bd create --json` prints it.
|
|
26998
27747
|
*/
|
|
26999
27748
|
async function createRecord(core, value) {
|
|
27000
|
-
let title = text$
|
|
27001
|
-
let type = knownType(text$
|
|
27002
|
-
let actor = text$
|
|
27749
|
+
let title = text$1(value.title, "title", 1e3, true);
|
|
27750
|
+
let type = knownType(text$1(value.type, "type", 128, true));
|
|
27751
|
+
let actor = text$1(value.actor, "actor", 512, true);
|
|
27003
27752
|
let names = labels(value.labels);
|
|
27004
27753
|
let metadata = metadataObject(value.metadata);
|
|
27005
27754
|
let wisp = type === "message";
|
|
@@ -27047,8 +27796,8 @@ async function createRecord(core, value) {
|
|
|
27047
27796
|
*/
|
|
27048
27797
|
async function addComment(core, id, value) {
|
|
27049
27798
|
identifier(id);
|
|
27050
|
-
let author = text$
|
|
27051
|
-
let body = text$
|
|
27799
|
+
let author = text$1(value.author, "author", 512, true);
|
|
27800
|
+
let body = text$1(value.body, "message", 4194304);
|
|
27052
27801
|
let plan = new Plan(core, id, core.now(), author);
|
|
27053
27802
|
let comment = {
|
|
27054
27803
|
id: crypto.randomUUID(),
|
|
@@ -27075,7 +27824,7 @@ async function addComment(core, id, value) {
|
|
|
27075
27824
|
async function addDependency(core, id, target, type) {
|
|
27076
27825
|
identifier(id);
|
|
27077
27826
|
identifier(target);
|
|
27078
|
-
let kind = text$
|
|
27827
|
+
let kind = text$1(type, "relationship type", 128, true);
|
|
27079
27828
|
if (!/^[a-z][a-z-]*$/.test(kind)) throw failure$4("input", "The relationship type is invalid.");
|
|
27080
27829
|
if (!await exists$1(core, id)) throw failure$4("not-found", messages.missing);
|
|
27081
27830
|
await checkEdge(core, id, target, kind);
|
|
@@ -27097,7 +27846,7 @@ async function addDependency(core, id, target, type) {
|
|
|
27097
27846
|
async function removeDependency(core, id, target) {
|
|
27098
27847
|
identifier(id);
|
|
27099
27848
|
identifier(target);
|
|
27100
|
-
if (!(await select
|
|
27849
|
+
if (!(await select(core, "SELECT 1 AS found FROM beads_dependencies WHERE issue_id = ? AND depends_on_id = ?", [id, target])).length) throw failure$4("not-found", "This relationship no longer exists. Refresh before trying again.");
|
|
27101
27850
|
let plan = new Plan(core, id, core.now());
|
|
27102
27851
|
plan.update({});
|
|
27103
27852
|
plan.removeEdge(target);
|
|
@@ -27115,9 +27864,9 @@ async function removeDependency(core, id, target) {
|
|
|
27115
27864
|
*/
|
|
27116
27865
|
async function promote(core, id, value) {
|
|
27117
27866
|
identifier(id);
|
|
27118
|
-
let plan = new Plan(core, id, core.now(), text$
|
|
27867
|
+
let plan = new Plan(core, id, core.now(), text$1(value.actor, "actor", 512, true));
|
|
27119
27868
|
plan.update({ ephemeral: 0 });
|
|
27120
|
-
plan.event("promoted", { comment: text$
|
|
27869
|
+
plan.event("promoted", { comment: text$1(value.reason, "reason", 4096) });
|
|
27121
27870
|
await apply(core, plan);
|
|
27122
27871
|
}
|
|
27123
27872
|
/**
|
|
@@ -27130,7 +27879,7 @@ async function close(core, id, reason) {
|
|
|
27130
27879
|
identifier(id);
|
|
27131
27880
|
let issue = await required(core, id);
|
|
27132
27881
|
let plan = new Plan(core, id, core.now());
|
|
27133
|
-
closePlan(core, plan, issue, text$
|
|
27882
|
+
closePlan(core, plan, issue, text$1(reason, "reason", 4096));
|
|
27134
27883
|
await apply(core, plan);
|
|
27135
27884
|
}
|
|
27136
27885
|
/**
|
|
@@ -27144,7 +27893,7 @@ async function reopen(core, id, reason) {
|
|
|
27144
27893
|
let issue = await required(core, id);
|
|
27145
27894
|
if (issue.status !== "closed") return;
|
|
27146
27895
|
let plan = new Plan(core, id, core.now());
|
|
27147
|
-
reopenPlan(plan, issue, text$
|
|
27896
|
+
reopenPlan(plan, issue, text$1(reason, "reason", 4096));
|
|
27148
27897
|
await apply(core, plan);
|
|
27149
27898
|
}
|
|
27150
27899
|
/**
|
|
@@ -27307,7 +28056,7 @@ async function importJsonl(core, jsonl) {
|
|
|
27307
28056
|
merged: 0
|
|
27308
28057
|
};
|
|
27309
28058
|
for (let record of records) {
|
|
27310
|
-
let [existing] = await select
|
|
28059
|
+
let [existing] = await select(core, "SELECT updated_at FROM beads_issues WHERE id = ?", [record.issue.id]);
|
|
27311
28060
|
let plan = new Plan(core, record.issue.id, at, record.issue.createdBy ?? core.actor);
|
|
27312
28061
|
if (!existing) {
|
|
27313
28062
|
plan.insert(record.values);
|
|
@@ -27392,7 +28141,7 @@ function protocol(operation, parse) {
|
|
|
27392
28141
|
})
|
|
27393
28142
|
});
|
|
27394
28143
|
}
|
|
27395
|
-
function section
|
|
28144
|
+
function section(items, limit) {
|
|
27396
28145
|
return {
|
|
27397
28146
|
state: "available",
|
|
27398
28147
|
items: items.slice(0, limit),
|
|
@@ -27547,13 +28296,13 @@ function makeD1BeadsStore(options) {
|
|
|
27547
28296
|
comments: (value, parse) => id(value).pipe(flatMap$1(() => run(() => listComments(store, value))), flatMap$1((rows) => protocol("comments", () => parse(rows)))),
|
|
27548
28297
|
ready: readyIssues,
|
|
27549
28298
|
issue,
|
|
27550
|
-
discussion: (value) => id(value).pipe(flatMap$1(() => run(() => listComments(store, value, 201))), flatMap$1((rows) => protocol("comments", () => section
|
|
28299
|
+
discussion: (value) => id(value).pipe(flatMap$1(() => run(() => listComments(store, value, 201))), flatMap$1((rows) => protocol("comments", () => section(parseCliDiscussion(rows), 200)))),
|
|
27551
28300
|
history: () => succeed$2({
|
|
27552
28301
|
state: "unsupported",
|
|
27553
28302
|
items: [],
|
|
27554
28303
|
truncated: false
|
|
27555
28304
|
}),
|
|
27556
|
-
events: (value) => id(value).pipe(flatMap$1(() => run(() => listEvents(store, value, 51))), flatMap$1((rows) => protocol("history", () => section
|
|
28305
|
+
events: (value) => id(value).pipe(flatMap$1(() => run(() => listEvents(store, value, 51))), flatMap$1((rows) => protocol("history", () => section(parseCliEvents(rows), 50)))),
|
|
27557
28306
|
snapshot: gen(function* () {
|
|
27558
28307
|
let issues = yield* run(() => listBoard(store, MAX_BEADS + 1)).pipe(flatMap$1((rows) => protocol("list", () => parseCliIssues(rows))));
|
|
27559
28308
|
let unblocked = yield* readyIssues(MAX_BEADS + 1);
|
|
@@ -35940,1023 +36689,383 @@ var watchNode = (path, info, options) => callback((queue) => acquireRelease(sync
|
|
|
35940
36689
|
})
|
|
35941
36690
|
}));
|
|
35942
36691
|
return;
|
|
35943
|
-
case "change":
|
|
35944
|
-
offerUnsafe(queue, {
|
|
35945
|
-
_tag: "Update",
|
|
35946
|
-
path
|
|
35947
|
-
});
|
|
35948
|
-
return;
|
|
35949
|
-
}
|
|
35950
|
-
});
|
|
35951
|
-
watcher.on("error", (error) => {
|
|
35952
|
-
failCauseUnsafe(queue, fail$7(systemError({
|
|
35953
|
-
module: "FileSystem",
|
|
35954
|
-
_tag: "Unknown",
|
|
35955
|
-
method: "watch",
|
|
35956
|
-
pathOrDescriptor: path,
|
|
35957
|
-
cause: error
|
|
35958
|
-
})));
|
|
35959
|
-
});
|
|
35960
|
-
watcher.on("close", () => {
|
|
35961
|
-
endUnsafe(queue);
|
|
35962
|
-
});
|
|
35963
|
-
return watcher;
|
|
35964
|
-
}), (watcher) => sync(() => watcher.close())).pipe(tap$1(() => sync(() => options?.onReady?.()))));
|
|
35965
|
-
var watch = (backend, path, options) => all$1([stat$1(path), serviceOption(WatchBackend)]).pipe(map$2(([stat, callerBackend]) => orElse(callerBackend, () => backend).pipe(flatMap$5((_) => _.register(path, stat, options)), getOrElse(() => watchNode(path, stat, options)))), unwrap$1);
|
|
35966
|
-
var writeFile$1 = (path, data, options) => callback$1((resume, signal) => {
|
|
35967
|
-
try {
|
|
35968
|
-
NFS.writeFile(path, data, {
|
|
35969
|
-
signal,
|
|
35970
|
-
flag: options?.flag,
|
|
35971
|
-
mode: options?.mode
|
|
35972
|
-
}, (err) => {
|
|
35973
|
-
if (err) resume(fail$6(handleErrnoException("FileSystem", "writeFile")(err, [path])));
|
|
35974
|
-
else resume(void_);
|
|
35975
|
-
});
|
|
35976
|
-
} catch (err) {
|
|
35977
|
-
resume(fail$6(handleBadArgument("writeFile")(err)));
|
|
35978
|
-
}
|
|
35979
|
-
});
|
|
35980
|
-
var makeFileSystem = /*#__PURE__*/ map$2(/*#__PURE__*/ serviceOption(WatchBackend), (backend) => make$14({
|
|
35981
|
-
access: access$1,
|
|
35982
|
-
chmod,
|
|
35983
|
-
chown,
|
|
35984
|
-
copy,
|
|
35985
|
-
copyFile,
|
|
35986
|
-
glob,
|
|
35987
|
-
link,
|
|
35988
|
-
makeDirectory,
|
|
35989
|
-
makeTempDirectory,
|
|
35990
|
-
makeTempDirectoryScoped,
|
|
35991
|
-
makeTempFile,
|
|
35992
|
-
makeTempFileScoped,
|
|
35993
|
-
open,
|
|
35994
|
-
readDirectory,
|
|
35995
|
-
readFile: readFile$1,
|
|
35996
|
-
readLink,
|
|
35997
|
-
realPath,
|
|
35998
|
-
remove,
|
|
35999
|
-
rename: rename$1,
|
|
36000
|
-
stat: stat$1,
|
|
36001
|
-
symlink: symlink$1,
|
|
36002
|
-
truncate,
|
|
36003
|
-
utimes,
|
|
36004
|
-
watch(path, options) {
|
|
36005
|
-
return watch(backend, path, options);
|
|
36006
|
-
},
|
|
36007
|
-
writeFile: writeFile$1
|
|
36008
|
-
}));
|
|
36009
|
-
//#endregion
|
|
36010
|
-
//#region ../../node_modules/.pnpm/@effect+platform-node@4.0.5_effect@4.0.5/node_modules/@effect/platform-node/dist/NodeFileSystem.js
|
|
36011
|
-
/**
|
|
36012
|
-
* Node.js `FileSystem` layer for programs that perform real filesystem I/O.
|
|
36013
|
-
*
|
|
36014
|
-
* The exported layer satisfies the platform-independent `FileSystem` service
|
|
36015
|
-
* with Node-backed operations for files, directories, metadata, permissions,
|
|
36016
|
-
* links, temporary paths, and path watching. Effects still call the service from
|
|
36017
|
-
* `effect/FileSystem`; this module only chooses the Node implementation.
|
|
36018
|
-
*
|
|
36019
|
-
* @stability unstable
|
|
36020
|
-
* @since 4.0.0
|
|
36021
|
-
*/
|
|
36022
|
-
/**
|
|
36023
|
-
* Provides the `FileSystem` service backed by Node filesystem APIs.
|
|
36024
|
-
*
|
|
36025
|
-
* @stability unstable
|
|
36026
|
-
* @category layers
|
|
36027
|
-
* @since 4.0.0
|
|
36028
|
-
*/
|
|
36029
|
-
var layer$2 = /* @__PURE__ */ effect(FileSystem)(makeFileSystem);
|
|
36030
|
-
//#endregion
|
|
36031
|
-
//#region ../../node_modules/.pnpm/@effect+platform-node-shared@4.0.5_effect@4.0.5/node_modules/@effect/platform-node-shared/dist/NodePath.js
|
|
36032
|
-
/**
|
|
36033
|
-
* Node-backed provider for Effect's `Path` service.
|
|
36034
|
-
*
|
|
36035
|
-
* This module turns Node's `node:path` and `node:url` APIs into `Path` layers.
|
|
36036
|
-
* `layer` uses the host platform path implementation, while `layerPosix` and
|
|
36037
|
-
* `layerWin32` provide fixed POSIX and Windows variants. All three layers also
|
|
36038
|
-
* include helpers for converting between file paths and file URLs.
|
|
36039
|
-
*
|
|
36040
|
-
* @stability unstable
|
|
36041
|
-
* @since 4.0.0
|
|
36042
|
-
*/
|
|
36043
|
-
var fileUrlOps = (windows) => ({
|
|
36044
|
-
fromFileUrl: (url) => try_({
|
|
36045
|
-
try: () => NodeUrl.fileURLToPath(url, { windows }),
|
|
36046
|
-
catch: (cause) => new BadArgument({
|
|
36047
|
-
module: "Path",
|
|
36048
|
-
method: "fromFileUrl",
|
|
36049
|
-
cause
|
|
36050
|
-
})
|
|
36051
|
-
}),
|
|
36052
|
-
toFileUrl: (path) => try_({
|
|
36053
|
-
try: () => NodeUrl.pathToFileURL(path, { windows }),
|
|
36054
|
-
catch: (cause) => new BadArgument({
|
|
36055
|
-
module: "Path",
|
|
36056
|
-
method: "toFileUrl",
|
|
36057
|
-
cause
|
|
36058
|
-
})
|
|
36059
|
-
})
|
|
36060
|
-
});
|
|
36061
|
-
({
|
|
36062
|
-
...Path.posix,
|
|
36063
|
-
.../*#__PURE__*/ fileUrlOps(false)
|
|
36064
|
-
});
|
|
36065
|
-
({
|
|
36066
|
-
...Path.win32,
|
|
36067
|
-
.../*#__PURE__*/ fileUrlOps(true)
|
|
36068
|
-
});
|
|
36069
|
-
provideMerge(layer$4, mergeAll$1(layer$2, /* @__PURE__ */ succeed$3(Path$1)(/*#__PURE__*/ make$13({
|
|
36070
|
-
...Path,
|
|
36071
|
-
.../*#__PURE__*/ fileUrlOps(void 0)
|
|
36072
|
-
}))));
|
|
36073
|
-
TaggedError("GitInputError");
|
|
36074
|
-
TaggedError("GitCommandError");
|
|
36075
|
-
TaggedError("GitRepositoryError");
|
|
36076
|
-
TaggedError("GitIdentityError");
|
|
36077
|
-
TaggedError("GitProtocolError");
|
|
36078
|
-
TaggedError("GitConflictError");
|
|
36079
|
-
TaggedError("GitIndexError");
|
|
36080
|
-
//#endregion
|
|
36081
|
-
//#region ../git/src/lib/validation.ts
|
|
36082
|
-
var text$1 = String$1.check(isNonEmpty(), isPattern(/^[^\0]*$/));
|
|
36083
|
-
var oid = String$1.check(isPattern(/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/));
|
|
36084
|
-
var path$1 = text$1.check(makeFilter((value) => !value.startsWith("/") && !value.includes("\\") && value.split("/").every((part) => part.length > 0 && part !== "." && part !== ".." && part.toLowerCase() !== ".git")));
|
|
36085
|
-
var ref$1 = text$1.check(makeFilter((value) => value.startsWith("refs/") && !/[\x00-\x20\x7f~^:?*\[\\]/.test(value) && !value.includes("..") && !value.includes("@{") && !value.endsWith(".") && value.split("/").every((part) => part.length > 0 && !part.startsWith(".") && !part.endsWith(".lock"))));
|
|
36086
|
-
var revision = Union([
|
|
36087
|
-
oid,
|
|
36088
|
-
ref$1,
|
|
36089
|
-
Literal("HEAD")
|
|
36090
|
-
]);
|
|
36091
|
-
String$1.check(isPattern(/^[A-Za-z0-9][A-Za-z0-9._-]*$/));
|
|
36092
|
-
var mode = Literals([
|
|
36093
|
-
"100644",
|
|
36094
|
-
"100755",
|
|
36095
|
-
"120000"
|
|
36096
|
-
]);
|
|
36097
|
-
Struct({
|
|
36098
|
-
name: text$1.check(isPattern(/^[^\r\n<>]+$/)),
|
|
36099
|
-
email: text$1.check(isPattern(/^[^\r\n<>]+$/))
|
|
36100
|
-
});
|
|
36101
|
-
Struct({
|
|
36102
|
-
ref: ref$1,
|
|
36103
|
-
expectedHead: NullOr(oid),
|
|
36104
|
-
files: ArraySchema(Struct({
|
|
36105
|
-
path: path$1,
|
|
36106
|
-
contents: Union([
|
|
36107
|
-
String$1,
|
|
36108
|
-
Uint8Array$1,
|
|
36109
|
-
Null
|
|
36110
|
-
]),
|
|
36111
|
-
mode: optional$1(mode)
|
|
36112
|
-
})).check(isMaxLength(1e4)),
|
|
36113
|
-
message: text$1,
|
|
36114
|
-
extraParents: optional$1(ArraySchema(oid).check(isMaxLength(16)))
|
|
36115
|
-
});
|
|
36116
|
-
Struct({
|
|
36117
|
-
...Struct({
|
|
36118
|
-
base: optional$1(oid),
|
|
36119
|
-
target: optional$1(oid),
|
|
36120
|
-
staged: optional$1(Boolean$1),
|
|
36121
|
-
paths: optional$1(ArraySchema(path$1).check(isMaxLength(1e4)))
|
|
36122
|
-
}).fields,
|
|
36123
|
-
context: optional$1(Int$1.check(isBetween({
|
|
36124
|
-
minimum: 0,
|
|
36125
|
-
maximum: 1e3
|
|
36126
|
-
})))
|
|
36127
|
-
});
|
|
36128
|
-
Struct({ environment: optional$1(Record(String$1, Union([String$1, Undefined]))) });
|
|
36129
|
-
Struct({
|
|
36130
|
-
commit: oid,
|
|
36131
|
-
path: path$1,
|
|
36132
|
-
limit: Int$1.check(isBetween({
|
|
36133
|
-
minimum: 1,
|
|
36134
|
-
maximum: 101
|
|
36135
|
-
})),
|
|
36136
|
-
skip: optional$1(Int$1.check(isBetween({
|
|
36137
|
-
minimum: 0,
|
|
36138
|
-
maximum: 1e4
|
|
36139
|
-
})))
|
|
36140
|
-
});
|
|
36141
|
-
Struct({
|
|
36142
|
-
from: revision,
|
|
36143
|
-
limit: Int$1.check(isBetween({
|
|
36144
|
-
minimum: 1,
|
|
36145
|
-
maximum: 500
|
|
36146
|
-
}))
|
|
36147
|
-
});
|
|
36148
|
-
Int$1.check(isBetween({
|
|
36149
|
-
minimum: 1,
|
|
36150
|
-
maximum: 1e3
|
|
36151
|
-
}));
|
|
36152
|
-
//#endregion
|
|
36153
|
-
//#region ../git/src/lib/git.ts
|
|
36154
|
-
/** Opens the Git repository that contains a directory. */
|
|
36155
|
-
var Git = class extends Service()("@bud.sh/git/Git") {};
|
|
36156
|
-
//#endregion
|
|
36157
|
-
//#region ../live/src/server/hub.ts
|
|
36158
|
-
/**
|
|
36159
|
-
* Orders every published change into one stream per companion. Clients resume from their
|
|
36160
|
-
* last event id; anything older than the replay buffer, or from an earlier companion,
|
|
36161
|
-
* receives a resync instead.
|
|
36162
|
-
*/
|
|
36163
|
-
var LiveHub = class {
|
|
36164
|
-
epoch = randomBytes(8).toString("hex");
|
|
36165
|
-
#sequence = 0;
|
|
36166
|
-
#buffer = [];
|
|
36167
|
-
#replay;
|
|
36168
|
-
#heartbeatMs;
|
|
36169
|
-
#listeners = /* @__PURE__ */ new Set();
|
|
36170
|
-
constructor(options = {}) {
|
|
36171
|
-
this.#replay = options.replay ?? 1024;
|
|
36172
|
-
this.#heartbeatMs = options.heartbeatMs ?? 15e3;
|
|
36173
|
-
}
|
|
36174
|
-
/**
|
|
36175
|
-
* Sends a change to every connected client.
|
|
36176
|
-
* @param topic - The domain, such as `previews` or `files`.
|
|
36177
|
-
* @param data - JSON data each domain validates with its own schema.
|
|
36178
|
-
*/
|
|
36179
|
-
publish(topic, data) {
|
|
36180
|
-
let event = {
|
|
36181
|
-
id: `${this.epoch}:${++this.#sequence}`,
|
|
36182
|
-
topic,
|
|
36183
|
-
data
|
|
36184
|
-
};
|
|
36185
|
-
this.#buffer.push(event);
|
|
36186
|
-
if (this.#buffer.length > this.#replay) this.#buffer.shift();
|
|
36187
|
-
for (let listener of [...this.#listeners]) listener(event);
|
|
36188
|
-
}
|
|
36189
|
-
/**
|
|
36190
|
-
* Opens one client's `text/event-stream`.
|
|
36191
|
-
* @param lastEventId - The client's `Last-Event-ID`, if it is resuming.
|
|
36192
|
-
* @param signal - The request's lifetime; aborting closes the stream.
|
|
36193
|
-
* @returns The response body.
|
|
36194
|
-
*/
|
|
36195
|
-
open(lastEventId, signal) {
|
|
36196
|
-
let encoder = new TextEncoder();
|
|
36197
|
-
let close = () => {};
|
|
36198
|
-
return new ReadableStream({
|
|
36199
|
-
start: (controller) => {
|
|
36200
|
-
let send = (text) => controller.enqueue(encoder.encode(text));
|
|
36201
|
-
let write = (event) => send(`id: ${event.id}\nevent: ${event.topic}\ndata: ${JSON.stringify(event.data)}\n\n`);
|
|
36202
|
-
let missed = this.#since(lastEventId);
|
|
36203
|
-
if (missed) for (let event of missed) write(event);
|
|
36204
|
-
else write({
|
|
36205
|
-
id: `${this.epoch}:${this.#sequence}`,
|
|
36206
|
-
topic: RESYNC,
|
|
36207
|
-
data: null
|
|
36208
|
-
});
|
|
36209
|
-
this.#listeners.add(write);
|
|
36210
|
-
let heartbeat = setInterval(() => send(": heartbeat\n\n"), this.#heartbeatMs);
|
|
36211
|
-
close = () => {
|
|
36212
|
-
clearInterval(heartbeat);
|
|
36213
|
-
this.#listeners.delete(write);
|
|
36214
|
-
try {
|
|
36215
|
-
controller.close();
|
|
36216
|
-
} catch {}
|
|
36217
|
-
};
|
|
36218
|
-
if (signal.aborted) close();
|
|
36219
|
-
else signal.addEventListener("abort", close, { once: true });
|
|
36220
|
-
},
|
|
36221
|
-
cancel: () => close()
|
|
36692
|
+
case "change":
|
|
36693
|
+
offerUnsafe(queue, {
|
|
36694
|
+
_tag: "Update",
|
|
36695
|
+
path
|
|
36696
|
+
});
|
|
36697
|
+
return;
|
|
36698
|
+
}
|
|
36699
|
+
});
|
|
36700
|
+
watcher.on("error", (error) => {
|
|
36701
|
+
failCauseUnsafe(queue, fail$7(systemError({
|
|
36702
|
+
module: "FileSystem",
|
|
36703
|
+
_tag: "Unknown",
|
|
36704
|
+
method: "watch",
|
|
36705
|
+
pathOrDescriptor: path,
|
|
36706
|
+
cause: error
|
|
36707
|
+
})));
|
|
36708
|
+
});
|
|
36709
|
+
watcher.on("close", () => {
|
|
36710
|
+
endUnsafe(queue);
|
|
36711
|
+
});
|
|
36712
|
+
return watcher;
|
|
36713
|
+
}), (watcher) => sync(() => watcher.close())).pipe(tap$1(() => sync(() => options?.onReady?.()))));
|
|
36714
|
+
var watch = (backend, path, options) => all$1([stat$1(path), serviceOption(WatchBackend)]).pipe(map$2(([stat, callerBackend]) => orElse(callerBackend, () => backend).pipe(flatMap$5((_) => _.register(path, stat, options)), getOrElse(() => watchNode(path, stat, options)))), unwrap$1);
|
|
36715
|
+
var writeFile$1 = (path, data, options) => callback$1((resume, signal) => {
|
|
36716
|
+
try {
|
|
36717
|
+
NFS.writeFile(path, data, {
|
|
36718
|
+
signal,
|
|
36719
|
+
flag: options?.flag,
|
|
36720
|
+
mode: options?.mode
|
|
36721
|
+
}, (err) => {
|
|
36722
|
+
if (err) resume(fail$6(handleErrnoException("FileSystem", "writeFile")(err, [path])));
|
|
36723
|
+
else resume(void_);
|
|
36222
36724
|
});
|
|
36725
|
+
} catch (err) {
|
|
36726
|
+
resume(fail$6(handleBadArgument("writeFile")(err)));
|
|
36223
36727
|
}
|
|
36224
|
-
|
|
36225
|
-
|
|
36226
|
-
|
|
36227
|
-
|
|
36228
|
-
|
|
36229
|
-
|
|
36230
|
-
|
|
36231
|
-
|
|
36728
|
+
});
|
|
36729
|
+
var makeFileSystem = /*#__PURE__*/ map$2(/*#__PURE__*/ serviceOption(WatchBackend), (backend) => make$14({
|
|
36730
|
+
access: access$1,
|
|
36731
|
+
chmod,
|
|
36732
|
+
chown,
|
|
36733
|
+
copy,
|
|
36734
|
+
copyFile,
|
|
36735
|
+
glob,
|
|
36736
|
+
link,
|
|
36737
|
+
makeDirectory,
|
|
36738
|
+
makeTempDirectory,
|
|
36739
|
+
makeTempDirectoryScoped,
|
|
36740
|
+
makeTempFile,
|
|
36741
|
+
makeTempFileScoped,
|
|
36742
|
+
open,
|
|
36743
|
+
readDirectory,
|
|
36744
|
+
readFile: readFile$1,
|
|
36745
|
+
readLink,
|
|
36746
|
+
realPath,
|
|
36747
|
+
remove,
|
|
36748
|
+
rename: rename$1,
|
|
36749
|
+
stat: stat$1,
|
|
36750
|
+
symlink: symlink$1,
|
|
36751
|
+
truncate,
|
|
36752
|
+
utimes,
|
|
36753
|
+
watch(path, options) {
|
|
36754
|
+
return watch(backend, path, options);
|
|
36755
|
+
},
|
|
36756
|
+
writeFile: writeFile$1
|
|
36757
|
+
}));
|
|
36232
36758
|
//#endregion
|
|
36233
|
-
//#region
|
|
36234
|
-
/** The companion's ordered live channel: publishes changes and opens client streams. */
|
|
36235
|
-
var LiveChannel = class extends Service()("@bud.sh/live/LiveChannel") {};
|
|
36759
|
+
//#region ../../node_modules/.pnpm/@effect+platform-node@4.0.5_effect@4.0.5/node_modules/@effect/platform-node/dist/NodeFileSystem.js
|
|
36236
36760
|
/**
|
|
36237
|
-
*
|
|
36238
|
-
*
|
|
36239
|
-
*
|
|
36761
|
+
* Node.js `FileSystem` layer for programs that perform real filesystem I/O.
|
|
36762
|
+
*
|
|
36763
|
+
* The exported layer satisfies the platform-independent `FileSystem` service
|
|
36764
|
+
* with Node-backed operations for files, directories, metadata, permissions,
|
|
36765
|
+
* links, temporary paths, and path watching. Effects still call the service from
|
|
36766
|
+
* `effect/FileSystem`; this module only chooses the Node implementation.
|
|
36767
|
+
*
|
|
36768
|
+
* @stability unstable
|
|
36769
|
+
* @since 4.0.0
|
|
36240
36770
|
*/
|
|
36241
|
-
function liveChannelLayer(options = {}) {
|
|
36242
|
-
return sync$1(LiveChannel, () => {
|
|
36243
|
-
let hub = new LiveHub(options);
|
|
36244
|
-
return {
|
|
36245
|
-
epoch: hub.epoch,
|
|
36246
|
-
publish: (topic, data) => sync(() => hub.publish(topic, data)),
|
|
36247
|
-
open: (lastEventId, signal) => hub.open(lastEventId, signal)
|
|
36248
|
-
};
|
|
36249
|
-
});
|
|
36250
|
-
}
|
|
36251
36771
|
/**
|
|
36252
|
-
*
|
|
36253
|
-
*
|
|
36254
|
-
* @
|
|
36255
|
-
* @
|
|
36256
|
-
* @
|
|
36772
|
+
* Provides the `FileSystem` service backed by Node filesystem APIs.
|
|
36773
|
+
*
|
|
36774
|
+
* @stability unstable
|
|
36775
|
+
* @category layers
|
|
36776
|
+
* @since 4.0.0
|
|
36257
36777
|
*/
|
|
36258
|
-
|
|
36259
|
-
return gen(function* () {
|
|
36260
|
-
let channel = yield* LiveChannel;
|
|
36261
|
-
yield* changes.pipe(runForEach((change) => channel.publish(topic, change)), ignore$2, forkScoped);
|
|
36262
|
-
});
|
|
36263
|
-
}
|
|
36264
|
-
//#endregion
|
|
36265
|
-
//#region ../project/src/server/errors.ts
|
|
36266
|
-
/** Expected project startup/configuration failure; nested causes remain server-only. */
|
|
36267
|
-
var ProjectError = class extends TaggedError("ProjectError") {};
|
|
36778
|
+
var layer$2 = /* @__PURE__ */ effect(FileSystem)(makeFileSystem);
|
|
36268
36779
|
//#endregion
|
|
36269
|
-
//#region
|
|
36780
|
+
//#region ../../node_modules/.pnpm/@effect+platform-node-shared@4.0.5_effect@4.0.5/node_modules/@effect/platform-node-shared/dist/NodePath.js
|
|
36270
36781
|
/**
|
|
36271
|
-
*
|
|
36272
|
-
*
|
|
36273
|
-
*
|
|
36274
|
-
*
|
|
36275
|
-
*
|
|
36782
|
+
* Node-backed provider for Effect's `Path` service.
|
|
36783
|
+
*
|
|
36784
|
+
* This module turns Node's `node:path` and `node:url` APIs into `Path` layers.
|
|
36785
|
+
* `layer` uses the host platform path implementation, while `layerPosix` and
|
|
36786
|
+
* `layerWin32` provide fixed POSIX and Windows variants. All three layers also
|
|
36787
|
+
* include helpers for converting between file paths and file URLs.
|
|
36788
|
+
*
|
|
36789
|
+
* @stability unstable
|
|
36790
|
+
* @since 4.0.0
|
|
36276
36791
|
*/
|
|
36277
|
-
|
|
36278
|
-
|
|
36279
|
-
|
|
36280
|
-
|
|
36281
|
-
|
|
36282
|
-
|
|
36283
|
-
reason: "root",
|
|
36284
|
-
message: "Cannot open the module project root.",
|
|
36285
|
-
cause
|
|
36286
|
-
})));
|
|
36287
|
-
let file = yield* fs.realPath(path.resolve(project, module)).pipe(mapError$2((cause) => new ProjectError({
|
|
36288
|
-
reason: "read",
|
|
36289
|
-
message: `${setting} cannot find ${module} in the project.`,
|
|
36290
|
-
cause
|
|
36291
|
-
})));
|
|
36292
|
-
let relative = path.relative(project, file);
|
|
36293
|
-
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative) || relative.split(path.sep).includes("node_modules")) return yield* new ProjectError({
|
|
36294
|
-
reason: "invalid",
|
|
36295
|
-
message: `${setting} must resolve to a project-owned file outside node_modules.`
|
|
36296
|
-
});
|
|
36297
|
-
if (kind === "server" && !file.endsWith(".server.ts")) return yield* new ProjectError({
|
|
36298
|
-
reason: "invalid",
|
|
36299
|
-
message: `${setting} must resolve to a .server.ts module.`
|
|
36300
|
-
});
|
|
36301
|
-
if (kind === "browser" && (!/\.(?:ts|tsx|js|jsx)$/.test(file) || /\.server\.[cm]?[jt]sx?$/.test(file))) return yield* new ProjectError({
|
|
36302
|
-
reason: "invalid",
|
|
36303
|
-
message: `${setting} must resolve to a browser module.`
|
|
36304
|
-
});
|
|
36305
|
-
if ((yield* fs.stat(file).pipe(mapError$2((cause) => new ProjectError({
|
|
36306
|
-
reason: "read",
|
|
36307
|
-
message: `${setting} cannot inspect ${module}.`,
|
|
36792
|
+
var fileUrlOps = (windows) => ({
|
|
36793
|
+
fromFileUrl: (url) => try_({
|
|
36794
|
+
try: () => NodeUrl.fileURLToPath(url, { windows }),
|
|
36795
|
+
catch: (cause) => new BadArgument({
|
|
36796
|
+
module: "Path",
|
|
36797
|
+
method: "fromFileUrl",
|
|
36308
36798
|
cause
|
|
36309
|
-
})
|
|
36310
|
-
|
|
36311
|
-
|
|
36312
|
-
})
|
|
36313
|
-
|
|
36314
|
-
|
|
36315
|
-
|
|
36799
|
+
})
|
|
36800
|
+
}),
|
|
36801
|
+
toFileUrl: (path) => try_({
|
|
36802
|
+
try: () => NodeUrl.pathToFileURL(path, { windows }),
|
|
36803
|
+
catch: (cause) => new BadArgument({
|
|
36804
|
+
module: "Path",
|
|
36805
|
+
method: "toFileUrl",
|
|
36316
36806
|
cause
|
|
36317
|
-
})
|
|
36318
|
-
|
|
36319
|
-
});
|
|
36320
|
-
}
|
|
36321
|
-
//#endregion
|
|
36322
|
-
//#region ../project/src/lib/validate.ts
|
|
36323
|
-
var errors = /* @__PURE__ */ new WeakMap();
|
|
36324
|
-
/**
|
|
36325
|
-
* Validates config, throwing its first problem as the `Error` the config checks report.
|
|
36326
|
-
* @param schema - The config schema.
|
|
36327
|
-
* @param value - The project's untrusted setting.
|
|
36328
|
-
* @returns The validated, normalized setting.
|
|
36329
|
-
*/
|
|
36330
|
-
function parseConfig(schema, value) {
|
|
36331
|
-
let result = parseSafe(schema, value, { abortEarly: true });
|
|
36332
|
-
if (result.success) return result.value;
|
|
36333
|
-
let issue = result.issues[0];
|
|
36334
|
-
throw errors.get(issue) ?? new Error(issue.message);
|
|
36335
|
-
}
|
|
36336
|
-
/**
|
|
36337
|
-
* Reports any failure of `schema` with one message.
|
|
36338
|
-
* @param schema - The schema to run.
|
|
36339
|
-
* @param message - The message for any of its failures.
|
|
36340
|
-
* @returns The described schema.
|
|
36341
|
-
*/
|
|
36342
|
-
function described(schema, message) {
|
|
36343
|
-
return createSchema((value, context) => {
|
|
36344
|
-
let result = schema["~run"](value, context);
|
|
36345
|
-
return result.issues ? fail(message, context.path) : result;
|
|
36346
|
-
});
|
|
36347
|
-
}
|
|
36348
|
-
/**
|
|
36349
|
-
* Applies a rule after `schema` validates.
|
|
36350
|
-
* @param schema - The schema to run first.
|
|
36351
|
-
* @param rule - Returns a message, or undefined when the value is valid.
|
|
36352
|
-
* @returns The checked schema.
|
|
36353
|
-
*/
|
|
36354
|
-
function check(schema, rule) {
|
|
36355
|
-
return createSchema((value, context) => {
|
|
36356
|
-
let result = schema["~run"](value, context);
|
|
36357
|
-
if (result.issues) return result;
|
|
36358
|
-
let message = rule(result.value);
|
|
36359
|
-
return message === void 0 ? result : fail(message, context.path);
|
|
36360
|
-
});
|
|
36361
|
-
}
|
|
36362
|
-
/**
|
|
36363
|
-
* Validates with a function that throws, such as a URL or route map parser, keeping its error.
|
|
36364
|
-
* @param schema - The schema to run first.
|
|
36365
|
-
* @param parse - Turns its value into the output, or throws.
|
|
36366
|
-
* @returns The delegating schema.
|
|
36367
|
-
*/
|
|
36368
|
-
function delegate(schema, parse) {
|
|
36369
|
-
return createSchema((value, context) => {
|
|
36370
|
-
let result = schema["~run"](value, context);
|
|
36371
|
-
if (result.issues) return result;
|
|
36372
|
-
try {
|
|
36373
|
-
return { value: parse(result.value) };
|
|
36374
|
-
} catch (error) {
|
|
36375
|
-
if (!(error instanceof Error)) throw error;
|
|
36376
|
-
let failure = fail(error.message, context.path);
|
|
36377
|
-
errors.set(failure.issues[0], error);
|
|
36378
|
-
return failure;
|
|
36379
|
-
}
|
|
36380
|
-
});
|
|
36381
|
-
}
|
|
36382
|
-
/**
|
|
36383
|
-
* Chooses a schema from the unvalidated value.
|
|
36384
|
-
* @param choose - Picks the schema for a value.
|
|
36385
|
-
* @returns The selecting schema.
|
|
36386
|
-
*/
|
|
36387
|
-
function select(choose) {
|
|
36388
|
-
return createSchema((value, context) => choose(value)["~run"](value, context));
|
|
36389
|
-
}
|
|
36390
|
-
/**
|
|
36391
|
-
* Substitutes a default for null or undefined.
|
|
36392
|
-
* @param schema - The schema for present values.
|
|
36393
|
-
* @param value - Makes the default.
|
|
36394
|
-
* @returns The schema with its default.
|
|
36395
|
-
*/
|
|
36396
|
-
function fallback(schema, value) {
|
|
36397
|
-
return createSchema((input, context) => input == null ? { value: value() } : schema["~run"](input, context));
|
|
36398
|
-
}
|
|
36399
|
-
/**
|
|
36400
|
-
* A settings object that names its unsupported keys before validating its fields.
|
|
36401
|
-
* @param name - The setting's name in messages, such as `previews`.
|
|
36402
|
-
* @param shape - The schema for each supported key.
|
|
36403
|
-
* @returns The settings schema.
|
|
36404
|
-
*/
|
|
36405
|
-
function settings(name, shape) {
|
|
36406
|
-
let schema = object(shape);
|
|
36407
|
-
return createSchema((value, context) => {
|
|
36408
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return fail(`${name} must be an object.`, context.path);
|
|
36409
|
-
for (let key of Object.keys(value)) if (!Object.hasOwn(shape, key)) return fail(`${name}.${key} is not a supported setting.`, [...context.path, key]);
|
|
36410
|
-
return schema["~run"](value, context);
|
|
36411
|
-
});
|
|
36412
|
-
}
|
|
36413
|
-
/**
|
|
36414
|
-
* A list whose length is checked before its entries.
|
|
36415
|
-
* @param item - The schema for each entry.
|
|
36416
|
-
* @param message - The message when it is not a list or is too short.
|
|
36417
|
-
* @param minimum - The fewest entries it may have.
|
|
36418
|
-
* @returns The list schema.
|
|
36419
|
-
*/
|
|
36420
|
-
function list(item, message, minimum = 0) {
|
|
36421
|
-
let items = array(item);
|
|
36422
|
-
return createSchema((value, context) => !Array.isArray(value) || value.length < minimum ? fail(message, context.path) : items["~run"](value, context));
|
|
36423
|
-
}
|
|
36424
|
-
//#endregion
|
|
36425
|
-
//#region ../project/src/lib/files.ts
|
|
36426
|
-
/** The Files settings, with their defaults filled in. */
|
|
36427
|
-
var BudFilesSchema = defaulted(settings("files", { edit: fallback(described(boolean(), "files.edit must be true or false."), () => true) }), () => ({ edit: true }));
|
|
36428
|
-
//#endregion
|
|
36429
|
-
//#region ../project/src/lib/flows.ts
|
|
36430
|
-
var message$1 = "flows must be [{ id, title, start, script, steps: [{ id, title }], block? }] with short ids, local paths and unique steps.";
|
|
36431
|
-
var slug = (value) => typeof value === "string" && /^[a-z0-9][a-z0-9-]{0,62}$/.test(value);
|
|
36432
|
-
var text = (value) => typeof value === "string" && value.trim() !== "" && value.length <= 200;
|
|
36433
|
-
function flow(value) {
|
|
36434
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
36435
|
-
let { id, title, start, script, steps, block = [] } = value;
|
|
36436
|
-
if (!slug(id) || !text(title) || !text(script) || script.includes("\0")) return null;
|
|
36437
|
-
if (typeof start !== "string" || !start.startsWith("/") || start.startsWith("//")) return null;
|
|
36438
|
-
if (new URL(start, "http://bud.invalid").origin !== "http://bud.invalid") return null;
|
|
36439
|
-
if (!Array.isArray(steps) || !steps.length || steps.length > 50) return null;
|
|
36440
|
-
let resolved = steps.map((step) => {
|
|
36441
|
-
let { id: stepId, title: stepTitle } = step ?? {};
|
|
36442
|
-
return slug(stepId) && text(stepTitle) ? {
|
|
36443
|
-
id: stepId,
|
|
36444
|
-
title: stepTitle.trim()
|
|
36445
|
-
} : null;
|
|
36446
|
-
});
|
|
36447
|
-
if (resolved.some((step) => !step)) return null;
|
|
36448
|
-
let ids = resolved.map((step) => step.id);
|
|
36449
|
-
if (new Set(ids).size !== ids.length) return null;
|
|
36450
|
-
if (!Array.isArray(block) || block.some((part) => !text(part))) return null;
|
|
36451
|
-
return {
|
|
36452
|
-
id,
|
|
36453
|
-
title: title.trim(),
|
|
36454
|
-
start,
|
|
36455
|
-
script: script.trim(),
|
|
36456
|
-
steps: resolved,
|
|
36457
|
-
block
|
|
36458
|
-
};
|
|
36459
|
-
}
|
|
36460
|
-
/** The flows, none when omitted. */
|
|
36461
|
-
var BudFlowsSchema = createSchema((value, context) => {
|
|
36462
|
-
if (value === void 0) return { value: [] };
|
|
36463
|
-
if (!Array.isArray(value)) return fail(message$1, context.path);
|
|
36464
|
-
let flows = value.map(flow);
|
|
36465
|
-
if (flows.some((resolved) => !resolved)) return fail(message$1, context.path);
|
|
36466
|
-
let ids = flows.map((resolved) => resolved.id);
|
|
36467
|
-
if (new Set(ids).size !== ids.length) return fail(message$1, context.path);
|
|
36468
|
-
return { value: flows };
|
|
36469
|
-
});
|
|
36470
|
-
/**
|
|
36471
|
-
* Where a flow's step is pictured: a route of its own, named `<flow>/<step>`, in a group named
|
|
36472
|
-
* after the flow. Its path is the start page with the step as its fragment, unique among routes.
|
|
36473
|
-
* @param flow - The flow.
|
|
36474
|
-
* @param step - One of its steps.
|
|
36475
|
-
* @returns The step's route.
|
|
36476
|
-
*/
|
|
36477
|
-
function flowStepRoute(flow, step) {
|
|
36478
|
-
return {
|
|
36479
|
-
id: `${flow.id}/${step.id}`,
|
|
36480
|
-
path: `${flow.start.replace(/#.*$/, "")}#${flow.id}-${step.id}`,
|
|
36481
|
-
title: step.title,
|
|
36482
|
-
group: flow.title,
|
|
36483
|
-
flow: {
|
|
36484
|
-
id: flow.id,
|
|
36485
|
-
step: step.id
|
|
36486
|
-
}
|
|
36487
|
-
};
|
|
36488
|
-
}
|
|
36489
|
-
//#endregion
|
|
36490
|
-
//#region ../project/src/lib/logo.ts
|
|
36491
|
-
/** The image types a logo may be, by extension. */
|
|
36492
|
-
var LOGO_TYPES = Object.freeze({
|
|
36493
|
-
".svg": "image/svg+xml",
|
|
36494
|
-
".png": "image/png",
|
|
36495
|
-
".webp": "image/webp",
|
|
36496
|
-
".jpg": "image/jpeg",
|
|
36497
|
-
".jpeg": "image/jpeg",
|
|
36498
|
-
".gif": "image/gif",
|
|
36499
|
-
".ico": "image/x-icon"
|
|
36807
|
+
})
|
|
36808
|
+
})
|
|
36500
36809
|
});
|
|
36501
|
-
|
|
36502
|
-
|
|
36503
|
-
|
|
36504
|
-
let path = value.trim().replaceAll("\\", "/");
|
|
36505
|
-
if (!path || path.startsWith("/") || /^[a-z]:/i.test(path)) return null;
|
|
36506
|
-
if (path.split("/").includes("..")) return null;
|
|
36507
|
-
let extension = path.slice(path.lastIndexOf(".")).toLowerCase();
|
|
36508
|
-
return Object.hasOwn(LOGO_TYPES, extension) ? path : null;
|
|
36509
|
-
}
|
|
36510
|
-
/** A logo in its one resolved form, or null for Bud's own mark. */
|
|
36511
|
-
var BudLogoSchema = createSchema((value, context) => {
|
|
36512
|
-
if (value === void 0) return { value: null };
|
|
36513
|
-
let light = image(typeof value === "object" && value ? value.light : value);
|
|
36514
|
-
let raw = typeof value === "object" && value ? value.dark : void 0;
|
|
36515
|
-
let dark = raw === void 0 ? null : image(raw);
|
|
36516
|
-
let keys = typeof value === "object" && value ? Object.keys(value) : [];
|
|
36517
|
-
if (light && (raw === void 0 || dark) && keys.every((key) => key === "light" || key === "dark")) return { value: {
|
|
36518
|
-
light,
|
|
36519
|
-
dark
|
|
36520
|
-
} };
|
|
36521
|
-
return fail(logoMessage, context.path);
|
|
36810
|
+
({
|
|
36811
|
+
...Path.posix,
|
|
36812
|
+
.../*#__PURE__*/ fileUrlOps(false)
|
|
36522
36813
|
});
|
|
36523
|
-
|
|
36524
|
-
|
|
36525
|
-
|
|
36526
|
-
function branch(value) {
|
|
36527
|
-
return typeof value === "string" && value.length <= 255 && /^[A-Za-z0-9._/-]+$/.test(value) && !value.startsWith("-") && !value.startsWith("/") && !value.endsWith("/") && !value.endsWith(".lock") && !value.includes("..") && !value.includes("//") ? value : null;
|
|
36528
|
-
}
|
|
36529
|
-
function command(value) {
|
|
36530
|
-
return typeof value === "string" && value.trim() && value.length <= 4096 && !value.includes("\0") ? value.trim() : null;
|
|
36531
|
-
}
|
|
36532
|
-
/** The branch settings, defaults filled in. */
|
|
36533
|
-
var BudBranchesSchema = createSchema((value, context) => {
|
|
36534
|
-
let empty = {
|
|
36535
|
-
main: null,
|
|
36536
|
-
install: null,
|
|
36537
|
-
build: null,
|
|
36538
|
-
serve: null
|
|
36539
|
-
};
|
|
36540
|
-
if (value === void 0) return { value: empty };
|
|
36541
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return fail(message, context.path);
|
|
36542
|
-
let input = value;
|
|
36543
|
-
let build = input.build ?? {};
|
|
36544
|
-
if (!build || typeof build !== "object" || Array.isArray(build) || Object.keys(input).some((key) => key !== "main" && key !== "build")) return fail(message, context.path);
|
|
36545
|
-
let steps = build;
|
|
36546
|
-
if (Object.keys(steps).some((key) => ![
|
|
36547
|
-
"install",
|
|
36548
|
-
"build",
|
|
36549
|
-
"serve"
|
|
36550
|
-
].includes(key))) return fail(message, context.path);
|
|
36551
|
-
let resolved = {
|
|
36552
|
-
main: input.main === void 0 ? null : branch(input.main),
|
|
36553
|
-
install: steps.install === void 0 ? null : command(steps.install),
|
|
36554
|
-
build: steps.build === void 0 ? null : command(steps.build),
|
|
36555
|
-
serve: steps.serve === void 0 ? null : command(steps.serve)
|
|
36556
|
-
};
|
|
36557
|
-
if (input.main !== void 0 && !resolved.main || [
|
|
36558
|
-
"install",
|
|
36559
|
-
"build",
|
|
36560
|
-
"serve"
|
|
36561
|
-
].some((key) => steps[key] !== void 0 && !resolved[key])) return fail(message, context.path);
|
|
36562
|
-
return { value: resolved };
|
|
36814
|
+
({
|
|
36815
|
+
...Path.win32,
|
|
36816
|
+
.../*#__PURE__*/ fileUrlOps(true)
|
|
36563
36817
|
});
|
|
36818
|
+
provideMerge(layer$4, mergeAll$1(layer$2, /* @__PURE__ */ succeed$3(Path$1)(/*#__PURE__*/ make$13({
|
|
36819
|
+
...Path,
|
|
36820
|
+
.../*#__PURE__*/ fileUrlOps(void 0)
|
|
36821
|
+
}))));
|
|
36822
|
+
TaggedError("GitInputError");
|
|
36823
|
+
TaggedError("GitCommandError");
|
|
36824
|
+
TaggedError("GitRepositoryError");
|
|
36825
|
+
TaggedError("GitIdentityError");
|
|
36826
|
+
TaggedError("GitProtocolError");
|
|
36827
|
+
TaggedError("GitConflictError");
|
|
36828
|
+
TaggedError("GitIndexError");
|
|
36564
36829
|
//#endregion
|
|
36565
|
-
//#region ../
|
|
36566
|
-
|
|
36567
|
-
|
|
36568
|
-
|
|
36569
|
-
|
|
36570
|
-
|
|
36571
|
-
|
|
36572
|
-
|
|
36573
|
-
|
|
36574
|
-
let extension = server ? /\.server\.ts$/ : /\.(?:ts|tsx|js|jsx)$/;
|
|
36575
|
-
return described(string().refine((value) => value.length <= 1024 && value.startsWith("./") && !/[\u0000-\u001f\u007f\\?#%]/.test(value) && extension.test(value) && (server || !/\.server\.[cm]?[jt]sx?$/.test(value)) && !value.slice(2).split("/").some((part) => !part || part === "." || part === ".." || part === "node_modules")), message ?? (server ? `${name} must be a built-in adapter or a ./ module ending in .server.ts inside the project.` : `${name} must be bud:beads or a ./ browser module ending in .ts, .tsx, .js or .jsx inside the project.`));
|
|
36576
|
-
}
|
|
36577
|
-
var builtInStores = enum_([
|
|
36578
|
-
false,
|
|
36579
|
-
"bud:beads",
|
|
36580
|
-
"bud:memory",
|
|
36581
|
-
"bud:git-comments"
|
|
36582
|
-
]);
|
|
36583
|
-
function store(kind) {
|
|
36584
|
-
let module = modulePath(`storage.${kind}`, true);
|
|
36585
|
-
return defaulted(check(select((value) => value === false || value === "bud:beads" || value === "bud:memory" || value === "bud:git-comments" ? builtInStores : module), (value) => kind === "work" && value === "bud:git-comments" ? "storage.work cannot use bud:git-comments; it only stores threads." : void 0), "bud:beads");
|
|
36586
|
-
}
|
|
36587
|
-
/** The selected work and discussion stores, with Beads as the default for both. */
|
|
36588
|
-
var BudStorageSchema = defaulted(settings("storage", {
|
|
36589
|
-
work: store("work"),
|
|
36590
|
-
threads: store("threads")
|
|
36591
|
-
}), () => ({
|
|
36592
|
-
work: "bud:beads",
|
|
36593
|
-
threads: "bud:beads"
|
|
36594
|
-
}));
|
|
36595
|
-
/** Task presentation, chosen separately from its storage. */
|
|
36596
|
-
var BudDesignsSchema = defaulted(settings("designs", {
|
|
36597
|
-
tasks: defaulted(select((value) => value === "bud:beads" ? enum_(["bud:beads"]) : modulePath("designs.tasks", false)), "bud:beads"),
|
|
36598
|
-
pages: defaulted(enum_(["path", "title"]), "path")
|
|
36599
|
-
}), () => ({
|
|
36600
|
-
tasks: "bud:beads",
|
|
36601
|
-
pages: "path"
|
|
36602
|
-
}));
|
|
36603
|
-
//#endregion
|
|
36604
|
-
//#region ../project/src/lib/picker.ts
|
|
36605
|
-
var builtIn = /* @__PURE__ */ new Set([
|
|
36606
|
-
"bud:dom",
|
|
36607
|
-
"bud:react",
|
|
36608
|
-
"bud:react-grab"
|
|
36830
|
+
//#region ../git/src/lib/validation.ts
|
|
36831
|
+
var text = String$1.check(isNonEmpty(), isPattern(/^[^\0]*$/));
|
|
36832
|
+
var oid = String$1.check(isPattern(/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/));
|
|
36833
|
+
var path$1 = text.check(makeFilter((value) => !value.startsWith("/") && !value.includes("\\") && value.split("/").every((part) => part.length > 0 && part !== "." && part !== ".." && part.toLowerCase() !== ".git")));
|
|
36834
|
+
var ref$1 = text.check(makeFilter((value) => value.startsWith("refs/") && !/[\x00-\x20\x7f~^:?*\[\\]/.test(value) && !value.includes("..") && !value.includes("@{") && !value.endsWith(".") && value.split("/").every((part) => part.length > 0 && !part.startsWith(".") && !part.endsWith(".lock"))));
|
|
36835
|
+
var revision = Union([
|
|
36836
|
+
oid,
|
|
36837
|
+
ref$1,
|
|
36838
|
+
Literal("HEAD")
|
|
36609
36839
|
]);
|
|
36610
|
-
|
|
36611
|
-
|
|
36612
|
-
|
|
36613
|
-
|
|
36614
|
-
|
|
36615
|
-
|
|
36616
|
-
|
|
36617
|
-
|
|
36618
|
-
|
|
36619
|
-
module: null
|
|
36620
|
-
} };
|
|
36621
|
-
let result = projectPicker["~run"](value, context);
|
|
36622
|
-
return result.issues ? result : { value: {
|
|
36623
|
-
name: result.value,
|
|
36624
|
-
module: result.value
|
|
36625
|
-
} };
|
|
36840
|
+
String$1.check(isPattern(/^[A-Za-z0-9][A-Za-z0-9._-]*$/));
|
|
36841
|
+
var mode = Literals([
|
|
36842
|
+
"100644",
|
|
36843
|
+
"100755",
|
|
36844
|
+
"120000"
|
|
36845
|
+
]);
|
|
36846
|
+
Struct({
|
|
36847
|
+
name: text.check(isPattern(/^[^\r\n<>]+$/)),
|
|
36848
|
+
email: text.check(isPattern(/^[^\r\n<>]+$/))
|
|
36626
36849
|
});
|
|
36627
|
-
|
|
36628
|
-
|
|
36629
|
-
|
|
36630
|
-
|
|
36631
|
-
|
|
36632
|
-
|
|
36633
|
-
|
|
36634
|
-
|
|
36635
|
-
|
|
36636
|
-
|
|
36637
|
-
|
|
36638
|
-
|
|
36850
|
+
Struct({
|
|
36851
|
+
ref: ref$1,
|
|
36852
|
+
expectedHead: NullOr(oid),
|
|
36853
|
+
files: ArraySchema(Struct({
|
|
36854
|
+
path: path$1,
|
|
36855
|
+
contents: Union([
|
|
36856
|
+
String$1,
|
|
36857
|
+
Uint8Array$1,
|
|
36858
|
+
Null
|
|
36859
|
+
]),
|
|
36860
|
+
mode: optional$1(mode)
|
|
36861
|
+
})).check(isMaxLength(1e4)),
|
|
36862
|
+
message: text,
|
|
36863
|
+
extraParents: optional$1(ArraySchema(oid).check(isMaxLength(16)))
|
|
36639
36864
|
});
|
|
36640
|
-
|
|
36641
|
-
|
|
36642
|
-
|
|
36643
|
-
|
|
36644
|
-
|
|
36645
|
-
|
|
36646
|
-
|
|
36647
|
-
|
|
36648
|
-
|
|
36649
|
-
|
|
36650
|
-
|
|
36651
|
-
}
|
|
36652
|
-
var ViewportsSchema = createSchema((value, context) => {
|
|
36653
|
-
if (!Array.isArray(value) || value.length === 0 || value.length > 4) return fail(viewportsMessage, context.path);
|
|
36654
|
-
let viewports = [];
|
|
36655
|
-
for (let entry of value) {
|
|
36656
|
-
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return fail(viewportsMessage, context.path);
|
|
36657
|
-
let { name, width, height, mobile, ...rest } = entry;
|
|
36658
|
-
let size = (pixels, from, to) => typeof pixels === "number" && Number.isInteger(pixels) && pixels >= from && pixels <= to;
|
|
36659
|
-
if (Object.keys(rest).length || typeof name !== "string" || !name.trim() || name.length > 40 || !viewportId(name) || !size(width, 240, 3840) || height !== void 0 && !size(height, 240, 4e3) || mobile !== void 0 && typeof mobile !== "boolean") return fail(viewportsMessage, context.path);
|
|
36660
|
-
let phone = mobile ?? width < 800;
|
|
36661
|
-
viewports.push({
|
|
36662
|
-
id: viewportId(name),
|
|
36663
|
-
name: name.trim(),
|
|
36664
|
-
width,
|
|
36665
|
-
height: height ?? (phone ? Math.round(width * 844 / 390) : 900),
|
|
36666
|
-
mobile: phone
|
|
36667
|
-
});
|
|
36668
|
-
}
|
|
36669
|
-
if (new Set(viewports.map((viewport) => viewport.id)).size !== viewports.length) return fail(viewportsMessage, context.path);
|
|
36670
|
-
return { value: viewports };
|
|
36865
|
+
Struct({
|
|
36866
|
+
...Struct({
|
|
36867
|
+
base: optional$1(oid),
|
|
36868
|
+
target: optional$1(oid),
|
|
36869
|
+
staged: optional$1(Boolean$1),
|
|
36870
|
+
paths: optional$1(ArraySchema(path$1).check(isMaxLength(1e4)))
|
|
36871
|
+
}).fields,
|
|
36872
|
+
context: optional$1(Int$1.check(isBetween({
|
|
36873
|
+
minimum: 0,
|
|
36874
|
+
maximum: 1e3
|
|
36875
|
+
})))
|
|
36671
36876
|
});
|
|
36672
|
-
|
|
36673
|
-
|
|
36674
|
-
|
|
36675
|
-
|
|
36676
|
-
|
|
36677
|
-
|
|
36678
|
-
|
|
36679
|
-
|
|
36680
|
-
|
|
36681
|
-
|
|
36682
|
-
|
|
36683
|
-
|
|
36684
|
-
|
|
36685
|
-
|
|
36686
|
-
|
|
36877
|
+
Struct({ environment: optional$1(Record(String$1, Union([String$1, Undefined]))) });
|
|
36878
|
+
Struct({
|
|
36879
|
+
commit: oid,
|
|
36880
|
+
path: path$1,
|
|
36881
|
+
limit: Int$1.check(isBetween({
|
|
36882
|
+
minimum: 1,
|
|
36883
|
+
maximum: 101
|
|
36884
|
+
})),
|
|
36885
|
+
skip: optional$1(Int$1.check(isBetween({
|
|
36886
|
+
minimum: 0,
|
|
36887
|
+
maximum: 1e4
|
|
36888
|
+
})))
|
|
36889
|
+
});
|
|
36890
|
+
Struct({
|
|
36891
|
+
from: revision,
|
|
36892
|
+
limit: Int$1.check(isBetween({
|
|
36893
|
+
minimum: 1,
|
|
36894
|
+
maximum: 500
|
|
36895
|
+
}))
|
|
36896
|
+
});
|
|
36897
|
+
Int$1.check(isBetween({
|
|
36898
|
+
minimum: 1,
|
|
36899
|
+
maximum: 1e3
|
|
36687
36900
|
}));
|
|
36688
36901
|
//#endregion
|
|
36689
|
-
//#region ../
|
|
36902
|
+
//#region ../git/src/lib/git.ts
|
|
36903
|
+
/** Opens the Git repository that contains a directory. */
|
|
36904
|
+
var Git = class extends Service()("@bud.sh/git/Git") {};
|
|
36905
|
+
//#endregion
|
|
36906
|
+
//#region ../live/src/server/hub.ts
|
|
36690
36907
|
/**
|
|
36691
|
-
*
|
|
36692
|
-
*
|
|
36693
|
-
*
|
|
36694
|
-
* @returns Named patterns confined to the screen's path.
|
|
36908
|
+
* Orders every published change into one stream per companion. Clients resume from their
|
|
36909
|
+
* last event id; anything older than the replay buffer, or from an earlier companion,
|
|
36910
|
+
* receives a resync instead.
|
|
36695
36911
|
*/
|
|
36696
|
-
|
|
36697
|
-
|
|
36698
|
-
|
|
36699
|
-
|
|
36700
|
-
|
|
36701
|
-
|
|
36702
|
-
|
|
36703
|
-
|
|
36704
|
-
|
|
36705
|
-
|
|
36706
|
-
visit(entry, name);
|
|
36707
|
-
continue;
|
|
36708
|
-
}
|
|
36709
|
-
if (!["GET", "ANY"].includes(entry.method) || typeof entry.href !== "function" || typeof entry.pattern?.source !== "string") throw new Error(`Screen route ${name} must be a GET route.`);
|
|
36710
|
-
let pattern = RoutePattern.parse(entry.pattern.source);
|
|
36711
|
-
let parts = pattern.toJSON();
|
|
36712
|
-
let pathname = `/${parts.pathname}`;
|
|
36713
|
-
if (parts.protocol || parts.hostname || parts.port || parts.pathname.split("/").some((segment) => /^\.{1,2}$/.test(decodeURIComponent(segment))) || pathname !== path && !pathname.startsWith(`${path}/`) && !pathname.startsWith(`${path}(/`)) throw new Error(`Screen route ${name} must stay under ${path}.`);
|
|
36714
|
-
result.push({
|
|
36715
|
-
name,
|
|
36716
|
-
pattern: pattern.source
|
|
36717
|
-
});
|
|
36718
|
-
}
|
|
36719
|
-
seen.delete(value);
|
|
36912
|
+
var LiveHub = class {
|
|
36913
|
+
epoch = randomBytes(8).toString("hex");
|
|
36914
|
+
#sequence = 0;
|
|
36915
|
+
#buffer = [];
|
|
36916
|
+
#replay;
|
|
36917
|
+
#heartbeatMs;
|
|
36918
|
+
#listeners = /* @__PURE__ */ new Set();
|
|
36919
|
+
constructor(options = {}) {
|
|
36920
|
+
this.#replay = options.replay ?? 1024;
|
|
36921
|
+
this.#heartbeatMs = options.heartbeatMs ?? 15e3;
|
|
36720
36922
|
}
|
|
36721
|
-
|
|
36722
|
-
|
|
36723
|
-
|
|
36724
|
-
|
|
36923
|
+
/**
|
|
36924
|
+
* Sends a change to every connected client.
|
|
36925
|
+
* @param topic - The domain, such as `previews` or `files`.
|
|
36926
|
+
* @param data - JSON data each domain validates with its own schema.
|
|
36927
|
+
*/
|
|
36928
|
+
publish(topic, data) {
|
|
36929
|
+
let event = {
|
|
36930
|
+
id: `${this.epoch}:${++this.#sequence}`,
|
|
36931
|
+
topic,
|
|
36932
|
+
data
|
|
36933
|
+
};
|
|
36934
|
+
this.#buffer.push(event);
|
|
36935
|
+
if (this.#buffer.length > this.#replay) this.#buffer.shift();
|
|
36936
|
+
for (let listener of [...this.#listeners]) listener(event);
|
|
36937
|
+
}
|
|
36938
|
+
/**
|
|
36939
|
+
* Opens one client's `text/event-stream`.
|
|
36940
|
+
* @param lastEventId - The client's `Last-Event-ID`, if it is resuming.
|
|
36941
|
+
* @param signal - The request's lifetime; aborting closes the stream.
|
|
36942
|
+
* @returns The response body.
|
|
36943
|
+
*/
|
|
36944
|
+
open(lastEventId, signal) {
|
|
36945
|
+
let encoder = new TextEncoder();
|
|
36946
|
+
let close = () => {};
|
|
36947
|
+
return new ReadableStream({
|
|
36948
|
+
start: (controller) => {
|
|
36949
|
+
let send = (text) => controller.enqueue(encoder.encode(text));
|
|
36950
|
+
let write = (event) => send(`id: ${event.id}\nevent: ${event.topic}\ndata: ${JSON.stringify(event.data)}\n\n`);
|
|
36951
|
+
let missed = this.#since(lastEventId);
|
|
36952
|
+
if (missed) for (let event of missed) write(event);
|
|
36953
|
+
else write({
|
|
36954
|
+
id: `${this.epoch}:${this.#sequence}`,
|
|
36955
|
+
topic: RESYNC,
|
|
36956
|
+
data: null
|
|
36957
|
+
});
|
|
36958
|
+
this.#listeners.add(write);
|
|
36959
|
+
let heartbeat = setInterval(() => send(": heartbeat\n\n"), this.#heartbeatMs);
|
|
36960
|
+
close = () => {
|
|
36961
|
+
clearInterval(heartbeat);
|
|
36962
|
+
this.#listeners.delete(write);
|
|
36963
|
+
try {
|
|
36964
|
+
controller.close();
|
|
36965
|
+
} catch {}
|
|
36966
|
+
};
|
|
36967
|
+
if (signal.aborted) close();
|
|
36968
|
+
else signal.addEventListener("abort", close, { once: true });
|
|
36969
|
+
},
|
|
36970
|
+
cancel: () => close()
|
|
36971
|
+
});
|
|
36972
|
+
}
|
|
36973
|
+
#since(lastEventId) {
|
|
36974
|
+
let last = lastEventId ? parseLiveId(lastEventId) : null;
|
|
36975
|
+
if (!last || last.epoch !== this.epoch || last.sequence > this.#sequence) return null;
|
|
36976
|
+
let oldest = this.#buffer[0] ? parseLiveId(this.#buffer[0].id).sequence : this.#sequence + 1;
|
|
36977
|
+
if (last.sequence < oldest - 1) return null;
|
|
36978
|
+
return this.#buffer.filter((event) => parseLiveId(event.id).sequence > last.sequence);
|
|
36979
|
+
}
|
|
36980
|
+
};
|
|
36725
36981
|
//#endregion
|
|
36726
|
-
//#region ../
|
|
36727
|
-
/**
|
|
36728
|
-
|
|
36729
|
-
* @returns The built-in screen, ready to relabel by spreading.
|
|
36730
|
-
*/
|
|
36731
|
-
function pages() {
|
|
36732
|
-
return {
|
|
36733
|
-
id: "pages",
|
|
36734
|
-
label: "Pages",
|
|
36735
|
-
icon: "browser",
|
|
36736
|
-
path: "/pages",
|
|
36737
|
-
module: "bud:pages"
|
|
36738
|
-
};
|
|
36739
|
-
}
|
|
36740
|
-
/**
|
|
36741
|
-
* Bud's canvas of every route's screenshot.
|
|
36742
|
-
* @returns The built-in screen, ready to relabel by spreading.
|
|
36743
|
-
*/
|
|
36744
|
-
function canvas() {
|
|
36745
|
-
return {
|
|
36746
|
-
id: "canvas",
|
|
36747
|
-
label: "Canvas",
|
|
36748
|
-
icon: "grid",
|
|
36749
|
-
path: "/canvas",
|
|
36750
|
-
module: "bud:canvas"
|
|
36751
|
-
};
|
|
36752
|
-
}
|
|
36753
|
-
/**
|
|
36754
|
-
* Bud's Beads task board.
|
|
36755
|
-
* @returns The built-in screen, ready to relabel by spreading.
|
|
36756
|
-
*/
|
|
36757
|
-
function tasks() {
|
|
36758
|
-
return {
|
|
36759
|
-
id: "tasks",
|
|
36760
|
-
label: "Tasks",
|
|
36761
|
-
icon: "tasks",
|
|
36762
|
-
path: "/tasks",
|
|
36763
|
-
module: "bud:tasks"
|
|
36764
|
-
};
|
|
36765
|
-
}
|
|
36982
|
+
//#region ../live/src/server/service.ts
|
|
36983
|
+
/** The companion's ordered live channel: publishes changes and opens client streams. */
|
|
36984
|
+
var LiveChannel = class extends Service()("@bud.sh/live/LiveChannel") {};
|
|
36766
36985
|
/**
|
|
36767
|
-
*
|
|
36768
|
-
* @
|
|
36986
|
+
* One ordered live channel per companion.
|
|
36987
|
+
* @param options - Replay buffer and heartbeat settings.
|
|
36988
|
+
* @returns The LiveChannel layer.
|
|
36769
36989
|
*/
|
|
36770
|
-
function
|
|
36771
|
-
return {
|
|
36772
|
-
|
|
36773
|
-
label: "Files",
|
|
36774
|
-
icon: "files",
|
|
36775
|
-
path: "/files",
|
|
36776
|
-
module: "bud:files"
|
|
36777
|
-
};
|
|
36778
|
-
}
|
|
36779
|
-
/** The screens shown when the config doesn't list any. */
|
|
36780
|
-
var defaultScreens = [
|
|
36781
|
-
pages(),
|
|
36782
|
-
canvas(),
|
|
36783
|
-
tasks(),
|
|
36784
|
-
files()
|
|
36785
|
-
];
|
|
36786
|
-
var builtIns$1 = new Map(defaultScreens.map((screen) => [screen.module, screen]));
|
|
36787
|
-
/** First path segments owned by Bud's routes, its development server and its public assets. */
|
|
36788
|
-
var reservedScreenPaths = [
|
|
36789
|
-
"/comments",
|
|
36790
|
-
...defaultScreens.map((screen) => screen.path),
|
|
36791
|
-
"/api",
|
|
36792
|
-
"/health",
|
|
36793
|
-
"/__bud",
|
|
36794
|
-
"/assets",
|
|
36795
|
-
"/app",
|
|
36796
|
-
"/src",
|
|
36797
|
-
"/node_modules",
|
|
36798
|
-
"/agents",
|
|
36799
|
-
"/brand",
|
|
36800
|
-
"/fonts",
|
|
36801
|
-
"/frameworks",
|
|
36802
|
-
"/licenses"
|
|
36803
|
-
];
|
|
36804
|
-
var ScreenSchema = check(described(any().refine((value) => !!value && typeof value === "object"), "Each screen must be an object."), ({ id, label, icon = "layers", module, path }) => {
|
|
36805
|
-
if (typeof id !== "string" || !/^[a-z][a-z0-9-]{0,31}$/.test(id)) return "Screen ids are 1-32 lowercase letters, digits and hyphens.";
|
|
36806
|
-
if (typeof label !== "string" || !label.trim()) return `Screen ${id} needs a label.`;
|
|
36807
|
-
if (typeof icon !== "string" || !icon) return `Screen ${id} needs an icon name.`;
|
|
36808
|
-
if (typeof module !== "string") return `Screen ${id} needs a module.`;
|
|
36809
|
-
let builtIn = builtIns$1.get(module);
|
|
36810
|
-
path ??= builtIn?.path ?? `/${id}`;
|
|
36811
|
-
if (builtIn) {
|
|
36812
|
-
if (id !== builtIn.id || path !== builtIn.path) return `${module} keeps id "${builtIn.id}" and path "${builtIn.path}".`;
|
|
36813
|
-
return;
|
|
36814
|
-
}
|
|
36815
|
-
if (!/^\.\/[^\0\\]+\.(?:ts|tsx|js|jsx)$/.test(module)) return `Screen ${id} must be a ./ module ending in .ts, .tsx, .js or .jsx.`;
|
|
36816
|
-
if (typeof path !== "string" || !/^\/[a-z0-9][a-z0-9-]*$/.test(path)) return `Screen ${id} needs one static path segment, such as /${id}.`;
|
|
36817
|
-
if (defaultScreens.some((screen) => screen.id === id)) return `Screen id "${id}" is reserved for a built-in screen.`;
|
|
36818
|
-
if (reservedScreenPaths.includes(path)) return `Bud reserves the path ${path}.`;
|
|
36819
|
-
});
|
|
36820
|
-
/** Bud's navigation in order; file and icon checks need the app and happen at startup. */
|
|
36821
|
-
var BudScreensSchema = defaulted(delegate(described(array(any()).refine((screens) => screens.length > 0), "screens must list at least one screen."), (screens) => {
|
|
36822
|
-
let ids = /* @__PURE__ */ new Set();
|
|
36823
|
-
let paths = /* @__PURE__ */ new Set();
|
|
36824
|
-
return screens.map((value) => {
|
|
36825
|
-
let input = parseConfig(ScreenSchema, value);
|
|
36826
|
-
let { id, label, icon = "layers", module } = input;
|
|
36827
|
-
let path = input.path ?? builtIns$1.get(module)?.path ?? `/${id}`;
|
|
36828
|
-
if (ids.has(id) || paths.has(path)) throw new Error("Screen ids and paths must be unique.");
|
|
36829
|
-
ids.add(id);
|
|
36830
|
-
paths.add(path);
|
|
36831
|
-
if (input.routes !== void 0) resolveScreenRoutes(input.routes, path);
|
|
36990
|
+
function liveChannelLayer(options = {}) {
|
|
36991
|
+
return sync$1(LiveChannel, () => {
|
|
36992
|
+
let hub = new LiveHub(options);
|
|
36832
36993
|
return {
|
|
36833
|
-
|
|
36834
|
-
|
|
36835
|
-
|
|
36836
|
-
path,
|
|
36837
|
-
module,
|
|
36838
|
-
...input.routes === void 0 ? {} : { routes: input.routes }
|
|
36994
|
+
epoch: hub.epoch,
|
|
36995
|
+
publish: (topic, data) => sync(() => hub.publish(topic, data)),
|
|
36996
|
+
open: (lastEventId, signal) => hub.open(lastEventId, signal)
|
|
36839
36997
|
};
|
|
36840
36998
|
});
|
|
36841
|
-
}), () => defaultScreens.map((screen) => ({ ...screen })));
|
|
36842
|
-
//#endregion
|
|
36843
|
-
//#region ../project/src/lib/config.ts
|
|
36844
|
-
var objectLike = (value) => !!value && typeof value === "object";
|
|
36845
|
-
function section(message, shape) {
|
|
36846
|
-
let schema = object(shape);
|
|
36847
|
-
return createSchema((value, context) => objectLike(value) ? schema["~run"](Array.isArray(value) ? { ...value } : value, context) : fail(message, context.path));
|
|
36848
|
-
}
|
|
36849
|
-
var ConfigFields = check(section("Bud config must export an object.", {
|
|
36850
|
-
routes: described(array(any()).refine((routes) => routes.length > 0), "bud.config must define at least one route."),
|
|
36851
|
-
name: optional(described(string().refine((name) => !!name.trim()), "name must be a nonempty string.")),
|
|
36852
|
-
target: optional(section("target must be an object.", {
|
|
36853
|
-
origin: any(),
|
|
36854
|
-
port: any()
|
|
36855
|
-
})),
|
|
36856
|
-
comments: optional(section("comments must be an object.", { remote: any() })),
|
|
36857
|
-
port: any(),
|
|
36858
|
-
screens: any(),
|
|
36859
|
-
storage: any(),
|
|
36860
|
-
designs: any(),
|
|
36861
|
-
previews: any(),
|
|
36862
|
-
files: any(),
|
|
36863
|
-
logo: any(),
|
|
36864
|
-
picker: any(),
|
|
36865
|
-
branches: any(),
|
|
36866
|
-
flows: any()
|
|
36867
|
-
}), ({ target }) => {
|
|
36868
|
-
if (target?.port === 0) return "target.port must be a listening port, not zero.";
|
|
36869
|
-
if (target?.origin !== void 0 && (typeof target.origin !== "string" || !target.origin.trim())) return "target.origin must be an HTTP(S) origin.";
|
|
36870
|
-
});
|
|
36871
|
-
var RouteSchema = check(any().transform((input) => typeof input === "string" ? {
|
|
36872
|
-
path: input,
|
|
36873
|
-
title: input === "/" ? "Home" : input
|
|
36874
|
-
} : input), (route) => {
|
|
36875
|
-
if (!route || typeof route.path !== "string" || !route.path.startsWith("/") || route.path.startsWith("//")) return "Canvas routes must be local paths beginning with a single /.";
|
|
36876
|
-
if (new URL(route.path, "http://bud.invalid").origin !== "http://bud.invalid" || route.path.includes("\\")) return "Canvas routes must stay on the target origin.";
|
|
36877
|
-
let id = route.id ?? routePath(route.path);
|
|
36878
|
-
if (typeof id !== "string" || !id.trim()) return "Canvas route IDs and paths must be unique.";
|
|
36879
|
-
if (typeof route.title !== "string" || !route.title.trim()) return "Each route needs a title.";
|
|
36880
|
-
if (route.pattern !== void 0 && (typeof route.pattern !== "string" || !route.pattern.startsWith("/"))) return "A route pattern must be a path beginning with /.";
|
|
36881
|
-
if (route.group !== void 0 && (typeof route.group !== "string" || !route.group.trim() || route.group.length > 120)) return "A route group must be a short name.";
|
|
36882
|
-
if (route.ready !== void 0 && route.ready !== false && !parseSafe(PreviewReadySchema, route.ready).success) return "A route ready must be false, a CSS selector, { selector } or { expression }.";
|
|
36883
|
-
}).transform((route) => {
|
|
36884
|
-
let path = routePath(route.path);
|
|
36885
|
-
return {
|
|
36886
|
-
id: route.id ?? path,
|
|
36887
|
-
path,
|
|
36888
|
-
title: route.title,
|
|
36889
|
-
...route.pattern === void 0 ? {} : { pattern: route.pattern },
|
|
36890
|
-
...route.group === void 0 ? {} : { group: route.group.trim() },
|
|
36891
|
-
...route.ready === void 0 ? {} : { ready: route.ready === false ? null : parseConfig(PreviewReadySchema, route.ready) }
|
|
36892
|
-
};
|
|
36893
|
-
});
|
|
36894
|
-
function routePath(path) {
|
|
36895
|
-
let url = new URL(path, "http://bud.invalid");
|
|
36896
|
-
let search = url.searchParams.toString();
|
|
36897
|
-
return `${url.pathname}${search ? `?${search}` : ""}${url.hash}`;
|
|
36898
36999
|
}
|
|
36899
|
-
/** Canvas routes with unique IDs and normalized paths. */
|
|
36900
|
-
var BudRoutesSchema = check(list(RouteSchema, "bud.config must define at least one route.", 1), (routes) => new Set(routes.map((route) => route.id)).size !== routes.length || new Set(routes.map((route) => route.path)).size !== routes.length ? "Canvas route IDs and paths must be unique." : void 0);
|
|
36901
|
-
var port = (value) => value === void 0 || Number.isInteger(value) && value >= 0 && value <= 65535;
|
|
36902
|
-
var remote = (value) => typeof value === "string" && /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(value);
|
|
36903
|
-
/** A project's config, validated and normalized before Bud starts any listeners. */
|
|
36904
|
-
var BudConfigSchema = delegate(check(delegate(check(delegate(check(delegate(ConfigFields, (config) => ({
|
|
36905
|
-
...config,
|
|
36906
|
-
routes: parseConfig(BudRoutesSchema, config.routes)
|
|
36907
|
-
})), ({ port: companion, target }) => port(companion ?? 0) && port(target?.port) ? void 0 : "Ports must be integers between 0 and 65535."), ({ target, ...config }) => {
|
|
36908
|
-
let origin = target?.origin ?? (target?.port ? `http://127.0.0.1:${target.port}` : void 0);
|
|
36909
|
-
return {
|
|
36910
|
-
...config,
|
|
36911
|
-
targetOrigin: origin ? normalizeOrigin(origin, "target.origin must be an HTTP(S) origin without a path or credentials.") : origin
|
|
36912
|
-
};
|
|
36913
|
-
}), ({ comments }) => remote(comments?.remote ?? "origin") ? void 0 : "comments.remote must be a Git remote name."), (config) => {
|
|
36914
|
-
let flows = parseConfig(BudFlowsSchema, config.flows);
|
|
36915
|
-
let steps = flows.flatMap((flow) => flow.steps.map((step) => flowStepRoute(flow, step)));
|
|
36916
|
-
let home = config.routes.findIndex((route) => route.path === "/") + 1;
|
|
36917
|
-
return {
|
|
36918
|
-
...config,
|
|
36919
|
-
flows,
|
|
36920
|
-
routes: [
|
|
36921
|
-
...config.routes.slice(0, home),
|
|
36922
|
-
...steps,
|
|
36923
|
-
...config.routes.slice(home)
|
|
36924
|
-
]
|
|
36925
|
-
};
|
|
36926
|
-
}), ({ routes }) => new Set(routes.map((route) => route.id)).size !== routes.length || new Set(routes.map((route) => route.path)).size !== routes.length ? "A flow step must not reuse a route’s id or path." : void 0), (config) => ({
|
|
36927
|
-
name: config.name ?? "Your project",
|
|
36928
|
-
routes: config.routes,
|
|
36929
|
-
flows: config.flows,
|
|
36930
|
-
port: config.port ?? 0,
|
|
36931
|
-
targetOrigin: config.targetOrigin,
|
|
36932
|
-
remote: config.comments?.remote ?? "origin",
|
|
36933
|
-
screens: parseConfig(BudScreensSchema, config.screens),
|
|
36934
|
-
storage: parseConfig(BudStorageSchema, config.storage),
|
|
36935
|
-
designs: parseConfig(BudDesignsSchema, config.designs),
|
|
36936
|
-
previews: parseConfig(BudPreviewsSchema, config.previews),
|
|
36937
|
-
files: parseConfig(BudFilesSchema, config.files),
|
|
36938
|
-
logo: parseConfig(BudLogoSchema, config.logo),
|
|
36939
|
-
picker: parseConfig(BudPickerSchema, config.picker),
|
|
36940
|
-
branches: parseConfig(BudBranchesSchema, config.branches)
|
|
36941
|
-
}));
|
|
36942
37000
|
/**
|
|
36943
|
-
*
|
|
36944
|
-
*
|
|
36945
|
-
* @
|
|
37001
|
+
* Publishes every element of a domain's change stream on the live channel, for the lifetime
|
|
37002
|
+
* of the layer that forks it.
|
|
37003
|
+
* @param topic - The topic clients subscribe to.
|
|
37004
|
+
* @param changes - The domain's change stream.
|
|
37005
|
+
* @returns An effect to fork in a scoped layer.
|
|
36946
37006
|
*/
|
|
36947
|
-
function
|
|
36948
|
-
return
|
|
37007
|
+
function publishChanges(topic, changes) {
|
|
37008
|
+
return gen(function* () {
|
|
37009
|
+
let channel = yield* LiveChannel;
|
|
37010
|
+
yield* changes.pipe(runForEach((change) => channel.publish(topic, change)), ignore$2, forkScoped);
|
|
37011
|
+
});
|
|
36949
37012
|
}
|
|
37013
|
+
//#endregion
|
|
37014
|
+
//#region ../project/src/server/errors.ts
|
|
37015
|
+
/** Expected project startup/configuration failure; nested causes remain server-only. */
|
|
37016
|
+
var ProjectError = class extends TaggedError("ProjectError") {};
|
|
37017
|
+
//#endregion
|
|
37018
|
+
//#region ../project/src/server/modules.ts
|
|
36950
37019
|
/**
|
|
36951
|
-
*
|
|
36952
|
-
* @param
|
|
36953
|
-
* @param
|
|
36954
|
-
* @
|
|
37020
|
+
* Confines a validated config module to a regular, bounded file in its project.
|
|
37021
|
+
* @param root - The trusted consumer directory.
|
|
37022
|
+
* @param module - A project-relative module already validated by the config resolver.
|
|
37023
|
+
* @param options - The config setting and whether its source runs on the server or browser.
|
|
37024
|
+
* @returns The canonical filename, without evaluating its code.
|
|
36955
37025
|
*/
|
|
36956
|
-
function
|
|
36957
|
-
|
|
36958
|
-
|
|
36959
|
-
|
|
37026
|
+
function projectModuleFile(root, module, options) {
|
|
37027
|
+
return gen(function* () {
|
|
37028
|
+
let fs = yield* FileSystem;
|
|
37029
|
+
let path = yield* Path$1;
|
|
37030
|
+
let { setting, kind } = options;
|
|
37031
|
+
let project = yield* fs.realPath(root).pipe(mapError$2((cause) => new ProjectError({
|
|
37032
|
+
reason: "root",
|
|
37033
|
+
message: "Cannot open the module project root.",
|
|
37034
|
+
cause
|
|
37035
|
+
})));
|
|
37036
|
+
let file = yield* fs.realPath(path.resolve(project, module)).pipe(mapError$2((cause) => new ProjectError({
|
|
37037
|
+
reason: "read",
|
|
37038
|
+
message: `${setting} cannot find ${module} in the project.`,
|
|
37039
|
+
cause
|
|
37040
|
+
})));
|
|
37041
|
+
let relative = path.relative(project, file);
|
|
37042
|
+
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative) || relative.split(path.sep).includes("node_modules")) return yield* new ProjectError({
|
|
37043
|
+
reason: "invalid",
|
|
37044
|
+
message: `${setting} must resolve to a project-owned file outside node_modules.`
|
|
37045
|
+
});
|
|
37046
|
+
if (kind === "server" && !file.endsWith(".server.ts")) return yield* new ProjectError({
|
|
37047
|
+
reason: "invalid",
|
|
37048
|
+
message: `${setting} must resolve to a .server.ts module.`
|
|
37049
|
+
});
|
|
37050
|
+
if (kind === "browser" && (!/\.(?:ts|tsx|js|jsx)$/.test(file) || /\.server\.[cm]?[jt]sx?$/.test(file))) return yield* new ProjectError({
|
|
37051
|
+
reason: "invalid",
|
|
37052
|
+
message: `${setting} must resolve to a browser module.`
|
|
37053
|
+
});
|
|
37054
|
+
if ((yield* fs.stat(file).pipe(mapError$2((cause) => new ProjectError({
|
|
37055
|
+
reason: "read",
|
|
37056
|
+
message: `${setting} cannot inspect ${module}.`,
|
|
37057
|
+
cause
|
|
37058
|
+
})))).type !== "File") return yield* new ProjectError({
|
|
37059
|
+
reason: "invalid",
|
|
37060
|
+
message: `${setting} must be a regular module file.`
|
|
37061
|
+
});
|
|
37062
|
+
yield* readText(file, { maxBytes: 262144 }).pipe(mapError$2((cause) => new ProjectError({
|
|
37063
|
+
reason: "read",
|
|
37064
|
+
message: `${setting} must be a readable module no larger than 256 KiB.`,
|
|
37065
|
+
cause
|
|
37066
|
+
})));
|
|
37067
|
+
return file;
|
|
37068
|
+
});
|
|
36960
37069
|
}
|
|
36961
37070
|
//#endregion
|
|
36962
37071
|
//#region ../project/src/server/config.ts
|
|
@@ -38007,7 +38116,7 @@ function createAppController(template, project, runtime) {
|
|
|
38007
38116
|
let title = (conversation ? "Discussion" : screen.label).replace(/[&<>]/g, (character) => `&#${character.charCodeAt(0)};`);
|
|
38008
38117
|
let logo = project?.config.logo;
|
|
38009
38118
|
let shell = logo ? withLogoIcons(template, logo, (theme) => routes.logo.href({ theme })) : template;
|
|
38010
|
-
return new Response(shell.replace("<title>bud.sh</title>", () => `<title>${title} · bud.sh</title>`).replace("<!--app-html-->", () => content), { headers: { "Content-Type": "text/html; charset=utf-8" } });
|
|
38119
|
+
return new Response(shell.replace("<title>bud.sh</title>", () => `<title>${title} · bud.sh</title>`).replace("</head>", () => project ? `${themeStyle(project.config.theme)}</head>` : "</head>").replace("<!--app-html-->", () => content), { headers: { "Content-Type": "text/html; charset=utf-8" } });
|
|
38011
38120
|
}
|
|
38012
38121
|
return createController(routes, { actions: {
|
|
38013
38122
|
conversation({ params, request }) {
|
|
@@ -41323,6 +41432,13 @@ async function createNodeHost(options) {
|
|
|
41323
41432
|
return response;
|
|
41324
41433
|
};
|
|
41325
41434
|
let { config } = await (await call("project")).json();
|
|
41435
|
+
if (options.theme) config = {
|
|
41436
|
+
...config,
|
|
41437
|
+
theme: resolveBudConfig({
|
|
41438
|
+
routes: ["/"],
|
|
41439
|
+
theme: options.theme
|
|
41440
|
+
}).theme
|
|
41441
|
+
};
|
|
41326
41442
|
let client = options.client ?? fileURLToPath(new URL("../client/", import.meta.url));
|
|
41327
41443
|
let template = await readFile(join(client, "index.html"), "utf8");
|
|
41328
41444
|
let pictures = httpPreviewStorage(remote("store"), {
|