process-graph 1.1.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -19
- package/dist/bin.js +4 -3
- package/dist/cli.d.ts +100 -14
- package/dist/cli.js +3 -2
- package/dist/{control-aDk9560d.js → control-DiygoEhm.js} +163 -1
- package/dist/data-file-BVMxkTug.js +95 -0
- package/dist/dist-81kTKnMq.js +3 -0
- package/dist/{dist-HLlW8vgT.js → dist-BI01yxFA.js} +256 -103
- package/dist/{dist-BpCBmnMl.js → dist-arkejVz2.js} +2 -112
- package/dist/{graphql-C1j5wQFz.d.ts → graphql-BE7xm5dB.d.ts} +3 -2
- package/dist/{index-DricKcyl.d.ts → index-9Au8pAmP.d.ts} +4 -4
- package/dist/{index-CrM5qWUA.d.ts → index-BC4tmAuQ.d.ts} +2 -2
- package/dist/{index-DhbvDgK4.d.ts → index-CR8xUK3S.d.ts} +388 -580
- package/dist/{index-CoA6vXqe.d.ts → index-D06bcdfw.d.ts} +1 -1
- package/dist/{index-CCfe0csl.d.ts → index-D4CnOhNf.d.ts} +34 -3
- package/dist/{index-CmZOswkx.d.ts → index-Dvz_I_uQ.d.ts} +17 -8
- package/dist/{index-CptUHq6F.d.ts → index-hri2a13-.d.ts} +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +5 -5
- package/dist/layout.d.ts +1 -1
- package/dist/{parse-CuipC_2K.js → parse-C1Q1zsGt.js} +1 -1
- package/dist/{queries-CCexJAE2.js → queries-C8kIDRiY.js} +2 -93
- package/dist/react-flow.d.ts +5 -5
- package/dist/react-flow.js +3 -3
- package/dist/react.d.ts +1 -1
- package/dist/react.js +2 -2
- package/dist/{router-D4gtTlXm.js → router-Dk91O_9p.js} +27 -2
- package/dist/{schema-CVnS6HiX.js → schema-BPT8fmfa.js} +2 -2
- package/dist/{selection-BW_xXWOy.js → selection-iet5RAlP.js} +1 -1
- package/dist/source/github/node.d.ts +1 -1
- package/dist/source/github.d.ts +2 -2
- package/dist/source/github.js +1 -1
- package/dist/{source-DRDpEA8Q.js → source-D51N7K3A.js} +23 -3
- package/dist/store/node.d.ts +27 -2
- package/dist/store/node.js +2 -2
- package/dist/store/wasm/worker.js +2 -2
- package/dist/store/wasm.d.ts +1 -1
- package/dist/{store-BcR70CaO.js → store-DKzvLGgl.js} +252 -37
- package/dist/store.d.ts +115 -3
- package/dist/store.js +43 -9
- package/dist/theme-values-BJJhAkUn-ORyRUrPf.js +114 -0
- package/dist/theme-values-BybZ-cle.d.ts +116 -0
- package/dist/ui/theme.d.ts +2 -0
- package/dist/ui/theme.js +2 -0
- package/dist/ui.d.ts +6 -120
- package/dist/ui.js +2 -1
- package/dist/version-Dagrkvwt.js +5 -0
- package/package.json +28 -23
- package/dist/dist-IjrgWhHQ.js +0 -3
- package/dist/open-CyRXJ4bt.js +0 -29
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
import { a as validateOverlay, d as overlayStatuses, f as participantKinds } from "./schema-
|
|
1
|
+
import { a as validateOverlay, d as overlayStatuses, f as participantKinds } from "./schema-BPT8fmfa.js";
|
|
2
2
|
import { t as validate } from "./validate-DF3mQZww.js";
|
|
3
|
-
import { n as parseModel, t as ModelParseError } from "./parse-
|
|
4
|
-
import {
|
|
5
|
-
import { f as
|
|
6
|
-
import { l as assembleSync, t as createGitHubIssuesSource, u as createGraphSourceRouter } from "./source-
|
|
3
|
+
import { n as parseModel, t as ModelParseError } from "./parse-C1Q1zsGt.js";
|
|
4
|
+
import { c as deriveStatuses, f as toStateMachine, g as handoffs, h as toFlowchart, i as readySet, n as criticalPath, p as toSwimlane, r as impact, s as explainStatus, t as blockedBy, u as toSequence } from "./queries-C8kIDRiY.js";
|
|
5
|
+
import { S as UnknownNodeError, f as layeredFacts, n as illegalCycles, u as factsFrom, x as CycleError } from "./control-DiygoEhm.js";
|
|
6
|
+
import { l as assembleSync, t as createGitHubIssuesSource, u as createGraphSourceRouter } from "./source-D51N7K3A.js";
|
|
7
|
+
import { t as GraphSourceError } from "./errors-BZe5WzCn.js";
|
|
7
8
|
import { t as githubTokenFromEnvironment } from "./node-AchI1T-K.js";
|
|
8
|
-
import {
|
|
9
|
-
import { t as createGraphStoreRouter } from "./router-
|
|
10
|
-
import {
|
|
9
|
+
import { g as emptyModel } from "./store-DKzvLGgl.js";
|
|
10
|
+
import { t as createGraphStoreRouter } from "./router-Dk91O_9p.js";
|
|
11
|
+
import { i as openNodeGraphStore, t as prepareDataFile } from "./data-file-BVMxkTug.js";
|
|
11
12
|
import { t as exitCodes } from "./exit-codes-0ultCqdS.js";
|
|
12
13
|
import { ORPCError, createRouterClient } from "@orpc/server";
|
|
14
|
+
import { readFileSync } from "node:fs";
|
|
15
|
+
import { join } from "node:path";
|
|
13
16
|
import { Argument, Command, CommanderError, Option } from "commander";
|
|
14
|
-
import { chmodSync, closeSync, mkdirSync, openSync, readFileSync, statSync } from "node:fs";
|
|
15
17
|
import { readFile } from "node:fs/promises";
|
|
16
18
|
import { homedir } from "node:os";
|
|
17
|
-
import { dirname, join } from "node:path";
|
|
18
19
|
//#region ../graph-cli/dist/errors.js
|
|
19
20
|
/** A failure the command line reports with its own exit status and message, and nothing else. */
|
|
20
21
|
var CliError = class extends Error {
|
|
@@ -46,6 +47,16 @@ const issueLines = (data) => {
|
|
|
46
47
|
return data.issues.flatMap((issue) => typeof issue === "object" && issue !== null && "code" in issue && "message" in issue && typeof issue.code === "string" && typeof issue.message === "string" ? [`${issue.code}: ${issue.message}`] : []);
|
|
47
48
|
};
|
|
48
49
|
/**
|
|
50
|
+
* The error underneath a source's failure, for a failure's cause. A source called in process through oRPC fails with an `ORPCError` whose cause is the `GraphSourceError` the source threw, whose own cause, when it has one, is what went wrong underneath, such as a token port's error. Those two layers say nothing more than the failure's message and exit status already do, so the innermost error is the one worth keeping.
|
|
51
|
+
* @param error - What the source call threw.
|
|
52
|
+
* @returns The `GraphSourceError`'s cause when it has one, otherwise that `GraphSourceError`; anything else as it is.
|
|
53
|
+
*/
|
|
54
|
+
const underlyingCause = (error) => {
|
|
55
|
+
const sourceError = error instanceof ORPCError && error.cause instanceof GraphSourceError ? error.cause : error;
|
|
56
|
+
if (!(sourceError instanceof GraphSourceError)) return error;
|
|
57
|
+
return sourceError.cause === void 0 ? sourceError : sourceError.cause;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
49
60
|
* Classifies anything a command threw into its exit status and the message for standard error. A commander usage error has already printed its own message, so it is reported with an empty one.
|
|
50
61
|
* @param error - What was thrown.
|
|
51
62
|
* @returns The exit status and message.
|
|
@@ -95,6 +106,13 @@ const failureOf = (error) => {
|
|
|
95
106
|
//#endregion
|
|
96
107
|
//#region ../graph-cli/dist/session.js
|
|
97
108
|
/**
|
|
109
|
+
* The facts a run's reasoning reads: the model's stored fact record first, then the session's own facts for every key the record does not answer. What `set artefact` and `set fact` record therefore overrides a host's facts only where it says something.
|
|
110
|
+
* @param session - The session, whose facts the host supplied.
|
|
111
|
+
* @param run - The stored run.
|
|
112
|
+
* @returns The `Facts` port for graph-domain.
|
|
113
|
+
*/
|
|
114
|
+
const runFacts = (session, run) => layeredFacts(factsFrom(run.facts), session.facts);
|
|
115
|
+
/**
|
|
98
116
|
* The oRPC client options carrying the session's signal. The key is left out when there is no signal, since the options type does not accept an explicit `undefined`.
|
|
99
117
|
* @param signal - The signal, if any.
|
|
100
118
|
*/
|
|
@@ -110,7 +128,8 @@ const findRun = async (session) => {
|
|
|
110
128
|
return {
|
|
111
129
|
revision: stored.revision,
|
|
112
130
|
model: stored.model,
|
|
113
|
-
overlay: stored.overlay
|
|
131
|
+
overlay: stored.overlay,
|
|
132
|
+
facts: stored.facts
|
|
114
133
|
};
|
|
115
134
|
} catch (error) {
|
|
116
135
|
if (error instanceof ORPCError && error.code === "NOT_FOUND") return;
|
|
@@ -212,6 +231,74 @@ const setState = async (session, id, state) => {
|
|
|
212
231
|
revision
|
|
213
232
|
};
|
|
214
233
|
};
|
|
234
|
+
/** What can be recorded about an artefact: `provided`, known not provided (`absent`), or `unknown`, which forgets whatever was recorded. */
|
|
235
|
+
const artefactStates = [
|
|
236
|
+
"provided",
|
|
237
|
+
"absent",
|
|
238
|
+
"unknown"
|
|
239
|
+
];
|
|
240
|
+
/**
|
|
241
|
+
* Records whether an artefact of the session's model is provided, so a consumer of an input no activity produces can become ready, or forgets it with `unknown`.
|
|
242
|
+
* @param session - The store and model.
|
|
243
|
+
* @param id - The artefact.
|
|
244
|
+
* @param state - `provided`, `absent` or `unknown`.
|
|
245
|
+
* @returns The artefact, its new state and the revision the change took.
|
|
246
|
+
* @throws CliError with `notFound` when the model or the artefact does not exist.
|
|
247
|
+
*/
|
|
248
|
+
const setArtefact = async (session, id, state) => {
|
|
249
|
+
const { model } = await loadRun(session);
|
|
250
|
+
if (!model.artefacts.some((artefact) => artefact.id === id)) throw new CliError(exitCodes.notFound, `no artefact "${id}" in the model`);
|
|
251
|
+
const options = callOptions(session.signal);
|
|
252
|
+
const { revision } = state === "unknown" ? await session.store.v1.facts.clear({
|
|
253
|
+
modelId: session.modelId,
|
|
254
|
+
facts: { artefacts: [id] }
|
|
255
|
+
}, options) : await session.store.v1.facts.set({
|
|
256
|
+
modelId: session.modelId,
|
|
257
|
+
facts: { artefacts: { [id]: state === "provided" } }
|
|
258
|
+
}, options);
|
|
259
|
+
return {
|
|
260
|
+
artefact: id,
|
|
261
|
+
state,
|
|
262
|
+
revision
|
|
263
|
+
};
|
|
264
|
+
};
|
|
265
|
+
/**
|
|
266
|
+
* Records a named fact's value for the session's model, so every condition reading that name evaluates against it, or forgets it with `unknown`, so those conditions are indeterminate again.
|
|
267
|
+
* @param session - The store and model.
|
|
268
|
+
* @param name - The fact's name, the key a condition's `reference` node gives.
|
|
269
|
+
* @param value - Its trilean value, or `unknown`.
|
|
270
|
+
* @returns The fact, its new value and the revision the change took.
|
|
271
|
+
* @throws CliError with `notFound` when the model does not exist.
|
|
272
|
+
*/
|
|
273
|
+
const setFact = async (session, name, value) => {
|
|
274
|
+
await loadRun(session);
|
|
275
|
+
const options = callOptions(session.signal);
|
|
276
|
+
const { revision } = value === "unknown" ? await session.store.v1.facts.clear({
|
|
277
|
+
modelId: session.modelId,
|
|
278
|
+
facts: { values: [name] }
|
|
279
|
+
}, options) : await session.store.v1.facts.set({
|
|
280
|
+
modelId: session.modelId,
|
|
281
|
+
facts: { values: { [name]: value } }
|
|
282
|
+
}, options);
|
|
283
|
+
return {
|
|
284
|
+
fact: name,
|
|
285
|
+
value,
|
|
286
|
+
revision
|
|
287
|
+
};
|
|
288
|
+
};
|
|
289
|
+
/**
|
|
290
|
+
* Reads what is recorded about the session's model's run: which artefacts are provided or absent, and each named fact's value.
|
|
291
|
+
* @param session - The store and model.
|
|
292
|
+
* @returns The fact record and the model's revision.
|
|
293
|
+
* @throws CliError with `notFound` when the model does not exist.
|
|
294
|
+
*/
|
|
295
|
+
const factsOf = async (session) => {
|
|
296
|
+
const { revision, facts } = await loadRun(session);
|
|
297
|
+
return {
|
|
298
|
+
revision,
|
|
299
|
+
facts
|
|
300
|
+
};
|
|
301
|
+
};
|
|
215
302
|
//#endregion
|
|
216
303
|
//#region ../graph-cli/dist/queries.js
|
|
217
304
|
/** Each element counts as one step, so the critical path is the longest chain of elements. */
|
|
@@ -221,8 +308,9 @@ const stepWeight = 1;
|
|
|
221
308
|
* @param session - The store, model and facts.
|
|
222
309
|
*/
|
|
223
310
|
const ready = async (session) => {
|
|
224
|
-
const
|
|
225
|
-
|
|
311
|
+
const run = await loadRun(session);
|
|
312
|
+
const { model, overlay } = run;
|
|
313
|
+
return { elements: summarise(model, readySet(model, overlay, runFacts(session, run))) };
|
|
226
314
|
};
|
|
227
315
|
const statusOf = (statuses, summary) => {
|
|
228
316
|
const status = statuses.get(summary.id);
|
|
@@ -238,15 +326,17 @@ const statusOf = (statuses, summary) => {
|
|
|
238
326
|
* @param id - The element asked about.
|
|
239
327
|
*/
|
|
240
328
|
const blocked = async (session, id) => {
|
|
241
|
-
const
|
|
329
|
+
const run = await loadRun(session);
|
|
330
|
+
const { model, overlay } = run;
|
|
331
|
+
const facts = runFacts(session, run);
|
|
242
332
|
const element = requireElement(model, id);
|
|
243
|
-
const statuses = deriveStatuses(model, overlay,
|
|
333
|
+
const statuses = deriveStatuses(model, overlay, facts);
|
|
244
334
|
return {
|
|
245
335
|
element: statusOf(statuses, {
|
|
246
336
|
id,
|
|
247
337
|
label: labelOf(element)
|
|
248
338
|
}),
|
|
249
|
-
blockedBy: summarise(model, blockedBy(model, overlay,
|
|
339
|
+
blockedBy: summarise(model, blockedBy(model, overlay, facts, id))
|
|
250
340
|
};
|
|
251
341
|
};
|
|
252
342
|
/**
|
|
@@ -255,15 +345,17 @@ const blocked = async (session, id) => {
|
|
|
255
345
|
* @param id - The element asked about.
|
|
256
346
|
*/
|
|
257
347
|
const impactOf = async (session, id) => {
|
|
258
|
-
const
|
|
348
|
+
const run = await loadRun(session);
|
|
349
|
+
const { model, overlay } = run;
|
|
350
|
+
const facts = runFacts(session, run);
|
|
259
351
|
const element = requireElement(model, id);
|
|
260
|
-
const statuses = deriveStatuses(model, overlay,
|
|
352
|
+
const statuses = deriveStatuses(model, overlay, facts);
|
|
261
353
|
return {
|
|
262
354
|
element: statusOf(statuses, {
|
|
263
355
|
id,
|
|
264
356
|
label: labelOf(element)
|
|
265
357
|
}),
|
|
266
|
-
impacted: summarise(model, impact(model, overlay,
|
|
358
|
+
impacted: summarise(model, impact(model, overlay, facts, id))
|
|
267
359
|
};
|
|
268
360
|
};
|
|
269
361
|
/**
|
|
@@ -271,8 +363,9 @@ const impactOf = async (session, id) => {
|
|
|
271
363
|
* @param session - The store, model and facts.
|
|
272
364
|
*/
|
|
273
365
|
const path = async (session) => {
|
|
274
|
-
const
|
|
275
|
-
const
|
|
366
|
+
const run = await loadRun(session);
|
|
367
|
+
const { model, overlay } = run;
|
|
368
|
+
const found = criticalPath(model, overlay, runFacts(session, run), () => stepWeight);
|
|
276
369
|
return {
|
|
277
370
|
path: summarise(model, found.path),
|
|
278
371
|
weight: found.weight
|
|
@@ -300,9 +393,10 @@ const handoffsOf = async (session) => {
|
|
|
300
393
|
* @param id - The element asked about.
|
|
301
394
|
*/
|
|
302
395
|
const why = async (session, id) => {
|
|
303
|
-
const
|
|
396
|
+
const run = await loadRun(session);
|
|
397
|
+
const { model, overlay } = run;
|
|
304
398
|
requireElement(model, id);
|
|
305
|
-
const explained = explainStatus(model, overlay, session
|
|
399
|
+
const explained = explainStatus(model, overlay, runFacts(session, run), id);
|
|
306
400
|
const chain = explained.chain.map((link) => ({
|
|
307
401
|
id: link.element,
|
|
308
402
|
label: labelOf(requireElement(model, link.element)),
|
|
@@ -375,8 +469,34 @@ const renderWhy = (result) => {
|
|
|
375
469
|
const renderAdded = (kind, id, written) => lines([`added ${kind} ${id} at revision ${String(written.revision)}`]);
|
|
376
470
|
/** `set`: the element's new run state and the revision it took. */
|
|
377
471
|
const renderSet = (result) => lines([`set ${result.element} to ${result.state} at revision ${String(result.revision)}`]);
|
|
472
|
+
/** A fact's value as the command line reads it back: a boolean or number as written, text as it is, and the other kinds as their JSON. */
|
|
473
|
+
const valueText = (value) => {
|
|
474
|
+
if (value.kind === "boolean" || value.kind === "number") return value.kind === "number" && value.unit !== void 0 ? JSON.stringify(value) : String(value.value);
|
|
475
|
+
return value.kind === "text" ? value.value : JSON.stringify(value);
|
|
476
|
+
};
|
|
477
|
+
/** `set artefact`: the artefact's new state and the revision it took. */
|
|
478
|
+
const renderSetArtefact = (result) => lines([`set artefact ${result.artefact} to ${result.state} at revision ${String(result.revision)}`]);
|
|
479
|
+
/** `set fact`: the fact's new value and the revision it took. */
|
|
480
|
+
const renderSetFact = (result) => lines([`set fact ${result.fact} to ${result.value === "unknown" ? "unknown" : valueText(result.value)} at revision ${String(result.revision)}`]);
|
|
481
|
+
/** `facts`: each recorded artefact as `artefact`, its id and `provided` or `absent`, then each named fact as `fact`, its name and its value, each sorted by id or name. */
|
|
482
|
+
const renderFacts = (result) => lines([...Object.entries(result.facts.artefacts).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([id, provided]) => `artefact\t${id}\t${provided ? "provided" : "absent"}`), ...Object.entries(result.facts.values).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([name, value]) => `fact\t${name}\t${valueText(value)}`)]);
|
|
378
483
|
/** `sync`: what was written, then each gap as its reason, target and message. */
|
|
379
484
|
const renderSync = (result) => lines([`synced ${String(result.activities)} activities, ${String(result.transitions)} transitions and ${String(result.participants)} participants into "${result.modelId}" at revision ${String(result.revision)}`, ...result.gaps.map((gap) => `gap\t${gap.reason}\t${gap.target}\t${gap.message}`)]);
|
|
485
|
+
/**
|
|
486
|
+
* A sync a source interrupted: the source's failure, whether the pages read before it were saved, and the commands that carry on, `resume with: sync <scopes> --after <cursor>` for the failing source and `then: sync <scopes>` for the sources not reached.
|
|
487
|
+
* @param message - The source's failure.
|
|
488
|
+
* @param interruption - Where the sync carries on.
|
|
489
|
+
* @returns The lines for standard error, without a final line break.
|
|
490
|
+
*/
|
|
491
|
+
const renderSyncInterruption = (message, interruption) => {
|
|
492
|
+
const { resume, remaining, saved } = interruption;
|
|
493
|
+
return [
|
|
494
|
+
message,
|
|
495
|
+
...saved ? ["the pages read before the failure are saved"] : [],
|
|
496
|
+
`resume with: sync ${resume.scopes.join(" ")}${resume.after === void 0 ? "" : ` --after ${resume.after}`}`,
|
|
497
|
+
...remaining.length === 0 ? [] : [`then: sync ${remaining.join(" ")}`]
|
|
498
|
+
].join("\n");
|
|
499
|
+
};
|
|
380
500
|
/** `validate`: `valid`, or each issue as its code, its JSON pointer when it has one, and its message. */
|
|
381
501
|
const renderValidate = (result) => result.valid ? lines(["valid"]) : lines(result.issues.map((issue) => issue.pointer === void 0 ? `${issue.code}\t${issue.message}` : `${issue.code}\t${issue.pointer === "" ? "/" : issue.pointer}\t${issue.message}`));
|
|
382
502
|
//#endregion
|
|
@@ -788,6 +908,27 @@ const show = async (session, request) => {
|
|
|
788
908
|
};
|
|
789
909
|
//#endregion
|
|
790
910
|
//#region ../graph-cli/dist/sync.js
|
|
911
|
+
/**
|
|
912
|
+
* Thrown by `sync` when a source fails part way. Its exit status and message are the source's failure, classified as `failureOf` classifies it; its cause is the error underneath that failure, such as the token port's own error; and what to run next is data, in `resume` and `remaining`, never text.
|
|
913
|
+
*/
|
|
914
|
+
var SyncInterruptedError = class extends CliError {
|
|
915
|
+
resume;
|
|
916
|
+
remaining;
|
|
917
|
+
saved;
|
|
918
|
+
/**
|
|
919
|
+
* @param exitCode - The exit status the source's failure maps to.
|
|
920
|
+
* @param message - The source's failure.
|
|
921
|
+
* @param interruption - Where the sync carries on.
|
|
922
|
+
* @param options - The underlying error, kept as the cause.
|
|
923
|
+
*/
|
|
924
|
+
constructor(exitCode, message, interruption, options) {
|
|
925
|
+
super(exitCode, message, options);
|
|
926
|
+
this.name = "SyncInterruptedError";
|
|
927
|
+
this.resume = interruption.resume;
|
|
928
|
+
this.remaining = interruption.remaining;
|
|
929
|
+
this.saved = interruption.saved;
|
|
930
|
+
}
|
|
931
|
+
};
|
|
791
932
|
const scopeSeparator = ":";
|
|
792
933
|
/** Groups scopes by their source prefix, keeping the order in which each prefix first appears. */
|
|
793
934
|
const bySource = (scopes) => {
|
|
@@ -823,12 +964,12 @@ const boundActivities = (pages) => {
|
|
|
823
964
|
/**
|
|
824
965
|
* Reads every page of the scopes from their sources and merges them into the session's model in one write, creating the model when the store has none. Records already in the model are replaced by identifier and the rest are kept, so syncing again refreshes what changed.
|
|
825
966
|
*
|
|
826
|
-
* When a source fails part way, the pages read before the failure are still written, and the error
|
|
967
|
+
* When a source fails part way, the pages read before the failure are still written, and the error carries where to carry on: the request that resumes the failing source after the cursor of its last page read, and the scopes of the sources not reached.
|
|
827
968
|
* @param session - The store and model.
|
|
828
969
|
* @param sources - The source for each identifier prefix.
|
|
829
970
|
* @param request - The scopes, and a cursor to resume after.
|
|
830
971
|
* @returns What was written, and the gaps the sources reported.
|
|
831
|
-
* @throws CliError with `usage` for a scope with no source prefix, a prefix no source serves, or `after` with scopes of more than one source; and with the source's failure
|
|
972
|
+
* @throws CliError with `usage` for a scope with no source prefix, a prefix no source serves, or `after` with scopes of more than one source; and SyncInterruptedError, with the source's failure and where to carry on, when a source fails part way.
|
|
832
973
|
*/
|
|
833
974
|
const sync = async (session, sources, request) => {
|
|
834
975
|
const groups = bySource(request.scopes);
|
|
@@ -875,13 +1016,14 @@ const sync = async (session, sources, request) => {
|
|
|
875
1016
|
const failure = failureOf(error);
|
|
876
1017
|
const saved = pages.length > 0;
|
|
877
1018
|
if (saved) await write();
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1019
|
+
throw new SyncInterruptedError(failure.exitCode, failure.message, {
|
|
1020
|
+
resume: cursor === void 0 ? { scopes: group.scopes } : {
|
|
1021
|
+
scopes: group.scopes,
|
|
1022
|
+
after: cursor
|
|
1023
|
+
},
|
|
1024
|
+
remaining: clients.slice(index + 1).flatMap((next) => next.scopes),
|
|
1025
|
+
saved
|
|
1026
|
+
}, { cause: underlyingCause(error) });
|
|
885
1027
|
}
|
|
886
1028
|
await readFrom(index + 1);
|
|
887
1029
|
};
|
|
@@ -962,12 +1104,42 @@ const readGlobals = (command, ports) => {
|
|
|
962
1104
|
json: json === true
|
|
963
1105
|
};
|
|
964
1106
|
};
|
|
1107
|
+
/** Narrows a variadic option's values, which commander gives as an untyped array, to the strings they are. */
|
|
1108
|
+
const isText = (value) => typeof value === "string";
|
|
965
1109
|
const isOneOf = (values, value) => values.some((candidate) => candidate === value);
|
|
966
1110
|
/** Reads an option whose choices commander has already checked, narrowing it to its type. */
|
|
967
1111
|
const choice = (values, value, name) => {
|
|
968
1112
|
if (!isOneOf(values, value)) throw new Error(`--${name} was not checked against its choices`);
|
|
969
1113
|
return value;
|
|
970
1114
|
};
|
|
1115
|
+
/** The word that forgets a named fact rather than giving it a value. */
|
|
1116
|
+
const unknownValue = "unknown";
|
|
1117
|
+
/** A number as JSON writes one, so a value that reads as a number is never also meant as text. */
|
|
1118
|
+
const jsonNumber = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/u;
|
|
1119
|
+
/**
|
|
1120
|
+
* Reads a fact's value from the command line: `true` and `false` are booleans, `unknown` forgets the fact, a JSON number is a number, and anything else is text; `--text` reads the value as text whatever it looks like.
|
|
1121
|
+
* @param value - The value as given.
|
|
1122
|
+
* @param asText - Whether `--text` was given.
|
|
1123
|
+
* @returns The trilean value, or `unknown`.
|
|
1124
|
+
*/
|
|
1125
|
+
const factValueOf = (value, asText) => {
|
|
1126
|
+
if (asText) return {
|
|
1127
|
+
kind: "text",
|
|
1128
|
+
value
|
|
1129
|
+
};
|
|
1130
|
+
if (value === unknownValue) return unknownValue;
|
|
1131
|
+
if (value === "true" || value === "false") return {
|
|
1132
|
+
kind: "boolean",
|
|
1133
|
+
value: value === "true"
|
|
1134
|
+
};
|
|
1135
|
+
return jsonNumber.test(value) ? {
|
|
1136
|
+
kind: "number",
|
|
1137
|
+
value: Number(value)
|
|
1138
|
+
} : {
|
|
1139
|
+
kind: "text",
|
|
1140
|
+
value
|
|
1141
|
+
};
|
|
1142
|
+
};
|
|
971
1143
|
/**
|
|
972
1144
|
* Runs the command line with the given arguments, writing only through the ports, and resolves with the exit status. It never exits the process or reads its arguments itself, so a test or a host program such as Switchboard runs it as a function.
|
|
973
1145
|
* @param args - The arguments after the program name, such as `["ready", "--json"]`.
|
|
@@ -1013,14 +1185,19 @@ const runCli = async (args, ports, identity) => {
|
|
|
1013
1185
|
program.command("sync").description("read the scopes from their sources and merge them into the model").argument("<scope...>", "source-qualified scopes, such as github:owner or github:owner/repo").option("--after <cursor>", "resume an interrupted sync after this cursor").action(async (scopes, options, command) => {
|
|
1014
1186
|
const { after } = options;
|
|
1015
1187
|
await withSession(async (session) => {
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1188
|
+
try {
|
|
1189
|
+
const result = await sync(session, ports.source, typeof after === "string" ? {
|
|
1190
|
+
scopes,
|
|
1191
|
+
after
|
|
1192
|
+
} : { scopes });
|
|
1193
|
+
return {
|
|
1194
|
+
json: result,
|
|
1195
|
+
text: renderSync(result)
|
|
1196
|
+
};
|
|
1197
|
+
} catch (error) {
|
|
1198
|
+
if (error instanceof SyncInterruptedError) throw new CliError(error.exitCode, renderSyncInterruption(error.message, error), { cause: error });
|
|
1199
|
+
throw error;
|
|
1200
|
+
}
|
|
1024
1201
|
})(command);
|
|
1025
1202
|
});
|
|
1026
1203
|
program.command("ready").description("list the work that can start now").action(async (_options, command) => {
|
|
@@ -1131,15 +1308,17 @@ const runCli = async (args, ports, identity) => {
|
|
|
1131
1308
|
}
|
|
1132
1309
|
}, "participant");
|
|
1133
1310
|
});
|
|
1134
|
-
addCommand.command("activity").description("add an activity: a step of work").argument("<id>", "its identifier").argument("<label>", "its label").option("--actor <participant>", "the role or system that does it").action(async (id, label, options, command) => {
|
|
1135
|
-
const { actor } = options;
|
|
1311
|
+
addCommand.command("activity").description("add an activity: a step of work").argument("<id>", "its identifier").argument("<label>", "its label").option("--actor <participant>", "the role or system that does it").option("--consumes <artefact...>", "the artefacts it needs before it can start").option("--produces <artefact...>", "the artefacts it makes").action(async (id, label, options, command) => {
|
|
1312
|
+
const { actor, consumes, produces } = options;
|
|
1136
1313
|
await added(command, {
|
|
1137
1314
|
kind: "element",
|
|
1138
1315
|
record: {
|
|
1139
1316
|
kind: "activity",
|
|
1140
1317
|
id,
|
|
1141
1318
|
label,
|
|
1142
|
-
...typeof actor === "string" ? { actor } : {}
|
|
1319
|
+
...typeof actor === "string" ? { actor } : {},
|
|
1320
|
+
...Array.isArray(consumes) ? { consumes: consumes.filter(isText) } : {},
|
|
1321
|
+
...Array.isArray(produces) ? { produces: produces.filter(isText) } : {}
|
|
1143
1322
|
}
|
|
1144
1323
|
}, "activity");
|
|
1145
1324
|
});
|
|
@@ -1202,7 +1381,7 @@ const runCli = async (args, ports, identity) => {
|
|
|
1202
1381
|
}
|
|
1203
1382
|
}, "transition");
|
|
1204
1383
|
});
|
|
1205
|
-
program.command("set").description("set an element's run state").argument("<id>", "the element").addArgument(new Argument("<state>", "its run state").choices(overlayStatuses)).action(async (id, state, _options, command) => {
|
|
1384
|
+
const setCommand = program.command("set").description("set an element's run state; set artefact and set fact record what is known from outside the process").argument("<id>", "the element").addArgument(new Argument("<state>", "its run state").choices(overlayStatuses)).action(async (id, state, _options, command) => {
|
|
1206
1385
|
const status = choice(overlayStatuses, state, "state");
|
|
1207
1386
|
await withSession(async (session) => {
|
|
1208
1387
|
const result = await setState(session, id, status);
|
|
@@ -1212,6 +1391,35 @@ const runCli = async (args, ports, identity) => {
|
|
|
1212
1391
|
};
|
|
1213
1392
|
})(command);
|
|
1214
1393
|
});
|
|
1394
|
+
setCommand.command("artefact").description("record whether an artefact is provided, so work consuming it can start; unknown forgets it").argument("<id>", "the artefact").addArgument(new Argument("<state>", "whether it is provided").choices(artefactStates)).action(async (id, state, _options, command) => {
|
|
1395
|
+
const provision = choice(artefactStates, state, "state");
|
|
1396
|
+
await withSession(async (session) => {
|
|
1397
|
+
const result = await setArtefact(session, id, provision);
|
|
1398
|
+
return {
|
|
1399
|
+
json: result,
|
|
1400
|
+
text: renderSetArtefact(result)
|
|
1401
|
+
};
|
|
1402
|
+
})(command);
|
|
1403
|
+
});
|
|
1404
|
+
setCommand.command("fact").description("record the value of a named fact that decision conditions read; unknown forgets it").argument("<name>", "the fact's name, as a condition's reference gives it").argument("<value>", "true, false, unknown, a number, or any other text").option("--text", "read the value as text, even true, false or a number").action(async (name, value, options, command) => {
|
|
1405
|
+
const parsed = factValueOf(value, options.text === true);
|
|
1406
|
+
await withSession(async (session) => {
|
|
1407
|
+
const result = await setFact(session, name, parsed);
|
|
1408
|
+
return {
|
|
1409
|
+
json: result,
|
|
1410
|
+
text: renderSetFact(result)
|
|
1411
|
+
};
|
|
1412
|
+
})(command);
|
|
1413
|
+
});
|
|
1414
|
+
program.command("facts").description("list what is recorded about the run: each artefact provided or absent, and each named fact's value").action(async (_options, command) => {
|
|
1415
|
+
await withSession(async (session) => {
|
|
1416
|
+
const result = await factsOf(session);
|
|
1417
|
+
return {
|
|
1418
|
+
json: result,
|
|
1419
|
+
text: renderFacts(result)
|
|
1420
|
+
};
|
|
1421
|
+
})(command);
|
|
1422
|
+
});
|
|
1215
1423
|
program.command("validate").description("check the stored model, or a model file, for problems; exits with the invalid model status when there are any").argument("[file]", "a model as JSON, checked instead of the stored model").action(async (file, _options, command) => {
|
|
1216
1424
|
const outcome = (result) => ({
|
|
1217
1425
|
json: result,
|
|
@@ -1233,61 +1441,6 @@ const runCli = async (args, ports, identity) => {
|
|
|
1233
1441
|
}
|
|
1234
1442
|
return exitCode;
|
|
1235
1443
|
};
|
|
1236
|
-
/** The permission bits of a file mode, without its type. */
|
|
1237
|
-
const permissionBits = 511;
|
|
1238
|
-
/** The bits that give the file's group or anyone else any access. */
|
|
1239
|
-
const groupAndOtherBits = 63;
|
|
1240
|
-
/** The SQLite files a store at `path` uses: the database and its write-ahead log and shared-memory index. */
|
|
1241
|
-
const storeFiles = (path) => [
|
|
1242
|
-
path,
|
|
1243
|
-
`${path}-wal`,
|
|
1244
|
-
`${path}-shm`
|
|
1245
|
-
];
|
|
1246
|
-
/** Whether an error is the file system's report that a path does not exist, or already does. */
|
|
1247
|
-
const hasCode = (error, code) => error instanceof Error && "code" in error && error.code === code;
|
|
1248
|
-
/**
|
|
1249
|
-
* Narrows a path's mode to `mode` when its group or others have any access.
|
|
1250
|
-
* @param path - The path.
|
|
1251
|
-
* @param mode - The private mode it should have.
|
|
1252
|
-
* @returns Whether the mode was changed; `false` too when the path does not exist.
|
|
1253
|
-
*/
|
|
1254
|
-
const tighten = (path, mode) => {
|
|
1255
|
-
let current;
|
|
1256
|
-
try {
|
|
1257
|
-
current = statSync(path).mode & permissionBits;
|
|
1258
|
-
} catch (error) {
|
|
1259
|
-
if (hasCode(error, "ENOENT")) return false;
|
|
1260
|
-
throw error;
|
|
1261
|
-
}
|
|
1262
|
-
if ((current & groupAndOtherBits) === 0) return false;
|
|
1263
|
-
chmodSync(path, mode);
|
|
1264
|
-
return true;
|
|
1265
|
-
};
|
|
1266
|
-
/**
|
|
1267
|
-
* Makes a data file ready for SQLite to open. The directory is created when missing, private when it is the command line's own. The database file is created private when missing, before SQLite opens it, so the file and the sidecars SQLite derives from it are never readable by others, whatever the umask; a file that already exists is left alone unless the directory is the command line's own. In its own directory, an existing directory or store file that group or others can read is narrowed to the private mode.
|
|
1268
|
-
* @param path - The database file.
|
|
1269
|
-
* @param options - How much of the path the command line owns, and the platform.
|
|
1270
|
-
* @returns One warning line naming what was narrowed, or nothing.
|
|
1271
|
-
*/
|
|
1272
|
-
const prepareDataFile = (path, options) => {
|
|
1273
|
-
const directory = dirname(path);
|
|
1274
|
-
if (options.platform === "win32") {
|
|
1275
|
-
mkdirSync(directory, { recursive: true });
|
|
1276
|
-
return [];
|
|
1277
|
-
}
|
|
1278
|
-
mkdirSync(directory, options.ownsDirectory ? {
|
|
1279
|
-
recursive: true,
|
|
1280
|
-
mode: 448
|
|
1281
|
-
} : { recursive: true });
|
|
1282
|
-
try {
|
|
1283
|
-
closeSync(openSync(path, "wx", 384));
|
|
1284
|
-
} catch (error) {
|
|
1285
|
-
if (!hasCode(error, "EEXIST")) throw error;
|
|
1286
|
-
}
|
|
1287
|
-
if (!options.ownsDirectory) return [];
|
|
1288
|
-
const narrowed = [...tighten(directory, 448) ? [directory] : [], ...storeFiles(path).filter((file) => tighten(file, 384))];
|
|
1289
|
-
return narrowed.length === 0 ? [] : [`made ${narrowed.join(", ")} readable by its owner only, since other users could read the synced work in it`];
|
|
1290
|
-
};
|
|
1291
1444
|
//#endregion
|
|
1292
1445
|
//#region ../graph-cli/dist/node.js
|
|
1293
1446
|
/** The variable that names the data file outright. */
|
|
@@ -1330,7 +1483,7 @@ const packageVersion = (manifest) => {
|
|
|
1330
1483
|
return parsed.version;
|
|
1331
1484
|
};
|
|
1332
1485
|
/**
|
|
1333
|
-
* The ports for a Node process: the SQLite store over `node:sqlite` (creating the data file's directory when needed), the GitHub issues source for `github:` scopes, the file system and the process's streams.
|
|
1486
|
+
* The ports for a Node process: the SQLite store over `node:sqlite` (creating the data file's directory when needed, private when the command line or the host owns it), the GitHub issues source for `github:` scopes, the file system and the process's streams.
|
|
1334
1487
|
* @param options - Replacements for any of the defaults.
|
|
1335
1488
|
* @returns The ports.
|
|
1336
1489
|
*/
|
|
@@ -1338,7 +1491,7 @@ const nodePorts = (options = {}) => {
|
|
|
1338
1491
|
const env = options.env ?? process.env;
|
|
1339
1492
|
const token = options.token ?? githubTokenFromEnvironment({ env });
|
|
1340
1493
|
const home = options.home ?? homedir();
|
|
1341
|
-
const ownDataPath = xdgDataPath(env, home);
|
|
1494
|
+
const ownDataPath = options.dataPath ?? xdgDataPath(env, home);
|
|
1342
1495
|
return {
|
|
1343
1496
|
stdout: options.stdout ?? ((text) => {
|
|
1344
1497
|
process.stdout.write(text);
|
|
@@ -1346,7 +1499,7 @@ const nodePorts = (options = {}) => {
|
|
|
1346
1499
|
stderr: options.stderr ?? ((text) => {
|
|
1347
1500
|
process.stderr.write(text);
|
|
1348
1501
|
}),
|
|
1349
|
-
defaultDataPath: defaultDataPath(env, home),
|
|
1502
|
+
defaultDataPath: options.dataPath ?? defaultDataPath(env, home),
|
|
1350
1503
|
openStore: (path) => {
|
|
1351
1504
|
const warnings = path === inMemory ? [] : prepareDataFile(path, {
|
|
1352
1505
|
ownsDirectory: path === ownDataPath,
|
|
@@ -1385,4 +1538,4 @@ const runProcess = async (identity) => {
|
|
|
1385
1538
|
return runCli(process.argv.slice(2), nodePorts({ signal: controller.signal }), identity);
|
|
1386
1539
|
};
|
|
1387
1540
|
//#endregion
|
|
1388
|
-
export {
|
|
1541
|
+
export { why as A, CliError as B, swimlaneToMermaid as C, impactOf as D, handoffsOf as E, setFact as F, setState as I, findRun as L, artefactStates as M, factsOf as N, path as O, setArtefact as P, loadRun as R, stateMachineToMermaid as S, cycles as T, failureOf as V, sequenceToDot as _, defaultModelId as a, flowchartToMermaid as b, validateStored as c, emit as d, formatNames as f, flowchartToDot as g, viewNames as h, runProcess as i, add as j, ready as k, SyncInterruptedError as l, show as m, nodePorts as n, runCli as o, project as p, packageVersion as r, validateDocument as s, defaultDataPath as t, sync as u, stateMachineToDot as v, blocked as w, sequenceToMermaid as x, swimlaneToDot as y, runFacts as z };
|