ghostrail 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands.d.ts +32 -1
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +409 -5
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +11 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/help.d.ts.map +1 -1
- package/dist/commands/help.js +49 -0
- package/dist/commands/help.js.map +1 -1
- package/dist/dashboard/index.d.ts +4 -0
- package/dist/dashboard/index.d.ts.map +1 -0
- package/dist/dashboard/index.js +4 -0
- package/dist/dashboard/index.js.map +1 -0
- package/dist/dashboard/page.d.ts +10 -0
- package/dist/dashboard/page.d.ts.map +1 -0
- package/dist/dashboard/page.js +94 -0
- package/dist/dashboard/page.js.map +1 -0
- package/dist/dashboard/routes.d.ts +48 -0
- package/dist/dashboard/routes.d.ts.map +1 -0
- package/dist/dashboard/routes.js +102 -0
- package/dist/dashboard/routes.js.map +1 -0
- package/dist/dashboard/server.d.ts +43 -0
- package/dist/dashboard/server.d.ts.map +1 -0
- package/dist/dashboard/server.js +110 -0
- package/dist/dashboard/server.js.map +1 -0
- package/dist/global/index.d.ts +1 -0
- package/dist/global/index.d.ts.map +1 -1
- package/dist/global/index.js +1 -0
- package/dist/global/index.js.map +1 -1
- package/dist/global/registry.d.ts +63 -0
- package/dist/global/registry.d.ts.map +1 -0
- package/dist/global/registry.js +120 -0
- package/dist/global/registry.js.map +1 -0
- package/dist/global/store.d.ts +10 -0
- package/dist/global/store.d.ts.map +1 -1
- package/dist/global/store.js +25 -1
- package/dist/global/store.js.map +1 -1
- package/dist/service/index.d.ts +3 -0
- package/dist/service/index.d.ts.map +1 -0
- package/dist/service/index.js +3 -0
- package/dist/service/index.js.map +1 -0
- package/dist/service/manager.d.ts +71 -0
- package/dist/service/manager.d.ts.map +1 -0
- package/dist/service/manager.js +125 -0
- package/dist/service/manager.js.map +1 -0
- package/dist/service/unit.d.ts +71 -0
- package/dist/service/unit.d.ts.map +1 -0
- package/dist/service/unit.js +138 -0
- package/dist/service/unit.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/** Map a request pathname to a route. Total; never throws. */
|
|
2
|
+
export function parseDashboardRoute(pathname) {
|
|
3
|
+
const parts = pathname.split("/").filter((p) => p.length > 0);
|
|
4
|
+
// Nothing but slashes is the root. `//` reaching a server is unusual but a
|
|
5
|
+
// proxy or a hand-built link produces it, and serving the page beats a 404
|
|
6
|
+
// for something that means the root by every reading.
|
|
7
|
+
if (parts.length === 0)
|
|
8
|
+
return { kind: "page" };
|
|
9
|
+
if (parts[0] !== "api" || parts.length !== 2)
|
|
10
|
+
return { kind: "notfound" };
|
|
11
|
+
if (parts[1] === "ghostrails")
|
|
12
|
+
return { kind: "ghostrails" };
|
|
13
|
+
if (parts[1] === "watch")
|
|
14
|
+
return { kind: "watch" };
|
|
15
|
+
return { kind: "notfound" };
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Every value for a header, case-insensitively.
|
|
19
|
+
*
|
|
20
|
+
* All of them, not the first. Node hands back an array for a repeated header,
|
|
21
|
+
* and a guard that inspects only `value[0]` can be walked past by sending the
|
|
22
|
+
* innocent spelling first. For a check whose whole job is refusing a request,
|
|
23
|
+
* "any value looks like an attack" is the only safe reading.
|
|
24
|
+
*
|
|
25
|
+
* Case-insensitive on the name because Node lowercases them but a hand-built
|
|
26
|
+
* object does not have to.
|
|
27
|
+
*/
|
|
28
|
+
function headerValues(headers, name) {
|
|
29
|
+
const wanted = name.toLowerCase();
|
|
30
|
+
const values = [];
|
|
31
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
32
|
+
if (key.toLowerCase() !== wanted || value === undefined)
|
|
33
|
+
continue;
|
|
34
|
+
if (Array.isArray(value))
|
|
35
|
+
values.push(...value);
|
|
36
|
+
else
|
|
37
|
+
values.push(String(value));
|
|
38
|
+
}
|
|
39
|
+
return values;
|
|
40
|
+
}
|
|
41
|
+
/** The three content types a form can send without a CORS preflight. */
|
|
42
|
+
const FORM_TYPES = ["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"];
|
|
43
|
+
/**
|
|
44
|
+
* True when a request looks cross-origin, which the write route refuses.
|
|
45
|
+
*
|
|
46
|
+
* This is not the token decision (TJ-1554 owns that). It is hygiene for a
|
|
47
|
+
* mutating route: a plain HTML form on any website can POST to a loopback port
|
|
48
|
+
* cross-origin, and by inherited board behaviour there is no token in the way
|
|
49
|
+
* unless the bind is non-loopback. Without this, any page the operator visits
|
|
50
|
+
* could flip their watch flags.
|
|
51
|
+
*
|
|
52
|
+
* A request with neither signal, such as `curl` sending JSON, is allowed.
|
|
53
|
+
* Locking that out is a token's job: anything able to set arbitrary headers can
|
|
54
|
+
* also read a token, so refusing it would break scripting for no gain.
|
|
55
|
+
*/
|
|
56
|
+
export function isCrossOriginWrite(headers) {
|
|
57
|
+
// Browsers send this unconditionally and script cannot forge it. Any value
|
|
58
|
+
// that is not same-origin rejects, so a repeated header cannot smuggle one
|
|
59
|
+
// past by leading with the innocent spelling.
|
|
60
|
+
const sites = headerValues(headers, "sec-fetch-site");
|
|
61
|
+
if (sites.some((site) => site.trim().toLowerCase() !== "same-origin"))
|
|
62
|
+
return true;
|
|
63
|
+
// Requiring a non-form content type forces a preflight a form cannot satisfy.
|
|
64
|
+
return headerValues(headers, "content-type").some((value) => {
|
|
65
|
+
const media = value.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
66
|
+
return FORM_TYPES.includes(media);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/** True if `value` is a plain object (not null, not an array). */
|
|
70
|
+
function isRecord(value) {
|
|
71
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Parse the write body: `{ path, watch }`. Undefined for anything else, which
|
|
75
|
+
* the route answers with 400.
|
|
76
|
+
*
|
|
77
|
+
* An empty `path` is rejected rather than passed through: it can never match a
|
|
78
|
+
* registered ghostrail, so accepting it would only produce a confusing 404.
|
|
79
|
+
*/
|
|
80
|
+
/**
|
|
81
|
+
* Unknown keys alongside the two it needs are ignored, and the entry still
|
|
82
|
+
* parses. Same forward compatibility the registry uses: a later dashboard that
|
|
83
|
+
* sends an extra field must not make an older server reject the write outright.
|
|
84
|
+
*/
|
|
85
|
+
export function parseWatchUpdate(body) {
|
|
86
|
+
let value;
|
|
87
|
+
try {
|
|
88
|
+
value = JSON.parse(body);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
if (!isRecord(value))
|
|
94
|
+
return undefined;
|
|
95
|
+
const { path, watch } = value;
|
|
96
|
+
if (typeof path !== "string" || path.length === 0)
|
|
97
|
+
return undefined;
|
|
98
|
+
if (typeof watch !== "boolean")
|
|
99
|
+
return undefined;
|
|
100
|
+
return { path, watch };
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/dashboard/routes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type Server } from "node:http";
|
|
2
|
+
import type { Ghostrail } from "../global/registry.js";
|
|
3
|
+
/** How a ghostrail stands on disk, mirroring what `status` reports. */
|
|
4
|
+
export type GhostrailHealth = "ok" | "no config" | "missing";
|
|
5
|
+
/** One row of the dashboard: registry entry plus everything observed about it. */
|
|
6
|
+
export interface GhostrailView extends Ghostrail {
|
|
7
|
+
health: GhostrailHealth;
|
|
8
|
+
/** Live unit state, e.g. `active`. `unknown` when no manager could be asked. */
|
|
9
|
+
state: string;
|
|
10
|
+
/** Most recent runs from this ghostrail's own `.factory`, newest first. */
|
|
11
|
+
runs: {
|
|
12
|
+
runId: string;
|
|
13
|
+
itemId?: string;
|
|
14
|
+
state?: string;
|
|
15
|
+
title?: string;
|
|
16
|
+
}[];
|
|
17
|
+
}
|
|
18
|
+
/** Everything the server needs, injected so it can be driven without a machine. */
|
|
19
|
+
export interface DashboardDeps {
|
|
20
|
+
/** The registry, or undefined when it could not be parsed. */
|
|
21
|
+
readRegistry(): Promise<Ghostrail[] | undefined>;
|
|
22
|
+
writeRegistry(entries: readonly Ghostrail[]): Promise<void>;
|
|
23
|
+
health(path: string): Promise<GhostrailHealth>;
|
|
24
|
+
/** Live unit state per ghostrail path. */
|
|
25
|
+
states(entries: readonly Ghostrail[]): Promise<Map<string, string>>;
|
|
26
|
+
}
|
|
27
|
+
export interface DashboardOptions {
|
|
28
|
+
bind?: string;
|
|
29
|
+
/** When set, every request must present it. Inherited from the board's behaviour. */
|
|
30
|
+
token?: string;
|
|
31
|
+
}
|
|
32
|
+
/** Assemble the view for every registered ghostrail. */
|
|
33
|
+
export declare function collectViews(deps: DashboardDeps, entries: readonly Ghostrail[]): Promise<GhostrailView[]>;
|
|
34
|
+
/**
|
|
35
|
+
* The dashboard's HTTP server: one page, one listing, and one write route that
|
|
36
|
+
* sets a ghostrail's `watch` flag and does nothing else.
|
|
37
|
+
*
|
|
38
|
+
* It never starts or stops anything. `start` and the service units act on the
|
|
39
|
+
* flag, which keeps process control off an HTTP surface: the worst a
|
|
40
|
+
* compromised dashboard can do is change which ghostrails come up next time.
|
|
41
|
+
*/
|
|
42
|
+
export declare function createDashboardServer(deps: DashboardDeps, options?: DashboardOptions): Server;
|
|
43
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/dashboard/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAqC,MAAM,WAAW,CAAC;AAE3E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAKvD,uEAAuE;AACvE,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,WAAW,GAAG,SAAS,CAAC;AAE7D,kFAAkF;AAClF,MAAM,WAAW,aAAc,SAAQ,SAAS;IAC9C,MAAM,EAAE,eAAe,CAAC;IACxB,gFAAgF;IAChF,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC5E;AAED,mFAAmF;AACnF,MAAM,WAAW,aAAa;IAC5B,8DAA8D;IAC9D,YAAY,IAAI,OAAO,CAAC,SAAS,EAAE,GAAG,SAAS,CAAC,CAAC;IACjD,aAAa,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAC/C,0CAA0C;IAC1C,MAAM,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACrE;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qFAAqF;IACrF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA0BD,wDAAwD;AACxD,wBAAsB,YAAY,CAChC,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,SAAS,SAAS,EAAE,GAC5B,OAAO,CAAC,aAAa,EAAE,CAAC,CAwB1B;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAsDjG"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { FileStore } from "../store/file-store.js";
|
|
4
|
+
import { DASHBOARD_PAGE_HTML } from "./page.js";
|
|
5
|
+
import { isCrossOriginWrite, parseDashboardRoute, parseWatchUpdate } from "./routes.js";
|
|
6
|
+
/** The store dir inside a ghostrail. */
|
|
7
|
+
const STORE_DIR = ".factory";
|
|
8
|
+
/** How many runs to show per ghostrail. Enough to see recent activity, not a log. */
|
|
9
|
+
const RUNS_PER_GHOSTRAIL = 5;
|
|
10
|
+
function sendJson(res, status, data) {
|
|
11
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
12
|
+
res.end(JSON.stringify(data));
|
|
13
|
+
}
|
|
14
|
+
/** Read a request body, bounded so a stray large POST cannot exhaust memory. */
|
|
15
|
+
async function readBody(req, limit = 64 * 1024) {
|
|
16
|
+
const chunks = [];
|
|
17
|
+
let size = 0;
|
|
18
|
+
for await (const chunk of req) {
|
|
19
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
20
|
+
size += buf.length;
|
|
21
|
+
if (size > limit)
|
|
22
|
+
break;
|
|
23
|
+
chunks.push(buf);
|
|
24
|
+
}
|
|
25
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
26
|
+
}
|
|
27
|
+
/** Assemble the view for every registered ghostrail. */
|
|
28
|
+
export async function collectViews(deps, entries) {
|
|
29
|
+
const states = await deps.states(entries);
|
|
30
|
+
const views = [];
|
|
31
|
+
for (const entry of entries) {
|
|
32
|
+
const health = await deps.health(entry.path);
|
|
33
|
+
// A ghostrail with no .factory yet is still a ghostrail; it just has
|
|
34
|
+
// nothing to report. Never let a missing store turn a row into an error.
|
|
35
|
+
let runs = [];
|
|
36
|
+
if (health === "ok") {
|
|
37
|
+
try {
|
|
38
|
+
const rows = await new FileStore(join(entry.path, STORE_DIR)).listRuns({});
|
|
39
|
+
runs = rows.slice(0, RUNS_PER_GHOSTRAIL).map((row) => ({
|
|
40
|
+
runId: row.runId,
|
|
41
|
+
...(row.itemId === undefined ? {} : { itemId: row.itemId }),
|
|
42
|
+
...(row.state === undefined ? {} : { state: row.state }),
|
|
43
|
+
...(row.title === undefined ? {} : { title: row.title }),
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
runs = [];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
views.push({ ...entry, health, state: states.get(entry.path) ?? "unknown", runs });
|
|
51
|
+
}
|
|
52
|
+
return views;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The dashboard's HTTP server: one page, one listing, and one write route that
|
|
56
|
+
* sets a ghostrail's `watch` flag and does nothing else.
|
|
57
|
+
*
|
|
58
|
+
* It never starts or stops anything. `start` and the service units act on the
|
|
59
|
+
* flag, which keeps process control off an HTTP surface: the worst a
|
|
60
|
+
* compromised dashboard can do is change which ghostrails come up next time.
|
|
61
|
+
*/
|
|
62
|
+
export function createDashboardServer(deps, options = {}) {
|
|
63
|
+
return createServer((req, res) => {
|
|
64
|
+
void (async () => {
|
|
65
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
66
|
+
if (options.token !== undefined) {
|
|
67
|
+
const presented = req.headers["x-ghostrail-token"] ?? url.searchParams.get("token") ?? "";
|
|
68
|
+
if (presented !== options.token)
|
|
69
|
+
return sendJson(res, 401, { error: "unauthorized" });
|
|
70
|
+
}
|
|
71
|
+
const route = parseDashboardRoute(url.pathname);
|
|
72
|
+
if (route.kind === "page") {
|
|
73
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
74
|
+
return res.end(DASHBOARD_PAGE_HTML);
|
|
75
|
+
}
|
|
76
|
+
const entries = await deps.readRegistry();
|
|
77
|
+
if (entries === undefined) {
|
|
78
|
+
return sendJson(res, 500, { error: "the ghostrails registry could not be parsed" });
|
|
79
|
+
}
|
|
80
|
+
if (route.kind === "ghostrails") {
|
|
81
|
+
return sendJson(res, 200, { ghostrails: await collectViews(deps, entries) });
|
|
82
|
+
}
|
|
83
|
+
if (route.kind === "watch") {
|
|
84
|
+
if (req.method !== "POST")
|
|
85
|
+
return sendJson(res, 405, { error: "use POST" });
|
|
86
|
+
// Hygiene for a mutating route, not the token decision (TJ-1554). A
|
|
87
|
+
// form on any site can POST here cross-origin, and by inherited board
|
|
88
|
+
// behaviour there is no token in the way on loopback.
|
|
89
|
+
if (isCrossOriginWrite(req.headers)) {
|
|
90
|
+
return sendJson(res, 403, { error: "cross-origin writes are refused" });
|
|
91
|
+
}
|
|
92
|
+
const update = parseWatchUpdate(await readBody(req));
|
|
93
|
+
if (update === undefined) {
|
|
94
|
+
return sendJson(res, 400, { error: "expected {path: string, watch: boolean}" });
|
|
95
|
+
}
|
|
96
|
+
// Must already be registered. Registering an arbitrary filesystem path
|
|
97
|
+
// over HTTP is a far larger surface than this route wants to be, and
|
|
98
|
+
// registration is `init`'s job.
|
|
99
|
+
if (!entries.some((e) => e.path === update.path)) {
|
|
100
|
+
return sendJson(res, 404, { error: "no registered ghostrail at that path" });
|
|
101
|
+
}
|
|
102
|
+
const next = entries.map((e) => e.path === update.path ? { ...e, watch: update.watch } : e);
|
|
103
|
+
await deps.writeRegistry(next);
|
|
104
|
+
return sendJson(res, 200, { ghostrails: await collectViews(deps, next) });
|
|
105
|
+
}
|
|
106
|
+
return sendJson(res, 404, { error: "not found" });
|
|
107
|
+
})().catch(() => sendJson(res, 500, { error: "internal error" }));
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/dashboard/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,YAAY,EAAE,MAAM,WAAW,CAAC;AAC3E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AA8BxF,wCAAwC;AACxC,MAAM,SAAS,GAAG,UAAU,CAAC;AAE7B,qFAAqF;AACrF,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAE7B,SAAS,QAAQ,CAAC,GAAmB,EAAE,MAAc,EAAE,IAAa;IAClE,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;IAC9D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;AAChC,CAAC;AAED,gFAAgF;AAChF,KAAK,UAAU,QAAQ,CAAC,GAA0B,EAAE,KAAK,GAAG,EAAE,GAAG,IAAI;IACnE,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,EAAE,CAAC;QAC9B,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAe,CAAC,CAAC;QAC1E,IAAI,IAAI,GAAG,CAAC,MAAM,CAAC;QACnB,IAAI,IAAI,GAAG,KAAK;YAAE,MAAM;QACxB,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAChD,CAAC;AAED,wDAAwD;AACxD,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAmB,EACnB,OAA6B;IAE7B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAoB,EAAE,CAAC;IAClC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC7C,qEAAqE;QACrE,yEAAyE;QACzE,IAAI,IAAI,GAA0B,EAAE,CAAC;QACrC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,MAAM,IAAI,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;gBAC3E,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,kBAAkB,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;oBACrD,KAAK,EAAE,GAAG,CAAC,KAAK;oBAChB,GAAG,CAAC,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;oBAC3D,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC;oBACxD,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC;iBACzD,CAAC,CAAC,CAAC;YACN,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,GAAG,EAAE,CAAC;YACZ,CAAC;QACH,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrF,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAmB,EAAE,UAA4B,EAAE;IACvF,OAAO,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC/B,KAAK,CAAC,KAAK,IAAI,EAAE;YACf,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,kBAAkB,CAAC,CAAC;YAExD,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBAChC,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,mBAAmB,CAAC,IAAI,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;gBAC1F,IAAI,SAAS,KAAK,OAAO,CAAC,KAAK;oBAAE,OAAO,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;YACxF,CAAC;YAED,MAAM,KAAK,GAAG,mBAAmB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAEhD,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBAC1B,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,0BAA0B,EAAE,CAAC,CAAC;gBACnE,OAAO,GAAG,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;YACtC,CAAC;YAED,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;YAC1C,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;gBAC1B,OAAO,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,6CAA6C,EAAE,CAAC,CAAC;YACtF,CAAC;YAED,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;gBAChC,OAAO,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;YAC/E,CAAC;YAED,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC3B,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM;oBAAE,OAAO,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC;gBAC5E,oEAAoE;gBACpE,sEAAsE;gBACtE,sDAAsD;gBACtD,IAAI,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;oBACpC,OAAO,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,iCAAiC,EAAE,CAAC,CAAC;gBAC1E,CAAC;gBACD,MAAM,MAAM,GAAG,gBAAgB,CAAC,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;gBACrD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;oBACzB,OAAO,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,yCAAyC,EAAE,CAAC,CAAC;gBAClF,CAAC;gBACD,uEAAuE;gBACvE,qEAAqE;gBACrE,gCAAgC;gBAChC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;oBACjD,OAAO,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,sCAAsC,EAAE,CAAC,CAAC;gBAC/E,CAAC;gBACD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAC7B,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAC3D,CAAC;gBACF,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;gBAC/B,OAAO,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;YAC5E,CAAC;YAED,OAAO,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;QACpD,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/dist/global/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { type Ghostrail, REGISTRY_FILE, findByPath, parseRegistry, renderRegistry, upsert, } from "./registry.js";
|
|
1
2
|
export { artifactFileName, type GlobalEnv, GlobalStore, resolveGlobalDir } from "./store.js";
|
|
2
3
|
export { STAGED_ARTIFACTS_DIR, stageArtifacts } from "./stage.js";
|
|
3
4
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/global/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,KAAK,SAAS,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC7F,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/global/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,SAAS,EACd,aAAa,EACb,UAAU,EACV,aAAa,EACb,cAAc,EACd,MAAM,GACP,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,gBAAgB,EAAE,KAAK,SAAS,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC7F,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/global/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { REGISTRY_FILE, findByPath, parseRegistry, renderRegistry, upsert, } from "./registry.js";
|
|
1
2
|
export { artifactFileName, GlobalStore, resolveGlobalDir } from "./store.js";
|
|
2
3
|
export { STAGED_ARTIFACTS_DIR, stageArtifacts } from "./stage.js";
|
|
3
4
|
//# sourceMappingURL=index.js.map
|
package/dist/global/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/global/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAkB,WAAW,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC7F,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/global/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,aAAa,EACb,UAAU,EACV,aAAa,EACb,cAAc,EACd,MAAM,GACP,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,gBAAgB,EAAkB,WAAW,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC7F,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The registry of ghostrails: which repos this account has set up, so
|
|
3
|
+
* `start` has something to offer and the board has something to list. Lives
|
|
4
|
+
* beside `settings.toml` in the global dir.
|
|
5
|
+
*
|
|
6
|
+
* Everything here is pure. Reading and writing the file is the store's job, so
|
|
7
|
+
* a malformed registry can be tested without a filesystem.
|
|
8
|
+
*/
|
|
9
|
+
/** One repo ghostrail knows about. */
|
|
10
|
+
export interface Ghostrail {
|
|
11
|
+
/** Display name, from `[factory].name` or the directory. Never an identity. */
|
|
12
|
+
name: string;
|
|
13
|
+
/** Absolute path to the repo. This is the identity. */
|
|
14
|
+
path: string;
|
|
15
|
+
/** ISO date the entry was added, for the operator. Nothing reads it. */
|
|
16
|
+
added: string;
|
|
17
|
+
/**
|
|
18
|
+
* Desired state: whether `start` should bring this one up. Not live state.
|
|
19
|
+
* One flag, so the CLI, the board, and the service units cannot disagree.
|
|
20
|
+
*/
|
|
21
|
+
watch: boolean;
|
|
22
|
+
}
|
|
23
|
+
/** The file name in the global dir. */
|
|
24
|
+
export declare const REGISTRY_FILE = "ghostrails.toml";
|
|
25
|
+
/**
|
|
26
|
+
* Parse the registry, or undefined when it cannot be trusted.
|
|
27
|
+
*
|
|
28
|
+
* All or nothing, exactly as `readManifest` treats a malformed manifest: a
|
|
29
|
+
* half-trusted registry is worse than none, because silently dropping one bad
|
|
30
|
+
* entry would quietly stop watching a ghostrail with no signal that anything
|
|
31
|
+
* was wrong.
|
|
32
|
+
*
|
|
33
|
+
* A file with no `[[ghostrail]]` array is an empty registry, not a broken one:
|
|
34
|
+
* nothing registered yet is a real state, and so is an explicitly empty
|
|
35
|
+
* `ghostrail = []`. Unknown keys on an otherwise valid entry are ignored rather
|
|
36
|
+
* than fatal, so a registry written by a later ghostrail still reads here.
|
|
37
|
+
* Order is preserved exactly, and the array is fresh on every call.
|
|
38
|
+
*
|
|
39
|
+
* Two entries at the same path are malformed for the same reason: a parsed
|
|
40
|
+
* registry never contains duplicate paths, which is what lets `upsert` and
|
|
41
|
+
* `findByPath` stay as simple as they are.
|
|
42
|
+
*/
|
|
43
|
+
export declare function parseRegistry(toml: string): Ghostrail[] | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Render entries back to TOML, in the order given. Round-trips through
|
|
46
|
+
* {@link parseRegistry}, which is what keeps a rewrite from mangling a file the
|
|
47
|
+
* operator has edited.
|
|
48
|
+
*
|
|
49
|
+
* The layout is part of the contract: keys padded so the `=` signs line up, one
|
|
50
|
+
* blank line between blocks. Both are for the human who opens this file, which
|
|
51
|
+
* the header invites them to do.
|
|
52
|
+
*/
|
|
53
|
+
export declare function renderRegistry(entries: readonly Ghostrail[]): string;
|
|
54
|
+
/** The entry at `path`, or undefined. Exact string equality; callers resolve first. */
|
|
55
|
+
export declare function findByPath(entries: readonly Ghostrail[], path: string): Ghostrail | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Add `entry`, or replace the one at the same path.
|
|
58
|
+
*
|
|
59
|
+
* A replacement keeps its position so a diff of the file shows a changed block
|
|
60
|
+
* rather than a move; a new entry appends. Neither argument is mutated.
|
|
61
|
+
*/
|
|
62
|
+
export declare function upsert(entries: readonly Ghostrail[], entry: Ghostrail): Ghostrail[];
|
|
63
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/global/registry.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,sCAAsC;AACtC,MAAM,WAAW,SAAS;IACxB,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,uCAAuC;AACvC,eAAO,MAAM,aAAa,oBAAoB,CAAC;AAyB/C;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,SAAS,CA2BnE;AAOD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,GAAG,MAAM,CAapE;AAED,uFAAuF;AACvF,wBAAgB,UAAU,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAE7F;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,GAAG,SAAS,EAAE,CAMnF"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { parse as parseToml } from "smol-toml";
|
|
2
|
+
/** The file name in the global dir. */
|
|
3
|
+
export const REGISTRY_FILE = "ghostrails.toml";
|
|
4
|
+
const HEADER = `# The ghostrails this account knows about: the repos ghostrail is set up for.
|
|
5
|
+
#
|
|
6
|
+
# Written by \`ghostrail init\` and topped up by \`ghostrail init --update\`.
|
|
7
|
+
# \`watch\` is desired state, what \`ghostrail start\` should bring up, not a
|
|
8
|
+
# statement about what is running right now.
|
|
9
|
+
#
|
|
10
|
+
# Safe to hand-edit. A file that does not parse is treated as no registry at
|
|
11
|
+
# all rather than half-trusted, so a typo here is loud, not silent.`;
|
|
12
|
+
/** True if `value` is a plain object (not null, not an array). */
|
|
13
|
+
function isRecord(value) {
|
|
14
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15
|
+
}
|
|
16
|
+
/** One entry, or undefined if any required field is missing or wrong-typed. */
|
|
17
|
+
function parseEntry(raw) {
|
|
18
|
+
if (!isRecord(raw))
|
|
19
|
+
return undefined;
|
|
20
|
+
const { name, path, added, watch } = raw;
|
|
21
|
+
if (typeof name !== "string" || typeof path !== "string")
|
|
22
|
+
return undefined;
|
|
23
|
+
if (typeof added !== "string" || typeof watch !== "boolean")
|
|
24
|
+
return undefined;
|
|
25
|
+
return { name, path, added, watch };
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Parse the registry, or undefined when it cannot be trusted.
|
|
29
|
+
*
|
|
30
|
+
* All or nothing, exactly as `readManifest` treats a malformed manifest: a
|
|
31
|
+
* half-trusted registry is worse than none, because silently dropping one bad
|
|
32
|
+
* entry would quietly stop watching a ghostrail with no signal that anything
|
|
33
|
+
* was wrong.
|
|
34
|
+
*
|
|
35
|
+
* A file with no `[[ghostrail]]` array is an empty registry, not a broken one:
|
|
36
|
+
* nothing registered yet is a real state, and so is an explicitly empty
|
|
37
|
+
* `ghostrail = []`. Unknown keys on an otherwise valid entry are ignored rather
|
|
38
|
+
* than fatal, so a registry written by a later ghostrail still reads here.
|
|
39
|
+
* Order is preserved exactly, and the array is fresh on every call.
|
|
40
|
+
*
|
|
41
|
+
* Two entries at the same path are malformed for the same reason: a parsed
|
|
42
|
+
* registry never contains duplicate paths, which is what lets `upsert` and
|
|
43
|
+
* `findByPath` stay as simple as they are.
|
|
44
|
+
*/
|
|
45
|
+
export function parseRegistry(toml) {
|
|
46
|
+
let root;
|
|
47
|
+
try {
|
|
48
|
+
root = parseToml(toml);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
if (!isRecord(root))
|
|
54
|
+
return undefined;
|
|
55
|
+
const raw = root.ghostrail;
|
|
56
|
+
if (raw === undefined)
|
|
57
|
+
return [];
|
|
58
|
+
if (!Array.isArray(raw))
|
|
59
|
+
return undefined;
|
|
60
|
+
const entries = [];
|
|
61
|
+
const paths = new Set();
|
|
62
|
+
for (const item of raw) {
|
|
63
|
+
const entry = parseEntry(item);
|
|
64
|
+
if (entry === undefined)
|
|
65
|
+
return undefined;
|
|
66
|
+
// `path` is the identity, so two blocks claiming it is a contradiction
|
|
67
|
+
// rather than a preference to resolve. Copying a block and forgetting to
|
|
68
|
+
// change the path is a plausible hand-edit, and honouring either one of
|
|
69
|
+
// them would silently ignore an edit the operator clearly meant.
|
|
70
|
+
if (paths.has(entry.path))
|
|
71
|
+
return undefined;
|
|
72
|
+
paths.add(entry.path);
|
|
73
|
+
entries.push(entry);
|
|
74
|
+
}
|
|
75
|
+
return entries;
|
|
76
|
+
}
|
|
77
|
+
/** TOML-quote a string. The values here are paths and names, not arbitrary data. */
|
|
78
|
+
function quote(value) {
|
|
79
|
+
return JSON.stringify(value);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Render entries back to TOML, in the order given. Round-trips through
|
|
83
|
+
* {@link parseRegistry}, which is what keeps a rewrite from mangling a file the
|
|
84
|
+
* operator has edited.
|
|
85
|
+
*
|
|
86
|
+
* The layout is part of the contract: keys padded so the `=` signs line up, one
|
|
87
|
+
* blank line between blocks. Both are for the human who opens this file, which
|
|
88
|
+
* the header invites them to do.
|
|
89
|
+
*/
|
|
90
|
+
export function renderRegistry(entries) {
|
|
91
|
+
const blocks = entries.map((entry) => [
|
|
92
|
+
"[[ghostrail]]",
|
|
93
|
+
`name = ${quote(entry.name)}`,
|
|
94
|
+
`path = ${quote(entry.path)}`,
|
|
95
|
+
`added = ${quote(entry.added)}`,
|
|
96
|
+
`watch = ${entry.watch}`,
|
|
97
|
+
].join("\n"));
|
|
98
|
+
// A blank line between blocks. This file is advertised as hand-editable, and
|
|
99
|
+
// run together the blocks are hard to read and give noisier diffs.
|
|
100
|
+
return `${[HEADER, ...blocks].join("\n\n")}\n`;
|
|
101
|
+
}
|
|
102
|
+
/** The entry at `path`, or undefined. Exact string equality; callers resolve first. */
|
|
103
|
+
export function findByPath(entries, path) {
|
|
104
|
+
return entries.find((entry) => entry.path === path);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Add `entry`, or replace the one at the same path.
|
|
108
|
+
*
|
|
109
|
+
* A replacement keeps its position so a diff of the file shows a changed block
|
|
110
|
+
* rather than a move; a new entry appends. Neither argument is mutated.
|
|
111
|
+
*/
|
|
112
|
+
export function upsert(entries, entry) {
|
|
113
|
+
const index = entries.findIndex((existing) => existing.path === entry.path);
|
|
114
|
+
if (index === -1)
|
|
115
|
+
return [...entries, entry];
|
|
116
|
+
const next = [...entries];
|
|
117
|
+
next[index] = entry;
|
|
118
|
+
return next;
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/global/registry.ts"],"names":[],"mappings":"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"}
|
package/dist/global/store.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Ghostrail } from "./registry.js";
|
|
1
2
|
export interface GlobalEnv {
|
|
2
3
|
GHOSTRAIL_HOME?: string;
|
|
3
4
|
XDG_CONFIG_HOME?: string;
|
|
@@ -24,6 +25,15 @@ export declare class GlobalStore {
|
|
|
24
25
|
readSettings(): Promise<string | undefined>;
|
|
25
26
|
/** Artifact base names present in the store, sorted. */
|
|
26
27
|
listArtifacts(): Promise<string[]>;
|
|
28
|
+
registryPath(): string;
|
|
29
|
+
/**
|
|
30
|
+
* The registered ghostrails. `undefined` means the file exists but could not
|
|
31
|
+
* be trusted, which is deliberately different from `[]` (nothing registered
|
|
32
|
+
* yet): a caller must be able to tell "broken" from "empty" so it can say so.
|
|
33
|
+
*/
|
|
34
|
+
readRegistry(): Promise<Ghostrail[] | undefined>;
|
|
35
|
+
/** Write the registry, creating the global dir if this is the first entry. */
|
|
36
|
+
writeRegistry(entries: readonly Ghostrail[]): Promise<void>;
|
|
27
37
|
/** An artifact's contents by name (with or without `.md`), or undefined. */
|
|
28
38
|
readArtifact(name: string): Promise<string | undefined>;
|
|
29
39
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/global/store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/global/store.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAgD,MAAM,eAAe,CAAC;AAE7F,MAAM,WAAW,SAAS;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAOxE;AAED,yDAAyD;AACzD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;;;GAIG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;gBAEjB,GAAG,EAAE,MAAM;IAIvB,YAAY,IAAI,MAAM;IAItB,mEAAmE;IAC7D,YAAY,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAQjD,wDAAwD;IAClD,aAAa,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAQxC,YAAY,IAAI,MAAM;IAItB;;;;OAIG;IACG,YAAY,IAAI,OAAO,CAAC,SAAS,EAAE,GAAG,SAAS,CAAC;IAUtD,8EAA8E;IACxE,aAAa,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjE,4EAA4E;IACtE,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;CAU9D"}
|
package/dist/global/store.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { readFile, readdir } from "node:fs/promises";
|
|
1
|
+
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { REGISTRY_FILE, parseRegistry, renderRegistry } from "./registry.js";
|
|
3
4
|
/**
|
|
4
5
|
* Resolve the global ghostrail config directory that holds account-level
|
|
5
6
|
* settings and shared artifacts across all factory instances:
|
|
@@ -49,6 +50,29 @@ export class GlobalStore {
|
|
|
49
50
|
return [];
|
|
50
51
|
}
|
|
51
52
|
}
|
|
53
|
+
registryPath() {
|
|
54
|
+
return join(this.dir, REGISTRY_FILE);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The registered ghostrails. `undefined` means the file exists but could not
|
|
58
|
+
* be trusted, which is deliberately different from `[]` (nothing registered
|
|
59
|
+
* yet): a caller must be able to tell "broken" from "empty" so it can say so.
|
|
60
|
+
*/
|
|
61
|
+
async readRegistry() {
|
|
62
|
+
let raw;
|
|
63
|
+
try {
|
|
64
|
+
raw = await readFile(this.registryPath(), "utf8");
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
return parseRegistry(raw);
|
|
70
|
+
}
|
|
71
|
+
/** Write the registry, creating the global dir if this is the first entry. */
|
|
72
|
+
async writeRegistry(entries) {
|
|
73
|
+
await mkdir(this.dir, { recursive: true });
|
|
74
|
+
await writeFile(this.registryPath(), renderRegistry(entries), "utf8");
|
|
75
|
+
}
|
|
52
76
|
/** An artifact's contents by name (with or without `.md`), or undefined. */
|
|
53
77
|
async readArtifact(name) {
|
|
54
78
|
for (const candidate of new Set([name, artifactFileName(name)])) {
|
package/dist/global/store.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/global/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/global/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { type InstallOutcome, type ManagerKind, type ServiceManager, detectManager, disableAndStop, enableAndStart, installUnit, reload, removeUnit, systemdManager, systemdUnitDir, unitStates, } from "./manager.js";
|
|
2
|
+
export { type UnitSpec, DASHBOARD_UNIT, renderDashboardUnit, parseUnitStates, renderSystemdUnit, stopTimeoutSeconds, unitName, } from "./unit.js";
|
|
3
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/service/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,aAAa,EACb,cAAc,EACd,cAAc,EACd,WAAW,EACX,MAAM,EACN,UAAU,EACV,cAAc,EACd,cAAc,EACd,UAAU,GACX,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,KAAK,QAAQ,EACb,cAAc,EACd,mBAAmB,EACnB,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,QAAQ,GACT,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { detectManager, disableAndStop, enableAndStart, installUnit, reload, removeUnit, systemdManager, systemdUnitDir, unitStates, } from "./manager.js";
|
|
2
|
+
export { DASHBOARD_UNIT, renderDashboardUnit, parseUnitStates, renderSystemdUnit, stopTimeoutSeconds, unitName, } from "./unit.js";
|
|
3
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/service/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,aAAa,EACb,cAAc,EACd,cAAc,EACd,WAAW,EACX,MAAM,EACN,UAAU,EACV,cAAc,EACd,cAAc,EACd,UAAU,GACX,MAAM,cAAc,CAAC;AACtB,OAAO,EAEL,cAAc,EACd,mBAAmB,EACnB,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,QAAQ,GACT,MAAM,WAAW,CAAC"}
|