@bud.sh/app 0.5.0 → 0.6.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/README.md +41 -31
- package/dist/client/assets/bud/design.js +1 -1
- package/dist/client/assets/canvas-Bih9EdKJ.js +1 -0
- package/dist/client/assets/difference-worker-Bzg-rizy.js +8 -0
- package/dist/client/assets/dist-CdE5wBkg.js +5 -0
- package/dist/client/assets/files-1dLuoM2f.js +1 -0
- package/dist/client/assets/i18n-CkO6VFI0.js +1 -0
- package/dist/client/assets/index-Cn2mxroU.css +2 -0
- package/dist/client/assets/{index-ChANGtl-.js → index-DtISWZp9.js} +2 -2
- package/dist/client/assets/pages-BJ-tNJ4J.js +1 -0
- package/dist/client/assets/{project-branches-DO897zM6.js → project-branches-Ce3rXrkc.js} +1 -1
- package/dist/client/assets/project-comments-CPUONfs8.js +1 -0
- package/dist/client/assets/project-thread-links-C-TGkq59.js +1 -0
- package/dist/client/assets/{project-threads-Lay351p4.js → project-threads-Bv7IDBgi.js} +105 -105
- package/dist/client/assets/project-work-DAE9-olg.js +1 -0
- package/dist/client/assets/{task-design-Avb2RO4w.js → task-design-CwySIAOl.js} +3 -3
- package/dist/client/assets/tasks-KWsCLi_h.js +3 -0
- package/dist/client/assets/wasm-inline-DSUyFn1L.js +1 -0
- package/dist/client/assets/workspace-CVdcfaaB.js +2 -0
- package/dist/client/index.html +3 -3
- package/dist/hosted/assets/{canvas-V36732ER.js → canvas-AII16n9i.js} +97 -6
- package/dist/hosted/assets/{comment-data-CF2ZV_3k.js → comment-data-BXfMElfx.js} +2004 -402
- package/dist/hosted/assets/dist-BCSTeNf4.js +4785 -0
- package/dist/hosted/assets/{files-DjSCjfU7.js → files-D_1yeAVO.js} +10 -5
- package/dist/hosted/assets/{pages-BzjzeuPu.js → pages-CN_SbKWA.js} +3 -3
- package/dist/hosted/assets/{project-branches-BT97J9i7.js → project-branches-Bxp0DCwg.js} +1 -1
- package/dist/hosted/assets/{project-thread-links-BRLgByEe.js → project-thread-links-KqQoKBLE.js} +76 -4
- package/dist/{node-host/assets/screen-CZXWJEds.js → hosted/assets/screen-DpAcDUKR.js} +29 -1
- package/dist/hosted/assets/tasks-D66LlwxM.js +1423 -0
- package/dist/hosted/assets/wasm-inline-Dl9tqabx.js +4 -0
- package/dist/hosted/hosted.js +7885 -6219
- package/dist/node/assets/Stream-CAm1eJ2n.js +16604 -0
- package/dist/{server/assets/bridge-B_Blt55E.js → node/assets/bridge-DQSBYHmw.js} +124 -290
- package/dist/node/assets/config-9_jWwNBh.js +1067 -0
- package/dist/node/assets/load-config.server-CMVtRVmO.js +261 -0
- package/dist/node/assets/module-3GgtT4Xs.js +308 -0
- package/dist/node/assets/server-DTQFuoX2.js +846 -0
- package/dist/node/assets/src-D-kG1GxB.js +2 -0
- package/dist/node/assets/{src-CerrKjID.js → src-Dwe9APUx.js} +93 -3
- package/dist/node/assets/src-DysjfbM8.js +8592 -0
- package/dist/node/bridge.js +1 -845
- package/dist/node/cli.js +206 -0
- package/dist/node/design.js +811 -13
- package/dist/node/index.js +2 -739
- package/dist/node/plugins.js +5711 -0
- package/dist/node/server.js +1 -6746
- package/dist/node/vite.js +121 -0
- package/dist/{server/assets/canvas-Dc1mYCwQ.js → node-host/assets/canvas-BeDoufy1.js} +97 -7
- package/dist/node-host/assets/{comment-data-DyIZNpfG.js → comment-data-DE2T9yTj.js} +1980 -376
- package/dist/node-host/assets/dist-BCSTeNf4.js +4785 -0
- package/dist/node-host/assets/{files-Dh2TiQZq.js → files-Dl-PTS09.js} +11 -6
- package/dist/node-host/assets/{pages-BxpjbYhs.js → pages-DL2sk6_U.js} +3 -3
- package/dist/node-host/assets/{project-branches-B7HUGiVK.js → project-branches-DGp5ELct.js} +1 -1
- package/dist/{server/assets/project-thread-links-CBOpf6zk.js → node-host/assets/project-thread-links-CgBV3Jcm.js} +76 -5
- package/dist/{hosted/assets/screen-CKp2C7kx.js → node-host/assets/screen-B55yyVfb.js} +29 -1
- package/dist/node-host/assets/tasks-DZRNkoxe.js +1425 -0
- package/dist/node-host/assets/wasm-inline-Dl9tqabx.js +4 -0
- package/dist/node-host/node-host.js +6127 -4438
- package/dist/server/assets/{branch-previews-ClkoJ0lM.js → branch-previews-DedrpnVR.js} +1555 -673
- package/dist/{node-host/assets/canvas-TRUfxfL5.js → server/assets/canvas-CJWQ-jjv.js} +98 -6
- package/dist/server/assets/{comment-data-CsjukwAW.js → comment-data-Rg42ToTW.js} +1666 -47
- package/dist/server/assets/dist-BCSTeNf4.js +4785 -0
- package/dist/server/assets/{events-iHpLGr3k.js → events-BH_rAu7O.js} +155 -20
- package/dist/server/assets/{files-E9mSFeoc.js → files-CPJqKeMo.js} +12 -7
- package/dist/server/assets/{listener-DN5TyOjJ.js → listener-D61O6PzF.js} +708 -12
- package/dist/server/assets/{pages-BBBXyJWV.js → pages-KJY_Ba92.js} +4 -4
- package/dist/server/assets/{project-branches-B0YIK6Ur.js → project-branches-qG9ACZvo.js} +2 -2
- package/dist/{node-host/assets/project-thread-links-CauJF0Cz.js → server/assets/project-thread-links-CqK0lqCF.js} +77 -4
- package/dist/{node-host/assets/tasks-request-ZWmbc8fE.js → server/assets/project-work-DoIwxWMm.js} +87 -2
- package/dist/server/assets/run-BJ8r5jSh.js +572 -0
- package/dist/server/assets/{screen-BIQA2mr4.js → screen-CBrHPFug.js} +30 -2
- package/dist/server/assets/{server-DxK-UB_Z.js → server-Dkbp5stG.js} +1521 -793
- package/dist/server/assets/{services-BRrgXYlT.js → services-BTfig8A8.js} +234 -997
- package/dist/server/assets/tasks-Bgu5Lg9Y.js +1427 -0
- package/dist/server/assets/wasm-inline-Dl9tqabx.js +4 -0
- package/dist/server/capture.js +1 -2
- package/dist/server/migration.js +569 -12
- package/dist/server/runtime.js +7 -5
- package/dist/server/server.js +3 -3
- package/dist/types/assets/{config-DIC2cf_S.d.ts → config-CvcYmE2s.d.ts} +188 -50
- package/dist/types/assets/{contract-CAT-XFNq.d.ts → contract-B7yuuqSn.d.ts} +44 -2
- package/dist/types/assets/{items-CaVkByHk.d.ts → items-BQdaVksf.d.ts} +12 -1
- package/dist/types/assets/memory-state-BBW-FAVA.d.ts +236 -0
- package/dist/types/assets/picker-Be5Gx4WR.d.ts +9 -0
- package/dist/types/assets/{task-design-xJOhRNWh.d.ts → task-design-BDHKFHpp.d.ts} +3 -1
- package/dist/types/bridge.d.ts +1 -1
- package/dist/types/companion.d.ts +3 -3
- package/dist/types/design.d.ts +2 -2
- package/dist/types/hosted.d.ts +3 -3
- package/dist/types/index.d.ts +2 -2
- package/dist/types/node-host.d.ts +2 -2
- package/dist/types/plugins.d.ts +445 -0
- package/dist/types/screen.d.ts +2 -2
- package/dist/types/server.d.ts +2 -228
- package/dist/types/vite.d.ts +16 -0
- package/package.json +19 -3
- package/dist/client/assets/canvas-Cu6R6xxT.js +0 -1
- package/dist/client/assets/difference-worker-jfTSn-Tw.js +0 -8
- package/dist/client/assets/files-js6ElfkU.js +0 -1
- package/dist/client/assets/i18n-DxqFr2mi.js +0 -1
- package/dist/client/assets/index-CFFxP0gS.css +0 -2
- package/dist/client/assets/pages-6_lhiQmz.js +0 -1
- package/dist/client/assets/project-comments-DFFKVpia.js +0 -1
- package/dist/client/assets/project-thread-links-NLFWQRD2.js +0 -1
- package/dist/client/assets/project-work-du0Y-L5Y.js +0 -1
- package/dist/client/assets/tasks-CA-E8bbK.js +0 -1
- package/dist/client/assets/tasks-request-C55IeuiP.js +0 -1
- package/dist/client/assets/workspace-Cw5HvX5d.js +0 -2
- package/dist/hosted/assets/tasks-Dj7AyBJB.js +0 -649
- package/dist/hosted/assets/tasks-request-DZob623r.js +0 -113
- package/dist/node-host/assets/tasks-Bs1IOpeU.js +0 -651
- package/dist/server/assets/project-work-qSKFO5yj.js +0 -88
- package/dist/server/assets/tasks-CiXodsLN.js +0 -652
- package/dist/server/assets/tasks-request-DhwCzpRi.js +0 -115
- package/dist/types/assets/picker-D1Bw3ndx.d.ts +0 -14
|
@@ -0,0 +1,1067 @@
|
|
|
1
|
+
import { any, array, boolean, createSchema, defaulted, enum_, fail, number, object, optional, parseSafe, string } from "remix/data-schema";
|
|
2
|
+
import { RoutePattern } from "remix/route-pattern";
|
|
3
|
+
//#region ../project/src/lib/validate.ts
|
|
4
|
+
var errors = /* @__PURE__ */ new WeakMap();
|
|
5
|
+
/**
|
|
6
|
+
* Validates config, throwing its first problem as the `Error` the config checks report.
|
|
7
|
+
* @param schema - The config schema.
|
|
8
|
+
* @param value - The project's untrusted setting.
|
|
9
|
+
* @returns The validated, normalized setting.
|
|
10
|
+
*/
|
|
11
|
+
function parseConfig(schema, value) {
|
|
12
|
+
let result = parseSafe(schema, value, { abortEarly: true });
|
|
13
|
+
if (result.success) return result.value;
|
|
14
|
+
let issue = result.issues[0];
|
|
15
|
+
throw errors.get(issue) ?? new Error(issue.message);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Reports any failure of `schema` with one message.
|
|
19
|
+
* @param schema - The schema to run.
|
|
20
|
+
* @param message - The message for any of its failures.
|
|
21
|
+
* @returns The described schema.
|
|
22
|
+
*/
|
|
23
|
+
function described(schema, message) {
|
|
24
|
+
return createSchema((value, context) => {
|
|
25
|
+
let result = schema["~run"](value, context);
|
|
26
|
+
return result.issues ? fail(message, context.path) : result;
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Applies a rule after `schema` validates.
|
|
31
|
+
* @param schema - The schema to run first.
|
|
32
|
+
* @param rule - Returns a message, or undefined when the value is valid.
|
|
33
|
+
* @returns The checked schema.
|
|
34
|
+
*/
|
|
35
|
+
function check(schema, rule) {
|
|
36
|
+
return createSchema((value, context) => {
|
|
37
|
+
let result = schema["~run"](value, context);
|
|
38
|
+
if (result.issues) return result;
|
|
39
|
+
let message = rule(result.value);
|
|
40
|
+
return message === void 0 ? result : fail(message, context.path);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Validates with a function that throws, such as a URL or route map parser, keeping its error.
|
|
45
|
+
* @param schema - The schema to run first.
|
|
46
|
+
* @param parse - Turns its value into the output, or throws.
|
|
47
|
+
* @returns The delegating schema.
|
|
48
|
+
*/
|
|
49
|
+
function delegate(schema, parse) {
|
|
50
|
+
return createSchema((value, context) => {
|
|
51
|
+
let result = schema["~run"](value, context);
|
|
52
|
+
if (result.issues) return result;
|
|
53
|
+
try {
|
|
54
|
+
return { value: parse(result.value) };
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (!(error instanceof Error)) throw error;
|
|
57
|
+
let failure = fail(error.message, context.path);
|
|
58
|
+
errors.set(failure.issues[0], error);
|
|
59
|
+
return failure;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Chooses a schema from the unvalidated value.
|
|
65
|
+
* @param choose - Picks the schema for a value.
|
|
66
|
+
* @returns The selecting schema.
|
|
67
|
+
*/
|
|
68
|
+
function select(choose) {
|
|
69
|
+
return createSchema((value, context) => choose(value)["~run"](value, context));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Substitutes a default for null or undefined.
|
|
73
|
+
* @param schema - The schema for present values.
|
|
74
|
+
* @param value - Makes the default.
|
|
75
|
+
* @returns The schema with its default.
|
|
76
|
+
*/
|
|
77
|
+
function fallback(schema, value) {
|
|
78
|
+
return createSchema((input, context) => input == null ? { value: value() } : schema["~run"](input, context));
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* A settings object that names its unsupported keys before validating its fields.
|
|
82
|
+
* @param name - The setting's name in messages, such as `previews`.
|
|
83
|
+
* @param shape - The schema for each supported key.
|
|
84
|
+
* @returns The settings schema.
|
|
85
|
+
*/
|
|
86
|
+
function settings(name, shape) {
|
|
87
|
+
let schema = object(shape);
|
|
88
|
+
return createSchema((value, context) => {
|
|
89
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return fail(`${name} must be an object.`, context.path);
|
|
90
|
+
for (let key of Object.keys(value)) if (!Object.hasOwn(shape, key)) return fail(`${name}.${key} is not a supported setting.`, [...context.path, key]);
|
|
91
|
+
return schema["~run"](value, context);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* A list whose length is checked before its entries.
|
|
96
|
+
* @param item - The schema for each entry.
|
|
97
|
+
* @param message - The message when it is not a list or is too short.
|
|
98
|
+
* @param minimum - The fewest entries it may have.
|
|
99
|
+
* @returns The list schema.
|
|
100
|
+
*/
|
|
101
|
+
function list(item, message, minimum = 0) {
|
|
102
|
+
let items = array(item);
|
|
103
|
+
return createSchema((value, context) => !Array.isArray(value) || value.length < minimum ? fail(message, context.path) : items["~run"](value, context));
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region ../project/src/lib/files.ts
|
|
107
|
+
/** The Files settings, with their defaults filled in. */
|
|
108
|
+
var BudFilesSchema = defaulted(settings("files", { edit: fallback(described(boolean(), "files.edit must be true or false."), () => true) }), () => ({ edit: true }));
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region ../project/src/lib/flows.ts
|
|
111
|
+
var message$1 = "flows must be [{ id, title, start, script, steps: [{ id, title }], block? }] with short ids, local paths and unique steps.";
|
|
112
|
+
var slug = (value) => typeof value === "string" && /^[a-z0-9][a-z0-9-]{0,62}$/.test(value);
|
|
113
|
+
var text = (value) => typeof value === "string" && value.trim() !== "" && value.length <= 200;
|
|
114
|
+
function flow(value) {
|
|
115
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
116
|
+
let { id, title, start, script, steps, block = [] } = value;
|
|
117
|
+
if (!slug(id) || !text(title) || !text(script) || script.includes("\0")) return null;
|
|
118
|
+
if (typeof start !== "string" || !start.startsWith("/") || start.startsWith("//")) return null;
|
|
119
|
+
if (new URL(start, "http://bud.invalid").origin !== "http://bud.invalid") return null;
|
|
120
|
+
if (!Array.isArray(steps) || !steps.length || steps.length > 50) return null;
|
|
121
|
+
let resolved = steps.map((step) => {
|
|
122
|
+
let { id: stepId, title: stepTitle } = step ?? {};
|
|
123
|
+
return slug(stepId) && text(stepTitle) ? {
|
|
124
|
+
id: stepId,
|
|
125
|
+
title: stepTitle.trim()
|
|
126
|
+
} : null;
|
|
127
|
+
});
|
|
128
|
+
if (resolved.some((step) => !step)) return null;
|
|
129
|
+
let ids = resolved.map((step) => step.id);
|
|
130
|
+
if (new Set(ids).size !== ids.length) return null;
|
|
131
|
+
if (!Array.isArray(block) || block.some((part) => !text(part))) return null;
|
|
132
|
+
return {
|
|
133
|
+
id,
|
|
134
|
+
title: title.trim(),
|
|
135
|
+
start,
|
|
136
|
+
script: script.trim(),
|
|
137
|
+
steps: resolved,
|
|
138
|
+
block
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/** The flows, none when omitted. */
|
|
142
|
+
var BudFlowsSchema = createSchema((value, context) => {
|
|
143
|
+
if (value === void 0) return { value: [] };
|
|
144
|
+
if (!Array.isArray(value)) return fail(message$1, context.path);
|
|
145
|
+
let flows = value.map(flow);
|
|
146
|
+
if (flows.some((resolved) => !resolved)) return fail(message$1, context.path);
|
|
147
|
+
let ids = flows.map((resolved) => resolved.id);
|
|
148
|
+
if (new Set(ids).size !== ids.length) return fail(message$1, context.path);
|
|
149
|
+
return { value: flows };
|
|
150
|
+
});
|
|
151
|
+
/**
|
|
152
|
+
* Where a flow's step is pictured: a route of its own, named `<flow>/<step>`, in a group named
|
|
153
|
+
* after the flow. Its path is the start page with the step as its fragment, unique among routes.
|
|
154
|
+
* @param flow - The flow.
|
|
155
|
+
* @param step - One of its steps.
|
|
156
|
+
* @returns The step's route.
|
|
157
|
+
*/
|
|
158
|
+
function flowStepRoute(flow, step) {
|
|
159
|
+
return {
|
|
160
|
+
id: `${flow.id}/${step.id}`,
|
|
161
|
+
path: `${flow.start.replace(/#.*$/, "")}#${flow.id}-${step.id}`,
|
|
162
|
+
title: step.title,
|
|
163
|
+
group: flow.title,
|
|
164
|
+
flow: {
|
|
165
|
+
id: flow.id,
|
|
166
|
+
step: step.id
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
//#endregion
|
|
171
|
+
//#region ../project/src/lib/logo.ts
|
|
172
|
+
/** The image types a logo may be, by extension. */
|
|
173
|
+
var LOGO_TYPES = Object.freeze({
|
|
174
|
+
".svg": "image/svg+xml",
|
|
175
|
+
".png": "image/png",
|
|
176
|
+
".webp": "image/webp",
|
|
177
|
+
".jpg": "image/jpeg",
|
|
178
|
+
".jpeg": "image/jpeg",
|
|
179
|
+
".gif": "image/gif",
|
|
180
|
+
".ico": "image/x-icon"
|
|
181
|
+
});
|
|
182
|
+
var logoMessage = "logo must be an image path inside the project (.svg, .png, .webp, .jpg, .gif or .ico), or { light, dark }.";
|
|
183
|
+
function image(value) {
|
|
184
|
+
if (typeof value !== "string" || value.length > 1024 || value.includes("\0")) return null;
|
|
185
|
+
let path = value.trim().replaceAll("\\", "/");
|
|
186
|
+
if (!path || path.startsWith("/") || /^[a-z]:/i.test(path)) return null;
|
|
187
|
+
if (path.split("/").includes("..")) return null;
|
|
188
|
+
let extension = path.slice(path.lastIndexOf(".")).toLowerCase();
|
|
189
|
+
return Object.hasOwn(LOGO_TYPES, extension) ? path : null;
|
|
190
|
+
}
|
|
191
|
+
/** A logo in its one resolved form, or null for Bud's own mark. */
|
|
192
|
+
var BudLogoSchema = createSchema((value, context) => {
|
|
193
|
+
if (value === void 0) return { value: null };
|
|
194
|
+
let light = image(typeof value === "object" && value ? value.light : value);
|
|
195
|
+
let raw = typeof value === "object" && value ? value.dark : void 0;
|
|
196
|
+
let dark = raw === void 0 ? null : image(raw);
|
|
197
|
+
let keys = typeof value === "object" && value ? Object.keys(value) : [];
|
|
198
|
+
if (light && (raw === void 0 || dark) && keys.every((key) => key === "light" || key === "dark")) return { value: {
|
|
199
|
+
light,
|
|
200
|
+
dark
|
|
201
|
+
} };
|
|
202
|
+
return fail(logoMessage, context.path);
|
|
203
|
+
});
|
|
204
|
+
//#endregion
|
|
205
|
+
//#region ../project/src/lib/factory.ts
|
|
206
|
+
/** The steps of a factory task, each a status in the project's task store. */
|
|
207
|
+
var FACTORY_STATUS_ROLES = [
|
|
208
|
+
"open",
|
|
209
|
+
"inProgress",
|
|
210
|
+
"needsApproval",
|
|
211
|
+
"readyToMerge",
|
|
212
|
+
"merged"
|
|
213
|
+
];
|
|
214
|
+
/** The default status names, as Beads stores them. */
|
|
215
|
+
var DEFAULT_FACTORY_STATUSES = {
|
|
216
|
+
open: "open",
|
|
217
|
+
inProgress: "in_progress",
|
|
218
|
+
needsApproval: "needs_approval",
|
|
219
|
+
readyToMerge: "ready_to_merge",
|
|
220
|
+
merged: "merged"
|
|
221
|
+
};
|
|
222
|
+
/**
|
|
223
|
+
* The model an agent uses when the config names none: DeepSeek V4.1 Flash on OpenCode Zen's free
|
|
224
|
+
* tier, which needs no key; enough for trying the factory, slower than a paid model.
|
|
225
|
+
*/
|
|
226
|
+
var DEFAULT_FACTORY_MODEL = "opencode/deepseek-v4.1-flash-free";
|
|
227
|
+
var factoryMessage = "factory must be false or an object with statuses, setup, dev, branchPrefix, sleepAfter, instructions and push as documented.";
|
|
228
|
+
var statusName = /^[A-Za-z][\w-]{0,63}$/;
|
|
229
|
+
var keys = [
|
|
230
|
+
"statuses",
|
|
231
|
+
"setup",
|
|
232
|
+
"dev",
|
|
233
|
+
"branchPrefix",
|
|
234
|
+
"sleepAfter",
|
|
235
|
+
"instructions",
|
|
236
|
+
"push"
|
|
237
|
+
];
|
|
238
|
+
var modulePath$1 = /^\.\/(?!.*(?:^|\/)\.\.?(?:\/|$))[\w./-]+$/;
|
|
239
|
+
function short(value, max) {
|
|
240
|
+
return typeof value === "string" && value.trim().length > 0 && value.length <= max;
|
|
241
|
+
}
|
|
242
|
+
function push(value) {
|
|
243
|
+
if (value === void 0) value = {};
|
|
244
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
245
|
+
let { remote = "origin", credential = null, username = "x-access-token", ...rest } = value;
|
|
246
|
+
if (Object.keys(rest).length || typeof remote !== "string" || !/^[\w][\w.-]{0,99}$/.test(remote) || !(credential === null || typeof credential === "string" && /^[A-Z_][A-Z0-9_]{0,99}$/.test(credential)) || typeof username !== "string" || !/^[\w][\w.@-]{0,99}$/.test(username)) return void 0;
|
|
247
|
+
return {
|
|
248
|
+
remote,
|
|
249
|
+
credential,
|
|
250
|
+
username
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function dev(value) {
|
|
254
|
+
if (value === void 0) return null;
|
|
255
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
256
|
+
let { command, port, path = "/", env = {}, ...rest } = value;
|
|
257
|
+
if (Object.keys(rest).length || !short(command, 4e3)) return void 0;
|
|
258
|
+
if (typeof path !== "string" || !/^\/(?!\/)[^\s#?]{0,500}$/.test(path)) return void 0;
|
|
259
|
+
if (typeof port !== "number" || !Number.isInteger(port) || port < 1 || port > 65535) return void 0;
|
|
260
|
+
if (!env || typeof env !== "object" || Array.isArray(env)) return void 0;
|
|
261
|
+
let entries = Object.entries(env);
|
|
262
|
+
if (entries.some(([key, entry]) => !/^[A-Z_][A-Z0-9_]*$/.test(key) || typeof entry !== "string")) return void 0;
|
|
263
|
+
return {
|
|
264
|
+
command,
|
|
265
|
+
port,
|
|
266
|
+
path,
|
|
267
|
+
env: Object.fromEntries(entries)
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
/** `factory` in bud.config.ts: off by default, with Beads' status names unless mapped. */
|
|
271
|
+
var BudFactorySchema = createSchema((value, context) => {
|
|
272
|
+
if (value === void 0 || value === false) return { value: null };
|
|
273
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return fail(factoryMessage, context.path);
|
|
274
|
+
let input = value;
|
|
275
|
+
let statuses = input.statuses ?? {};
|
|
276
|
+
if (Object.keys(input).some((key) => !keys.includes(key)) || !statuses || typeof statuses !== "object") return fail(factoryMessage, context.path);
|
|
277
|
+
let given = statuses;
|
|
278
|
+
if (Object.keys(given).some((key) => !FACTORY_STATUS_ROLES.includes(key))) return fail(factoryMessage, context.path);
|
|
279
|
+
let resolved = { ...DEFAULT_FACTORY_STATUSES };
|
|
280
|
+
for (let role of FACTORY_STATUS_ROLES) {
|
|
281
|
+
let name = given[role];
|
|
282
|
+
if (name === void 0) continue;
|
|
283
|
+
if (typeof name !== "string" || !statusName.test(name)) return fail(factoryMessage, context.path);
|
|
284
|
+
resolved[role] = name;
|
|
285
|
+
}
|
|
286
|
+
if (new Set(Object.values(resolved)).size !== FACTORY_STATUS_ROLES.length) return fail(factoryMessage, context.path);
|
|
287
|
+
let server = dev(input.dev);
|
|
288
|
+
let pushing = push(input.push);
|
|
289
|
+
if (server === void 0 || pushing === void 0) return fail(factoryMessage, context.path);
|
|
290
|
+
let prefix = input.branchPrefix ?? "dev/";
|
|
291
|
+
if (typeof prefix !== "string" || !/^[a-z0-9][a-z0-9._-]*\/$/i.test(prefix) || prefix.length > 50 || input.sleepAfter !== void 0 && !(typeof input.sleepAfter === "number" && Number.isInteger(input.sleepAfter) && input.sleepAfter >= 0 && input.sleepAfter <= 1440) || input.setup !== void 0 && !short(input.setup, 4e3) || input.instructions !== void 0 && !(typeof input.instructions === "string" && modulePath$1.test(input.instructions))) return fail(factoryMessage, context.path);
|
|
292
|
+
return { value: {
|
|
293
|
+
statuses: resolved,
|
|
294
|
+
sandbox: null,
|
|
295
|
+
agent: {
|
|
296
|
+
harness: "bud:opencode",
|
|
297
|
+
model: DEFAULT_FACTORY_MODEL,
|
|
298
|
+
keys: []
|
|
299
|
+
},
|
|
300
|
+
setup: input.setup ?? null,
|
|
301
|
+
dev: server,
|
|
302
|
+
branchPrefix: prefix,
|
|
303
|
+
sleepAfter: input.sleepAfter ?? 5,
|
|
304
|
+
instructions: input.instructions ?? null,
|
|
305
|
+
push: pushing
|
|
306
|
+
} };
|
|
307
|
+
});
|
|
308
|
+
/**
|
|
309
|
+
* What the factory keeps of a harness plugin: its name, model and keys, checked.
|
|
310
|
+
* @param name - The plugin's name.
|
|
311
|
+
* @param harness - The plugin's `harness`.
|
|
312
|
+
* @returns The agent settings for the resolved factory.
|
|
313
|
+
*/
|
|
314
|
+
function factoryAgent(name, harness) {
|
|
315
|
+
let { model, keys = [] } = harness ?? {};
|
|
316
|
+
if (!short(model, 200) || !Array.isArray(keys) || keys.length > 20 || keys.some((key) => typeof key !== "string" || !/^[A-Z_][A-Z0-9_]{0,99}$/.test(key))) throw new Error(`Plugin ${name}: harness needs a model and at most 20 keys naming server environment variables, such as DEEPSEEK_API_KEY.`);
|
|
317
|
+
return {
|
|
318
|
+
harness: name,
|
|
319
|
+
model,
|
|
320
|
+
keys: [...new Set(keys)]
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
//#endregion
|
|
324
|
+
//#region ../project/src/lib/theme.ts
|
|
325
|
+
/**
|
|
326
|
+
* Bud's colors and shadows a theme may set, as `--bud-<name>` in its styles. Everything else
|
|
327
|
+
* Bud derives from them.
|
|
328
|
+
*/
|
|
329
|
+
var THEME_TOKENS = Object.freeze([
|
|
330
|
+
"background",
|
|
331
|
+
"shell",
|
|
332
|
+
"main",
|
|
333
|
+
"canvas",
|
|
334
|
+
"panel",
|
|
335
|
+
"board",
|
|
336
|
+
"editor",
|
|
337
|
+
"editor-tabs",
|
|
338
|
+
"foreground",
|
|
339
|
+
"muted",
|
|
340
|
+
"primary",
|
|
341
|
+
"primary-hover",
|
|
342
|
+
"primary-foreground",
|
|
343
|
+
"border",
|
|
344
|
+
"tab-divider",
|
|
345
|
+
"hover",
|
|
346
|
+
"selected",
|
|
347
|
+
"control",
|
|
348
|
+
"control-active",
|
|
349
|
+
"control-selected",
|
|
350
|
+
"card",
|
|
351
|
+
"menu",
|
|
352
|
+
"menu-hover",
|
|
353
|
+
"card-frame",
|
|
354
|
+
"card-surface",
|
|
355
|
+
"card-border",
|
|
356
|
+
"card-frame-border",
|
|
357
|
+
"card-badge",
|
|
358
|
+
"overlay-shadow",
|
|
359
|
+
"tooltip",
|
|
360
|
+
"board-control",
|
|
361
|
+
"board-control-hover",
|
|
362
|
+
"board-card",
|
|
363
|
+
"control-elevation",
|
|
364
|
+
"board-elevation",
|
|
365
|
+
"board-card-elevation",
|
|
366
|
+
"card-elevation",
|
|
367
|
+
"board-card-lift",
|
|
368
|
+
"frame-control",
|
|
369
|
+
"modal-elevation",
|
|
370
|
+
"segment-elevation",
|
|
371
|
+
"control-open-elevation",
|
|
372
|
+
"board-segment",
|
|
373
|
+
"scrollbar",
|
|
374
|
+
"focus",
|
|
375
|
+
"scrollbar-hover",
|
|
376
|
+
"tooltip-border",
|
|
377
|
+
"tooltip-shadow",
|
|
378
|
+
"terminal",
|
|
379
|
+
"terminal-foreground",
|
|
380
|
+
"terminal-cursor",
|
|
381
|
+
"terminal-selection",
|
|
382
|
+
"terminal-black",
|
|
383
|
+
"terminal-red",
|
|
384
|
+
"terminal-green",
|
|
385
|
+
"terminal-yellow",
|
|
386
|
+
"terminal-blue",
|
|
387
|
+
"terminal-magenta",
|
|
388
|
+
"terminal-cyan",
|
|
389
|
+
"terminal-white",
|
|
390
|
+
"terminal-bright-black",
|
|
391
|
+
"terminal-bright-red",
|
|
392
|
+
"terminal-bright-green",
|
|
393
|
+
"terminal-bright-yellow",
|
|
394
|
+
"terminal-bright-blue",
|
|
395
|
+
"terminal-bright-magenta",
|
|
396
|
+
"terminal-bright-cyan",
|
|
397
|
+
"terminal-bright-white"
|
|
398
|
+
]);
|
|
399
|
+
var themeMessage = `theme must be { light, dark }, each mapping Bud's tokens (${THEME_TOKENS.slice(0, 3).join(", ")}, …) to CSS colors or shadows.`;
|
|
400
|
+
function cssValue(value) {
|
|
401
|
+
if (typeof value !== "string") return null;
|
|
402
|
+
let trimmed = value.trim();
|
|
403
|
+
if (!trimmed || trimmed.length > 300) return null;
|
|
404
|
+
if (!/^[#a-zA-Z0-9(),.%\s/+-]+$/.test(trimmed) || /url\s*\(/i.test(trimmed)) return null;
|
|
405
|
+
return trimmed;
|
|
406
|
+
}
|
|
407
|
+
function colors(value) {
|
|
408
|
+
if (value === void 0) return {};
|
|
409
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
410
|
+
let result = {};
|
|
411
|
+
for (let [name, raw] of Object.entries(value)) {
|
|
412
|
+
let checked = cssValue(raw);
|
|
413
|
+
if (!THEME_TOKENS.includes(name) || checked === null) return null;
|
|
414
|
+
result[name] = checked;
|
|
415
|
+
}
|
|
416
|
+
return Object.freeze(result);
|
|
417
|
+
}
|
|
418
|
+
/** A theme in its one resolved form; Bud's own colors when absent. */
|
|
419
|
+
var BudThemeSchema = createSchema((value, context) => {
|
|
420
|
+
if (value === void 0) return { value: {
|
|
421
|
+
light: {},
|
|
422
|
+
dark: {}
|
|
423
|
+
} };
|
|
424
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return fail(themeMessage, context.path);
|
|
425
|
+
let { light, dark, ...rest } = value;
|
|
426
|
+
let resolved = {
|
|
427
|
+
light: colors(light),
|
|
428
|
+
dark: colors(dark)
|
|
429
|
+
};
|
|
430
|
+
if (Object.keys(rest).length || !resolved.light || !resolved.dark) return fail(themeMessage, context.path);
|
|
431
|
+
return { value: {
|
|
432
|
+
light: resolved.light,
|
|
433
|
+
dark: resolved.dark
|
|
434
|
+
} };
|
|
435
|
+
});
|
|
436
|
+
//#endregion
|
|
437
|
+
//#region ../project/src/lib/branches.ts
|
|
438
|
+
var message = "branches must be { main?, build?: { install?, build?, serve? } } with a branch name and nonempty shell commands.";
|
|
439
|
+
function branch(value) {
|
|
440
|
+
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;
|
|
441
|
+
}
|
|
442
|
+
function command(value) {
|
|
443
|
+
return typeof value === "string" && value.trim() && value.length <= 4096 && !value.includes("\0") ? value.trim() : null;
|
|
444
|
+
}
|
|
445
|
+
/** The branch settings, defaults filled in. */
|
|
446
|
+
var BudBranchesSchema = createSchema((value, context) => {
|
|
447
|
+
let empty = {
|
|
448
|
+
main: null,
|
|
449
|
+
install: null,
|
|
450
|
+
build: null,
|
|
451
|
+
serve: null
|
|
452
|
+
};
|
|
453
|
+
if (value === void 0) return { value: empty };
|
|
454
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return fail(message, context.path);
|
|
455
|
+
let input = value;
|
|
456
|
+
let build = input.build ?? {};
|
|
457
|
+
if (!build || typeof build !== "object" || Array.isArray(build) || Object.keys(input).some((key) => key !== "main" && key !== "build")) return fail(message, context.path);
|
|
458
|
+
let steps = build;
|
|
459
|
+
if (Object.keys(steps).some((key) => ![
|
|
460
|
+
"install",
|
|
461
|
+
"build",
|
|
462
|
+
"serve"
|
|
463
|
+
].includes(key))) return fail(message, context.path);
|
|
464
|
+
let resolved = {
|
|
465
|
+
main: input.main === void 0 ? null : branch(input.main),
|
|
466
|
+
install: steps.install === void 0 ? null : command(steps.install),
|
|
467
|
+
build: steps.build === void 0 ? null : command(steps.build),
|
|
468
|
+
serve: steps.serve === void 0 ? null : command(steps.serve)
|
|
469
|
+
};
|
|
470
|
+
if (input.main !== void 0 && !resolved.main || [
|
|
471
|
+
"install",
|
|
472
|
+
"build",
|
|
473
|
+
"serve"
|
|
474
|
+
].some((key) => steps[key] !== void 0 && !resolved[key])) return fail(message, context.path);
|
|
475
|
+
return { value: resolved };
|
|
476
|
+
});
|
|
477
|
+
/**
|
|
478
|
+
* Validates the branch settings.
|
|
479
|
+
* @param value - The config's optional `branches` setting.
|
|
480
|
+
* @returns The settings with null defaults.
|
|
481
|
+
*/
|
|
482
|
+
function resolveBranches(value) {
|
|
483
|
+
return parseConfig(BudBranchesSchema, value);
|
|
484
|
+
}
|
|
485
|
+
//#endregion
|
|
486
|
+
//#region ../project/src/lib/storage.ts
|
|
487
|
+
/**
|
|
488
|
+
* A project module named in the config: a ./ path inside the project, outside node_modules.
|
|
489
|
+
* @param name - The config option, for the error message.
|
|
490
|
+
* @param server - Whether it runs on the server (.server.ts) rather than in the browser.
|
|
491
|
+
* @param message - What to say instead of the default when it is invalid.
|
|
492
|
+
* @returns A schema for the module's path.
|
|
493
|
+
*/
|
|
494
|
+
function modulePath(name, server, message) {
|
|
495
|
+
let extension = server ? /\.server\.ts$/ : /\.(?:ts|tsx|js|jsx)$/;
|
|
496
|
+
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.`));
|
|
497
|
+
}
|
|
498
|
+
function store(kind) {
|
|
499
|
+
return defaulted(select((value) => value === false ? enum_([false]) : described(string().refine((name) => /^[\w@:./-]{1,100}$/.test(name)), `storage.${kind} is false, or a store named by its plugin, such as bud:beads.`)), "bud:beads");
|
|
500
|
+
}
|
|
501
|
+
/** The selected work and discussion stores, with Beads as the default for both. */
|
|
502
|
+
var BudStorageSchema = defaulted(settings("storage", {
|
|
503
|
+
work: store("work"),
|
|
504
|
+
threads: store("threads")
|
|
505
|
+
}), () => ({
|
|
506
|
+
work: "bud:beads",
|
|
507
|
+
threads: "bud:beads"
|
|
508
|
+
}));
|
|
509
|
+
/** Task presentation, chosen separately from its storage. */
|
|
510
|
+
var BudDesignsSchema = defaulted(settings("designs", {
|
|
511
|
+
tasks: defaulted(select((value) => value === "bud:beads" ? enum_(["bud:beads"]) : modulePath("designs.tasks", false)), "bud:beads"),
|
|
512
|
+
pages: defaulted(enum_(["path", "title"]), "path")
|
|
513
|
+
}), () => ({
|
|
514
|
+
tasks: "bud:beads",
|
|
515
|
+
pages: "path"
|
|
516
|
+
}));
|
|
517
|
+
//#endregion
|
|
518
|
+
//#region ../project/src/lib/picker.ts
|
|
519
|
+
var builtIn = /* @__PURE__ */ new Set([
|
|
520
|
+
"bud:dom",
|
|
521
|
+
"bud:react",
|
|
522
|
+
"bud:react-grab"
|
|
523
|
+
]);
|
|
524
|
+
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.");
|
|
525
|
+
/** The picker, with Bud's DOM picker as the default. */
|
|
526
|
+
var BudPickerSchema = createSchema((value, context) => {
|
|
527
|
+
if (value === void 0) return { value: {
|
|
528
|
+
name: "bud:dom",
|
|
529
|
+
module: null
|
|
530
|
+
} };
|
|
531
|
+
if (typeof value === "string" && builtIn.has(value)) return { value: {
|
|
532
|
+
name: value,
|
|
533
|
+
module: null
|
|
534
|
+
} };
|
|
535
|
+
let result = projectPicker["~run"](value, context);
|
|
536
|
+
return result.issues ? result : { value: {
|
|
537
|
+
name: result.value,
|
|
538
|
+
module: result.value
|
|
539
|
+
} };
|
|
540
|
+
});
|
|
541
|
+
/**
|
|
542
|
+
* Validates the picker setting.
|
|
543
|
+
* @param value - The config's optional `picker` setting.
|
|
544
|
+
* @returns The picker and its project module, if it is the project's own.
|
|
545
|
+
*/
|
|
546
|
+
function resolvePicker(value) {
|
|
547
|
+
return parseConfig(BudPickerSchema, value);
|
|
548
|
+
}
|
|
549
|
+
//#endregion
|
|
550
|
+
//#region ../project/src/lib/screen-routes.ts
|
|
551
|
+
/**
|
|
552
|
+
* Validates a screen's route map without importing its component module.
|
|
553
|
+
* @param routes - A Remix route map shared with the screen's definition.
|
|
554
|
+
* @param path - The configured screen's static path.
|
|
555
|
+
* @returns Named patterns confined to the screen's path.
|
|
556
|
+
*/
|
|
557
|
+
function resolveScreenRoutes(routes, path) {
|
|
558
|
+
let result = [];
|
|
559
|
+
let seen = /* @__PURE__ */ new Set();
|
|
560
|
+
function visit(value, prefix) {
|
|
561
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || seen.has(value)) throw new Error("Screen routes must be a Remix route map.");
|
|
562
|
+
seen.add(value);
|
|
563
|
+
for (let [key, entry] of Object.entries(value)) {
|
|
564
|
+
let name = prefix ? `${prefix}.${key}` : key;
|
|
565
|
+
if (!entry || typeof entry !== "object") throw new Error(`Screen route ${name} must be a Remix route.`);
|
|
566
|
+
if (!("pattern" in entry)) {
|
|
567
|
+
visit(entry, name);
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
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.`);
|
|
571
|
+
let pattern = RoutePattern.parse(entry.pattern.source);
|
|
572
|
+
let parts = pattern.toJSON();
|
|
573
|
+
let pathname = `/${parts.pathname}`;
|
|
574
|
+
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}.`);
|
|
575
|
+
result.push({
|
|
576
|
+
name,
|
|
577
|
+
pattern: pattern.source
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
seen.delete(value);
|
|
581
|
+
}
|
|
582
|
+
visit(routes, "");
|
|
583
|
+
if (!result.length) throw new Error("Screen routes must contain at least one route.");
|
|
584
|
+
return result;
|
|
585
|
+
}
|
|
586
|
+
//#endregion
|
|
587
|
+
//#region ../project/src/lib/screens.ts
|
|
588
|
+
/**
|
|
589
|
+
* Bud's browser of your live pages, with their comments.
|
|
590
|
+
* @returns The built-in screen, ready to relabel by spreading.
|
|
591
|
+
*/
|
|
592
|
+
function pages() {
|
|
593
|
+
return {
|
|
594
|
+
id: "pages",
|
|
595
|
+
label: "Pages",
|
|
596
|
+
icon: "browser",
|
|
597
|
+
path: "/pages",
|
|
598
|
+
module: "bud:pages"
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Bud's canvas of every route's screenshot.
|
|
603
|
+
* @returns The built-in screen, ready to relabel by spreading.
|
|
604
|
+
*/
|
|
605
|
+
function canvas() {
|
|
606
|
+
return {
|
|
607
|
+
id: "canvas",
|
|
608
|
+
label: "Canvas",
|
|
609
|
+
icon: "grid",
|
|
610
|
+
path: "/canvas",
|
|
611
|
+
module: "bud:canvas"
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Bud's Beads task board.
|
|
616
|
+
* @returns The built-in screen, ready to relabel by spreading.
|
|
617
|
+
*/
|
|
618
|
+
function tasks() {
|
|
619
|
+
return {
|
|
620
|
+
id: "tasks",
|
|
621
|
+
label: "Tasks",
|
|
622
|
+
icon: "tasks",
|
|
623
|
+
path: "/tasks",
|
|
624
|
+
module: "bud:tasks"
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Bud's file browser and review.
|
|
629
|
+
* @returns The built-in screen, ready to relabel by spreading.
|
|
630
|
+
*/
|
|
631
|
+
function files() {
|
|
632
|
+
return {
|
|
633
|
+
id: "files",
|
|
634
|
+
label: "Files",
|
|
635
|
+
icon: "files",
|
|
636
|
+
path: "/files",
|
|
637
|
+
module: "bud:files"
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
/** The screens shown when the config doesn't list any. */
|
|
641
|
+
var defaultScreens = [
|
|
642
|
+
pages(),
|
|
643
|
+
canvas(),
|
|
644
|
+
tasks(),
|
|
645
|
+
files()
|
|
646
|
+
];
|
|
647
|
+
var builtIns = new Map(defaultScreens.map((screen) => [screen.module, screen]));
|
|
648
|
+
/** First path segments owned by Bud's routes, its development server and its public assets. */
|
|
649
|
+
var reservedScreenPaths = [
|
|
650
|
+
"/comments",
|
|
651
|
+
...defaultScreens.map((screen) => screen.path),
|
|
652
|
+
"/api",
|
|
653
|
+
"/health",
|
|
654
|
+
"/__bud",
|
|
655
|
+
"/assets",
|
|
656
|
+
"/app",
|
|
657
|
+
"/src",
|
|
658
|
+
"/node_modules",
|
|
659
|
+
"/agents",
|
|
660
|
+
"/brand",
|
|
661
|
+
"/fonts",
|
|
662
|
+
"/frameworks",
|
|
663
|
+
"/licenses"
|
|
664
|
+
];
|
|
665
|
+
var ScreenSchema = check(described(any().refine((value) => !!value && typeof value === "object"), "Each screen must be an object."), ({ id, label, icon = "layers", module, path }) => {
|
|
666
|
+
if (typeof id !== "string" || !/^[a-z][a-z0-9-]{0,31}$/.test(id)) return "Screen ids are 1-32 lowercase letters, digits and hyphens.";
|
|
667
|
+
if (typeof label !== "string" || !label.trim()) return `Screen ${id} needs a label.`;
|
|
668
|
+
if (typeof icon !== "string" || !icon) return `Screen ${id} needs an icon name.`;
|
|
669
|
+
if (typeof module !== "string") return `Screen ${id} needs a module.`;
|
|
670
|
+
let builtIn = builtIns.get(module);
|
|
671
|
+
path ??= builtIn?.path ?? `/${id}`;
|
|
672
|
+
if (builtIn) {
|
|
673
|
+
if (id !== builtIn.id || path !== builtIn.path) return `${module} keeps id "${builtIn.id}" and path "${builtIn.path}".`;
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
if (!/^\.\/[^\0\\]+\.(?:ts|tsx|js|jsx)$/.test(module)) return `Screen ${id} must be a ./ module ending in .ts, .tsx, .js or .jsx.`;
|
|
677
|
+
if (typeof path !== "string" || !/^\/[a-z0-9][a-z0-9-]*$/.test(path)) return `Screen ${id} needs one static path segment, such as /${id}.`;
|
|
678
|
+
if (defaultScreens.some((screen) => screen.id === id)) return `Screen id "${id}" is reserved for a built-in screen.`;
|
|
679
|
+
if (reservedScreenPaths.includes(path)) return `Bud reserves the path ${path}.`;
|
|
680
|
+
});
|
|
681
|
+
/** Bud's navigation in order; file and icon checks need the app and happen at startup. */
|
|
682
|
+
var BudScreensSchema = defaulted(delegate(described(array(any()).refine((screens) => screens.length > 0), "screens must list at least one screen."), (screens) => {
|
|
683
|
+
let ids = /* @__PURE__ */ new Set();
|
|
684
|
+
let paths = /* @__PURE__ */ new Set();
|
|
685
|
+
return screens.map((value) => {
|
|
686
|
+
let input = parseConfig(ScreenSchema, value);
|
|
687
|
+
let { id, label, icon = "layers", module } = input;
|
|
688
|
+
let path = input.path ?? builtIns.get(module)?.path ?? `/${id}`;
|
|
689
|
+
if (ids.has(id) || paths.has(path)) throw new Error("Screen ids and paths must be unique.");
|
|
690
|
+
ids.add(id);
|
|
691
|
+
paths.add(path);
|
|
692
|
+
if (input.routes !== void 0) resolveScreenRoutes(input.routes, path);
|
|
693
|
+
return {
|
|
694
|
+
id,
|
|
695
|
+
label,
|
|
696
|
+
icon,
|
|
697
|
+
path,
|
|
698
|
+
module,
|
|
699
|
+
...input.routes === void 0 ? {} : { routes: input.routes }
|
|
700
|
+
};
|
|
701
|
+
});
|
|
702
|
+
}), () => defaultScreens.map((screen) => ({ ...screen })));
|
|
703
|
+
//#endregion
|
|
704
|
+
//#region ../project/src/lib/plugins.ts
|
|
705
|
+
var registry = globalThis;
|
|
706
|
+
var key = Symbol.for("@bud.sh/project/plugins");
|
|
707
|
+
registry[key] ??= /* @__PURE__ */ new WeakMap();
|
|
708
|
+
var known = registry[key];
|
|
709
|
+
/**
|
|
710
|
+
* Remembers the plugins a resolved config came from.
|
|
711
|
+
* @param config - The resolved config.
|
|
712
|
+
* @param plugins - Its plugins.
|
|
713
|
+
* @returns The config.
|
|
714
|
+
*/
|
|
715
|
+
function withPlugins(config, plugins) {
|
|
716
|
+
known.set(config, plugins);
|
|
717
|
+
return config;
|
|
718
|
+
}
|
|
719
|
+
function flatten(option, into) {
|
|
720
|
+
if (option === false || option === null || option === void 0) return;
|
|
721
|
+
if (Array.isArray(option)) {
|
|
722
|
+
for (let entry of option) flatten(entry, into);
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
let plugin = option;
|
|
726
|
+
if (!plugin || typeof plugin !== "object" || typeof plugin.name !== "string" || !/^[\w@:./-]{1,100}$/.test(plugin.name)) throw new Error("Each plugin must be an object with a name such as bud:beads.");
|
|
727
|
+
if (plugin.enforce !== void 0 && plugin.enforce !== "pre" && plugin.enforce !== "post") throw new Error(`Plugin ${plugin.name}: enforce must be pre or post.`);
|
|
728
|
+
if (plugin.config !== void 0 && typeof plugin.config !== "function") throw new Error(`Plugin ${plugin.name}: config must be a function.`);
|
|
729
|
+
into.push(plugin);
|
|
730
|
+
}
|
|
731
|
+
function isObject(value) {
|
|
732
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
733
|
+
}
|
|
734
|
+
function merge(config, partial) {
|
|
735
|
+
let merged = { ...config };
|
|
736
|
+
for (let [key, value] of Object.entries(partial)) {
|
|
737
|
+
if (key === "plugins") continue;
|
|
738
|
+
merged[key] = isObject(merged[key]) && isObject(value) ? {
|
|
739
|
+
...merged[key],
|
|
740
|
+
...value
|
|
741
|
+
} : value;
|
|
742
|
+
}
|
|
743
|
+
return merged;
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Runs the config's plugins: flattens `plugins`, orders them, applies their `config` hooks, and
|
|
747
|
+
* finds which plugin provides each store, the picker, the task design and the sandbox.
|
|
748
|
+
* @param value - The config module's default export.
|
|
749
|
+
* @returns The config after the hooks, without `plugins`, the plugins in order and what they
|
|
750
|
+
* provide.
|
|
751
|
+
*/
|
|
752
|
+
function applyPlugins(value) {
|
|
753
|
+
if (!isObject(value)) return {
|
|
754
|
+
config: value,
|
|
755
|
+
plugins: [],
|
|
756
|
+
provides: none()
|
|
757
|
+
};
|
|
758
|
+
let found = [];
|
|
759
|
+
flatten(value.plugins, found);
|
|
760
|
+
let names = /* @__PURE__ */ new Set();
|
|
761
|
+
for (let plugin of found) {
|
|
762
|
+
if (names.has(plugin.name)) throw new Error(`Two plugins are named ${plugin.name}.`);
|
|
763
|
+
names.add(plugin.name);
|
|
764
|
+
}
|
|
765
|
+
let plugins = [
|
|
766
|
+
...found.filter((plugin) => plugin.enforce === "pre"),
|
|
767
|
+
...found.filter((plugin) => !plugin.enforce),
|
|
768
|
+
...found.filter((plugin) => plugin.enforce === "post")
|
|
769
|
+
];
|
|
770
|
+
let { plugins: _plugins, ...config } = value;
|
|
771
|
+
for (let plugin of plugins) {
|
|
772
|
+
let partial = plugin.config?.(config);
|
|
773
|
+
if (partial !== void 0 && partial !== null) {
|
|
774
|
+
if (!isObject(partial)) throw new Error(`Plugin ${plugin.name}: config must return an object or nothing.`);
|
|
775
|
+
config = merge(config, partial);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
let screens = plugins.flatMap((plugin) => plugin.screens ?? []);
|
|
779
|
+
if (screens.length) config.screens = [...config.screens ?? defaultScreens.map((screen) => ({ ...screen })), ...screens];
|
|
780
|
+
let one = (key, what) => {
|
|
781
|
+
let providers = plugins.filter((plugin) => plugin[key] !== void 0);
|
|
782
|
+
if (providers.length > 1) throw new Error(`Only one plugin may provide ${what}; ${providers.map((plugin) => plugin.name).join(" and ")} both do.`);
|
|
783
|
+
return providers[0] ?? null;
|
|
784
|
+
};
|
|
785
|
+
return {
|
|
786
|
+
config,
|
|
787
|
+
plugins,
|
|
788
|
+
provides: {
|
|
789
|
+
work: one("work", "the task store"),
|
|
790
|
+
threads: one("threads", "the discussion store"),
|
|
791
|
+
picker: one("picker", "the picker"),
|
|
792
|
+
taskDesign: one("taskDesign", "the task design"),
|
|
793
|
+
sandbox: one("sandbox", "the factory’s sandboxes"),
|
|
794
|
+
harness: one("harness", "the agent harness")
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
function none() {
|
|
799
|
+
return {
|
|
800
|
+
work: null,
|
|
801
|
+
threads: null,
|
|
802
|
+
picker: null,
|
|
803
|
+
taskDesign: null,
|
|
804
|
+
sandbox: null,
|
|
805
|
+
harness: null
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Fails for settings that moved into plugins, naming the plugin that replaces each.
|
|
810
|
+
* @param config - The config after the plugins' hooks.
|
|
811
|
+
*/
|
|
812
|
+
function rejectMovedSettings(config) {
|
|
813
|
+
if (!isObject(config)) return;
|
|
814
|
+
let storage = config.storage;
|
|
815
|
+
if (isObject(storage)) for (let kind of ["work", "threads"]) {
|
|
816
|
+
let value = storage[kind];
|
|
817
|
+
if (value !== void 0 && value !== false) throw new Error(`storage.${kind} no longer names a store: add the store's plugin to plugins, such as beads(), gitComments() or memory() from @bud.sh/app/plugins, or your own; storage.${kind}: false still turns it off.`);
|
|
818
|
+
}
|
|
819
|
+
if (isObject(config.designs) && config.designs.tasks !== void 0) throw new Error("designs.tasks moved into a plugin: add taskDesign(\"./your-design.tsx\") from @bud.sh/app/plugins to plugins.");
|
|
820
|
+
if (config.picker !== void 0) throw new Error("picker moved into a plugin: add reactGrab(), react() or picker(\"./your-picker.ts\") from @bud.sh/app/plugins to plugins.");
|
|
821
|
+
if (isObject(config.factory) && config.factory.sandbox !== void 0) throw new Error("factory.sandbox moved into a plugin: add local() or boat() from @bud.sh/app/plugins to plugins.");
|
|
822
|
+
if (isObject(config.factory) && config.factory.agent !== void 0) throw new Error("factory.agent moved into a plugin: add opencode({ model, keys }) from @bud.sh/app/plugins to plugins.");
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* A task design from the project: a `./` browser module whose default export designs the Tasks
|
|
826
|
+
* screen.
|
|
827
|
+
* @param module - The module, such as `./designs/compact-card.tsx`.
|
|
828
|
+
* @returns The plugin.
|
|
829
|
+
*/
|
|
830
|
+
function taskDesign(module) {
|
|
831
|
+
return {
|
|
832
|
+
name: `design:${module}`,
|
|
833
|
+
taskDesign: module
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* A picker from the project: a `./` browser module whose default export is a picker adapter.
|
|
838
|
+
* @param module - The module, such as `./bud/picker.ts`.
|
|
839
|
+
* @returns The plugin.
|
|
840
|
+
*/
|
|
841
|
+
function picker(module) {
|
|
842
|
+
return {
|
|
843
|
+
name: `picker:${module}`,
|
|
844
|
+
picker: module
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
//#endregion
|
|
848
|
+
//#region ../project/src/lib/previews.ts
|
|
849
|
+
var readyMessage = "ready must be a CSS selector, { selector } or { expression }.";
|
|
850
|
+
/** A readiness condition; a string is a selector. */
|
|
851
|
+
var PreviewReadySchema = createSchema((value, context) => {
|
|
852
|
+
let ready = typeof value === "string" ? { selector: value } : value;
|
|
853
|
+
if (ready && typeof ready === "object" && !Array.isArray(ready)) {
|
|
854
|
+
let entries = Object.entries(ready);
|
|
855
|
+
let [key, text] = entries[0] ?? [];
|
|
856
|
+
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() } };
|
|
857
|
+
}
|
|
858
|
+
return fail(readyMessage, context.path);
|
|
859
|
+
});
|
|
860
|
+
var defaultThemes = () => ["light", "dark"];
|
|
861
|
+
var defaultViewports = () => [{
|
|
862
|
+
id: "desktop",
|
|
863
|
+
name: "Desktop",
|
|
864
|
+
width: 1440,
|
|
865
|
+
height: 900,
|
|
866
|
+
mobile: false
|
|
867
|
+
}];
|
|
868
|
+
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.";
|
|
869
|
+
function viewportId(name) {
|
|
870
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32);
|
|
871
|
+
}
|
|
872
|
+
var ViewportsSchema = createSchema((value, context) => {
|
|
873
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > 4) return fail(viewportsMessage, context.path);
|
|
874
|
+
let viewports = [];
|
|
875
|
+
for (let entry of value) {
|
|
876
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return fail(viewportsMessage, context.path);
|
|
877
|
+
let { name, width, height, mobile, ...rest } = entry;
|
|
878
|
+
let size = (pixels, from, to) => typeof pixels === "number" && Number.isInteger(pixels) && pixels >= from && pixels <= to;
|
|
879
|
+
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);
|
|
880
|
+
let phone = mobile ?? width < 800;
|
|
881
|
+
viewports.push({
|
|
882
|
+
id: viewportId(name),
|
|
883
|
+
name: name.trim(),
|
|
884
|
+
width,
|
|
885
|
+
height: height ?? (phone ? Math.round(width * 844 / 390) : 900),
|
|
886
|
+
mobile: phone
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
if (new Set(viewports.map((viewport) => viewport.id)).size !== viewports.length) return fail(viewportsMessage, context.path);
|
|
890
|
+
return { value: viewports };
|
|
891
|
+
});
|
|
892
|
+
/** The preview settings, with their defaults filled in. */
|
|
893
|
+
var BudPreviewsSchema = defaulted(settings("previews", {
|
|
894
|
+
scale: fallback(described(number().refine((scale) => Number.isInteger(scale) && scale >= 1 && scale <= 3), "previews.scale must be 1, 2 or 3."), () => 2),
|
|
895
|
+
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),
|
|
896
|
+
viewports: fallback(ViewportsSchema, defaultViewports),
|
|
897
|
+
ready: fallback(PreviewReadySchema, () => null),
|
|
898
|
+
readyTimeout: fallback(described(number().refine((ms) => Number.isInteger(ms) && ms >= 1e3 && ms <= 6e5), "previews.readyTimeout must be whole milliseconds from 1000 to 600000."), () => 6e4),
|
|
899
|
+
css: fallback(described(string().refine((css) => css.length <= 2e4 && !css.includes("<")), "previews.css must be CSS text without <, up to 20000 characters."), () => "")
|
|
900
|
+
}), () => ({
|
|
901
|
+
scale: 2,
|
|
902
|
+
themes: defaultThemes(),
|
|
903
|
+
viewports: defaultViewports(),
|
|
904
|
+
ready: null,
|
|
905
|
+
readyTimeout: 6e4,
|
|
906
|
+
css: ""
|
|
907
|
+
}));
|
|
908
|
+
//#endregion
|
|
909
|
+
//#region ../project/src/lib/config.ts
|
|
910
|
+
/**
|
|
911
|
+
* Keeps bud.config.js and bud.config.ts typed without changing their values.
|
|
912
|
+
* @param config - The project's routes and development settings.
|
|
913
|
+
* @returns The same configuration.
|
|
914
|
+
*/
|
|
915
|
+
function defineConfig(config) {
|
|
916
|
+
return config;
|
|
917
|
+
}
|
|
918
|
+
var objectLike = (value) => !!value && typeof value === "object";
|
|
919
|
+
function section(message, shape) {
|
|
920
|
+
let schema = object(shape);
|
|
921
|
+
return createSchema((value, context) => objectLike(value) ? schema["~run"](Array.isArray(value) ? { ...value } : value, context) : fail(message, context.path));
|
|
922
|
+
}
|
|
923
|
+
var ConfigFields = check(section("Bud config must export an object.", {
|
|
924
|
+
routes: described(array(any()).refine((routes) => routes.length > 0), "bud.config must define at least one route."),
|
|
925
|
+
name: optional(described(string().refine((name) => !!name.trim()), "name must be a nonempty string.")),
|
|
926
|
+
target: optional(section("target must be an object.", {
|
|
927
|
+
origin: any(),
|
|
928
|
+
port: any()
|
|
929
|
+
})),
|
|
930
|
+
comments: optional(section("comments must be an object.", { remote: any() })),
|
|
931
|
+
port: any(),
|
|
932
|
+
screens: any(),
|
|
933
|
+
storage: any(),
|
|
934
|
+
designs: any(),
|
|
935
|
+
previews: any(),
|
|
936
|
+
files: any(),
|
|
937
|
+
logo: any(),
|
|
938
|
+
theme: any(),
|
|
939
|
+
locale: any(),
|
|
940
|
+
factory: any(),
|
|
941
|
+
picker: any(),
|
|
942
|
+
branches: any(),
|
|
943
|
+
flows: any()
|
|
944
|
+
}), ({ target }) => {
|
|
945
|
+
if (target?.port === 0) return "target.port must be a listening port, not zero.";
|
|
946
|
+
if (target?.origin !== void 0 && (typeof target.origin !== "string" || !target.origin.trim())) return "target.origin must be an HTTP(S) origin.";
|
|
947
|
+
});
|
|
948
|
+
var RouteSchema = check(any().transform((input) => typeof input === "string" ? {
|
|
949
|
+
path: input,
|
|
950
|
+
title: input === "/" ? "Home" : input
|
|
951
|
+
} : input), (route) => {
|
|
952
|
+
if (!route || typeof route.path !== "string" || !route.path.startsWith("/") || route.path.startsWith("//")) return "Canvas routes must be local paths beginning with a single /.";
|
|
953
|
+
if (new URL(route.path, "http://bud.invalid").origin !== "http://bud.invalid" || route.path.includes("\\")) return "Canvas routes must stay on the target origin.";
|
|
954
|
+
let id = route.id ?? routePath(route.path);
|
|
955
|
+
if (typeof id !== "string" || !id.trim()) return "Canvas route IDs and paths must be unique.";
|
|
956
|
+
if (typeof route.title !== "string" || !route.title.trim()) return "Each route needs a title.";
|
|
957
|
+
if (route.pattern !== void 0 && (typeof route.pattern !== "string" || !route.pattern.startsWith("/"))) return "A route pattern must be a path beginning with /.";
|
|
958
|
+
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.";
|
|
959
|
+
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 }.";
|
|
960
|
+
}).transform((route) => {
|
|
961
|
+
let path = routePath(route.path);
|
|
962
|
+
return {
|
|
963
|
+
id: route.id ?? path,
|
|
964
|
+
path,
|
|
965
|
+
title: route.title,
|
|
966
|
+
...route.pattern === void 0 ? {} : { pattern: route.pattern },
|
|
967
|
+
...route.group === void 0 ? {} : { group: route.group.trim() },
|
|
968
|
+
...route.ready === void 0 ? {} : { ready: route.ready === false ? null : parseConfig(PreviewReadySchema, route.ready) }
|
|
969
|
+
};
|
|
970
|
+
});
|
|
971
|
+
function routePath(path) {
|
|
972
|
+
let url = new URL(path, "http://bud.invalid");
|
|
973
|
+
let search = url.searchParams.toString();
|
|
974
|
+
return `${url.pathname}${search ? `?${search}` : ""}${url.hash}`;
|
|
975
|
+
}
|
|
976
|
+
/** Canvas routes with unique IDs and normalized paths. */
|
|
977
|
+
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);
|
|
978
|
+
var port = (value) => value === void 0 || Number.isInteger(value) && value >= 0 && value <= 65535;
|
|
979
|
+
var remote = (value) => typeof value === "string" && /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(value);
|
|
980
|
+
/** A project's config, validated and normalized before Bud starts any listeners. */
|
|
981
|
+
var BudConfigSchema = delegate(check(delegate(check(check(delegate(check(delegate(ConfigFields, (config) => ({
|
|
982
|
+
...config,
|
|
983
|
+
routes: parseConfig(BudRoutesSchema, config.routes)
|
|
984
|
+
})), ({ port: companion, target }) => port(companion ?? 0) && port(target?.port) ? void 0 : "Ports must be integers between 0 and 65535."), ({ target, ...config }) => {
|
|
985
|
+
let origin = target?.origin ?? (target?.port ? `http://127.0.0.1:${target.port}` : void 0);
|
|
986
|
+
return {
|
|
987
|
+
...config,
|
|
988
|
+
targetOrigin: origin ? normalizeOrigin(origin, "target.origin must be an HTTP(S) origin without a path or credentials.") : origin
|
|
989
|
+
};
|
|
990
|
+
}), ({ locale }) => locale === void 0 || locale === "en" || locale === "de" ? void 0 : "locale must be \"en\" or \"de\"."), ({ comments }) => remote(comments?.remote ?? "origin") ? void 0 : "comments.remote must be a Git remote name."), (config) => {
|
|
991
|
+
let flows = parseConfig(BudFlowsSchema, config.flows);
|
|
992
|
+
let steps = flows.flatMap((flow) => flow.steps.map((step) => flowStepRoute(flow, step)));
|
|
993
|
+
let home = config.routes.findIndex((route) => route.path === "/") + 1;
|
|
994
|
+
return {
|
|
995
|
+
...config,
|
|
996
|
+
flows,
|
|
997
|
+
routes: [
|
|
998
|
+
...config.routes.slice(0, home),
|
|
999
|
+
...steps,
|
|
1000
|
+
...config.routes.slice(home)
|
|
1001
|
+
]
|
|
1002
|
+
};
|
|
1003
|
+
}), ({ 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) => ({
|
|
1004
|
+
name: config.name ?? "Your project",
|
|
1005
|
+
routes: config.routes,
|
|
1006
|
+
flows: config.flows,
|
|
1007
|
+
port: config.port ?? 0,
|
|
1008
|
+
targetOrigin: config.targetOrigin,
|
|
1009
|
+
remote: config.comments?.remote ?? "origin",
|
|
1010
|
+
screens: parseConfig(BudScreensSchema, config.screens),
|
|
1011
|
+
storage: parseConfig(BudStorageSchema, config.storage),
|
|
1012
|
+
designs: parseConfig(BudDesignsSchema, config.designs),
|
|
1013
|
+
previews: parseConfig(BudPreviewsSchema, config.previews),
|
|
1014
|
+
files: parseConfig(BudFilesSchema, config.files),
|
|
1015
|
+
logo: parseConfig(BudLogoSchema, config.logo),
|
|
1016
|
+
theme: parseConfig(BudThemeSchema, config.theme),
|
|
1017
|
+
locale: config.locale === void 0 ? "en" : config.locale,
|
|
1018
|
+
factory: parseConfig(BudFactorySchema, config.factory),
|
|
1019
|
+
picker: parseConfig(BudPickerSchema, config.picker),
|
|
1020
|
+
branches: parseConfig(BudBranchesSchema, config.branches)
|
|
1021
|
+
}));
|
|
1022
|
+
/**
|
|
1023
|
+
* Validates executable project config before starting any listeners.
|
|
1024
|
+
* @param value - The config module's default export.
|
|
1025
|
+
* @returns Normalized route IDs, ports, and URLs.
|
|
1026
|
+
*/
|
|
1027
|
+
function resolveBudConfig(value) {
|
|
1028
|
+
let { config, plugins, provides } = applyPlugins(value);
|
|
1029
|
+
rejectMovedSettings(config);
|
|
1030
|
+
let resolved = parseConfig(BudConfigSchema, config);
|
|
1031
|
+
let store = (kind) => resolved.storage[kind] === false ? false : provides[kind]?.name ?? "bud:beads";
|
|
1032
|
+
let design = provides.taskDesign;
|
|
1033
|
+
let picker = provides.picker;
|
|
1034
|
+
return withPlugins({
|
|
1035
|
+
...resolved,
|
|
1036
|
+
storage: {
|
|
1037
|
+
work: store("work"),
|
|
1038
|
+
threads: store("threads")
|
|
1039
|
+
},
|
|
1040
|
+
designs: {
|
|
1041
|
+
...resolved.designs,
|
|
1042
|
+
tasks: design ? parseConfig(modulePath(`Plugin ${design.name}: taskDesign`, false), design.taskDesign) : "bud:beads"
|
|
1043
|
+
},
|
|
1044
|
+
picker: picker ? picker.picker ? {
|
|
1045
|
+
name: picker.name,
|
|
1046
|
+
module: parseConfig(modulePath(`Plugin ${picker.name}: picker`, false), picker.picker)
|
|
1047
|
+
} : resolvePicker(picker.name) : resolvePicker(void 0),
|
|
1048
|
+
factory: resolved.factory ? {
|
|
1049
|
+
...resolved.factory,
|
|
1050
|
+
sandbox: provides.sandbox?.name ?? null,
|
|
1051
|
+
agent: provides.harness ? factoryAgent(provides.harness.name, provides.harness.harness) : resolved.factory.agent
|
|
1052
|
+
} : null
|
|
1053
|
+
}, plugins);
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Returns a bare HTTP(S) origin, rejecting credentials, paths, queries and fragments.
|
|
1057
|
+
* @param origin - The URL to check.
|
|
1058
|
+
* @param message - The error's message when it is not a bare origin.
|
|
1059
|
+
* @returns The URL's origin.
|
|
1060
|
+
*/
|
|
1061
|
+
function normalizeOrigin(origin, message) {
|
|
1062
|
+
let url = new URL(origin);
|
|
1063
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.pathname !== "/" || url.search || url.hash) throw new Error(message);
|
|
1064
|
+
return url.origin;
|
|
1065
|
+
}
|
|
1066
|
+
//#endregion
|
|
1067
|
+
export { taskDesign as a, pages as c, DEFAULT_FACTORY_MODEL as d, picker as i, tasks as l, normalizeOrigin as n, canvas as o, resolveBudConfig as r, files as s, defineConfig as t, resolveBranches as u };
|