@scrthq/runlog 0.0.39 → 0.0.41
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 +1 -0
- package/dist/runlog.js +912 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@ release, and issue sealed copies to buyers.
|
|
|
7
7
|
```bash
|
|
8
8
|
npx @scrthq/runlog validate my-game.yaml --strict
|
|
9
9
|
npx @scrthq/runlog test my-game.yaml
|
|
10
|
+
npx @scrthq/runlog docs my-game.yaml -o dist/docs # rulebook, quick start, reference card, run log sheet, summary
|
|
10
11
|
npx @scrthq/runlog keygen -o my-key.json
|
|
11
12
|
npx @scrthq/runlog sign my-game.yaml --key my-key.json --as "Your Name"
|
|
12
13
|
npx @scrthq/runlog issue my-game.yaml --to "Buyer" --ref order-1 --key my-key.json --seal
|
package/dist/runlog.js
CHANGED
|
@@ -7369,8 +7369,8 @@ var require_dist = __commonJS({
|
|
|
7369
7369
|
|
|
7370
7370
|
// packages/cli/src/bin.ts
|
|
7371
7371
|
var import_yaml4 = __toESM(require_dist(), 1);
|
|
7372
|
-
import { readFileSync as
|
|
7373
|
-
import { basename, dirname, resolve as
|
|
7372
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, existsSync as existsSync3 } from "node:fs";
|
|
7373
|
+
import { basename as basename2, dirname, resolve as resolve4 } from "node:path";
|
|
7374
7374
|
import { createHash } from "node:crypto";
|
|
7375
7375
|
|
|
7376
7376
|
// node_modules/zod/v4/classic/external.js
|
|
@@ -17794,8 +17794,8 @@ var error43 = () => {
|
|
|
17794
17794
|
case "not_multiple_of":
|
|
17795
17795
|
return `N\xFAmero inv\xE1lido: deve ser m\xFAltiplo de ${issue2.divisor}`;
|
|
17796
17796
|
case "unrecognized_keys": {
|
|
17797
|
-
const
|
|
17798
|
-
return `Chave${
|
|
17797
|
+
const plural2 = issue2.keys.length > 1 ? "s" : "";
|
|
17798
|
+
return `Chave${plural2} inv\xE1lida${plural2}: ${joinValues(issue2.keys, ", ")}`;
|
|
17799
17799
|
}
|
|
17800
17800
|
case "invalid_key":
|
|
17801
17801
|
return `Entrada inv\xE1lida n${translateOriginWithArticle(issue2.origin, "definite")}`;
|
|
@@ -17937,8 +17937,8 @@ var error44 = () => {
|
|
|
17937
17937
|
case "not_multiple_of":
|
|
17938
17938
|
return `N\xFAmero inv\xE1lido: deve ser m\xFAltiplo de ${issue2.divisor}`;
|
|
17939
17939
|
case "unrecognized_keys": {
|
|
17940
|
-
const
|
|
17941
|
-
return `Chave${
|
|
17940
|
+
const plural2 = issue2.keys.length > 1 ? "s" : "";
|
|
17941
|
+
return `Chave${plural2} inv\xE1lida${plural2}: ${joinValues(issue2.keys, ", ")}`;
|
|
17942
17942
|
}
|
|
17943
17943
|
case "invalid_key":
|
|
17944
17944
|
return `Entrada inv\xE1lida n${translateOriginWithArticle(issue2.origin, "definite")}`;
|
|
@@ -20135,9 +20135,9 @@ function codePointLengthVar(doc, ctx, accessor, inDoubt) {
|
|
|
20135
20135
|
doc.write(`const ${v} = typeof ${accessor} === "string" && ${inDoubt} ? ${cpLen}(${accessor}) : ${accessor}.length;`);
|
|
20136
20136
|
return v;
|
|
20137
20137
|
}
|
|
20138
|
-
function numericOperand(value,
|
|
20138
|
+
function numericOperand(value, label2) {
|
|
20139
20139
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
20140
|
-
throw new ZodCompileUnsupportedError(`${
|
|
20140
|
+
throw new ZodCompileUnsupportedError(`${label2} bound of type ${typeof value}`);
|
|
20141
20141
|
}
|
|
20142
20142
|
return `${value}`;
|
|
20143
20143
|
}
|
|
@@ -26617,7 +26617,7 @@ var Deck = external_exports.discriminatedUnion("kind", [
|
|
|
26617
26617
|
),
|
|
26618
26618
|
resolveBy: external_exports.enum(["rank", "suit"]).default("rank").describe("Whether the draw's rank or its suit selects the table entry.")
|
|
26619
26619
|
}).strict().describe(
|
|
26620
|
-
"A standard 52-card deck. Included because a whole family of solo
|
|
26620
|
+
"A standard 52-card deck. Included because a whole family of solo journaling games is built on drawing from one."
|
|
26621
26621
|
)
|
|
26622
26622
|
]).describe("A deck the game draws from.");
|
|
26623
26623
|
|
|
@@ -26794,7 +26794,7 @@ var JournalDef = external_exports.object({
|
|
|
26794
26794
|
prompt: external_exports.string().optional().describe("The question shown above the note field."),
|
|
26795
26795
|
required: external_exports.boolean().default(false).describe("Require a note before a unit can be closed.")
|
|
26796
26796
|
}).strict().describe(
|
|
26797
|
-
"Free-text notes attached to units. Essential for
|
|
26797
|
+
"Free-text notes attached to units. Essential for journaling games, and useful anywhere, since a log of rolls alone does not tell you what you actually made."
|
|
26798
26798
|
);
|
|
26799
26799
|
var EndingDef = external_exports.object({
|
|
26800
26800
|
id: Id.describe("Unique among endings."),
|
|
@@ -27524,13 +27524,13 @@ function lintPack(pack) {
|
|
|
27524
27524
|
}
|
|
27525
27525
|
}
|
|
27526
27526
|
const claimed = new Set(pack.capabilities);
|
|
27527
|
-
const requireCap = (
|
|
27528
|
-
if (used && !claimed.has(
|
|
27527
|
+
const requireCap = (cap2, used, why) => {
|
|
27528
|
+
if (used && !claimed.has(cap2)) {
|
|
27529
27529
|
d.push(
|
|
27530
27530
|
warn(
|
|
27531
27531
|
"capability/undeclared",
|
|
27532
27532
|
"capabilities",
|
|
27533
|
-
`pack ${why} but does not declare the "${
|
|
27533
|
+
`pack ${why} but does not declare the "${cap2}" capability`
|
|
27534
27534
|
)
|
|
27535
27535
|
);
|
|
27536
27536
|
}
|
|
@@ -27638,12 +27638,12 @@ function parsePack(input2) {
|
|
|
27638
27638
|
const unsupported = pack.capabilities.filter(
|
|
27639
27639
|
(c) => !IMPLEMENTED_CAPABILITIES.includes(c)
|
|
27640
27640
|
);
|
|
27641
|
-
for (const
|
|
27641
|
+
for (const cap2 of unsupported) {
|
|
27642
27642
|
diagnostics.push({
|
|
27643
27643
|
level: "error",
|
|
27644
27644
|
code: "pack/unsupported-capability",
|
|
27645
27645
|
path: "capabilities",
|
|
27646
|
-
message: `pack requires the "${
|
|
27646
|
+
message: `pack requires the "${cap2}" capability, which this build does not implement`
|
|
27647
27647
|
});
|
|
27648
27648
|
}
|
|
27649
27649
|
if (hasErrors(diagnostics)) return { ok: false, pack: null, diagnostics };
|
|
@@ -27885,6 +27885,830 @@ async function verifyPack(document) {
|
|
|
27885
27885
|
}
|
|
27886
27886
|
}
|
|
27887
27887
|
|
|
27888
|
+
// packages/rules-schema/src/describe.ts
|
|
27889
|
+
function entryKeys(table) {
|
|
27890
|
+
switch (table.resolution) {
|
|
27891
|
+
case "lookup":
|
|
27892
|
+
return table.entries.map((e) => e.range[0] === e.range[1] ? `${e.range[0]}` : `${e.range[0]}\u2013${e.range[1]}`);
|
|
27893
|
+
case "bands":
|
|
27894
|
+
return table.entries.map(
|
|
27895
|
+
(e) => [e.gte !== void 0 ? `\u2265${e.gte}` : "", e.lte !== void 0 ? `\u2264${e.lte}` : ""].filter(Boolean).join(" ")
|
|
27896
|
+
);
|
|
27897
|
+
case "opposed":
|
|
27898
|
+
return table.entries.map((e) => `beats ${e.beats}`);
|
|
27899
|
+
case "keyed":
|
|
27900
|
+
return table.entries.map((e) => e.key);
|
|
27901
|
+
}
|
|
27902
|
+
}
|
|
27903
|
+
function howConsulted(table) {
|
|
27904
|
+
switch (table.resolution) {
|
|
27905
|
+
case "lookup":
|
|
27906
|
+
return `Roll ${table.roll}.`;
|
|
27907
|
+
case "bands":
|
|
27908
|
+
return `Roll ${table.roll} and find the band it lands in.`;
|
|
27909
|
+
case "opposed":
|
|
27910
|
+
return `Roll ${table.action}${table.addResource ? ` and add ${table.addResource}` : ""} against ${table.challenge.count} \xD7 ${table.challenge.dice}; count how many of the challenge dice your total beats.`;
|
|
27911
|
+
case "keyed":
|
|
27912
|
+
return "Find the row whose key matches what was drawn.";
|
|
27913
|
+
}
|
|
27914
|
+
}
|
|
27915
|
+
function nouns(pack) {
|
|
27916
|
+
const v = pack.vocabulary;
|
|
27917
|
+
return {
|
|
27918
|
+
run: v.run.one,
|
|
27919
|
+
runs: v.run.many,
|
|
27920
|
+
unit: v.unit.one,
|
|
27921
|
+
units: v.unit.many,
|
|
27922
|
+
subject: v.subject.one,
|
|
27923
|
+
subjects: v.subject.many,
|
|
27924
|
+
finalize: v.finalize
|
|
27925
|
+
};
|
|
27926
|
+
}
|
|
27927
|
+
var label = (pack, kind, id) => {
|
|
27928
|
+
const entry = pack[kind]?.[id];
|
|
27929
|
+
return entry?.label ?? entry?.title ?? id;
|
|
27930
|
+
};
|
|
27931
|
+
function targetInWords(pack, to) {
|
|
27932
|
+
const n = nouns(pack);
|
|
27933
|
+
if (to === void 0) return `this ${n.subject}`;
|
|
27934
|
+
if (typeof to === "object") return `the ${n.subject} chosen as \u201C${to.var}\u201D`;
|
|
27935
|
+
switch (to) {
|
|
27936
|
+
case "thisSubject":
|
|
27937
|
+
return `this ${n.subject}`;
|
|
27938
|
+
case "targetSubject":
|
|
27939
|
+
return `the target`;
|
|
27940
|
+
case "allSubjects":
|
|
27941
|
+
return `every ${n.subject}`;
|
|
27942
|
+
case "allPriorSubjects":
|
|
27943
|
+
return `every earlier ${n.subject}`;
|
|
27944
|
+
case "run":
|
|
27945
|
+
return `the ${n.run}`;
|
|
27946
|
+
}
|
|
27947
|
+
}
|
|
27948
|
+
function boundInWords(pack, b) {
|
|
27949
|
+
const parts = [];
|
|
27950
|
+
if (b.eq !== void 0) parts.push(`exactly ${b.eq}`);
|
|
27951
|
+
if (b.gte !== void 0 && b.lte !== void 0) parts.push(`between ${b.gte} and ${b.lte}`);
|
|
27952
|
+
else {
|
|
27953
|
+
if (b.gte !== void 0) parts.push(`${b.gte} or more`);
|
|
27954
|
+
if (b.lte !== void 0) parts.push(`${b.lte} or fewer`);
|
|
27955
|
+
}
|
|
27956
|
+
if (b.gteCounter !== void 0) parts.push(`at least the ${label(pack, "counters", b.gteCounter)} tally`);
|
|
27957
|
+
if (b.lteCounter !== void 0) parts.push(`no more than the ${label(pack, "counters", b.lteCounter)} tally`);
|
|
27958
|
+
return parts.join(" and ");
|
|
27959
|
+
}
|
|
27960
|
+
function predicateInWords(pack, p) {
|
|
27961
|
+
const n = nouns(pack);
|
|
27962
|
+
if ("ask" in p) return `you answer yes to \u201C${p.ask}\u201D`;
|
|
27963
|
+
if ("unitIndex" in p) return p.unitIndex.eq !== void 0 ? `this is ${n.unit} ${p.unitIndex.eq}` : `the ${n.unit} number is ${boundInWords(pack, p.unitIndex)}`;
|
|
27964
|
+
if ("subjectCount" in p) return `there are ${boundInWords(pack, p.subjectCount)} ${n.subjects}`;
|
|
27965
|
+
if ("eligibleTargets" in p) return `there are ${boundInWords(pack, p.eligibleTargets)} ${n.subjects} that can be targeted`;
|
|
27966
|
+
if ("counter" in p) return `${label(pack, "counters", p.counter)} is ${boundInWords(pack, p.is)}`;
|
|
27967
|
+
if ("resource" in p) return `${label(pack, "resources", p.resource)} is ${boundInWords(pack, p.is)}`;
|
|
27968
|
+
if ("flag" in p) return `${p.flag} is ${p.is === false ? "not " : ""}set`;
|
|
27969
|
+
if ("subjectHasState" in p) return `${targetInWords(pack, p.of)} is ${label(pack, "states", p.subjectHasState)}`;
|
|
27970
|
+
if ("priorSubjectTagged" in p) return `an earlier ${n.subject} was tagged \u201C${p.priorSubjectTagged}\u201D`;
|
|
27971
|
+
if ("modeIs" in p) return `playing ${p.modeIs.map((m) => label(pack, "modes", m)).join(" or ")}`;
|
|
27972
|
+
if ("not" in p) return `not (${predicateInWords(pack, p.not)})`;
|
|
27973
|
+
if ("allOf" in p) return p.allOf.map((q) => predicateInWords(pack, q)).join(" and ");
|
|
27974
|
+
if ("anyOf" in p) return p.anyOf.map((q) => predicateInWords(pack, q)).join(" or ");
|
|
27975
|
+
return "";
|
|
27976
|
+
}
|
|
27977
|
+
function conditionsInWords(pack, preds, joiner = "all") {
|
|
27978
|
+
if (!preds || preds.length === 0) return "";
|
|
27979
|
+
return preds.map((p) => predicateInWords(pack, p)).join(joiner === "all" ? " and " : ", or ");
|
|
27980
|
+
}
|
|
27981
|
+
var signed = (by, set2) => set2 !== void 0 ? `set to ${set2}` : by === void 0 ? "+1" : by >= 0 ? `+${by}` : `\u2212${Math.abs(by)}`;
|
|
27982
|
+
function actionInWords(pack, a) {
|
|
27983
|
+
const n = nouns(pack);
|
|
27984
|
+
switch (a.do) {
|
|
27985
|
+
case "roll":
|
|
27986
|
+
return a.label ? `${a.label} (${a.dice})` : `roll ${a.dice}`;
|
|
27987
|
+
case "rollOn": {
|
|
27988
|
+
const t = label(pack, "tables", a.table);
|
|
27989
|
+
if (a.times && a.times > 1) return `roll ${a.times} times on ${t}${a.choose === "one" ? " and keep one" : ""}`;
|
|
27990
|
+
return `roll on ${t}`;
|
|
27991
|
+
}
|
|
27992
|
+
case "branch": {
|
|
27993
|
+
const cases = a.cases.map((c) => {
|
|
27994
|
+
const cond = c.in ? `on ${c.in.join(", ")}` : c.is ? boundInWords(pack, c.is) : "otherwise";
|
|
27995
|
+
return `${cond} \u2192 ${actionsInWords(pack, c.then)}`;
|
|
27996
|
+
});
|
|
27997
|
+
if (a.else) cases.push(`otherwise \u2192 ${actionsInWords(pack, a.else)}`);
|
|
27998
|
+
return `then, by the result: ${cases.join("; ")}`;
|
|
27999
|
+
}
|
|
28000
|
+
case "prompt":
|
|
28001
|
+
return a.label;
|
|
28002
|
+
case "resolveTarget":
|
|
28003
|
+
return `pick the target${a.from === "choice" ? " yourself" : a.from === "event" ? " by the pack's targeting rule" : " from the roll"}`;
|
|
28004
|
+
case "applyState":
|
|
28005
|
+
return `mark ${targetInWords(pack, a.to)} ${label(pack, "states", a.state)}`;
|
|
28006
|
+
case "removeState":
|
|
28007
|
+
return `clear ${label(pack, "states", a.state)} from ${targetInWords(pack, a.to)}`;
|
|
28008
|
+
case "removeSubject":
|
|
28009
|
+
return `remove ${targetInWords(pack, a.to)} from play`;
|
|
28010
|
+
case "ban":
|
|
28011
|
+
return a.label ?? (a.subjectType ? `no more ${a.subjectType} ${n.subjects} this ${n.run}` : `no more ${n.subjects} of ${targetInWords(pack, a.from)}'s kind this ${n.run}`);
|
|
28012
|
+
case "forceUnit": {
|
|
28013
|
+
const c = a.count ?? 1;
|
|
28014
|
+
return `add ${c} more ${c === 1 ? n.unit : n.units} before the ${n.run} may end`;
|
|
28015
|
+
}
|
|
28016
|
+
case "modCounter":
|
|
28017
|
+
return `${label(pack, "counters", a.counter)} ${signed(a.by, a.set)}`;
|
|
28018
|
+
case "modResource":
|
|
28019
|
+
return `${label(pack, "resources", a.resource)} ${signed(a.by, a.set)}`;
|
|
28020
|
+
case "grantCard": {
|
|
28021
|
+
const c = a.count ?? 1;
|
|
28022
|
+
return `draw ${c} from ${label(pack, "decks", a.deck)}`;
|
|
28023
|
+
}
|
|
28024
|
+
case "discardCard": {
|
|
28025
|
+
const c = a.count ?? 1;
|
|
28026
|
+
return `discard ${c} from ${label(pack, "decks", a.deck)}`;
|
|
28027
|
+
}
|
|
28028
|
+
case "startTimer":
|
|
28029
|
+
return a.label ? `${a.label} (${a.minutes} min)` : `start a ${a.minutes}-minute timer`;
|
|
28030
|
+
case "note":
|
|
28031
|
+
return a.text;
|
|
28032
|
+
case "setFlag":
|
|
28033
|
+
return a.value === false ? `unset ${a.flag}` : `set ${a.flag}`;
|
|
28034
|
+
case "endRunAttempt":
|
|
28035
|
+
return `try to end the ${n.run}`;
|
|
28036
|
+
case "when": {
|
|
28037
|
+
const then = `if ${conditionsInWords(pack, a.all)}: ${actionsInWords(pack, a.then)}`;
|
|
28038
|
+
return a.else ? `${then}; otherwise ${actionsInWords(pack, a.else)}` : then;
|
|
28039
|
+
}
|
|
28040
|
+
}
|
|
28041
|
+
}
|
|
28042
|
+
function actionsInWords(pack, actions) {
|
|
28043
|
+
return actions.map((a) => unstop(actionInWords(pack, a))).join("; ");
|
|
28044
|
+
}
|
|
28045
|
+
function unstop(s) {
|
|
28046
|
+
return s.replace(/\.\s*$/, "");
|
|
28047
|
+
}
|
|
28048
|
+
function sentence(s) {
|
|
28049
|
+
const t = s.trim();
|
|
28050
|
+
return /[.!?…]$/.test(t) ? t : `${t}.`;
|
|
28051
|
+
}
|
|
28052
|
+
function triggerPointInWords(pack, on) {
|
|
28053
|
+
const n = nouns(pack);
|
|
28054
|
+
switch (on) {
|
|
28055
|
+
case "immediately":
|
|
28056
|
+
return "Then";
|
|
28057
|
+
case "onEnterUnit":
|
|
28058
|
+
return `When a ${n.unit} begins`;
|
|
28059
|
+
case "onDeclareSubject":
|
|
28060
|
+
return `When the ${n.subject} is declared`;
|
|
28061
|
+
case "afterWork":
|
|
28062
|
+
return "After the work";
|
|
28063
|
+
case "onFinalize":
|
|
28064
|
+
return `When you ${n.finalize}`;
|
|
28065
|
+
case "onDeclareRunOver":
|
|
28066
|
+
return `When you declare the ${n.run} over`;
|
|
28067
|
+
case "onRunEnd":
|
|
28068
|
+
return `When the ${n.run} ends`;
|
|
28069
|
+
}
|
|
28070
|
+
}
|
|
28071
|
+
function triggerInWords(pack, t) {
|
|
28072
|
+
const when = conditionsInWords(pack, t.when);
|
|
28073
|
+
const head = t.label ? `${t.label} \u2014 ${triggerPointInWords(pack, t.on).toLowerCase()}` : triggerPointInWords(pack, t.on);
|
|
28074
|
+
return `${head}${when ? ` (if ${when})` : ""}: ${actionsInWords(pack, t.do)}`;
|
|
28075
|
+
}
|
|
28076
|
+
function modeLength(pack, mode) {
|
|
28077
|
+
const n = nouns(pack);
|
|
28078
|
+
const u = mode.units;
|
|
28079
|
+
if (u?.fixed !== void 0) return `${u.fixed} ${u.fixed === 1 ? n.unit : n.units}`;
|
|
28080
|
+
if (u?.roll) return `roll ${u.roll} for the number of ${n.units}`;
|
|
28081
|
+
if (u?.min !== void 0 && u?.max !== void 0) return `${u.min}\u2013${u.max} ${n.units}`;
|
|
28082
|
+
if (u?.max !== void 0) return `up to ${u.max} ${n.units}`;
|
|
28083
|
+
if (u?.min !== void 0) return `at least ${u.min} ${n.units}`;
|
|
28084
|
+
const max = pack.unit.max;
|
|
28085
|
+
return `as many ${n.units} as you like${max ? `, up to ${max}` : ""}`;
|
|
28086
|
+
}
|
|
28087
|
+
function modePlayers(mode) {
|
|
28088
|
+
const p = mode.players;
|
|
28089
|
+
if (!p || p.min <= 1 && p.max <= 1) return "solo";
|
|
28090
|
+
return p.min === p.max ? `${p.min} players` : `${p.min}\u2013${p.max} players`;
|
|
28091
|
+
}
|
|
28092
|
+
var diceOf = (expr) => {
|
|
28093
|
+
const m = /^(\d*)d(\d+)/.exec(expr);
|
|
28094
|
+
return m ? `d${m[2]}` : expr;
|
|
28095
|
+
};
|
|
28096
|
+
function diceNeeded(pack) {
|
|
28097
|
+
const found = /* @__PURE__ */ new Set();
|
|
28098
|
+
const seeActions = (actions) => {
|
|
28099
|
+
for (const a of actions ?? []) {
|
|
28100
|
+
if (a.do === "roll") found.add(diceOf(a.dice));
|
|
28101
|
+
if (a.do === "branch") {
|
|
28102
|
+
for (const c of a.cases) seeActions(c.then);
|
|
28103
|
+
seeActions(a.else);
|
|
28104
|
+
}
|
|
28105
|
+
if (a.do === "when") {
|
|
28106
|
+
seeActions(a.then);
|
|
28107
|
+
seeActions(a.else);
|
|
28108
|
+
}
|
|
28109
|
+
}
|
|
28110
|
+
};
|
|
28111
|
+
const seeTriggers = (triggers) => {
|
|
28112
|
+
for (const t of triggers ?? []) seeActions(t.do);
|
|
28113
|
+
};
|
|
28114
|
+
for (const table of Object.values(pack.tables)) {
|
|
28115
|
+
if (table.resolution === "lookup" || table.resolution === "bands") found.add(diceOf(table.roll));
|
|
28116
|
+
if (table.resolution === "opposed") {
|
|
28117
|
+
found.add(diceOf(table.action));
|
|
28118
|
+
found.add(diceOf(table.challenge.dice));
|
|
28119
|
+
}
|
|
28120
|
+
for (const e of table.entries) seeTriggers(e.triggers);
|
|
28121
|
+
}
|
|
28122
|
+
for (const deck of Object.values(pack.decks ?? {})) if (deck.kind === "cards") for (const c of deck.cards) seeTriggers(c.triggers);
|
|
28123
|
+
for (const c of Object.values(pack.counters ?? {})) for (const t of c.triggers ?? []) seeActions(t.do);
|
|
28124
|
+
for (const m of Object.values(pack.moves ?? {})) seeActions(m.do);
|
|
28125
|
+
for (const m of Object.values(pack.modes)) {
|
|
28126
|
+
if (m.units?.roll) found.add(diceOf(m.units.roll));
|
|
28127
|
+
for (const p of m.perUnit ?? []) seeActions(p.extra);
|
|
28128
|
+
}
|
|
28129
|
+
for (const p of pack.phases) for (const s of p.steps) if (s.kind === "actions") seeActions(s.do);
|
|
28130
|
+
seeTriggers(pack.triggers);
|
|
28131
|
+
if (pack.targeting?.strategy === "anchoredOffset" && pack.targeting.eventFallback) found.add(diceOf(pack.targeting.eventFallback.roll));
|
|
28132
|
+
return [...found].sort((a, b) => Number(a.slice(1)) - Number(b.slice(1)));
|
|
28133
|
+
}
|
|
28134
|
+
function diceInWords(dice) {
|
|
28135
|
+
const names = dice.map((d) => d === "d100" ? "percentile dice (d100)" : `a ${d}`);
|
|
28136
|
+
if (names.length === 0) return "";
|
|
28137
|
+
if (names.length === 1) return names[0];
|
|
28138
|
+
return `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`;
|
|
28139
|
+
}
|
|
28140
|
+
|
|
28141
|
+
// packages/rules-schema/src/docs.ts
|
|
28142
|
+
var DOC_KINDS = [
|
|
28143
|
+
{ kind: "summary", label: "Summary", what: "What a catalog shows: the shape of the game without its rules.", full: false },
|
|
28144
|
+
{ kind: "rulebook", label: "Rulebook", what: "Everything, in reading order: setup, flow, every table, every rule.", full: true },
|
|
28145
|
+
{ kind: "quickstart", label: "Quick start", what: "Enough to play the first time; the rest is on the reference card.", full: true },
|
|
28146
|
+
{ kind: "reference", label: "Reference card", what: "Every table and the things people forget, compact enough to keep beside you.", full: true },
|
|
28147
|
+
{ kind: "runlog", label: "Run log sheet", what: "A printable sheet to write a run on by hand.", full: true }
|
|
28148
|
+
];
|
|
28149
|
+
var Builder = class {
|
|
28150
|
+
blocks = [];
|
|
28151
|
+
h(level, text) {
|
|
28152
|
+
this.blocks.push({ kind: "heading", level, text });
|
|
28153
|
+
return this;
|
|
28154
|
+
}
|
|
28155
|
+
p(text, tone) {
|
|
28156
|
+
if (text && text.trim()) this.blocks.push(tone ? { kind: "paragraph", text: text.trim(), tone } : { kind: "paragraph", text: text.trim() });
|
|
28157
|
+
return this;
|
|
28158
|
+
}
|
|
28159
|
+
list(items, ordered = false) {
|
|
28160
|
+
if (items.length > 0) this.blocks.push(ordered ? { kind: "list", items, ordered } : { kind: "list", items });
|
|
28161
|
+
return this;
|
|
28162
|
+
}
|
|
28163
|
+
table(columns, rows, compact = false) {
|
|
28164
|
+
if (rows.length > 0) this.blocks.push(compact ? { kind: "table", columns, rows, compact } : { kind: "table", columns, rows });
|
|
28165
|
+
return this;
|
|
28166
|
+
}
|
|
28167
|
+
terms(items) {
|
|
28168
|
+
if (items.length > 0) this.blocks.push({ kind: "terms", items });
|
|
28169
|
+
return this;
|
|
28170
|
+
}
|
|
28171
|
+
form(fields) {
|
|
28172
|
+
this.blocks.push({ kind: "form", fields });
|
|
28173
|
+
return this;
|
|
28174
|
+
}
|
|
28175
|
+
rule() {
|
|
28176
|
+
this.blocks.push({ kind: "rule" });
|
|
28177
|
+
return this;
|
|
28178
|
+
}
|
|
28179
|
+
page() {
|
|
28180
|
+
this.blocks.push({ kind: "pagebreak" });
|
|
28181
|
+
return this;
|
|
28182
|
+
}
|
|
28183
|
+
};
|
|
28184
|
+
var plural = (n, one, many) => `${n} ${n === 1 ? one : many}`;
|
|
28185
|
+
var cap = (s) => s ? s[0].toUpperCase() + s.slice(1) : s;
|
|
28186
|
+
function stepInWords(pack, step) {
|
|
28187
|
+
const n = nouns(pack);
|
|
28188
|
+
switch (step.kind) {
|
|
28189
|
+
case "rollTable":
|
|
28190
|
+
return `${step.label ?? `Roll on ${pack.tables[step.table]?.title ?? step.table}`}${step.optional ? " (optional)" : ""}`;
|
|
28191
|
+
case "declareSubject":
|
|
28192
|
+
return step.label ?? `Declare the ${n.subject}${step.constrainedBy ? `, within what ${pack.tables[step.constrainedBy]?.title ?? step.constrainedBy} allowed` : ""}`;
|
|
28193
|
+
case "manual":
|
|
28194
|
+
return step.label;
|
|
28195
|
+
case "actions":
|
|
28196
|
+
return sentence(cap(actionsInWords(pack, step.do)));
|
|
28197
|
+
case "finalizeUnit":
|
|
28198
|
+
return step.label ?? `${n.finalize} the ${n.unit}`;
|
|
28199
|
+
}
|
|
28200
|
+
}
|
|
28201
|
+
var checklistText = (item) => typeof item === "string" ? item : item.text;
|
|
28202
|
+
function stepDetail(pack, step) {
|
|
28203
|
+
const out = [];
|
|
28204
|
+
if (step.kind === "manual") {
|
|
28205
|
+
if (step.description) out.push(step.description);
|
|
28206
|
+
for (const c of step.checklist ?? []) out.push(`\u2610 ${checklistText(c)}`);
|
|
28207
|
+
}
|
|
28208
|
+
if (step.kind === "finalizeUnit") for (const c of step.confirm ?? []) out.push(`\u2610 ${checklistText(c)}`);
|
|
28209
|
+
if ("skipWhen" in step && step.skipWhen?.length) out.push(`Skipped when ${conditionsInWords(pack, step.skipWhen, "any")}.`);
|
|
28210
|
+
return out;
|
|
28211
|
+
}
|
|
28212
|
+
function whatYouNeed(pack) {
|
|
28213
|
+
const n = nouns(pack);
|
|
28214
|
+
const items = [];
|
|
28215
|
+
const dice = diceInWords(diceNeeded(pack));
|
|
28216
|
+
if (dice) items.push(cap(dice) + ".");
|
|
28217
|
+
for (const deck of Object.values(pack.decks ?? {})) {
|
|
28218
|
+
if (deck.kind === "standard52") items.push(`A standard deck of playing cards${deck.includeJokers ? " with the jokers in" : ""}.`);
|
|
28219
|
+
else items.push(`${plural(deck.cards.length, "card", "cards")} for ${deck.title} \u2014 write them out, or use the app.`);
|
|
28220
|
+
}
|
|
28221
|
+
if (pack.capabilities.includes("timers")) items.push("A timer.");
|
|
28222
|
+
items.push(pack.journal?.enabled === false ? `Somewhere to keep the ${n.run} log.` : `Somewhere to keep the ${n.run} log \u2014 the run log sheet, or the app.`);
|
|
28223
|
+
return items;
|
|
28224
|
+
}
|
|
28225
|
+
function flowSteps(pack, opts) {
|
|
28226
|
+
const items = [];
|
|
28227
|
+
for (const phase of pack.phases) {
|
|
28228
|
+
const head = phase.steps.length === 1 ? `${phase.label}: ${stepInWords(pack, phase.steps[0])}` : phase.label;
|
|
28229
|
+
const notes = [];
|
|
28230
|
+
if (opts.detail && phase.description) notes.push(phase.description);
|
|
28231
|
+
if (opts.detail && phase.skipWhen?.length) notes.push(`Skipped when ${conditionsInWords(pack, phase.skipWhen, "any")}.`);
|
|
28232
|
+
if (phase.steps.length > 1) for (const s of phase.steps) notes.push(`${stepInWords(pack, s)}${opts.detail ? stepDetail(pack, s).map((d) => ` \u2014 ${d}`).join("") : ""}`);
|
|
28233
|
+
else if (opts.detail) notes.push(...stepDetail(pack, phase.steps[0]));
|
|
28234
|
+
items.push([head, ...notes.map((t) => ` ${t}`)].join("\n"));
|
|
28235
|
+
}
|
|
28236
|
+
return items;
|
|
28237
|
+
}
|
|
28238
|
+
function modeRows(pack, opts) {
|
|
28239
|
+
return Object.entries(pack.modes).map(([id, m]) => [
|
|
28240
|
+
`${m.label}${id === pack.defaultMode ? " (default)" : ""}`,
|
|
28241
|
+
modeLength(pack, m),
|
|
28242
|
+
modePlayers(m) + (m.seeded ? ", seeded" : ""),
|
|
28243
|
+
[m.description ?? "", ...opts.notes ? modeExtras(pack, m) : []].filter(Boolean).join(" ")
|
|
28244
|
+
]);
|
|
28245
|
+
}
|
|
28246
|
+
function modeExtras(pack, m) {
|
|
28247
|
+
const n = nouns(pack);
|
|
28248
|
+
const out = [];
|
|
28249
|
+
for (const note of m.notes ?? []) out.push(note);
|
|
28250
|
+
const off = [
|
|
28251
|
+
...(m.disable?.tables ?? []).map((t) => pack.tables[t]?.title ?? t),
|
|
28252
|
+
...(m.disable?.decks ?? []).map((d) => pack.decks?.[d]?.title ?? d),
|
|
28253
|
+
...(m.disable?.counters ?? []).map((c) => pack.counters?.[c]?.label ?? c),
|
|
28254
|
+
...(m.disable?.phases ?? []).map((p) => pack.phases.find((x) => x.id === p)?.label ?? p)
|
|
28255
|
+
];
|
|
28256
|
+
if (off.length) out.push(`Without ${off.join(", ")}.`);
|
|
28257
|
+
for (const p of m.perUnit ?? []) {
|
|
28258
|
+
const which = p.unit === "all" ? `Every ${n.unit}` : `${cap(n.unit)} ${p.unit}`;
|
|
28259
|
+
const skip = (p.skipPhases ?? []).map((id) => pack.phases.find((x) => x.id === id)?.label ?? id);
|
|
28260
|
+
const parts = [skip.length ? `skips ${skip.join(", ")}` : "", p.extra?.length ? actionsInWords(pack, p.extra) : ""].filter(Boolean);
|
|
28261
|
+
if (parts.length) out.push(sentence(`${which}: ${parts.join("; ")}`));
|
|
28262
|
+
}
|
|
28263
|
+
for (const r of m.players?.roles ?? []) out.push(`${r.label}: ${r.description ?? ""}`.trim());
|
|
28264
|
+
return out;
|
|
28265
|
+
}
|
|
28266
|
+
function tableRows(pack, table, opts) {
|
|
28267
|
+
const keys = entryKeys(table);
|
|
28268
|
+
return table.entries.map((e, i) => {
|
|
28269
|
+
const extras = [];
|
|
28270
|
+
if (opts.full) {
|
|
28271
|
+
for (const g of e.grants ?? []) extras.push(`\u2192 ${pack.states?.[g]?.label ?? g}`);
|
|
28272
|
+
for (const t of e.triggers ?? []) extras.push(sentence(triggerInWords(pack, t)));
|
|
28273
|
+
if (e.requires?.length) extras.push(`Only if ${conditionsInWords(pack, e.requires)}; otherwise roll again.`);
|
|
28274
|
+
}
|
|
28275
|
+
const text = [e.title ? `${e.title}. ` : "", sentence(e.text), ...extras.map((x) => ` ${x}`)].join("");
|
|
28276
|
+
return [keys[i] ?? "", text];
|
|
28277
|
+
});
|
|
28278
|
+
}
|
|
28279
|
+
function license(pack) {
|
|
28280
|
+
const l = pack.license;
|
|
28281
|
+
const who = l.holder ? ` \xA9 ${l.holder}` : "";
|
|
28282
|
+
return `${l.id}${who}${l.redistributable ? "" : " \u2014 private; not for redistribution"}${l.notice ? `. ${l.notice}` : ""}${l.url ? ` (${l.url})` : ""}`;
|
|
28283
|
+
}
|
|
28284
|
+
function byline(pack) {
|
|
28285
|
+
return [pack.author ? `by ${pack.author}` : "", `version ${pack.version}`].filter(Boolean).join(" \xB7 ");
|
|
28286
|
+
}
|
|
28287
|
+
function summaryDoc(pack) {
|
|
28288
|
+
const n = nouns(pack);
|
|
28289
|
+
const b = new Builder();
|
|
28290
|
+
b.p(pack.description);
|
|
28291
|
+
b.p(license(pack), "muted");
|
|
28292
|
+
b.h(2, "How it plays");
|
|
28293
|
+
const unitRange = `${pack.unit.min === pack.unit.max ? pack.unit.min : `${pack.unit.min}\u2013${pack.unit.max}`}`;
|
|
28294
|
+
b.p(
|
|
28295
|
+
`A ${n.run} is a series of ${n.units}${pack.unit.createsSubject ? `, each producing a ${n.subject}` : ""}; ${unitRange} of them, depending on the mode. Each ${n.unit} moves through ${plural(pack.phases.length, "phase", "phases")}: ${pack.phases.map((p) => p.label).join(", ")}.`
|
|
28296
|
+
);
|
|
28297
|
+
b.table(["Mode", "Length", "Players", "About"], modeRows(pack, { notes: false }));
|
|
28298
|
+
b.h(2, "What is inside");
|
|
28299
|
+
const tables = Object.values(pack.tables);
|
|
28300
|
+
const decks = Object.values(pack.decks ?? {});
|
|
28301
|
+
const parts = [];
|
|
28302
|
+
if (tables.length) parts.push(`${plural(tables.length, "table", "tables")}: ${tables.map((t) => `${t.title} (${howConsulted(t).replace(/\.$/, "").toLowerCase()}, ${plural(t.entries.length, "entry", "entries")})`).join("; ")}.`);
|
|
28303
|
+
if (decks.length) parts.push(`${plural(decks.length, "deck", "decks")}: ${decks.map((d) => d.kind === "cards" ? `${d.title} (${plural(d.cards.length, "card", "cards")})` : `${d.title} (a standard deck)`).join("; ")}.`);
|
|
28304
|
+
const states = Object.values(pack.states ?? {});
|
|
28305
|
+
if (states.length) parts.push(`${plural(states.length, "state", "states")} a ${n.subject} or ${n.run} can carry: ${states.map((s) => s.label).join(", ")}.`);
|
|
28306
|
+
const counters = Object.values(pack.counters ?? {}).filter((c) => !c.hidden);
|
|
28307
|
+
if (counters.length) parts.push(`${plural(counters.length, "tally", "tallies")}: ${counters.map((c) => c.label).join(", ")}.`);
|
|
28308
|
+
const resources = Object.values(pack.resources ?? {});
|
|
28309
|
+
if (resources.length) parts.push(`${plural(resources.length, "track", "tracks")}: ${resources.map((r) => r.label).join(", ")}.`);
|
|
28310
|
+
const moves = Object.values(pack.moves ?? {});
|
|
28311
|
+
if (moves.length) parts.push(`${plural(moves.length, "move", "moves")} you may make: ${moves.map((m) => m.label).join(", ")}.`);
|
|
28312
|
+
if (pack.endings?.length) parts.push(`${plural(pack.endings.length, "ending", "endings")}: ${pack.endings.map((e) => e.label).join(", ")}.`);
|
|
28313
|
+
if (pack.targeting && pack.targeting.strategy !== "none") parts.push(`Consequences can reach back to earlier ${n.subjects}.`);
|
|
28314
|
+
if (pack.journal?.enabled !== false) parts.push(`A journal line per ${n.unit}${pack.journal?.required ? ", required" : ""}.`);
|
|
28315
|
+
b.list(parts);
|
|
28316
|
+
b.h(2, "What you need");
|
|
28317
|
+
b.list(whatYouNeed(pack));
|
|
28318
|
+
if (pack.homepage) b.p(`More at ${pack.homepage}`, "muted");
|
|
28319
|
+
return { kind: "summary", layout: "book", title: pack.title, subtitle: byline(pack), blocks: b.blocks };
|
|
28320
|
+
}
|
|
28321
|
+
function rulebookDoc(pack) {
|
|
28322
|
+
const n = nouns(pack);
|
|
28323
|
+
const b = new Builder();
|
|
28324
|
+
b.p(pack.description);
|
|
28325
|
+
b.p(license(pack), "muted");
|
|
28326
|
+
b.h(2, "What you need");
|
|
28327
|
+
b.list(whatYouNeed(pack));
|
|
28328
|
+
b.h(2, "Terms");
|
|
28329
|
+
b.terms([
|
|
28330
|
+
{ term: cap(n.run), text: `One session of play. A ${n.run} is a series of ${n.units}, and ends when you say so or when the game does.` },
|
|
28331
|
+
{ term: cap(n.unit), text: `One round. Every ${n.unit} passes through the same phases${pack.unit.createsSubject ? ` and produces one ${n.subject}` : ""}.` },
|
|
28332
|
+
...pack.unit.createsSubject ? [{ term: cap(n.subject), text: `The thing a ${n.unit} makes. States attach to it, and consequences can land on it later.` }] : [],
|
|
28333
|
+
{ term: cap(n.finalize), text: `Closing a ${n.unit}. Once ${n.finalize.toLowerCase()}d, what it made is fixed unless a rule says otherwise.` },
|
|
28334
|
+
...Object.entries(pack.vocabulary.terms ?? {}).map(([term, text]) => ({ term, text }))
|
|
28335
|
+
]);
|
|
28336
|
+
if (pack.hierarchy?.length) b.p(`When rules contradict each other, the more specific wins, in this order: ${pack.hierarchy.join(" > ")}.`, "note");
|
|
28337
|
+
b.h(2, `A ${n.unit}, step by step`);
|
|
28338
|
+
b.list(flowSteps(pack, { detail: true }), true);
|
|
28339
|
+
if (pack.journal?.enabled !== false) b.p(`${pack.journal?.prompt ? `After each ${n.unit}: \u201C${pack.journal.prompt}\u201D` : `Keep a line of notes per ${n.unit}.`}${pack.journal?.required ? " A note is required before moving on." : ""}`);
|
|
28340
|
+
b.h(2, "Modes");
|
|
28341
|
+
b.table(["Mode", "Length", "Players", "About"], modeRows(pack, { notes: true }));
|
|
28342
|
+
b.h(2, "Tables");
|
|
28343
|
+
for (const [id, table] of Object.entries(pack.tables)) {
|
|
28344
|
+
b.h(3, table.title);
|
|
28345
|
+
b.p([howConsulted(table), table.description ?? ""].filter(Boolean).join(" "));
|
|
28346
|
+
b.table([table.resolution === "keyed" ? "Key" : "Roll", "Result"], tableRows(pack, table, { full: true }));
|
|
28347
|
+
void id;
|
|
28348
|
+
}
|
|
28349
|
+
const decks = Object.entries(pack.decks ?? {});
|
|
28350
|
+
if (decks.length) {
|
|
28351
|
+
b.h(2, "Cards");
|
|
28352
|
+
for (const [, deck] of decks) {
|
|
28353
|
+
b.h(3, deck.title);
|
|
28354
|
+
if (deck.kind === "standard52") {
|
|
28355
|
+
b.p([deck.description, `A standard deck${deck.includeJokers ? " with jokers" : ""}; ${deck.drawAtStart ? `draw ${deck.drawAtStart} at the start. ` : ""}Each draw is read by ${deck.resolveBy}${deck.resolveOn ? ` on ${pack.tables[deck.resolveOn]?.title ?? deck.resolveOn}` : ""}.`].filter(Boolean).join(" "));
|
|
28356
|
+
} else {
|
|
28357
|
+
b.p([deck.description, deck.drawAtStart ? `Draw ${deck.drawAtStart} at the start.` : "", deck.unique ? "" : "Cards may repeat.", deck.carriesOver ? `Unused cards carry over to the next ${n.run}.` : ""].filter(Boolean).join(" "));
|
|
28358
|
+
b.table(
|
|
28359
|
+
["Card", "Effect"],
|
|
28360
|
+
deck.cards.map((c) => [c.title, [c.text, ...(c.triggers ?? []).map((t) => triggerInWords(pack, t)), c.requires?.length ? `Only if ${conditionsInWords(pack, c.requires)}.` : ""].filter(Boolean).join(" ")])
|
|
28361
|
+
);
|
|
28362
|
+
}
|
|
28363
|
+
}
|
|
28364
|
+
}
|
|
28365
|
+
const states = Object.entries(pack.states ?? {});
|
|
28366
|
+
if (states.length) {
|
|
28367
|
+
b.h(2, "States");
|
|
28368
|
+
b.table(
|
|
28369
|
+
["State", "On", "Means"],
|
|
28370
|
+
states.map(([, s]) => [
|
|
28371
|
+
s.short ? `${s.label} (${s.short})` : s.label,
|
|
28372
|
+
s.scope === "run" ? `the ${n.run}` : `a ${n.subject}`,
|
|
28373
|
+
[s.description ?? "", ...(s.semantics ?? []).map(semanticInWords)].filter(Boolean).join(" ")
|
|
28374
|
+
])
|
|
28375
|
+
);
|
|
28376
|
+
}
|
|
28377
|
+
const counters = Object.entries(pack.counters ?? {}).filter(([, c]) => !c.hidden);
|
|
28378
|
+
const resources = Object.entries(pack.resources ?? {});
|
|
28379
|
+
if (counters.length || resources.length) {
|
|
28380
|
+
b.h(2, "Tallies and tracks");
|
|
28381
|
+
b.table(
|
|
28382
|
+
["Name", "Starts at", "Rule"],
|
|
28383
|
+
[
|
|
28384
|
+
...counters.map(([, c]) => [
|
|
28385
|
+
c.label,
|
|
28386
|
+
String(c.initial ?? 0),
|
|
28387
|
+
[
|
|
28388
|
+
c.min !== void 0 && c.max !== void 0 ? `Stays within ${c.min}\u2013${c.max}.` : c.max !== void 0 ? `Never above ${c.max}.` : c.min !== void 0 && c.min !== 0 ? `Never below ${c.min}.` : "",
|
|
28389
|
+
...(c.triggers ?? []).map((t) => sentence(`${t.label ? `${t.label} \u2014 ` : ""}at ${boundText(t.when)}${t.oncePerRun ? `, once per ${n.run}` : ""}: ${actionsInWords(pack, t.do)}`))
|
|
28390
|
+
].filter(Boolean).join(" ")
|
|
28391
|
+
]),
|
|
28392
|
+
...resources.map(([, r]) => [r.label, String(r.initial ?? 0), [r.description ?? "", `From ${r.min ?? 0}${r.max !== void 0 ? ` to ${r.max}` : ""}, in steps of ${r.step ?? 1}.`].filter(Boolean).join(" ")])
|
|
28393
|
+
]
|
|
28394
|
+
);
|
|
28395
|
+
}
|
|
28396
|
+
const moves = Object.entries(pack.moves ?? {});
|
|
28397
|
+
if (moves.length) {
|
|
28398
|
+
b.h(2, "Moves");
|
|
28399
|
+
b.table(
|
|
28400
|
+
["Move", "When", "What happens"],
|
|
28401
|
+
moves.map(([, m]) => [
|
|
28402
|
+
m.label,
|
|
28403
|
+
[whenInWords(m.when, n), m.oncePerRun ? `once per ${n.run}` : "", m.available?.length ? `if ${conditionsInWords(pack, m.available)}` : ""].filter(Boolean).join("; "),
|
|
28404
|
+
[m.description ?? "", sentence(cap(actionsInWords(pack, m.do)))].filter(Boolean).join(" ")
|
|
28405
|
+
])
|
|
28406
|
+
);
|
|
28407
|
+
}
|
|
28408
|
+
if (pack.targeting && pack.targeting.strategy !== "none") {
|
|
28409
|
+
b.h(2, "Reaching back");
|
|
28410
|
+
b.p(targetingInWords(pack));
|
|
28411
|
+
}
|
|
28412
|
+
if (pack.triggers?.length) {
|
|
28413
|
+
b.h(2, "Always in effect");
|
|
28414
|
+
b.list(pack.triggers.map((t) => sentence(cap(triggerInWords(pack, t)))));
|
|
28415
|
+
}
|
|
28416
|
+
if (pack.endings?.length) {
|
|
28417
|
+
b.h(2, `Ending a ${n.run}`);
|
|
28418
|
+
b.terms(pack.endings.map((e) => ({ term: e.label, text: [e.text ?? "", e.requires?.length ? `Available when ${conditionsInWords(pack, e.requires)}.` : ""].filter(Boolean).join(" ") })));
|
|
28419
|
+
}
|
|
28420
|
+
return { kind: "rulebook", layout: "book", title: pack.title, subtitle: byline(pack), blocks: b.blocks };
|
|
28421
|
+
}
|
|
28422
|
+
function quickstartDoc(pack) {
|
|
28423
|
+
const n = nouns(pack);
|
|
28424
|
+
const b = new Builder();
|
|
28425
|
+
b.p(pack.description);
|
|
28426
|
+
b.h(2, "What you need");
|
|
28427
|
+
b.list(whatYouNeed(pack));
|
|
28428
|
+
b.h(2, "The words");
|
|
28429
|
+
b.p(`A ${n.run} is one session. It is made of ${n.units}${pack.unit.createsSubject ? `, and each ${n.unit} makes one ${n.subject}` : ""}. Closing a ${n.unit} is called \u201C${n.finalize}\u201D.`);
|
|
28430
|
+
b.h(2, `Each ${n.unit}`);
|
|
28431
|
+
b.list(flowSteps(pack, { detail: false }), true);
|
|
28432
|
+
const d = pack.modes[pack.defaultMode];
|
|
28433
|
+
if (d) {
|
|
28434
|
+
b.h(2, `Start with ${d.label}`);
|
|
28435
|
+
b.p([d.description ?? "", `${cap(modeLength(pack, d))}, ${modePlayers(d)}.`, ...modeExtras(pack, d)].filter(Boolean).join(" "));
|
|
28436
|
+
const others = Object.entries(pack.modes).filter(([id]) => id !== pack.defaultMode);
|
|
28437
|
+
if (others.length) b.p(`Other ways to play: ${others.map(([, m]) => m.label).join(", ")}. They are in the rulebook.`, "muted");
|
|
28438
|
+
}
|
|
28439
|
+
if (pack.endings?.length) {
|
|
28440
|
+
b.h(2, "When you stop");
|
|
28441
|
+
b.terms(pack.endings.map((e) => ({ term: e.label, text: e.text ?? "" })));
|
|
28442
|
+
}
|
|
28443
|
+
b.p("Every table is on the reference card. Everything else is in the rulebook.", "muted");
|
|
28444
|
+
return { kind: "quickstart", layout: "book", title: pack.title, subtitle: "Quick start", blocks: b.blocks };
|
|
28445
|
+
}
|
|
28446
|
+
function referenceDoc(pack) {
|
|
28447
|
+
const n = nouns(pack);
|
|
28448
|
+
const b = new Builder();
|
|
28449
|
+
b.h(2, `Each ${n.unit}`);
|
|
28450
|
+
b.list(flowSteps(pack, { detail: false }), true);
|
|
28451
|
+
for (const [, table] of Object.entries(pack.tables)) {
|
|
28452
|
+
b.h(2, `${table.title} \u2014 ${howConsulted(table).replace(/\.$/, "")}`);
|
|
28453
|
+
b.table([table.resolution === "keyed" ? "Key" : "Roll", ""], tableRows(pack, table, { full: true }), true);
|
|
28454
|
+
}
|
|
28455
|
+
const states = Object.values(pack.states ?? {});
|
|
28456
|
+
if (states.length) {
|
|
28457
|
+
b.h(2, "States");
|
|
28458
|
+
b.table(["", "State"], states.map((s) => [s.short ?? "", `${s.label}${s.description ? ` \u2014 ${s.description}` : ""}`]), true);
|
|
28459
|
+
}
|
|
28460
|
+
const moves = Object.values(pack.moves ?? {});
|
|
28461
|
+
if (moves.length) {
|
|
28462
|
+
b.h(2, "Moves");
|
|
28463
|
+
b.list(moves.map((m) => sentence(`${m.label} (${whenInWords(m.when, n)}${m.oncePerRun ? `, once per ${n.run}` : ""}): ${actionsInWords(pack, m.do)}`)));
|
|
28464
|
+
}
|
|
28465
|
+
const remember = [];
|
|
28466
|
+
for (const t of pack.triggers ?? []) remember.push(sentence(cap(triggerInWords(pack, t))));
|
|
28467
|
+
for (const c of Object.values(pack.counters ?? {})) for (const t of c.triggers ?? []) remember.push(sentence(`${c.label} at ${boundText(t.when)}: ${actionsInWords(pack, t.do)}`));
|
|
28468
|
+
for (const m of Object.values(pack.modes)) for (const note of m.notes ?? []) remember.push(`${m.label}: ${note}`);
|
|
28469
|
+
if (pack.hierarchy?.length) remember.push(`Precedence: ${pack.hierarchy.join(" > ")}.`);
|
|
28470
|
+
if (remember.length) {
|
|
28471
|
+
b.h(2, "Frequently forgotten");
|
|
28472
|
+
b.list(remember);
|
|
28473
|
+
}
|
|
28474
|
+
if (pack.endings?.length) {
|
|
28475
|
+
b.h(2, "Endings");
|
|
28476
|
+
b.list(pack.endings.map((e) => `${e.label}${e.text ? `: ${e.text}` : ""}`));
|
|
28477
|
+
}
|
|
28478
|
+
return { kind: "reference", layout: "card", title: pack.title, subtitle: `Reference \xB7 v${pack.version}`, blocks: b.blocks };
|
|
28479
|
+
}
|
|
28480
|
+
function runlogDoc(pack) {
|
|
28481
|
+
const n = nouns(pack);
|
|
28482
|
+
const b = new Builder();
|
|
28483
|
+
const head = [{ label: "Date", width: "short" }, { label: "Mode", width: "short" }];
|
|
28484
|
+
if (Object.values(pack.modes).some((m) => m.seeded)) head.push({ label: "Seed", width: "short" });
|
|
28485
|
+
if (Object.values(pack.modes).some((m) => (m.players?.max ?? 1) > 1)) head.push({ label: "Players", width: "long" });
|
|
28486
|
+
for (const deck of Object.values(pack.decks ?? {})) if (deck.kind === "cards" && deck.drawAtStart) head.push({ label: deck.title, width: "short" });
|
|
28487
|
+
for (const c of Object.values(pack.counters ?? {}).filter((c2) => !c2.hidden)) head.push({ label: c.label, width: "short", box: true });
|
|
28488
|
+
for (const r of Object.values(pack.resources ?? {})) head.push({ label: r.label, width: "short", box: true });
|
|
28489
|
+
head.push({ label: "Page", width: "short" });
|
|
28490
|
+
b.form(head);
|
|
28491
|
+
b.rule();
|
|
28492
|
+
const unitFields = [];
|
|
28493
|
+
unitFields.push({ label: `${cap(n.unit)} \u2116`, width: "short" });
|
|
28494
|
+
if (pack.unit.createsSubject) unitFields.push({ label: `${cap(n.subject)} / states`, width: "long" });
|
|
28495
|
+
for (const phase of pack.phases) {
|
|
28496
|
+
for (const step of phase.steps) {
|
|
28497
|
+
if (step.kind === "rollTable") unitFields.push({ label: pack.tables[step.table]?.title ?? step.table, width: "full", lines: 2 });
|
|
28498
|
+
}
|
|
28499
|
+
}
|
|
28500
|
+
if (pack.journal?.enabled !== false) unitFields.push({ label: "Notes", width: "full", lines: 2 });
|
|
28501
|
+
for (let i = 0; i < 6; i++) b.form(unitFields);
|
|
28502
|
+
b.rule();
|
|
28503
|
+
b.form([{ label: `${cap(n.run)} states`, width: "full", lines: 2 }, { label: "Ending", width: "long" }]);
|
|
28504
|
+
return { kind: "runlog", layout: "sheet", title: pack.title, subtitle: `${cap(n.run)} log`, blocks: b.blocks };
|
|
28505
|
+
}
|
|
28506
|
+
function semanticInWords(s) {
|
|
28507
|
+
switch (s) {
|
|
28508
|
+
case "blocksEdit":
|
|
28509
|
+
return "Nothing about it may be changed.";
|
|
28510
|
+
case "makesUntargetable":
|
|
28511
|
+
return "Consequences cannot land on it.";
|
|
28512
|
+
case "excludesFromResult":
|
|
28513
|
+
return "It does not count toward the result.";
|
|
28514
|
+
case "locksValue":
|
|
28515
|
+
return "Its value is fixed.";
|
|
28516
|
+
case "removesFromPlay":
|
|
28517
|
+
return "It is out of play.";
|
|
28518
|
+
default:
|
|
28519
|
+
return s;
|
|
28520
|
+
}
|
|
28521
|
+
}
|
|
28522
|
+
function whenInWords(when, n) {
|
|
28523
|
+
switch (when) {
|
|
28524
|
+
case "betweenUnits":
|
|
28525
|
+
return `between ${n.units}`;
|
|
28526
|
+
case "beforeEnding":
|
|
28527
|
+
return `before ending the ${n.run}`;
|
|
28528
|
+
default:
|
|
28529
|
+
return "any time";
|
|
28530
|
+
}
|
|
28531
|
+
}
|
|
28532
|
+
function boundText(b) {
|
|
28533
|
+
if (b.eq !== void 0) return String(b.eq);
|
|
28534
|
+
if (b.gte !== void 0 && b.lte !== void 0) return `${b.gte}\u2013${b.lte}`;
|
|
28535
|
+
if (b.gte !== void 0) return `${b.gte}+`;
|
|
28536
|
+
if (b.lte !== void 0) return `\u2264${b.lte}`;
|
|
28537
|
+
return "";
|
|
28538
|
+
}
|
|
28539
|
+
function targetingInWords(pack) {
|
|
28540
|
+
const n = nouns(pack);
|
|
28541
|
+
const t = pack.targeting;
|
|
28542
|
+
if (!t) return "";
|
|
28543
|
+
switch (t.strategy) {
|
|
28544
|
+
case "none":
|
|
28545
|
+
return "";
|
|
28546
|
+
case "playerChoice":
|
|
28547
|
+
return `When a consequence reaches back, you choose which earlier ${n.subject} it lands on.`;
|
|
28548
|
+
case "random":
|
|
28549
|
+
return `When a consequence reaches back, pick an earlier ${n.subject} at random.`;
|
|
28550
|
+
case "oldest":
|
|
28551
|
+
return `When a consequence reaches back, it lands on the oldest ${n.subject} still in play.`;
|
|
28552
|
+
case "newest":
|
|
28553
|
+
return `When a consequence reaches back, it lands on the newest ${n.subject} still in play.`;
|
|
28554
|
+
case "anchoredOffset": {
|
|
28555
|
+
const bands = t.bands.map((b) => {
|
|
28556
|
+
const anchor2 = b.anchor === "playerChoice" ? "you choose" : `count ${b.direction ?? "before"} from the ${b.anchor}`;
|
|
28557
|
+
return `${b.range[0]}\u2013${b.range[1]}: ${anchor2}`;
|
|
28558
|
+
});
|
|
28559
|
+
return `When a roll reaches back, its ones digit says how far: ${bands.join("; ")}. Counting ${t.wraparound === false ? "stops at the end" : "wraps around"}${t.skipIneligible === false ? "" : ` and skips any ${n.subject} that cannot be targeted`}. A 0 lands on the anchor itself.${t.eventFallback ? ` When something other than a roll reaches back, roll ${t.eventFallback.roll} and read it the same way${t.eventFallback.missOn ? `; a ${t.eventFallback.missOn} misses` : ""}.` : ""}`;
|
|
28560
|
+
}
|
|
28561
|
+
}
|
|
28562
|
+
}
|
|
28563
|
+
function generateDoc(pack, kind) {
|
|
28564
|
+
switch (kind) {
|
|
28565
|
+
case "summary":
|
|
28566
|
+
return summaryDoc(pack);
|
|
28567
|
+
case "rulebook":
|
|
28568
|
+
return rulebookDoc(pack);
|
|
28569
|
+
case "quickstart":
|
|
28570
|
+
return quickstartDoc(pack);
|
|
28571
|
+
case "reference":
|
|
28572
|
+
return referenceDoc(pack);
|
|
28573
|
+
case "runlog":
|
|
28574
|
+
return runlogDoc(pack);
|
|
28575
|
+
}
|
|
28576
|
+
}
|
|
28577
|
+
var mdEscape = (s) => s.replace(/\|/g, "\\|").replace(/\n/g, " ");
|
|
28578
|
+
function toMarkdown(doc) {
|
|
28579
|
+
const out = [`# ${doc.title}`];
|
|
28580
|
+
if (doc.subtitle) out.push(`*${doc.subtitle}*`);
|
|
28581
|
+
for (const block of doc.blocks) {
|
|
28582
|
+
switch (block.kind) {
|
|
28583
|
+
case "heading":
|
|
28584
|
+
out.push(`${"#".repeat(block.level)} ${block.text}`);
|
|
28585
|
+
break;
|
|
28586
|
+
case "paragraph":
|
|
28587
|
+
out.push(block.tone === "note" ? `> ${block.text}` : block.text);
|
|
28588
|
+
break;
|
|
28589
|
+
case "list":
|
|
28590
|
+
out.push(block.items.map((item, i) => `${block.ordered ? `${i + 1}.` : "-"} ${item.replace(/\n\s*/g, " \n ")}`).join("\n"));
|
|
28591
|
+
break;
|
|
28592
|
+
case "table":
|
|
28593
|
+
out.push([`| ${block.columns.map(mdEscape).join(" | ")} |`, `| ${block.columns.map(() => "---").join(" | ")} |`, ...block.rows.map((r) => `| ${r.map(mdEscape).join(" | ")} |`)].join("\n"));
|
|
28594
|
+
break;
|
|
28595
|
+
case "terms":
|
|
28596
|
+
out.push(block.items.map((t) => `**${t.term}** \u2014 ${t.text}`).join("\n\n"));
|
|
28597
|
+
break;
|
|
28598
|
+
case "form":
|
|
28599
|
+
out.push(block.fields.map((f) => `${f.label}: ${f.box ? "[ ]" : "_".repeat(f.width === "full" ? 40 : f.width === "long" ? 24 : 12)}`).join(" \n"));
|
|
28600
|
+
break;
|
|
28601
|
+
case "rule":
|
|
28602
|
+
out.push("---");
|
|
28603
|
+
break;
|
|
28604
|
+
case "pagebreak":
|
|
28605
|
+
out.push("");
|
|
28606
|
+
break;
|
|
28607
|
+
}
|
|
28608
|
+
}
|
|
28609
|
+
return `${out.join("\n\n")}
|
|
28610
|
+
`;
|
|
28611
|
+
}
|
|
28612
|
+
var esc2 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
28613
|
+
var multiline = (s) => esc2(s).replace(/\n/g, "<br>");
|
|
28614
|
+
var CSS = `
|
|
28615
|
+
:root { color-scheme: light; }
|
|
28616
|
+
body { margin: 0; background: #fff; color: #161513; font: 11pt/1.45 Georgia, "Times New Roman", serif; }
|
|
28617
|
+
main { max-width: 42em; margin: 0 auto; padding: 2.5rem 1.5rem; }
|
|
28618
|
+
h1 { font-size: 2rem; margin: 0 0 .25rem; line-height: 1.1; }
|
|
28619
|
+
.subtitle { margin: 0 0 2rem; color: #5b5751; font-style: italic; }
|
|
28620
|
+
h2 { font-size: 1.25rem; margin: 2rem 0 .5rem; border-bottom: 1px solid #d9d4cb; padding-bottom: .2rem; }
|
|
28621
|
+
h3 { font-size: 1.05rem; margin: 1.4rem 0 .35rem; }
|
|
28622
|
+
p { margin: 0 0 .75rem; }
|
|
28623
|
+
p.muted { color: #5b5751; font-size: .92em; }
|
|
28624
|
+
p.note { border-left: 3px solid #b9b2a5; padding-left: .75rem; color: #3f3b36; }
|
|
28625
|
+
ol, ul { margin: 0 0 .9rem; padding-left: 1.4rem; }
|
|
28626
|
+
li { margin: .2rem 0; white-space: pre-line; }
|
|
28627
|
+
table { border-collapse: collapse; width: 100%; margin: 0 0 1rem; font-size: .95em; }
|
|
28628
|
+
th, td { text-align: left; vertical-align: top; padding: .3rem .5rem; border-bottom: 1px solid #e4dfd6; }
|
|
28629
|
+
th { font-family: system-ui, sans-serif; font-size: .75em; letter-spacing: .04em; text-transform: uppercase; color: #5b5751; }
|
|
28630
|
+
td:first-child { white-space: nowrap; font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: .9em; }
|
|
28631
|
+
table.compact td { padding: .12rem .35rem; font-size: .85em; }
|
|
28632
|
+
dl { margin: 0 0 1rem; }
|
|
28633
|
+
dt { font-weight: 700; margin-top: .5rem; }
|
|
28634
|
+
dd { margin: 0; }
|
|
28635
|
+
hr { border: 0; border-top: 1px solid #d9d4cb; margin: 1.5rem 0; }
|
|
28636
|
+
.page { break-after: page; }
|
|
28637
|
+
.form { display: flex; flex-wrap: wrap; gap: .5rem 1.25rem; margin: .6rem 0 .9rem; font-family: system-ui, sans-serif; font-size: .85em; }
|
|
28638
|
+
.field { display: flex; align-items: flex-end; gap: .4rem; }
|
|
28639
|
+
.field .line { display: inline-block; border-bottom: 1px solid #161513; height: 1.3em; }
|
|
28640
|
+
.field.short .line { width: 7em; } .field.long .line { width: 16em; } .field.full { flex: 1 1 100%; }
|
|
28641
|
+
.field.full .line { flex: 1; }
|
|
28642
|
+
.field .lines { flex: 1; display: grid; gap: .55em; }
|
|
28643
|
+
.field .lines .line { width: 100%; }
|
|
28644
|
+
.field .box { display: inline-block; width: 2.6em; height: 1.6em; border: 1px solid #161513; }
|
|
28645
|
+
.layout-card main { max-width: none; column-width: 17em; column-gap: 2rem; padding: 1.25rem; font-size: 9pt; }
|
|
28646
|
+
.layout-card h1 { column-span: all; font-size: 1.4rem; }
|
|
28647
|
+
.layout-card .subtitle { column-span: all; margin-bottom: 1rem; }
|
|
28648
|
+
.layout-card h2 { margin-top: 1rem; font-size: .95rem; break-after: avoid; }
|
|
28649
|
+
.layout-card table { break-inside: auto; }
|
|
28650
|
+
.layout-card tr { break-inside: avoid; }
|
|
28651
|
+
.layout-sheet main { max-width: none; padding: 1.25rem; }
|
|
28652
|
+
.layout-sheet .form + .form { border-top: 1px dashed #d9d4cb; padding-top: .6rem; }
|
|
28653
|
+
@media print { main { padding: 0; } @page { margin: 1.6cm; } .layout-card @page { margin: 1cm; } }
|
|
28654
|
+
`;
|
|
28655
|
+
function toHtml(doc) {
|
|
28656
|
+
const body = [];
|
|
28657
|
+
for (const block of doc.blocks) {
|
|
28658
|
+
switch (block.kind) {
|
|
28659
|
+
case "heading":
|
|
28660
|
+
body.push(`<h${block.level}>${esc2(block.text)}</h${block.level}>`);
|
|
28661
|
+
break;
|
|
28662
|
+
case "paragraph":
|
|
28663
|
+
body.push(`<p${block.tone ? ` class="${block.tone}"` : ""}>${multiline(block.text)}</p>`);
|
|
28664
|
+
break;
|
|
28665
|
+
case "list":
|
|
28666
|
+
body.push(`<${block.ordered ? "ol" : "ul"}>${block.items.map((i) => `<li>${esc2(i)}</li>`).join("")}</${block.ordered ? "ol" : "ul"}>`);
|
|
28667
|
+
break;
|
|
28668
|
+
case "table":
|
|
28669
|
+
body.push(
|
|
28670
|
+
`<table${block.compact ? ' class="compact"' : ""}><thead><tr>${block.columns.map((c) => `<th>${esc2(c)}</th>`).join("")}</tr></thead><tbody>${block.rows.map((r) => `<tr>${r.map((c) => `<td>${multiline(c)}</td>`).join("")}</tr>`).join("")}</tbody></table>`
|
|
28671
|
+
);
|
|
28672
|
+
break;
|
|
28673
|
+
case "terms":
|
|
28674
|
+
body.push(`<dl>${block.items.map((t) => `<dt>${esc2(t.term)}</dt><dd>${multiline(t.text)}</dd>`).join("")}</dl>`);
|
|
28675
|
+
break;
|
|
28676
|
+
case "form":
|
|
28677
|
+
body.push(
|
|
28678
|
+
`<div class="form">${block.fields.map((f) => {
|
|
28679
|
+
const width = f.width ?? "short";
|
|
28680
|
+
const line = f.box ? `<span class="box"></span>` : f.lines && f.lines > 1 ? `<span class="lines">${'<span class="line"></span>'.repeat(f.lines)}</span>` : `<span class="line"></span>`;
|
|
28681
|
+
return `<span class="field ${width}"><span class="label">${esc2(f.label)}</span>${line}</span>`;
|
|
28682
|
+
}).join("")}</div>`
|
|
28683
|
+
);
|
|
28684
|
+
break;
|
|
28685
|
+
case "rule":
|
|
28686
|
+
body.push("<hr>");
|
|
28687
|
+
break;
|
|
28688
|
+
case "pagebreak":
|
|
28689
|
+
body.push('<div class="page"></div>');
|
|
28690
|
+
break;
|
|
28691
|
+
}
|
|
28692
|
+
}
|
|
28693
|
+
return `<!doctype html>
|
|
28694
|
+
<html lang="en" class="layout-${doc.layout}">
|
|
28695
|
+
<head>
|
|
28696
|
+
<meta charset="utf-8">
|
|
28697
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
28698
|
+
<title>${esc2(doc.title)}${doc.subtitle ? ` \u2014 ${esc2(doc.subtitle)}` : ""}</title>
|
|
28699
|
+
<style>${CSS}</style>
|
|
28700
|
+
</head>
|
|
28701
|
+
<body>
|
|
28702
|
+
<main>
|
|
28703
|
+
<h1>${esc2(doc.title)}</h1>
|
|
28704
|
+
${doc.subtitle ? `<p class="subtitle">${esc2(doc.subtitle)}</p>` : ""}
|
|
28705
|
+
${body.join("\n")}
|
|
28706
|
+
</main>
|
|
28707
|
+
</body>
|
|
28708
|
+
</html>
|
|
28709
|
+
`;
|
|
28710
|
+
}
|
|
28711
|
+
|
|
27888
28712
|
// packages/engine/src/log.ts
|
|
27889
28713
|
function voidedIds(events) {
|
|
27890
28714
|
const out = /* @__PURE__ */ new Set();
|
|
@@ -28622,11 +29446,11 @@ async function cmdSign(args) {
|
|
|
28622
29446
|
const format = detectFormat(path);
|
|
28623
29447
|
const document = import_yaml3.default.parse(text);
|
|
28624
29448
|
const signature = await signPack(document, key.privateKey, flag2(args, "--as"));
|
|
28625
|
-
const
|
|
29449
|
+
const signed2 = { ...document, signature };
|
|
28626
29450
|
writeFileSync2(
|
|
28627
29451
|
path,
|
|
28628
|
-
format === "json" ? `${JSON.stringify(
|
|
28629
|
-
` : import_yaml3.default.stringify(
|
|
29452
|
+
format === "json" ? `${JSON.stringify(signed2, null, 2)}
|
|
29453
|
+
` : import_yaml3.default.stringify(signed2, { lineWidth: 90 }),
|
|
28630
29454
|
"utf8"
|
|
28631
29455
|
);
|
|
28632
29456
|
console.log(`${paint(GREEN, "signed")} ${input2}`);
|
|
@@ -28767,6 +29591,62 @@ async function reportSignature(document) {
|
|
|
28767
29591
|
);
|
|
28768
29592
|
}
|
|
28769
29593
|
|
|
29594
|
+
// packages/cli/src/docs.ts
|
|
29595
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
29596
|
+
import { basename, join as join2, resolve as resolve3 } from "node:path";
|
|
29597
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
29598
|
+
function cmdDocs(args) {
|
|
29599
|
+
const files = args.filter((a) => !a.startsWith("-"));
|
|
29600
|
+
const input2 = files[0];
|
|
29601
|
+
if (!input2) {
|
|
29602
|
+
console.error("usage: runlog docs <pack.yaml> [-o dir] [--only summary,rulebook,quickstart,reference,runlog] [--md | --html]");
|
|
29603
|
+
return 2;
|
|
29604
|
+
}
|
|
29605
|
+
const outIndex = args.findIndex((a) => a === "-o" || a === "--out");
|
|
29606
|
+
const onlyIndex = args.findIndex((a) => a === "--only");
|
|
29607
|
+
const wanted = new Set(
|
|
29608
|
+
onlyIndex >= 0 && args[onlyIndex + 1] ? args[onlyIndex + 1].split(",").map((k) => k.trim()) : DOC_KINDS.map((k) => k.kind)
|
|
29609
|
+
);
|
|
29610
|
+
for (const k of wanted) {
|
|
29611
|
+
if (!DOC_KINDS.some((d) => d.kind === k)) {
|
|
29612
|
+
console.error(`no such document: ${k}. Choose from ${DOC_KINDS.map((d) => d.kind).join(", ")}.`);
|
|
29613
|
+
return 2;
|
|
29614
|
+
}
|
|
29615
|
+
}
|
|
29616
|
+
const formats = args.includes("--md") && !args.includes("--html") ? ["md"] : args.includes("--html") && !args.includes("--md") ? ["html"] : ["html", "md"];
|
|
29617
|
+
const path = resolve3(input2);
|
|
29618
|
+
let text;
|
|
29619
|
+
try {
|
|
29620
|
+
text = readFileSync3(path, "utf8");
|
|
29621
|
+
} catch {
|
|
29622
|
+
console.error(`cannot read ${input2}`);
|
|
29623
|
+
return 1;
|
|
29624
|
+
}
|
|
29625
|
+
const loaded = loadPackText(text, detectFormat(path));
|
|
29626
|
+
if (!loaded.ok) {
|
|
29627
|
+
console.error(`${basename(input2)} does not load; run \`runlog validate\` to see why`);
|
|
29628
|
+
return 1;
|
|
29629
|
+
}
|
|
29630
|
+
const { pack } = loaded;
|
|
29631
|
+
const dir = outIndex >= 0 && args[outIndex + 1] ? resolve3(args[outIndex + 1]) : resolve3(`${pack.id}-docs`);
|
|
29632
|
+
mkdirSync2(dir, { recursive: true });
|
|
29633
|
+
for (const kind of DOC_KINDS) {
|
|
29634
|
+
if (!wanted.has(kind.kind)) continue;
|
|
29635
|
+
const doc = generateDoc(pack, kind.kind);
|
|
29636
|
+
for (const format of formats) {
|
|
29637
|
+
const file2 = join2(dir, `${kind.kind}.${format}`);
|
|
29638
|
+
writeFileSync3(file2, format === "md" ? toMarkdown(doc) : toHtml(doc), "utf8");
|
|
29639
|
+
console.log(`${kind.label.padEnd(14)} ${file2}`);
|
|
29640
|
+
}
|
|
29641
|
+
}
|
|
29642
|
+
if (!pack.license.redistributable) {
|
|
29643
|
+
console.log("note: this pack is marked non-redistributable. The summary is safe to show anyone; keep the rest as private as the pack.");
|
|
29644
|
+
} else {
|
|
29645
|
+
console.log("open an .html in a browser and print it for a PDF; the reference card lays out in columns and the run log as a sheet.");
|
|
29646
|
+
}
|
|
29647
|
+
return 0;
|
|
29648
|
+
}
|
|
29649
|
+
|
|
28770
29650
|
// packages/cli/src/bin.ts
|
|
28771
29651
|
var RESET2 = "\x1B[0m";
|
|
28772
29652
|
var RED2 = "\x1B[31m";
|
|
@@ -28794,17 +29674,17 @@ function printDiagnostics(diagnostics, file2) {
|
|
|
28794
29674
|
console.error(` ${parts.join(", ")} in ${file2}`);
|
|
28795
29675
|
}
|
|
28796
29676
|
function read(path) {
|
|
28797
|
-
const abs =
|
|
29677
|
+
const abs = resolve4(path);
|
|
28798
29678
|
if (!existsSync3(abs)) {
|
|
28799
29679
|
console.error(paint2(RED2, `no such file: ${path}`));
|
|
28800
29680
|
process.exit(2);
|
|
28801
29681
|
}
|
|
28802
|
-
const text =
|
|
29682
|
+
const text = readFileSync4(abs, "utf8");
|
|
28803
29683
|
const result = loadPackText(text, detectFormat(abs));
|
|
28804
29684
|
return { text, pack: result.pack, diagnostics: result.diagnostics };
|
|
28805
29685
|
}
|
|
28806
29686
|
function report(path, diagnostics, strict) {
|
|
28807
|
-
printDiagnostics(diagnostics,
|
|
29687
|
+
printDiagnostics(diagnostics, basename2(path));
|
|
28808
29688
|
if (hasErrors(diagnostics)) return 1;
|
|
28809
29689
|
if (strict && diagnostics.length > 0) {
|
|
28810
29690
|
console.error(paint2(YELLOW2, " --strict: treating warnings as failures"));
|
|
@@ -28830,7 +29710,7 @@ async function cmdValidate(args) {
|
|
|
28830
29710
|
console.log(
|
|
28831
29711
|
`${paint2(GREEN2, "ok")} ${paint2(BOLD, pack.title)} ${paint2(DIM2, `v${pack.version}`)} \u2014 ${tables} table${tables === 1 ? "" : "s"}, ${entries} entries, ${Object.keys(pack.modes).length} mode(s)` + (diagnostics.length ? paint2(YELLOW2, `, ${diagnostics.length} warning(s)`) : "")
|
|
28832
29712
|
);
|
|
28833
|
-
await reportSignature(import_yaml4.default.parse(
|
|
29713
|
+
await reportSignature(import_yaml4.default.parse(readFileSync4(resolve4(file2), "utf8")));
|
|
28834
29714
|
}
|
|
28835
29715
|
}
|
|
28836
29716
|
return worst;
|
|
@@ -28850,14 +29730,14 @@ function cmdBundle(args) {
|
|
|
28850
29730
|
}
|
|
28851
29731
|
printDiagnostics(
|
|
28852
29732
|
diagnostics.filter((d) => d.level === "warning"),
|
|
28853
|
-
|
|
29733
|
+
basename2(input2)
|
|
28854
29734
|
);
|
|
28855
29735
|
const body = JSON.stringify(pack, null, 2);
|
|
28856
29736
|
const hash2 = createHash("sha256").update(body).digest("hex").slice(0, 16);
|
|
28857
29737
|
const bundled = { ...pack, bundledAt: (/* @__PURE__ */ new Date()).toISOString(), contentHash: hash2 };
|
|
28858
|
-
const out = outIndex >= 0 && args[outIndex + 1] ?
|
|
28859
|
-
|
|
28860
|
-
|
|
29738
|
+
const out = outIndex >= 0 && args[outIndex + 1] ? resolve4(args[outIndex + 1]) : resolve4(`${pack.id}-${pack.version}.pack.json`);
|
|
29739
|
+
mkdirSync3(dirname(out), { recursive: true });
|
|
29740
|
+
writeFileSync4(out, `${JSON.stringify(bundled, null, 2)}
|
|
28861
29741
|
`, "utf8");
|
|
28862
29742
|
console.log(`${paint2(GREEN2, "bundled")} ${out} ${paint2(DIM2, `sha256:${hash2}`)}`);
|
|
28863
29743
|
if (!pack.license.redistributable) {
|
|
@@ -28910,12 +29790,12 @@ function cmdTest(args) {
|
|
|
28910
29790
|
}
|
|
28911
29791
|
function cmdInit(args) {
|
|
28912
29792
|
const name = args.filter((a) => !a.startsWith("-"))[0] ?? "my-pack";
|
|
28913
|
-
const out =
|
|
29793
|
+
const out = resolve4(`${name}.yaml`);
|
|
28914
29794
|
if (existsSync3(out)) {
|
|
28915
29795
|
console.error(paint2(RED2, `refusing to overwrite ${out}`));
|
|
28916
29796
|
return 1;
|
|
28917
29797
|
}
|
|
28918
|
-
|
|
29798
|
+
writeFileSync4(out, SKELETON.replaceAll("__NAME__", name), "utf8");
|
|
28919
29799
|
console.log(`${paint2(GREEN2, "created")} ${out}`);
|
|
28920
29800
|
console.log(paint2(DIM2, ` next: runlog validate ${name}.yaml`));
|
|
28921
29801
|
return 0;
|
|
@@ -28977,6 +29857,8 @@ usage:
|
|
|
28977
29857
|
runlog bundle <pack> [-o out.json] normalize to a distributable JSON
|
|
28978
29858
|
runlog test <pack> replay the pack's own fixtures
|
|
28979
29859
|
runlog init [name] scaffold a new pack
|
|
29860
|
+
runlog docs <pack> [-o dir] write its rulebook, quick start, reference card,
|
|
29861
|
+
[--only kinds] run log sheet and catalog summary (HTML and Markdown)
|
|
28980
29862
|
runlog keygen [-o key.json] make a signing key for your packs
|
|
28981
29863
|
runlog sign <pack> --key key.json sign a pack, proving you wrote it
|
|
28982
29864
|
runlog issue <pack> --to "Name" stamp a copy with a buyer's name and sign it
|
|
@@ -29006,6 +29888,8 @@ async function main(argv) {
|
|
|
29006
29888
|
return cmdValidate(args);
|
|
29007
29889
|
case "bundle":
|
|
29008
29890
|
return cmdBundle(args);
|
|
29891
|
+
case "docs":
|
|
29892
|
+
return cmdDocs(args);
|
|
29009
29893
|
case "test":
|
|
29010
29894
|
return cmdTest(args);
|
|
29011
29895
|
case "keygen":
|