youiyoux 0.1.2 → 0.1.3
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/chunks/{chunk-FUB7B3VK.js → chunk-74KBRI2H.js} +1 -1
- package/dist/chunks/{chunk-ZLRFD5JI.js → chunk-C3TSTYP7.js} +68 -7
- package/dist/chunks/{chunk-3ZHGONNA.js → chunk-L5BV4PI7.js} +399 -37
- package/dist/chunks/{src-7ENJHYG4.js → chunk-OIHNEGWH.js} +278 -78
- package/dist/chunks/{chunk-CLNMVQZJ.js → chunk-RTOTNSOV.js} +133 -13
- package/dist/chunks/{chunk-UHPK62Q5.js → chunk-UEB6MJDX.js} +75 -10
- package/dist/chunks/{src-VEHGMKYV.js → src-DS43LCRA.js} +2 -2
- package/dist/chunks/src-U5NTDPUX.js +148 -0
- package/dist/chunks/{src-WQWYJ6IQ.js → src-YGEZCUAO.js} +48 -6
- package/dist/chunks/{src-4KFNL3MY.js → src-YUC5PIDK.js} +6 -2
- package/dist/chunks/{src-B742AYFC.js → src-ZS76UD7E.js} +30 -2
- package/dist/schema/youi.request.schema.json +229 -0
- package/dist/youi.js +5 -5
- package/dist/youiyoux.js +5 -5
- package/package.json +4 -4
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createRequire as __youiRequire } from "node:module"; const require = __youiRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
|
+
PICTURE_SCALE,
|
|
3
4
|
SCANNER_VERSION,
|
|
4
5
|
buildTokens,
|
|
5
6
|
checkInputFor,
|
|
@@ -12,10 +13,10 @@ import {
|
|
|
12
13
|
ruleCapability,
|
|
13
14
|
runChecks,
|
|
14
15
|
scanProject,
|
|
16
|
+
screenSlug,
|
|
15
17
|
systemPairs,
|
|
16
18
|
tokenImpact
|
|
17
|
-
} from "./chunk-
|
|
18
|
-
import "./chunk-S7UV34H5.js";
|
|
19
|
+
} from "./chunk-L5BV4PI7.js";
|
|
19
20
|
import {
|
|
20
21
|
BUILT_IN_CHECKS,
|
|
21
22
|
COMPONENT_STATES,
|
|
@@ -23,6 +24,10 @@ import {
|
|
|
23
24
|
ElementId,
|
|
24
25
|
LIBRARY_DIR,
|
|
25
26
|
LibraryEntry,
|
|
27
|
+
REQUEST_ID,
|
|
28
|
+
REQUEST_STATUSES,
|
|
29
|
+
RequestDoc,
|
|
30
|
+
RequestTarget,
|
|
26
31
|
RuleCheck,
|
|
27
32
|
RuleRollout,
|
|
28
33
|
RuleSpec,
|
|
@@ -38,7 +43,7 @@ import {
|
|
|
38
43
|
toPosix,
|
|
39
44
|
tokenValueProblems,
|
|
40
45
|
writeFileAtomic
|
|
41
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-C3TSTYP7.js";
|
|
42
47
|
import {
|
|
43
48
|
__toESM
|
|
44
49
|
} from "./chunk-E4HNK7VF.js";
|
|
@@ -805,7 +810,7 @@ import { readFile as readFile2, stat } from "node:fs/promises";
|
|
|
805
810
|
import { extname, join as join3, normalize, resolve, sep } from "node:path";
|
|
806
811
|
|
|
807
812
|
// ../workflow/src/policy.ts
|
|
808
|
-
var PERSON_ONLY = /* @__PURE__ */ new Set(["approve", "handoff"]);
|
|
813
|
+
var PERSON_ONLY = /* @__PURE__ */ new Set(["approve", "handoff", "close"]);
|
|
809
814
|
var WorkflowError = class extends Error {
|
|
810
815
|
name = "WorkflowError";
|
|
811
816
|
code;
|
|
@@ -817,7 +822,7 @@ var WorkflowError = class extends Error {
|
|
|
817
822
|
}
|
|
818
823
|
};
|
|
819
824
|
function authorize(actor, action) {
|
|
820
|
-
if (PERSON_ONLY.has(action) && actor.kind !== "person") throw new WorkflowError("forbidden", `an ${actor.kind} cannot ${action}: approving a design change is a person's decision`);
|
|
825
|
+
if (PERSON_ONLY.has(action) && actor.kind !== "person") throw new WorkflowError("forbidden", action === "close" ? `an ${actor.kind} cannot close or reopen a request: mark it addressed, and a person closes it` : `an ${actor.kind} cannot ${action}: approving a design change is a person's decision`);
|
|
821
826
|
}
|
|
822
827
|
|
|
823
828
|
// ../workflow/src/http.ts
|
|
@@ -846,6 +851,9 @@ var CreateInput = external_exports.union([
|
|
|
846
851
|
external_exports.discriminatedUnion("mode", [external_exports.object({ kind: external_exports.literal("rule").optional(), mode: external_exports.literal("edit"), ruleId: ElementId }), external_exports.object({ kind: external_exports.literal("rule").optional(), mode: external_exports.literal("create") })])
|
|
847
852
|
]);
|
|
848
853
|
var SaveInput = external_exports.object({ revision: external_exports.number().int().positive(), content: external_exports.union([DraftContentInput, StyleContentInput, LibraryContentInput]) });
|
|
854
|
+
var RequestInput = external_exports.object({ kind: external_exports.enum(["comment", "change"]), body: external_exports.string().min(1).max(4e3), target: RequestTarget });
|
|
855
|
+
var ReplyInput = external_exports.object({ body: external_exports.string().min(1).max(4e3) });
|
|
856
|
+
var StatusInput = external_exports.object({ status: external_exports.enum(REQUEST_STATUSES), body: external_exports.string().max(4e3).optional() });
|
|
849
857
|
function styleContent(content) {
|
|
850
858
|
return { ...content, tokens: content.tokens.map((t) => Object.fromEntries(Object.entries(t).filter(([, v]) => v !== void 0))) };
|
|
851
859
|
}
|
|
@@ -927,6 +935,31 @@ async function api(req, res, path, options) {
|
|
|
927
935
|
if ("kind" in input && input.kind === "library") return send(res, 201, await workflow.create(actor, { kind: "library", content: input.content }));
|
|
928
936
|
return send(res, 201, await workflow.create(actor, "kind" in input && input.kind === "style" ? { kind: "style", ...input.content ? { content: styleContent(input.content) } : {} } : input));
|
|
929
937
|
}
|
|
938
|
+
if (path === "/api/requests" && method === "GET") return send(res, 200, await (await workflow.requests()).list());
|
|
939
|
+
if (path === "/api/requests" && method === "POST") return send(res, 201, await (await workflow.requests()).create(actor, parse2(RequestInput, await readJson(req))));
|
|
940
|
+
const request = /^\/api\/requests\/(r-[0-9a-f]{10})(?:\/(replies|status|compare|before\.webp|after\.webp))?$/.exec(path);
|
|
941
|
+
if (request) {
|
|
942
|
+
const [, id, action] = request;
|
|
943
|
+
const store = await workflow.requests();
|
|
944
|
+
if (!action && method === "GET") return send(res, 200, await store.get(id));
|
|
945
|
+
if (action === "compare" && method === "GET") {
|
|
946
|
+
const { before, after } = await store.compare(id);
|
|
947
|
+
const view = (picture, which) => picture ? { ...picture, file: void 0, url: `/api/requests/${id}/${which}.webp` } : null;
|
|
948
|
+
return send(res, 200, { before: view(before, "before"), after: view(after, "after") });
|
|
949
|
+
}
|
|
950
|
+
if ((action === "before.webp" || action === "after.webp") && method === "GET") {
|
|
951
|
+
const picture = (await store.compare(id))[action === "before.webp" ? "before" : "after"];
|
|
952
|
+
if (!picture) throw new WorkflowError("not-found", `${id} has no ${action.replace(".webp", "")} picture`);
|
|
953
|
+
res.writeHead(200, { ...BASE_HEADERS, "content-type": "image/webp", "cache-control": "no-store" });
|
|
954
|
+
res.end(await readFile2(picture.file));
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
if (action === "replies" && method === "POST") return send(res, 200, await store.reply(actor, id, parse2(ReplyInput, await readJson(req)).body));
|
|
958
|
+
if (action === "status" && method === "POST") {
|
|
959
|
+
const input = parse2(StatusInput, await readJson(req));
|
|
960
|
+
return send(res, 200, await store.setStatus(actor, id, input.status, input.body ?? ""));
|
|
961
|
+
}
|
|
962
|
+
}
|
|
930
963
|
const match = /^\/api\/drafts\/(d-[0-9a-f]{10})(?:\/(preview|approve|rebase|handoff))?$/.exec(path);
|
|
931
964
|
if (match) {
|
|
932
965
|
const id = match[1];
|
|
@@ -1101,20 +1134,178 @@ function comparePreview(input) {
|
|
|
1101
1134
|
};
|
|
1102
1135
|
}
|
|
1103
1136
|
|
|
1104
|
-
// ../workflow/src/
|
|
1137
|
+
// ../workflow/src/requests.ts
|
|
1105
1138
|
import { randomBytes } from "node:crypto";
|
|
1139
|
+
import { existsSync } from "node:fs";
|
|
1140
|
+
import { copyFile, mkdir, readdir, readFile as readFile3, stat as stat2 } from "node:fs/promises";
|
|
1141
|
+
import { join as join4 } from "node:path";
|
|
1142
|
+
var REQUESTS_DIR = ".youi/requests";
|
|
1143
|
+
var FILE = /^(r-[0-9a-f]{10})\.json$/;
|
|
1144
|
+
var authorOf = (actor) => ({ kind: actor.kind, name: actor.name });
|
|
1145
|
+
var screensDir = (root) => join4(root, ".youi/screens");
|
|
1146
|
+
var RequestStore = class {
|
|
1147
|
+
dir;
|
|
1148
|
+
root;
|
|
1149
|
+
project;
|
|
1150
|
+
options;
|
|
1151
|
+
/** `root`: the product's root, where `.youi/` lives; `project`: its id. */
|
|
1152
|
+
constructor(root, project, options = {}) {
|
|
1153
|
+
this.dir = join4(root, REQUESTS_DIR);
|
|
1154
|
+
this.root = root;
|
|
1155
|
+
this.project = project;
|
|
1156
|
+
this.options = options;
|
|
1157
|
+
}
|
|
1158
|
+
now() {
|
|
1159
|
+
return (this.options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
|
|
1160
|
+
}
|
|
1161
|
+
file(id) {
|
|
1162
|
+
if (!REQUEST_ID.test(id)) throw new WorkflowError("bad-request", `"${id.slice(0, 40)}" is not a request id (r-<10 hex>)`);
|
|
1163
|
+
return join4(this.dir, `${id}.json`);
|
|
1164
|
+
}
|
|
1165
|
+
async write(request) {
|
|
1166
|
+
const checked = RequestDoc.parse(request);
|
|
1167
|
+
await writeFileAtomic(this.file(checked.id), `${JSON.stringify(checked, null, 2)}
|
|
1168
|
+
`);
|
|
1169
|
+
return checked;
|
|
1170
|
+
}
|
|
1171
|
+
/** Every request, newest first. */
|
|
1172
|
+
async list() {
|
|
1173
|
+
const names = await readdir(this.dir).catch(() => []);
|
|
1174
|
+
const requests = [];
|
|
1175
|
+
const unreadable = [];
|
|
1176
|
+
for (const name of names.sort()) {
|
|
1177
|
+
const match = FILE.exec(name);
|
|
1178
|
+
if (!match) continue;
|
|
1179
|
+
const file = join4(REQUESTS_DIR, name);
|
|
1180
|
+
let value;
|
|
1181
|
+
try {
|
|
1182
|
+
value = JSON.parse(await readFile3(join4(this.dir, name), "utf8"));
|
|
1183
|
+
} catch (error) {
|
|
1184
|
+
unreadable.push({ file, reason: error instanceof Error ? error.message.split("\n")[0] : String(error) });
|
|
1185
|
+
continue;
|
|
1186
|
+
}
|
|
1187
|
+
const result = RequestDoc.safeParse(value);
|
|
1188
|
+
if (!result.success) unreadable.push({ file, reason: result.error.issues.map((i) => `${i.path.join(".") || "file"}: ${i.message}`).join("; ") });
|
|
1189
|
+
else if (result.data.id !== match[1]) unreadable.push({ file, reason: "its id is not its file name" });
|
|
1190
|
+
else requests.push(result.data);
|
|
1191
|
+
}
|
|
1192
|
+
requests.sort((a, b) => b.createdAt.localeCompare(a.createdAt) || a.id.localeCompare(b.id));
|
|
1193
|
+
return { requests, unreadable };
|
|
1194
|
+
}
|
|
1195
|
+
async get(id) {
|
|
1196
|
+
const text = await readFile3(this.file(id), "utf8").catch(() => null);
|
|
1197
|
+
if (text === null) throw new WorkflowError("not-found", `no request ${id}`);
|
|
1198
|
+
const parsed = RequestDoc.safeParse(JSON.parse(text));
|
|
1199
|
+
if (!parsed.success) throw new WorkflowError("invalid", `${REQUESTS_DIR}/${id}.json is not a valid youi.request/v1`, parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`));
|
|
1200
|
+
return parsed.data;
|
|
1201
|
+
}
|
|
1202
|
+
async create(actor, input) {
|
|
1203
|
+
authorize(actor, "draft");
|
|
1204
|
+
const body = input.body.trim();
|
|
1205
|
+
if (!body) throw new WorkflowError("invalid", "a request needs words: what to look at, or what to change");
|
|
1206
|
+
const at = this.now();
|
|
1207
|
+
const id = (this.options.newId ?? (() => `r-${randomBytes(5).toString("hex")}`))();
|
|
1208
|
+
const shot = join4(screensDir(this.root), `${screenSlug(input.target.route)}.light.webp`);
|
|
1209
|
+
let before;
|
|
1210
|
+
if (existsSync(shot)) {
|
|
1211
|
+
before = `${id}.before.webp`;
|
|
1212
|
+
await mkdir(this.dir, { recursive: true });
|
|
1213
|
+
await copyFile(shot, join4(this.dir, before));
|
|
1214
|
+
}
|
|
1215
|
+
return this.write({ schema: "youi.request/v1", id, project: this.project, kind: input.kind, status: "open", body, author: authorOf(actor), createdAt: at, updatedAt: at, target: input.target, ...before ? { before } : {}, thread: [] });
|
|
1216
|
+
}
|
|
1217
|
+
async reply(actor, id, body) {
|
|
1218
|
+
authorize(actor, "draft");
|
|
1219
|
+
const text = body.trim();
|
|
1220
|
+
if (!text) throw new WorkflowError("invalid", "a reply needs words");
|
|
1221
|
+
const request = await this.get(id);
|
|
1222
|
+
const at = this.now();
|
|
1223
|
+
return this.write({ ...request, updatedAt: at, thread: [...request.thread, { author: authorOf(actor), at, body: text }] });
|
|
1224
|
+
}
|
|
1225
|
+
/**
|
|
1226
|
+
* Before and after for a request: the picture kept when it was filed, and the screen's picture
|
|
1227
|
+
* now, with the component found again on it by its file and line (from the new capture's places).
|
|
1228
|
+
*/
|
|
1229
|
+
async compare(id) {
|
|
1230
|
+
const request = await this.get(id);
|
|
1231
|
+
const t = request.target;
|
|
1232
|
+
const before = request.before === `${request.id}.before.webp` && existsSync(join4(this.dir, request.before)) ? { file: join4(this.dir, request.before), box: t.box ?? null, page: t.page ?? null, at: request.createdAt } : null;
|
|
1233
|
+
const slug = screenSlug(t.route);
|
|
1234
|
+
const shot = join4(screensDir(this.root), `${slug}.light.webp`);
|
|
1235
|
+
const info = await stat2(shot).catch(() => null);
|
|
1236
|
+
if (!info) return { before, after: null };
|
|
1237
|
+
let box = t.box ?? null;
|
|
1238
|
+
let page = t.page ?? null;
|
|
1239
|
+
let found = false;
|
|
1240
|
+
try {
|
|
1241
|
+
const places = JSON.parse(await readFile3(join4(screensDir(this.root), `${slug}.places.json`), "utf8"));
|
|
1242
|
+
const again = t.file && t.line ? places.places?.find((p) => p.file === t.file && p.line === t.line && p.name === t.name) : void 0;
|
|
1243
|
+
if (places.size) page = places.size;
|
|
1244
|
+
if (again) {
|
|
1245
|
+
box = again.box;
|
|
1246
|
+
found = true;
|
|
1247
|
+
}
|
|
1248
|
+
} catch {
|
|
1249
|
+
}
|
|
1250
|
+
const since = request.thread.filter((e) => e.status === "open").at(-1)?.at ?? request.createdAt;
|
|
1251
|
+
const at = info.mtime.toISOString();
|
|
1252
|
+
return { before, after: { file: shot, box, page, at, sinceFiled: at > since, found } };
|
|
1253
|
+
}
|
|
1254
|
+
/**
|
|
1255
|
+
* Move a request on: `addressed` (anyone; an agent says what it did), or `closed` and `open`
|
|
1256
|
+
* (a person's: closing accepts the work, reopening says it is not done).
|
|
1257
|
+
*/
|
|
1258
|
+
async setStatus(actor, id, status, body = "") {
|
|
1259
|
+
if (status !== "addressed") authorize(actor, "close");
|
|
1260
|
+
else authorize(actor, "draft");
|
|
1261
|
+
const request = await this.get(id);
|
|
1262
|
+
if (request.status === status) throw new WorkflowError("conflict", `${id} is already ${status}`);
|
|
1263
|
+
if (status === "addressed" && request.status === "closed") throw new WorkflowError("conflict", `${id} is closed: a person reopens it first`);
|
|
1264
|
+
const at = this.now();
|
|
1265
|
+
return this.write({ ...request, status, updatedAt: at, thread: [...request.thread, { author: authorOf(actor), at, body: body.trim(), status }] });
|
|
1266
|
+
}
|
|
1267
|
+
};
|
|
1268
|
+
function requestBrief(request, root) {
|
|
1269
|
+
const t = request.target;
|
|
1270
|
+
const picture = join4(root, ".youi/screens", `${screenSlug(t.route)}.light.webp`);
|
|
1271
|
+
const at = t.file && t.line ? `${t.file}:${t.line}` : null;
|
|
1272
|
+
const every = t.scope === "element" && t.element;
|
|
1273
|
+
const where = every ? `every use of ${t.element} (system_where_used lists them)` : at ? `${at}` : `the ${t.name} on ${t.route} (its line was not captured: find it from the picture and the route)`;
|
|
1274
|
+
const next = request.status === "closed" ? "Closed by a person: nothing to do." : request.status === "addressed" ? "Marked addressed: a person checks it and closes or reopens it. Reply only if asked." : request.kind === "comment" ? `A comment on ${where}: reply with request_reply if it asks something; change code only if it asks for a change.` : `Make the change at ${where}${t.lookalike && t.element ? `, using the System's ${t.element} rather than the hand-built lookalike where that is the fix` : ""}; capture the screen again (youiyoux screens capture --base <the app in development> --route ${t.route}) so the person sees it after; then call request_addressed with what you changed (file:line). Ask with request_reply first if it is unclear. A person closes it.`;
|
|
1275
|
+
return {
|
|
1276
|
+
id: request.id,
|
|
1277
|
+
kind: request.kind,
|
|
1278
|
+
status: request.status,
|
|
1279
|
+
ask: request.body,
|
|
1280
|
+
from: `${request.author.kind} ${request.author.name}`,
|
|
1281
|
+
createdAt: request.createdAt,
|
|
1282
|
+
route: t.route,
|
|
1283
|
+
component: t.name,
|
|
1284
|
+
element: t.element ?? null,
|
|
1285
|
+
lookalike: t.lookalike === true,
|
|
1286
|
+
at,
|
|
1287
|
+
scope: every ? `everywhere ${t.element} is used` : "this one, on this screen",
|
|
1288
|
+
picture: existsSync(picture) ? picture : null,
|
|
1289
|
+
box: t.box ? { x: Math.round(t.box.x * PICTURE_SCALE), y: Math.round(t.box.y * PICTURE_SCALE), width: Math.round(t.box.width * PICTURE_SCALE), height: Math.round(t.box.height * PICTURE_SCALE) } : null,
|
|
1290
|
+
thread: request.thread.map((e) => `${e.author.kind} ${e.author.name}${e.status ? ` (marked ${e.status})` : ""}: ${e.body || "(no words)"}`),
|
|
1291
|
+
next
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
// ../workflow/src/service.ts
|
|
1296
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
1106
1297
|
|
|
1107
1298
|
// ../workflow/src/style-preview.ts
|
|
1108
|
-
import { mkdir, mkdtemp as mkdtemp2, rm as rm2, writeFile } from "node:fs/promises";
|
|
1299
|
+
import { mkdir as mkdir2, mkdtemp as mkdtemp2, rm as rm2, writeFile } from "node:fs/promises";
|
|
1109
1300
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
1110
|
-
import { dirname, join as
|
|
1301
|
+
import { dirname, join as join5 } from "node:path";
|
|
1111
1302
|
var MAX_LISTED = 5e3;
|
|
1112
1303
|
async function buildFrom(project, tree) {
|
|
1113
|
-
const root = await mkdtemp2(
|
|
1304
|
+
const root = await mkdtemp2(join5(tmpdir2(), "youi-style-"));
|
|
1114
1305
|
try {
|
|
1115
1306
|
for (const [path, text] of tree) {
|
|
1116
|
-
await
|
|
1117
|
-
await writeFile(
|
|
1307
|
+
await mkdir2(dirname(join5(root, path)), { recursive: true });
|
|
1308
|
+
await writeFile(join5(root, path), text);
|
|
1118
1309
|
}
|
|
1119
1310
|
return (await buildTokens(root, project.system.config, project.config.outputs)).tokens;
|
|
1120
1311
|
} finally {
|
|
@@ -1271,7 +1462,7 @@ function previewSummary(draft) {
|
|
|
1271
1462
|
const p = draft.preview;
|
|
1272
1463
|
return p ? `changes ${p.totals.tokens} token(s) and ${p.totals.contracts} component(s): ${p.totals.sites} site(s) and ${p.totals.direct} direct use(s) in ${p.totals.files} file(s) on ${p.totals.routes} screen(s)` : "no preview";
|
|
1273
1464
|
}
|
|
1274
|
-
var randomId = (prefix) => `${prefix}-${
|
|
1465
|
+
var randomId = (prefix) => `${prefix}-${randomBytes2(5).toString("hex")}`;
|
|
1275
1466
|
var RuleWorkflow = class {
|
|
1276
1467
|
options;
|
|
1277
1468
|
execution;
|
|
@@ -1282,6 +1473,12 @@ var RuleWorkflow = class {
|
|
|
1282
1473
|
this.options = options;
|
|
1283
1474
|
this.execution = options.execution ?? scannerExecution;
|
|
1284
1475
|
}
|
|
1476
|
+
/** The product's comments and change requests on its captured screens (requests.ts), in its `.youi/requests`. */
|
|
1477
|
+
async requests() {
|
|
1478
|
+
const { projectRoot, configPath, designRoot, productRoot } = this.options;
|
|
1479
|
+
const project = await loadProject(projectRoot, { ...configPath ? { configPath } : {}, ...designRoot ? { designRoot } : {}, ...productRoot ? { productRoot } : {} });
|
|
1480
|
+
return new RequestStore(project.root, project.config.id, this.options.now ? { now: this.options.now } : {});
|
|
1481
|
+
}
|
|
1285
1482
|
now() {
|
|
1286
1483
|
return (this.options.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
1287
1484
|
}
|
|
@@ -1925,8 +2122,8 @@ function shellQuote(path) {
|
|
|
1925
2122
|
}
|
|
1926
2123
|
|
|
1927
2124
|
// ../workflow/src/store.ts
|
|
1928
|
-
import { mkdir as
|
|
1929
|
-
import { join as
|
|
2125
|
+
import { mkdir as mkdir3, readdir as readdir2, readFile as readFile4 } from "node:fs/promises";
|
|
2126
|
+
import { join as join6 } from "node:path";
|
|
1930
2127
|
var DRAFT_ID = /^d-[0-9a-f]{10}$/;
|
|
1931
2128
|
var FORMAT = "youiyoux.draft/v1";
|
|
1932
2129
|
var COMMIT = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
@@ -1951,18 +2148,18 @@ var FileStore = class {
|
|
|
1951
2148
|
artifacts;
|
|
1952
2149
|
constructor(dir) {
|
|
1953
2150
|
this.location = dir;
|
|
1954
|
-
this.drafts =
|
|
1955
|
-
this.artifacts =
|
|
2151
|
+
this.drafts = join6(dir, "drafts");
|
|
2152
|
+
this.artifacts = join6(dir, "handoffs");
|
|
1956
2153
|
}
|
|
1957
2154
|
async list() {
|
|
1958
|
-
await
|
|
1959
|
-
const names = (await
|
|
2155
|
+
await mkdir3(this.drafts, { recursive: true });
|
|
2156
|
+
const names = (await readdir2(this.drafts)).filter((name) => /^d-[0-9a-f]{10}\.json$/.test(name)).sort();
|
|
1960
2157
|
const drafts = [];
|
|
1961
2158
|
const unreadable = [];
|
|
1962
2159
|
for (const name of names) {
|
|
1963
|
-
const file =
|
|
2160
|
+
const file = join6(this.drafts, name);
|
|
1964
2161
|
try {
|
|
1965
|
-
drafts.push(decode(await
|
|
2162
|
+
drafts.push(decode(await readFile4(file, "utf8"), file));
|
|
1966
2163
|
} catch (error) {
|
|
1967
2164
|
unreadable.push({ file, reason: error instanceof Error ? error.message : String(error) });
|
|
1968
2165
|
}
|
|
@@ -1971,10 +2168,10 @@ var FileStore = class {
|
|
|
1971
2168
|
}
|
|
1972
2169
|
async get(id) {
|
|
1973
2170
|
checkId(id);
|
|
1974
|
-
const file =
|
|
2171
|
+
const file = join6(this.drafts, `${id}.json`);
|
|
1975
2172
|
let text;
|
|
1976
2173
|
try {
|
|
1977
|
-
text = await
|
|
2174
|
+
text = await readFile4(file, "utf8");
|
|
1978
2175
|
} catch (error) {
|
|
1979
2176
|
if (error.code === "ENOENT") return null;
|
|
1980
2177
|
throw error;
|
|
@@ -1983,12 +2180,12 @@ var FileStore = class {
|
|
|
1983
2180
|
}
|
|
1984
2181
|
async put(draft) {
|
|
1985
2182
|
checkId(draft.id);
|
|
1986
|
-
await writeFileAtomic(
|
|
2183
|
+
await writeFileAtomic(join6(this.drafts, `${draft.id}.json`), `${JSON.stringify({ schema: FORMAT, draft }, null, 2)}
|
|
1987
2184
|
`);
|
|
1988
2185
|
}
|
|
1989
2186
|
async putArtifact(name, content) {
|
|
1990
2187
|
if (!/^[a-z0-9][a-z0-9.-]*$/.test(name)) throw new Error(`not an artifact name: ${name}`);
|
|
1991
|
-
const file =
|
|
2188
|
+
const file = join6(this.artifacts, name);
|
|
1992
2189
|
await writeFileAtomic(file, content);
|
|
1993
2190
|
return file;
|
|
1994
2191
|
}
|
|
@@ -2012,72 +2209,75 @@ var MemoryStore = class {
|
|
|
2012
2209
|
return `memory:${name}`;
|
|
2013
2210
|
}
|
|
2014
2211
|
};
|
|
2212
|
+
|
|
2015
2213
|
export {
|
|
2016
|
-
DEFAULT_FILES,
|
|
2017
|
-
DRAFT_ID,
|
|
2018
|
-
DesignFiles,
|
|
2019
|
-
RuleWorkflow as DesignWorkflow,
|
|
2020
|
-
FileStore,
|
|
2021
|
-
GitError,
|
|
2022
2214
|
JsonEditError,
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
MAX_TITLE,
|
|
2026
|
-
MemoryStore,
|
|
2027
|
-
RuleWorkflow,
|
|
2028
|
-
SESSION_HEADER,
|
|
2029
|
-
WORKFLOW_VERSION,
|
|
2030
|
-
WorkflowError,
|
|
2031
|
-
adoptionRefusal,
|
|
2032
|
-
authorize,
|
|
2033
|
-
branchTip,
|
|
2034
|
-
changesDigest,
|
|
2035
|
-
comparePreview,
|
|
2036
|
-
componentChanges,
|
|
2037
|
-
contentDigest,
|
|
2038
|
-
createHandler,
|
|
2039
|
-
createReviewBranch,
|
|
2040
|
-
defaultBranch,
|
|
2215
|
+
readJsonPath,
|
|
2216
|
+
setJsonPath,
|
|
2041
2217
|
deleteJsonPath,
|
|
2042
|
-
|
|
2043
|
-
describeLibrary,
|
|
2044
|
-
describeStyle,
|
|
2045
|
-
draftStatus,
|
|
2046
|
-
effectiveStateEdits,
|
|
2047
|
-
effectiveTokenEdits,
|
|
2048
|
-
emptyStyleContent,
|
|
2218
|
+
DEFAULT_FILES,
|
|
2049
2219
|
fieldsFromRule,
|
|
2220
|
+
newRuleFields,
|
|
2221
|
+
toRuleSpec,
|
|
2222
|
+
validateContent,
|
|
2223
|
+
contentDigest,
|
|
2224
|
+
renderRuleYaml,
|
|
2225
|
+
renderContract,
|
|
2226
|
+
draftStatus,
|
|
2227
|
+
componentChanges,
|
|
2228
|
+
GitError,
|
|
2050
2229
|
git,
|
|
2051
|
-
|
|
2230
|
+
locate,
|
|
2231
|
+
defaultBranch,
|
|
2232
|
+
branchTip,
|
|
2052
2233
|
headCommit,
|
|
2234
|
+
readAt,
|
|
2053
2235
|
isAncestor,
|
|
2054
|
-
libraryDigest,
|
|
2055
|
-
libraryPath,
|
|
2056
|
-
librarySlug,
|
|
2057
2236
|
listAt,
|
|
2058
2237
|
listTreeAt,
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2238
|
+
hasChanges,
|
|
2239
|
+
changesDigest,
|
|
2240
|
+
createReviewBranch,
|
|
2241
|
+
MAX_TITLE,
|
|
2242
|
+
emptyStyleContent,
|
|
2063
2243
|
normalizeStyleContent,
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
renderContractStates,
|
|
2070
|
-
renderLibraryEntry,
|
|
2071
|
-
renderRuleYaml,
|
|
2244
|
+
styleDigest,
|
|
2245
|
+
tokenPathOf,
|
|
2246
|
+
effectiveTokenEdits,
|
|
2247
|
+
validateStyleContent,
|
|
2248
|
+
effectiveStateEdits,
|
|
2072
2249
|
renderTokenEdits,
|
|
2250
|
+
renderContractStates,
|
|
2251
|
+
styleSlug,
|
|
2252
|
+
describeStyle,
|
|
2253
|
+
DesignFiles,
|
|
2073
2254
|
scannerExecution,
|
|
2255
|
+
WorkflowError,
|
|
2256
|
+
authorize,
|
|
2257
|
+
SESSION_HEADER,
|
|
2258
|
+
createHandler,
|
|
2074
2259
|
serveWorkflow,
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
validateContent,
|
|
2260
|
+
normalizeLibraryContent,
|
|
2261
|
+
libraryDigest,
|
|
2262
|
+
libraryPath,
|
|
2263
|
+
librarySlug,
|
|
2264
|
+
adoptionRefusal,
|
|
2081
2265
|
validateLibraryContent,
|
|
2082
|
-
|
|
2266
|
+
renderLibraryEntry,
|
|
2267
|
+
describeLibrary,
|
|
2268
|
+
MAX_FINDINGS,
|
|
2269
|
+
comparePreview,
|
|
2270
|
+
REQUESTS_DIR,
|
|
2271
|
+
RequestStore,
|
|
2272
|
+
requestBrief,
|
|
2273
|
+
MAX_LISTED,
|
|
2274
|
+
previewStyle,
|
|
2275
|
+
WORKFLOW_VERSION,
|
|
2276
|
+
previewSummary,
|
|
2277
|
+
RuleWorkflow,
|
|
2278
|
+
describeContrast,
|
|
2279
|
+
newContrastFailures,
|
|
2280
|
+
DRAFT_ID,
|
|
2281
|
+
FileStore,
|
|
2282
|
+
MemoryStore
|
|
2083
2283
|
};
|