craftar 0.29.0 → 0.30.1
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 +25 -7
- package/dist/cli.js +63 -6
- package/dist/cli.js.map +1 -1
- package/dist/core/doctor.js +1 -1
- package/dist/core/doctor.js.map +1 -1
- package/dist/core/impact.js +10 -5
- package/dist/core/impact.js.map +1 -1
- package/dist/core/integrations.js +31 -6
- package/dist/core/integrations.js.map +1 -1
- package/dist/core/registry.js +3 -2
- package/dist/core/registry.js.map +1 -1
- package/dist/core/report.js +5 -1
- package/dist/core/report.js.map +1 -1
- package/dist/importers/claude-code.js +11 -5
- package/dist/importers/claude-code.js.map +1 -1
- package/dist/importers/drift-promote.js +24 -11
- package/dist/importers/drift-promote.js.map +1 -1
- package/dist/importers/kiro-report.js +11 -3
- package/dist/importers/kiro-report.js.map +1 -1
- package/dist/ui/page.js +393 -0
- package/dist/ui/page.js.map +1 -0
- package/dist/ui/routes.js +132 -0
- package/dist/ui/routes.js.map +1 -0
- package/dist/ui/server.js +122 -0
- package/dist/ui/server.js.map +1 -0
- package/package.json +1 -1
|
@@ -5,7 +5,10 @@ import { parseFrontmatter } from "../core/frontmatter.js";
|
|
|
5
5
|
import { loadWorkspaceConfig, plan, status, unsetDeclared, unsetSummary } from "../core/sync.js";
|
|
6
6
|
import { stripBom, toLf } from "../core/text.js";
|
|
7
7
|
import { workspaceParams, workspaceSections } from "./decide.js";
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* The note of a steering mirror that grew past its rule — the hint that `--prefer-kiro` may apply (not given to a rule
|
|
10
|
+
* `--prefer-kiro` just took from its steering file: that mirror is the source now, so the hint does not apply).
|
|
11
|
+
*/
|
|
9
12
|
export const STEERING_BIGGER = "steering bigger than the rule";
|
|
10
13
|
/** The banner the kiro emitter puts on a generated steering file. */
|
|
11
14
|
export const GENERATED_BANNER = /<!--\s*GENERATED from /;
|
|
@@ -32,10 +35,13 @@ function steeringBody(text) {
|
|
|
32
35
|
* `STEERING_BIGGER` for a rule's steering mirror that holds more lines than the rule's source in `.claude/rules/` —
|
|
33
36
|
* the documented symptom of a mirror kept by hand. The file's name is the rule's output name.
|
|
34
37
|
*/
|
|
35
|
-
async function noteFor(workspaceRoot, rel, from, steering) {
|
|
38
|
+
async function noteFor(workspaceRoot, rel, from, steering, preferred) {
|
|
36
39
|
const m = /^\.kiro\/steering\/([^/]+)\.md$/.exec(rel);
|
|
37
40
|
if (!m || !from.startsWith("rule/"))
|
|
38
41
|
return "";
|
|
42
|
+
// A rule --prefer-kiro took from its steering file: that mirror is the source now, so the note does not apply.
|
|
43
|
+
if (preferred.includes(m[1]))
|
|
44
|
+
return "";
|
|
39
45
|
const rule = path.join(workspaceRoot, ".claude", "rules", `${m[1]}.md`);
|
|
40
46
|
if (!(await exists(rule)))
|
|
41
47
|
return "";
|
|
@@ -55,6 +61,8 @@ export async function kiroReport(o, deps = { plan }) {
|
|
|
55
61
|
const root = path.resolve(o.workspaceRoot);
|
|
56
62
|
if (!(await exists(path.join(root, ".kiro"))))
|
|
57
63
|
return { kind: "none" };
|
|
64
|
+
// The rules this run took from their steering file (`report.preferKiro`).
|
|
65
|
+
const preferred = o.preferred ?? [];
|
|
58
66
|
try {
|
|
59
67
|
const read = (abs) => fs.readFile(abs, "utf8");
|
|
60
68
|
const overrides = { params: await workspaceParams(root, read), sections: await workspaceSections(root, read) };
|
|
@@ -75,7 +83,7 @@ export async function kiroReport(o, deps = { plan }) {
|
|
|
75
83
|
from,
|
|
76
84
|
workspaceLines: textLines(disk),
|
|
77
85
|
generatedLines: textLines(s.planned.content.toString("utf8")),
|
|
78
|
-
note: await noteFor(root, s.path, from, disk),
|
|
86
|
+
note: await noteFor(root, s.path, from, disk, preferred),
|
|
79
87
|
});
|
|
80
88
|
}
|
|
81
89
|
const planned = new Set(p.files.map((f) => f.path));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kiro-report.js","sourceRoot":"","sources":["../../src/importers/kiro-report.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACjG,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAmBjE
|
|
1
|
+
{"version":3,"file":"kiro-report.js","sourceRoot":"","sources":["../../src/importers/kiro-report.ts"],"names":[],"mappings":"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"}
|
package/dist/ui/page.js
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The page of `craftar ui` (spec 36 §4.7, §7.4): one document, one script, one stylesheet, hand-written, as three
|
|
3
|
+
* string constants — `dist/` holds only what `tsc` emits. The server sends the document with a CSP that allows
|
|
4
|
+
* no inline script and no inline style, and the script puts every value it receives into the document as text
|
|
5
|
+
* (`textContent`), never as markup. `test/ui-page.test.ts` reads these sources; what the page does in a browser
|
|
6
|
+
* is a by-hand run.
|
|
7
|
+
*/
|
|
8
|
+
import { diffShows } from "../core/report.js";
|
|
9
|
+
export const PAGE_HTML = `<!doctype html>
|
|
10
|
+
<html lang="en">
|
|
11
|
+
<head>
|
|
12
|
+
<meta charset="utf-8">
|
|
13
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
14
|
+
<title>craftar</title>
|
|
15
|
+
<link rel="stylesheet" href="/app.css">
|
|
16
|
+
</head>
|
|
17
|
+
<body>
|
|
18
|
+
<main id="app"></main>
|
|
19
|
+
<script src="/app.js"></script>
|
|
20
|
+
</body>
|
|
21
|
+
</html>
|
|
22
|
+
`;
|
|
23
|
+
export const PAGE_CSS = `:root { color-scheme: light dark; --fg: #1b1f24; --bg: #ffffff; --dim: #5c6670; --line: #d5dae0; --soft: #f3f5f7; --ok: #1a7f37; --warn: #9a6700; --bad: #b42318; --add: #1a7f37; --del: #b42318; }
|
|
24
|
+
@media (prefers-color-scheme: dark) { :root { --fg: #e6e9ec; --bg: #0f1317; --dim: #9aa5b1; --line: #2b333b; --soft: #171c21; --ok: #4ac26b; --warn: #d4a72c; --bad: #ff7b72; --add: #4ac26b; --del: #ff7b72; } }
|
|
25
|
+
* { box-sizing: border-box; }
|
|
26
|
+
body { margin: 0; font: 14px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif; color: var(--fg); background: var(--bg); }
|
|
27
|
+
main { max-width: 1200px; margin: 0 auto; padding: 16px 20px 48px; }
|
|
28
|
+
h1 { font-size: 18px; margin: 0 0 4px; }
|
|
29
|
+
h2 { font-size: 15px; margin: 24px 0 8px; }
|
|
30
|
+
h3 { font-size: 13px; margin: 16px 0 6px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--dim); }
|
|
31
|
+
p { margin: 4px 0; }
|
|
32
|
+
.dim { color: var(--dim); }
|
|
33
|
+
.mono, code, pre, td.path { font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: 12.5px; }
|
|
34
|
+
.bar { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin: 8px 0 12px; }
|
|
35
|
+
button { font: inherit; color: inherit; background: var(--soft); border: 1px solid var(--line); border-radius: 6px; padding: 3px 10px; cursor: pointer; }
|
|
36
|
+
button:hover { border-color: var(--dim); }
|
|
37
|
+
button.link { background: none; border: 0; padding: 0; color: inherit; text-decoration: underline; font-weight: 600; }
|
|
38
|
+
button.on { border-color: var(--fg); }
|
|
39
|
+
.scroll { overflow-x: auto; }
|
|
40
|
+
table { border-collapse: collapse; width: 100%; }
|
|
41
|
+
th, td { text-align: left; vertical-align: top; padding: 5px 10px 5px 0; border-bottom: 1px solid var(--line); }
|
|
42
|
+
th { font-weight: 600; color: var(--dim); white-space: nowrap; }
|
|
43
|
+
td.path { overflow-wrap: anywhere; }
|
|
44
|
+
.state { font-weight: 600; white-space: nowrap; }
|
|
45
|
+
.s-up-to-date, .s-unchanged { color: var(--ok); }
|
|
46
|
+
.s-outdated, .s-update, .s-new, .s-adopt, .s-orphan, .s-no-lock { color: var(--warn); }
|
|
47
|
+
.s-drift, .s-collision, .s-orphan-drift, .s-error, .s-missing { color: var(--bad); }
|
|
48
|
+
.note { border-left: 3px solid var(--line); padding: 2px 10px; margin: 8px 0; }
|
|
49
|
+
.note.warn { border-color: var(--warn); }
|
|
50
|
+
.note.bad { border-color: var(--bad); }
|
|
51
|
+
.detail { background: var(--soft); border: 1px solid var(--line); border-radius: 6px; padding: 8px 12px; margin: 6px 0 10px; }
|
|
52
|
+
pre { margin: 6px 0 0; overflow-x: auto; white-space: pre; }
|
|
53
|
+
pre .add { color: var(--add); }
|
|
54
|
+
pre .del { color: var(--del); }
|
|
55
|
+
dl { display: grid; grid-template-columns: max-content 1fr; gap: 2px 14px; margin: 0; }
|
|
56
|
+
dt { color: var(--dim); }
|
|
57
|
+
dd { margin: 0; overflow-wrap: anywhere; }
|
|
58
|
+
ul { margin: 4px 0; padding-left: 18px; }
|
|
59
|
+
code.cmd { background: var(--soft); border: 1px solid var(--line); border-radius: 4px; padding: 1px 6px; user-select: all; }
|
|
60
|
+
`;
|
|
61
|
+
/**
|
|
62
|
+
* How the page lists each file state — a `Record` over every `FileState`, so a new state does not compile until it is placed here,
|
|
63
|
+
* as `FILE_STATE_ORDER_MAP` does for the CLI: `order` on the page, `syncActs` (sync writes or removes it without a flag, so the page
|
|
64
|
+
* offers the command), and `diff` (the core's `diffShows`).
|
|
65
|
+
*/
|
|
66
|
+
export const PAGE_STATES = {
|
|
67
|
+
new: { order: 0, syncActs: true, diff: diffShows("new") },
|
|
68
|
+
update: { order: 1, syncActs: true, diff: diffShows("update") },
|
|
69
|
+
adopt: { order: 2, syncActs: true, diff: diffShows("adopt") },
|
|
70
|
+
drift: { order: 3, syncActs: false, diff: diffShows("drift") },
|
|
71
|
+
collision: { order: 4, syncActs: false, diff: diffShows("collision") },
|
|
72
|
+
orphan: { order: 5, syncActs: true, diff: diffShows("orphan") },
|
|
73
|
+
"orphan-drift": { order: 6, syncActs: false, diff: diffShows("orphan-drift") },
|
|
74
|
+
unchanged: { order: 7, syncActs: false, diff: diffShows("unchanged") },
|
|
75
|
+
};
|
|
76
|
+
export const PAGE_JS = String.raw `"use strict";
|
|
77
|
+
(function () {
|
|
78
|
+
var NO_TOKEN = "open the address printed by craftar ui — this tab has no token";
|
|
79
|
+
var KEY = "craftar-ui-token";
|
|
80
|
+
var MAX_DIFF_LINES = 2000;
|
|
81
|
+
var _PS = ${JSON.stringify(PAGE_STATES)};
|
|
82
|
+
var STATES = Object.keys(_PS).sort(function(a, b) { return _PS[a].order - _PS[b].order; });
|
|
83
|
+
/* The states sync acts on without a flag. */
|
|
84
|
+
var SYNC_ACTS = ${JSON.stringify(Object.fromEntries(Object.entries(PAGE_STATES).filter(([, v]) => v.syncActs).map(([k]) => [k, true])))};
|
|
85
|
+
var HAS_DIFF = ${JSON.stringify(Object.fromEntries(Object.entries(PAGE_STATES).map(([k, v]) => [k, v.diff])))};
|
|
86
|
+
var app = document.getElementById("app");
|
|
87
|
+
var table = null;
|
|
88
|
+
|
|
89
|
+
var fromHash = location.hash.slice(1);
|
|
90
|
+
var token = fromHash;
|
|
91
|
+
try {
|
|
92
|
+
if (fromHash) sessionStorage.setItem(KEY, fromHash);
|
|
93
|
+
else token = sessionStorage.getItem(KEY) || "";
|
|
94
|
+
} catch (e) {
|
|
95
|
+
/* storage refused: the token lives for this page load only */
|
|
96
|
+
}
|
|
97
|
+
if (fromHash) history.replaceState(null, "", location.pathname);
|
|
98
|
+
|
|
99
|
+
function el(tag, className, text) {
|
|
100
|
+
var node = document.createElement(tag);
|
|
101
|
+
if (className) node.className = className;
|
|
102
|
+
if (text !== undefined && text !== null) node.textContent = String(text);
|
|
103
|
+
return node;
|
|
104
|
+
}
|
|
105
|
+
function add(parent) {
|
|
106
|
+
for (var i = 1; i < arguments.length; i++) if (arguments[i]) parent.appendChild(arguments[i]);
|
|
107
|
+
return parent;
|
|
108
|
+
}
|
|
109
|
+
function clear(node) {
|
|
110
|
+
while (node.firstChild) node.removeChild(node.firstChild);
|
|
111
|
+
return node;
|
|
112
|
+
}
|
|
113
|
+
function button(label, onClick, className) {
|
|
114
|
+
var b = el("button", className || "", label);
|
|
115
|
+
b.type = "button";
|
|
116
|
+
b.addEventListener("click", onClick);
|
|
117
|
+
return b;
|
|
118
|
+
}
|
|
119
|
+
function list(items, className) {
|
|
120
|
+
var ul = el("ul", className || "");
|
|
121
|
+
items.forEach(function (item) { add(ul, el("li", "", item)); });
|
|
122
|
+
return ul;
|
|
123
|
+
}
|
|
124
|
+
function row(tag, cells) {
|
|
125
|
+
var tr = el("tr");
|
|
126
|
+
cells.forEach(function (cell) {
|
|
127
|
+
var td = el(tag);
|
|
128
|
+
if (cell && cell.nodeType) add(td, cell);
|
|
129
|
+
else td.textContent = cell === null || cell === undefined ? "" : String(cell);
|
|
130
|
+
add(tr, td);
|
|
131
|
+
});
|
|
132
|
+
return tr;
|
|
133
|
+
}
|
|
134
|
+
function grid(head, rows) {
|
|
135
|
+
var t = el("table");
|
|
136
|
+
add(t, add(el("thead"), row("th", head)));
|
|
137
|
+
var body = el("tbody");
|
|
138
|
+
rows.forEach(function (cells) { add(body, row("td", cells)); });
|
|
139
|
+
return add(el("div", "scroll"), add(t, body));
|
|
140
|
+
}
|
|
141
|
+
function note(text, kind) {
|
|
142
|
+
return el("p", "note" + (kind ? " " + kind : ""), text);
|
|
143
|
+
}
|
|
144
|
+
function short(commit) {
|
|
145
|
+
return commit ? commit.slice(0, 7) : "no git";
|
|
146
|
+
}
|
|
147
|
+
/* A path as one word of a command line: as it is when plain; single-quoted for a POSIX path (nothing expands
|
|
148
|
+
inside single quotes); double-quoted, only the quote escaped, otherwise. */
|
|
149
|
+
function shellWord(text) {
|
|
150
|
+
if (text.charAt(0) === "/") return /^[A-Za-z0-9_.,:=@%+\/-]+$/.test(text) ? text : "'" + text.replace(/'/g, "'\\''") + "'";
|
|
151
|
+
if (/^[A-Za-z0-9_.,:=@%+\/\\-]+$/.test(text)) return text;
|
|
152
|
+
return '"' + text.replace(/"/g, '\\"') + '"';
|
|
153
|
+
}
|
|
154
|
+
function forgeLine(forge) {
|
|
155
|
+
return forge.source + (forge.ref ? " (ref " + forge.ref + ")" : "") + " @ " + short(forge.commit);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function api(path) {
|
|
159
|
+
return fetch(path, { headers: { Authorization: "Bearer " + token }, cache: "no-store" }).then(function (res) {
|
|
160
|
+
return res.json().then(
|
|
161
|
+
function (body) { return { status: res.status, body: body }; },
|
|
162
|
+
function () { return { status: res.status, body: null }; }
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/* What the page says for an answer that is not 200. asked: the file's path when the request named one. */
|
|
168
|
+
function problem(into, answer, asked) {
|
|
169
|
+
clear(into);
|
|
170
|
+
if (answer.status === 403) return add(into, note(NO_TOKEN, "bad"));
|
|
171
|
+
if (answer.status === 404) {
|
|
172
|
+
if (asked !== undefined) return add(into, note("craftar does not manage " + asked, "warn"));
|
|
173
|
+
return add(into, note("this workspace is no longer registered", "warn"), button("Refresh", showWorkspaces));
|
|
174
|
+
}
|
|
175
|
+
if (answer.status === 409 && answer.body) {
|
|
176
|
+
add(into, note(String(answer.body.error), "bad"));
|
|
177
|
+
if (answer.body.warnings && answer.body.warnings.length) add(into, list(answer.body.warnings, "dim"));
|
|
178
|
+
return into;
|
|
179
|
+
}
|
|
180
|
+
if (answer.status === 400 && answer.body) return add(into, note(String(answer.body.error), "bad"));
|
|
181
|
+
if (answer.status === 500) return add(into, note("craftar ui failed — see its terminal", "bad"));
|
|
182
|
+
return add(into, note("unexpected answer: " + answer.status, "bad"));
|
|
183
|
+
}
|
|
184
|
+
function unreachable(into) {
|
|
185
|
+
clear(into);
|
|
186
|
+
return add(into, note("craftar ui is not answering — it may have been stopped in its terminal", "bad"));
|
|
187
|
+
}
|
|
188
|
+
function load(path, into, asked, render) {
|
|
189
|
+
add(clear(into), el("p", "dim", "loading…"));
|
|
190
|
+
api(path).then(
|
|
191
|
+
function (answer) {
|
|
192
|
+
if (answer.status !== 200 || answer.body === null) return problem(into, answer, asked);
|
|
193
|
+
clear(into);
|
|
194
|
+
render(answer.body);
|
|
195
|
+
},
|
|
196
|
+
function () { unreachable(into); }
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* View 1: the table of craftar workspaces. */
|
|
201
|
+
function showWorkspaces() {
|
|
202
|
+
clear(app);
|
|
203
|
+
var head = add(el("div"), el("h1", "", "craftar — workspaces"));
|
|
204
|
+
var body = el("div");
|
|
205
|
+
add(app, head, add(el("div", "bar"), button("Refresh", showWorkspaces)), body);
|
|
206
|
+
if (!token) return add(clear(body), note(NO_TOKEN, "bad"));
|
|
207
|
+
load("/api/workspaces", body, undefined, function (doc) {
|
|
208
|
+
table = doc;
|
|
209
|
+
add(head, el("p", "dim", "craftar " + doc.version + " · registry " + doc.registry));
|
|
210
|
+
if (!doc.workspaces.length) {
|
|
211
|
+
add(body, note("no workspace is registered on this machine yet — run craftar sync in one"));
|
|
212
|
+
} else {
|
|
213
|
+
add(body, grid(
|
|
214
|
+
["workspace", "path", "profile", "stack", "targets", "forge", "last sync", "status"],
|
|
215
|
+
doc.workspaces.map(function (w) {
|
|
216
|
+
var stack = Object.keys(w.stack || {}).map(function (k) { return k + " " + w.stack[k]; }).join(", ");
|
|
217
|
+
var state = el("span", "state s-" + w.status, w.status.replace(/-/g, " ") + (w.forgeMoved ? " · forge moved" : ""));
|
|
218
|
+
var path = el("span", "mono", w.path);
|
|
219
|
+
return [button(w.name, function () { showWorkspace(w, "files"); }, "link"), path, w.profile, stack, w.targets.join(", "), forgeLine(w.forge), w.lastSync, state];
|
|
220
|
+
})
|
|
221
|
+
));
|
|
222
|
+
if (doc.workspaces.some(function (w) { return w.forge.kind === "remote"; })) add(body, el("p", "dim", "status read offline from the Forge cache"));
|
|
223
|
+
}
|
|
224
|
+
if (doc.warnings.length) add(body, el("h3", "", "warnings"), list(doc.warnings));
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function wordLayer(layer, profile) {
|
|
229
|
+
if (layer === "profile") return "profile " + profile;
|
|
230
|
+
if (typeof layer === "string") return layer;
|
|
231
|
+
if (layer && layer.recipe !== undefined) return "recipe " + layer.recipe;
|
|
232
|
+
if (layer && layer.profile !== undefined) return "profile " + layer.profile;
|
|
233
|
+
return "";
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function frame(w, tab) {
|
|
237
|
+
clear(app);
|
|
238
|
+
var body = el("div");
|
|
239
|
+
add(
|
|
240
|
+
app,
|
|
241
|
+
add(el("div", "bar"), button("← Workspaces", showWorkspaces)),
|
|
242
|
+
el("h1", "", w.name),
|
|
243
|
+
el("p", "mono dim", w.path),
|
|
244
|
+
add(
|
|
245
|
+
el("div", "bar"),
|
|
246
|
+
button("Files", function () { showWorkspace(w, "files"); }, tab === "files" ? "on" : ""),
|
|
247
|
+
button("Its Forge", function () { showWorkspace(w, "forge"); }, tab === "forge" ? "on" : ""),
|
|
248
|
+
button("Refresh", function () { showWorkspace(w, tab); })
|
|
249
|
+
),
|
|
250
|
+
body
|
|
251
|
+
);
|
|
252
|
+
return body;
|
|
253
|
+
}
|
|
254
|
+
function cachedLine(into, kind) {
|
|
255
|
+
if (kind === "remote") add(into, el("p", "dim", "read from the cached copy of the Forge — not fetched"));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function showWorkspace(w, tab) {
|
|
259
|
+
var body = frame(w, tab);
|
|
260
|
+
if (!token) return add(body, note(NO_TOKEN, "bad"));
|
|
261
|
+
if (tab === "forge") return showForge(w, body);
|
|
262
|
+
load("/api/workspaces/" + w.id, body, undefined, function (doc) {
|
|
263
|
+
cachedLine(body, doc.forge.kind);
|
|
264
|
+
var facts = el("dl");
|
|
265
|
+
[
|
|
266
|
+
["profile", doc.profile],
|
|
267
|
+
["recipes", doc.recipes.join(", ")],
|
|
268
|
+
["targets", doc.targets.join(", ")],
|
|
269
|
+
["forge", forgeLine(doc.forge)],
|
|
270
|
+
["lock", doc.forge.lockCommit ? "generated from " + short(doc.forge.lockCommit) : "records no commit"]
|
|
271
|
+
].forEach(function (pair) { add(facts, el("dt", "", pair[0]), el("dd", "", pair[1])); });
|
|
272
|
+
add(body, facts);
|
|
273
|
+
if (doc.unsetParams.length) {
|
|
274
|
+
add(body, note(doc.refused, "bad"));
|
|
275
|
+
add(body, list(doc.unsetParams.map(function (u) {
|
|
276
|
+
return u.key + " — declared by " + u.declaredBy.join(", ") + "; cited by " + u.citedBy.join(", ");
|
|
277
|
+
})));
|
|
278
|
+
}
|
|
279
|
+
if (doc.warnings.length) add(body, el("h3", "", "warnings"), list(doc.warnings));
|
|
280
|
+
if (!doc.statuses.length) add(body, note("the Forge produces no file for this workspace"));
|
|
281
|
+
STATES.concat(doc.statuses.map(function (s) { return s.state; }).filter(function (s, i, all) { return STATES.indexOf(s) === -1 && all.indexOf(s) === i; })).forEach(function (state) {
|
|
282
|
+
var files = doc.statuses.filter(function (s) { return s.state === state; });
|
|
283
|
+
if (!files.length) return;
|
|
284
|
+
var title = el("h2", "", "");
|
|
285
|
+
add(title, el("span", "state s-" + state, state), document.createTextNode(" · " + files.length));
|
|
286
|
+
add(body, title);
|
|
287
|
+
if (SYNC_ACTS[state] && !doc.unsetParams.length) {
|
|
288
|
+
add(body, add(el("p", "dim", "to apply, run in a terminal: "), el("code", "cmd", "craftar sync --workspace " + shellWord(doc.path))));
|
|
289
|
+
}
|
|
290
|
+
files.forEach(function (f) { add(body, fileRow(w, doc, f)); });
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function fileRow(w, doc, f) {
|
|
296
|
+
var box = el("div");
|
|
297
|
+
var detail = el("div", "detail");
|
|
298
|
+
detail.hidden = true;
|
|
299
|
+
var bar = add(el("div", "bar"), el("span", "mono", f.path), el("span", "dim", [f.target, f.ingredient].filter(Boolean).join(" · ")));
|
|
300
|
+
var query = "?path=" + encodeURIComponent(f.path);
|
|
301
|
+
if (HAS_DIFF[f.state] !== false) {
|
|
302
|
+
add(bar, button("diff", function () {
|
|
303
|
+
detail.hidden = false;
|
|
304
|
+
load("/api/workspaces/" + w.id + "/diff" + query, detail, f.path, function (d) { showDiff(detail, d); });
|
|
305
|
+
}));
|
|
306
|
+
}
|
|
307
|
+
if (f.state !== "orphan" && f.state !== "orphan-drift") {
|
|
308
|
+
add(bar, button("explain", function () {
|
|
309
|
+
detail.hidden = false;
|
|
310
|
+
load("/api/workspaces/" + w.id + "/explain" + query, detail, f.path, function (x) { showExplain(detail, x); });
|
|
311
|
+
}));
|
|
312
|
+
}
|
|
313
|
+
add(bar, button("close", function () { detail.hidden = true; clear(detail); }));
|
|
314
|
+
return add(box, bar, detail);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function showDiff(into, d) {
|
|
318
|
+
if (d.from) add(into, el("p", "mono dim", "--- " + d.from));
|
|
319
|
+
if (d.to) add(into, el("p", "mono dim", "+++ " + d.to));
|
|
320
|
+
if (d.note) add(into, note(d.note, d.hidden ? "warn" : ""));
|
|
321
|
+
if (d.diff === null) {
|
|
322
|
+
if (!d.note) add(into, el("p", "dim", "nothing to show: sync leaves this file as it is"));
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
var lines = d.diff.split("\n");
|
|
326
|
+
if (lines.length && lines[lines.length - 1] === "") lines.pop();
|
|
327
|
+
var pre = el("pre");
|
|
328
|
+
lines.slice(0, MAX_DIFF_LINES).forEach(function (line) {
|
|
329
|
+
var kind = line.charAt(0) === "+" ? "add" : line.charAt(0) === "-" ? "del" : "";
|
|
330
|
+
add(pre, el("span", kind, line), document.createTextNode("\n"));
|
|
331
|
+
});
|
|
332
|
+
add(into, pre);
|
|
333
|
+
if (lines.length > MAX_DIFF_LINES) add(into, el("p", "dim", "… " + (lines.length - MAX_DIFF_LINES) + " more lines"));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function showExplain(into, x) {
|
|
337
|
+
var dl = el("dl");
|
|
338
|
+
var pairs = [
|
|
339
|
+
["ingredient", x.ingredient],
|
|
340
|
+
["target", x.target],
|
|
341
|
+
["brought by", x.via === null ? "no single recipe" : x.via.join(", ")],
|
|
342
|
+
["imported from", x.origin ? x.origin.workspace + " · " + x.origin.path : "not imported"],
|
|
343
|
+
["profile", x.profile],
|
|
344
|
+
["recipes", x.recipes.join(", ")],
|
|
345
|
+
["hash", x.hash]
|
|
346
|
+
];
|
|
347
|
+
x.sections.forEach(function (s) { pairs.push(["section " + s.name, wordLayer(s.layer, x.profile)]); });
|
|
348
|
+
x.params.forEach(function (p) { pairs.push(["param " + p.key, wordLayer(p.layer, x.profile)]); });
|
|
349
|
+
pairs.forEach(function (pair) { add(dl, el("dt", "", pair[0]), el("dd", "mono", pair[1])); });
|
|
350
|
+
add(into, dl);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/* View 3: the Forge of one workspace — its recipes, then its ingredients. */
|
|
354
|
+
function showForge(w, body) {
|
|
355
|
+
cachedLine(body, w.forge.kind);
|
|
356
|
+
var recipes = el("div");
|
|
357
|
+
var ingredients = el("div");
|
|
358
|
+
add(body, el("h2", "", "Recipes"), recipes, el("h2", "", "Ingredients"), ingredients);
|
|
359
|
+
load("/api/workspaces/" + w.id + "/recipes", recipes, undefined, function (doc) {
|
|
360
|
+
add(recipes, el("p", "dim", "forge " + doc.forge.name + " @ " + short(doc.forge.commit)));
|
|
361
|
+
add(recipes, grid(
|
|
362
|
+
["recipe", "slot", "parents", "profiles", "registered workspaces", "here"],
|
|
363
|
+
doc.recipes.map(function (r) {
|
|
364
|
+
var users = !table || w.forge.key === null ? [] : table.workspaces.filter(function (o) {
|
|
365
|
+
return o.forge.key !== null && o.forge.key === w.forge.key && o.recipes.indexOf(r.name) !== -1;
|
|
366
|
+
}).map(function (o) { return o.name; });
|
|
367
|
+
var here = r.removedByWorkspace ? "removed by this workspace" : r.inUse ? "in use (" + r.inUse.by.join(", ") + ")" : "";
|
|
368
|
+
return [r.name, r.slot, r.extends.join(", "), r.profiles.join(", "), users.join(", "), here];
|
|
369
|
+
})
|
|
370
|
+
));
|
|
371
|
+
if (doc.warnings.length) add(recipes, el("h3", "", "warnings"), list(doc.warnings));
|
|
372
|
+
var said = doc.warnings;
|
|
373
|
+
load("/api/workspaces/" + w.id + "/ingredients", ingredients, undefined, function (ing) {
|
|
374
|
+
add(ingredients, grid(
|
|
375
|
+
["ingredient", "type", "written as", "recipes", "targets", "here"],
|
|
376
|
+
ing.ingredients.map(function (i) {
|
|
377
|
+
var here = i.disabled ? "disabled by this workspace" : i.inUse ? "in use" : "";
|
|
378
|
+
return [i.ref, i.type, i.outputName, i.recipes.join(", "), i.targets === "*" ? "every target" : i.targets.join(", "), here];
|
|
379
|
+
})
|
|
380
|
+
));
|
|
381
|
+
var more = ing.warnings.filter(function (w) { return said.indexOf(w) === -1; });
|
|
382
|
+
if (more.length) add(ingredients, el("h3", "", "warnings"), list(more));
|
|
383
|
+
if (ing.missing.length) {
|
|
384
|
+
add(ingredients, el("h3", "", "named by a recipe, not in the Forge"), list(ing.missing.map(function (m) { return m.ref + " — " + m.recipes.join(", "); })));
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
showWorkspaces();
|
|
391
|
+
})();
|
|
392
|
+
`;
|
|
393
|
+
//# sourceMappingURL=page.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page.js","sourceRoot":"","sources":["../../src/ui/page.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAE9C,MAAM,CAAC,MAAM,SAAS,GAAG;;;;;;;;;;;;;CAaxB,CAAC;AAEF,MAAM,CAAC,MAAM,QAAQ,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqCvB,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAA2E;IACjG,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,KAAK,CAAC,EAAE;IACzD,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,QAAQ,CAAC,EAAE;IAC/D,KAAK,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,OAAO,CAAC,EAAE;IAC7D,KAAK,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,CAAC,OAAO,CAAC,EAAE;IAC9D,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,CAAC,WAAW,CAAC,EAAE;IACtE,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,QAAQ,CAAC,EAAE;IAC/D,cAAc,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,CAAC,cAAc,CAAC,EAAE;IAC9E,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,CAAC,WAAW,CAAC,EAAE;CACvE,CAAC;AAEF,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;cAKnB,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;;;oBAGrB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;mBACtH,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmT9G,CAAC"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { catalogueContext, listIngredients, listRecipes, withLoadWarnings } from "../core/catalogue.js";
|
|
2
|
+
import { renderDiff } from "../core/diff.js";
|
|
3
|
+
import { oneLine } from "../core/doctor.js";
|
|
4
|
+
import { isMissing, listWorkspaces, readRegistry } from "../core/registry.js";
|
|
5
|
+
import { credentialFault, CREDENTIAL_HIDDEN, refHoldsCredentials } from "../core/remote.js";
|
|
6
|
+
import { EXAMPLE_CONTENT_HIDDEN, diffReport, explainFile, statusReport } from "../core/report.js";
|
|
7
|
+
import { loadWorkspace, plan, readLock, status, unsetSummary } from "../core/sync.js";
|
|
8
|
+
import { hashNormalized } from "../core/text.js";
|
|
9
|
+
export const NOT_FOUND = { error: "not found" };
|
|
10
|
+
export const PATH_REQUIRED = { error: "path is required, once" };
|
|
11
|
+
export const MISSING = "the directory or its craftar.yaml is gone — craftar workspaces prune";
|
|
12
|
+
/** A workspace's id in a URL: 16 hex characters of the hash of its registered path — the path itself never travels. */
|
|
13
|
+
export const workspaceId = (registeredPath) => hashNormalized(registeredPath).slice("sha256:".length, "sha256:".length + 16);
|
|
14
|
+
/** Why a workspace cannot be shown, in one line — the wording `doctor` uses, so the table and a 409 agree. */
|
|
15
|
+
export function routeReason(e) {
|
|
16
|
+
return e instanceof Error ? oneLine(e) : String(e).split("\n")[0];
|
|
17
|
+
}
|
|
18
|
+
/** The route a path names, as its pattern — what the one stderr line of a 500 prints, never the request's text. */
|
|
19
|
+
export function routePattern(path) {
|
|
20
|
+
if (path === "/api/workspaces")
|
|
21
|
+
return path;
|
|
22
|
+
const m = /^\/api\/workspaces\/[0-9a-f]{16}(\/(diff|explain|recipes|ingredients))?$/.exec(path);
|
|
23
|
+
return m ? `/api/workspaces/<id>${m[1] ?? ""}` : null;
|
|
24
|
+
}
|
|
25
|
+
/** The query's `path`, given exactly once and decoded once (`+` is not a space) — or null. */
|
|
26
|
+
function onePath(query) {
|
|
27
|
+
const given = query.split("&").filter((kv) => kv === "path" || kv.startsWith("path="));
|
|
28
|
+
if (given.length !== 1)
|
|
29
|
+
return null;
|
|
30
|
+
try {
|
|
31
|
+
return decodeURIComponent(given[0].slice("path=".length));
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A row of the table as the page is given it: its id, and — whatever the core answered — never a Forge URL that
|
|
39
|
+
* `credentialFault` flags. Since 0.28.1 the core gives the label itself for a `missing` or an `error` row; this
|
|
40
|
+
* is the page's own check, a second line: the label is the core's (`CREDENTIAL_HIDDEN`), and the key goes with it.
|
|
41
|
+
* A recorded `ref` that `refHoldsCredentials` flags is given the same label (`CREDENTIAL_HIDDEN`).
|
|
42
|
+
*/
|
|
43
|
+
export function servedRow(r) {
|
|
44
|
+
const row = { id: workspaceId(r.path), ...r };
|
|
45
|
+
if (credentialFault(r.forge.source))
|
|
46
|
+
row.forge = { ...r.forge, source: CREDENTIAL_HIDDEN, key: null };
|
|
47
|
+
if (r.forge.ref !== null && refHoldsCredentials(r.forge.ref))
|
|
48
|
+
row.forge = { ...row.forge, ref: CREDENTIAL_HIDDEN };
|
|
49
|
+
return row;
|
|
50
|
+
}
|
|
51
|
+
export async function route(req, deps) {
|
|
52
|
+
const pattern = routePattern(req.path);
|
|
53
|
+
if (pattern === null)
|
|
54
|
+
return { status: 404, body: NOT_FOUND };
|
|
55
|
+
if (pattern === "/api/workspaces") {
|
|
56
|
+
const t = await listWorkspaces(deps.home, { fetch: false, reason: routeReason });
|
|
57
|
+
const rows = t.rows.map(servedRow);
|
|
58
|
+
return {
|
|
59
|
+
status: 200,
|
|
60
|
+
body: { version: deps.version, registry: t.registry, fetch: false, workspaces: rows, warnings: t.warnings },
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const [, , , id, what] = req.path.split("/");
|
|
64
|
+
// Decided from the request alone, before the registry is read.
|
|
65
|
+
let wanted = "";
|
|
66
|
+
if (what === "diff" || what === "explain") {
|
|
67
|
+
const given = onePath(req.query);
|
|
68
|
+
if (given === null)
|
|
69
|
+
return { status: 400, body: PATH_REQUIRED };
|
|
70
|
+
wanted = given;
|
|
71
|
+
}
|
|
72
|
+
// A registry that does not read is not this workspace's fault: it propagates, and the server answers 500.
|
|
73
|
+
const entry = (await readRegistry(deps.home)).workspaces.find((e) => workspaceId(e.path) === id);
|
|
74
|
+
if (!entry)
|
|
75
|
+
return { status: 404, body: NOT_FOUND };
|
|
76
|
+
if (await isMissing(entry))
|
|
77
|
+
return { status: 409, body: { error: MISSING, warnings: [] } };
|
|
78
|
+
// What cannot be loaded, planned or read is this workspace's state: 409, in one line. A failure past that point is ours: 500.
|
|
79
|
+
let ws;
|
|
80
|
+
let p;
|
|
81
|
+
let lock;
|
|
82
|
+
let st;
|
|
83
|
+
try {
|
|
84
|
+
ws = await loadWorkspace(entry.path, { mode: "no-fetch", home: deps.home });
|
|
85
|
+
if (what === "recipes" || what === "ingredients") {
|
|
86
|
+
// The catalogue resolves and never plans; it does not read the lock either.
|
|
87
|
+
const context = catalogueContext(ws.forge, { kind: "workspace", config: ws.config });
|
|
88
|
+
const listing = what === "recipes" ? listRecipes(ws.forge, context) : listIngredients(ws.forge, context);
|
|
89
|
+
return { status: 200, body: withLoadWarnings(listing, ws.warnings) };
|
|
90
|
+
}
|
|
91
|
+
p = await plan(ws);
|
|
92
|
+
lock = await readLock(ws.root);
|
|
93
|
+
st = await status(ws, p, lock);
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
return { status: 409, body: { error: routeReason(e), warnings: ws?.warnings ?? [] } };
|
|
97
|
+
}
|
|
98
|
+
if (what === undefined) {
|
|
99
|
+
const report = statusReport(ws, p, lock, st);
|
|
100
|
+
return {
|
|
101
|
+
status: 200,
|
|
102
|
+
body: { id, path: entry.path, profile: ws.config.profile, recipes: p.resolution.recipes, targets: p.resolution.targets, ...report, refused: report.unsetParams.length ? unsetSummary(report.unsetParams) : null },
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (what === "explain") {
|
|
106
|
+
// Never `notProduced`'s message: it quotes the path asked.
|
|
107
|
+
const explained = explainFile(ws, p, wanted);
|
|
108
|
+
return explained ? { status: 200, body: explained } : { status: 404, body: NOT_FOUND };
|
|
109
|
+
}
|
|
110
|
+
// The row whose path is the string asked, character for character: nothing is opened that status() did not name.
|
|
111
|
+
const row = st.find((s) => s.path === wanted);
|
|
112
|
+
if (!row)
|
|
113
|
+
return { status: 404, body: NOT_FOUND };
|
|
114
|
+
const [d] = await diffReport(ws, [row], { withholdMcp: false });
|
|
115
|
+
if (!d)
|
|
116
|
+
return { status: 200, body: { path: row.path, state: row.state, from: null, to: null, hidden: false, note: null, diff: null } };
|
|
117
|
+
const from = `${row.path} (disk, ${row.state})`;
|
|
118
|
+
const to = row.state === "orphan-drift" ? null : row.state === "orphan" ? `${row.path} (forge: no longer produced — sync removes it)` : `${row.path} (forge)`;
|
|
119
|
+
return {
|
|
120
|
+
status: 200,
|
|
121
|
+
body: {
|
|
122
|
+
path: row.path,
|
|
123
|
+
state: row.state,
|
|
124
|
+
from,
|
|
125
|
+
to,
|
|
126
|
+
hidden: d.note === EXAMPLE_CONTENT_HIDDEN,
|
|
127
|
+
note: d.note,
|
|
128
|
+
diff: d.note === null ? renderDiff(d.disk ?? "", d.forge ?? "") : null,
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/ui/routes.ts"],"names":[],"mappings":"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"}
|