@craftrpgs/cli 0.2.0-alpha.0 → 0.2.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +1402 -189
- package/package.json +3 -3
package/dist/bin.js
CHANGED
|
@@ -26811,6 +26811,345 @@ function contentTypeForContentExtension(extension) {
|
|
|
26811
26811
|
// ../shared/src/constants/gm-engine.ts
|
|
26812
26812
|
var GM_ENGINES = ["v1", "v2"];
|
|
26813
26813
|
|
|
26814
|
+
// ../shared/src/board-time.ts
|
|
26815
|
+
var TIME_TRACKER_KEY = "time";
|
|
26816
|
+
|
|
26817
|
+
// ../shared/src/same-json.ts
|
|
26818
|
+
function sortedKeys(value) {
|
|
26819
|
+
if (Array.isArray(value)) {
|
|
26820
|
+
return value.map(sortedKeys);
|
|
26821
|
+
}
|
|
26822
|
+
if (value !== null && typeof value === "object") {
|
|
26823
|
+
const record2 = value;
|
|
26824
|
+
return Object.fromEntries(Object.keys(record2).filter((key) => record2[key] !== undefined).sort().map((key) => [key, sortedKeys(record2[key])]));
|
|
26825
|
+
}
|
|
26826
|
+
return value;
|
|
26827
|
+
}
|
|
26828
|
+
function sameJson(a, b) {
|
|
26829
|
+
return JSON.stringify(sortedKeys(a)) === JSON.stringify(sortedKeys(b));
|
|
26830
|
+
}
|
|
26831
|
+
|
|
26832
|
+
// ../shared/src/board-trackers.ts
|
|
26833
|
+
var TRACKER_TONES = ["good", "bad"];
|
|
26834
|
+
var TRACKER_ITEM_STATES = ["open", "done", "failed"];
|
|
26835
|
+
var TRACKER_LASTS = ["here", "game", "always"];
|
|
26836
|
+
var COUNT_DISPLAYS = ["number", "bar", "pips"];
|
|
26837
|
+
var RECORDS_DISPLAYS = ["table", "roster"];
|
|
26838
|
+
var TRACKER_LIMITS = {
|
|
26839
|
+
board: 10,
|
|
26840
|
+
name: 60,
|
|
26841
|
+
label: 40,
|
|
26842
|
+
text: 160,
|
|
26843
|
+
fields: 8,
|
|
26844
|
+
items: 16,
|
|
26845
|
+
entries: 20,
|
|
26846
|
+
records: 12,
|
|
26847
|
+
links: 12,
|
|
26848
|
+
recordFields: 4,
|
|
26849
|
+
show: 6,
|
|
26850
|
+
types: 6,
|
|
26851
|
+
sourceName: 80,
|
|
26852
|
+
marks: 10,
|
|
26853
|
+
pipsMax: 20,
|
|
26854
|
+
clockMin: 2,
|
|
26855
|
+
clockMax: 12,
|
|
26856
|
+
scaleSteps: 21
|
|
26857
|
+
};
|
|
26858
|
+
var labelSchema = exports_external.string().trim().max(TRACKER_LIMITS.label).nullish();
|
|
26859
|
+
var hiddenSchema = exports_external.boolean().nullish();
|
|
26860
|
+
var refSchema = exports_external.object({
|
|
26861
|
+
name: exports_external.string().trim().min(1).max(TRACKER_LIMITS.name),
|
|
26862
|
+
file: exports_external.string().nullish()
|
|
26863
|
+
});
|
|
26864
|
+
var showSchema = exports_external.array(exports_external.string().trim().min(1).max(TRACKER_LIMITS.label)).max(TRACKER_LIMITS.show).nullish();
|
|
26865
|
+
var showSourceSchema = exports_external.object({
|
|
26866
|
+
value: exports_external.string().trim().min(1).max(TRACKER_LIMITS.sourceName),
|
|
26867
|
+
max: exports_external.string().trim().min(1).max(TRACKER_LIMITS.sourceName).nullish()
|
|
26868
|
+
});
|
|
26869
|
+
var showSourcesSchema = exports_external.record(exports_external.string().trim().min(1).max(TRACKER_LIMITS.label), exports_external.record(exports_external.string().trim().min(1).max(TRACKER_LIMITS.sourceName), showSourceSchema)).nullish();
|
|
26870
|
+
var entryTypesSchema = exports_external.array(exports_external.string().trim().min(1).max(TRACKER_LIMITS.sourceName)).max(TRACKER_LIMITS.types).nullish();
|
|
26871
|
+
var numberShape = {
|
|
26872
|
+
label: labelSchema,
|
|
26873
|
+
hidden: hiddenSchema,
|
|
26874
|
+
value: exports_external.number(),
|
|
26875
|
+
min: exports_external.number().nullish(),
|
|
26876
|
+
max: exports_external.number().nullish(),
|
|
26877
|
+
tone: exports_external.enum(TRACKER_TONES).nullish(),
|
|
26878
|
+
at: exports_external.record(exports_external.string(), exports_external.string()).nullish(),
|
|
26879
|
+
start: exports_external.number().nullish()
|
|
26880
|
+
};
|
|
26881
|
+
var countFieldSchema = exports_external.object({
|
|
26882
|
+
kind: exports_external.literal("count"),
|
|
26883
|
+
...numberShape,
|
|
26884
|
+
display: exports_external.enum(COUNT_DISPLAYS).nullish()
|
|
26885
|
+
});
|
|
26886
|
+
var clockFieldSchema = exports_external.object({ kind: exports_external.literal("clock"), ...numberShape });
|
|
26887
|
+
var scaleFieldSchema = exports_external.object({
|
|
26888
|
+
kind: exports_external.literal("scale"),
|
|
26889
|
+
...numberShape,
|
|
26890
|
+
ends: exports_external.tuple([exports_external.string(), exports_external.string()]).nullish()
|
|
26891
|
+
});
|
|
26892
|
+
var listItemSchema = exports_external.object({
|
|
26893
|
+
text: exports_external.string().trim().min(1).max(TRACKER_LIMITS.text),
|
|
26894
|
+
state: exports_external.enum(TRACKER_ITEM_STATES),
|
|
26895
|
+
note: exports_external.string().trim().max(TRACKER_LIMITS.text).nullish(),
|
|
26896
|
+
ref: refSchema.nullish(),
|
|
26897
|
+
hidden: hiddenSchema
|
|
26898
|
+
});
|
|
26899
|
+
var listFieldSchema = exports_external.object({
|
|
26900
|
+
kind: exports_external.literal("list"),
|
|
26901
|
+
label: labelSchema,
|
|
26902
|
+
hidden: hiddenSchema,
|
|
26903
|
+
items: exports_external.array(listItemSchema).max(TRACKER_LIMITS.items)
|
|
26904
|
+
});
|
|
26905
|
+
var orderEntrySchema = exports_external.object({
|
|
26906
|
+
name: exports_external.string().trim().min(1),
|
|
26907
|
+
score: exports_external.number().nullish()
|
|
26908
|
+
});
|
|
26909
|
+
var orderFieldSchema = exports_external.object({
|
|
26910
|
+
kind: exports_external.literal("order"),
|
|
26911
|
+
label: labelSchema,
|
|
26912
|
+
hidden: hiddenSchema,
|
|
26913
|
+
entries: exports_external.array(orderEntrySchema).max(TRACKER_LIMITS.entries),
|
|
26914
|
+
current: exports_external.string().nullish(),
|
|
26915
|
+
round: exports_external.number().int().nullish(),
|
|
26916
|
+
show: showSchema,
|
|
26917
|
+
types: entryTypesSchema,
|
|
26918
|
+
sources: showSourcesSchema
|
|
26919
|
+
});
|
|
26920
|
+
var textFieldSchema = exports_external.object({
|
|
26921
|
+
kind: exports_external.literal("text"),
|
|
26922
|
+
label: labelSchema,
|
|
26923
|
+
hidden: hiddenSchema,
|
|
26924
|
+
text: exports_external.string().trim().max(TRACKER_LIMITS.text)
|
|
26925
|
+
});
|
|
26926
|
+
var refFieldSchema = exports_external.object({
|
|
26927
|
+
kind: exports_external.literal("ref"),
|
|
26928
|
+
label: labelSchema,
|
|
26929
|
+
hidden: hiddenSchema,
|
|
26930
|
+
ref: refSchema,
|
|
26931
|
+
show: showSchema
|
|
26932
|
+
});
|
|
26933
|
+
var trackerRecordFieldSchema = exports_external.discriminatedUnion("kind", [
|
|
26934
|
+
countFieldSchema,
|
|
26935
|
+
clockFieldSchema,
|
|
26936
|
+
scaleFieldSchema,
|
|
26937
|
+
listFieldSchema,
|
|
26938
|
+
textFieldSchema
|
|
26939
|
+
]);
|
|
26940
|
+
var recordSchema = exports_external.object({
|
|
26941
|
+
ref: refSchema,
|
|
26942
|
+
hidden: hiddenSchema,
|
|
26943
|
+
fields: exports_external.array(trackerRecordFieldSchema).max(TRACKER_LIMITS.recordFields)
|
|
26944
|
+
});
|
|
26945
|
+
var recordsFieldSchema = exports_external.object({
|
|
26946
|
+
kind: exports_external.literal("records"),
|
|
26947
|
+
label: labelSchema,
|
|
26948
|
+
hidden: hiddenSchema,
|
|
26949
|
+
entries: exports_external.array(recordSchema).max(TRACKER_LIMITS.records),
|
|
26950
|
+
show: showSchema,
|
|
26951
|
+
types: entryTypesSchema,
|
|
26952
|
+
sources: showSourcesSchema,
|
|
26953
|
+
display: exports_external.enum(RECORDS_DISPLAYS).nullish(),
|
|
26954
|
+
each: exports_external.array(trackerRecordFieldSchema).max(TRACKER_LIMITS.recordFields).nullish()
|
|
26955
|
+
});
|
|
26956
|
+
var linkSchema = exports_external.object({
|
|
26957
|
+
between: exports_external.tuple([refSchema, refSchema]),
|
|
26958
|
+
hidden: hiddenSchema,
|
|
26959
|
+
fields: exports_external.array(trackerRecordFieldSchema).max(TRACKER_LIMITS.recordFields)
|
|
26960
|
+
});
|
|
26961
|
+
var linksFieldSchema = exports_external.object({
|
|
26962
|
+
kind: exports_external.literal("links"),
|
|
26963
|
+
label: labelSchema,
|
|
26964
|
+
hidden: hiddenSchema,
|
|
26965
|
+
entries: exports_external.array(linkSchema).max(TRACKER_LIMITS.links),
|
|
26966
|
+
types: entryTypesSchema,
|
|
26967
|
+
each: exports_external.array(trackerRecordFieldSchema).max(TRACKER_LIMITS.recordFields).nullish()
|
|
26968
|
+
});
|
|
26969
|
+
var trackerFieldSchema = exports_external.discriminatedUnion("kind", [
|
|
26970
|
+
countFieldSchema,
|
|
26971
|
+
clockFieldSchema,
|
|
26972
|
+
scaleFieldSchema,
|
|
26973
|
+
listFieldSchema,
|
|
26974
|
+
orderFieldSchema,
|
|
26975
|
+
textFieldSchema,
|
|
26976
|
+
refFieldSchema,
|
|
26977
|
+
recordsFieldSchema,
|
|
26978
|
+
linksFieldSchema
|
|
26979
|
+
]);
|
|
26980
|
+
var trackerShapeSchema = exports_external.object({
|
|
26981
|
+
name: exports_external.string().trim().min(1).max(TRACKER_LIMITS.name),
|
|
26982
|
+
fields: exports_external.array(trackerFieldSchema).min(1).max(TRACKER_LIMITS.fields),
|
|
26983
|
+
for: exports_external.string().trim().min(1).nullish(),
|
|
26984
|
+
hidden: exports_external.boolean().nullish(),
|
|
26985
|
+
lasts: exports_external.enum(TRACKER_LASTS).nullish(),
|
|
26986
|
+
from: exports_external.string().nullish(),
|
|
26987
|
+
since: exports_external.string().nullish(),
|
|
26988
|
+
changed: exports_external.string().nullish()
|
|
26989
|
+
});
|
|
26990
|
+
function isObject2(value) {
|
|
26991
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
26992
|
+
}
|
|
26993
|
+
function upgradedRow(row) {
|
|
26994
|
+
if (!isObject2(row)) {
|
|
26995
|
+
return row;
|
|
26996
|
+
}
|
|
26997
|
+
if (row.kind === "count") {
|
|
26998
|
+
const { style, ...rest } = row;
|
|
26999
|
+
if (style === "clock" || style === "scale") {
|
|
27000
|
+
return { ...rest, kind: style };
|
|
27001
|
+
}
|
|
27002
|
+
return rest;
|
|
27003
|
+
}
|
|
27004
|
+
if (row.kind === "order" && Array.isArray(row.order)) {
|
|
27005
|
+
const { order, ...rest } = row;
|
|
27006
|
+
return { ...rest, entries: order };
|
|
27007
|
+
}
|
|
27008
|
+
return row;
|
|
27009
|
+
}
|
|
27010
|
+
function upgradedTracker(value) {
|
|
27011
|
+
if (!isObject2(value) || "fields" in value || !Array.isArray(value.rows)) {
|
|
27012
|
+
return value;
|
|
27013
|
+
}
|
|
27014
|
+
const { rows, ...rest } = value;
|
|
27015
|
+
return { ...rest, fields: rows.map(upgradedRow) };
|
|
27016
|
+
}
|
|
27017
|
+
var trackerSchema = exports_external.preprocess(upgradedTracker, trackerShapeSchema);
|
|
27018
|
+
var TRACKER_STARTS = [
|
|
27019
|
+
"game-start",
|
|
27020
|
+
"first-change",
|
|
27021
|
+
"when-started"
|
|
27022
|
+
];
|
|
27023
|
+
var TRACKER_UPDATES = ["on-trigger", "every-turn"];
|
|
27024
|
+
var TRACKER_DEFINITION_LIMITS = {
|
|
27025
|
+
key: 60,
|
|
27026
|
+
when: 300,
|
|
27027
|
+
trigger: 200,
|
|
27028
|
+
question: 400,
|
|
27029
|
+
answer: 600
|
|
27030
|
+
};
|
|
27031
|
+
var trackerDefinitionSchema = exports_external.object({
|
|
27032
|
+
key: exports_external.string().trim().min(1).max(TRACKER_DEFINITION_LIMITS.key).regex(/^[a-z0-9][a-z0-9-]*$/, "Keys are lowercase letters, digits and -"),
|
|
27033
|
+
start: trackerShapeSchema.omit({ since: true, changed: true, from: true }),
|
|
27034
|
+
starts: exports_external.enum(TRACKER_STARTS),
|
|
27035
|
+
when: exports_external.string().trim().max(TRACKER_DEFINITION_LIMITS.when).nullish(),
|
|
27036
|
+
updates: exports_external.enum(TRACKER_UPDATES).nullish(),
|
|
27037
|
+
trigger: exports_external.string().trim().max(TRACKER_DEFINITION_LIMITS.trigger).nullish(),
|
|
27038
|
+
watch: exports_external.object({
|
|
27039
|
+
question: exports_external.string().trim().min(1).max(TRACKER_DEFINITION_LIMITS.question),
|
|
27040
|
+
yes: exports_external.string().trim().max(TRACKER_DEFINITION_LIMITS.answer).nullish(),
|
|
27041
|
+
no: exports_external.string().trim().max(TRACKER_DEFINITION_LIMITS.answer).nullish(),
|
|
27042
|
+
remind: exports_external.string().trim().max(TRACKER_DEFINITION_LIMITS.answer).nullish()
|
|
27043
|
+
}).nullish(),
|
|
27044
|
+
perPerson: exports_external.boolean().nullish(),
|
|
27045
|
+
personTypes: entryTypesSchema
|
|
27046
|
+
});
|
|
27047
|
+
var NUMBER_KINDS = new Set(["count", "clock", "scale"]);
|
|
27048
|
+
function isNumberField(field) {
|
|
27049
|
+
return NUMBER_KINDS.has(field.kind);
|
|
27050
|
+
}
|
|
27051
|
+
var ARTICLE_RE = /^(?:the|a|an)\s+/;
|
|
27052
|
+
var POSSESSIVE_RE = /['’]s\b/g;
|
|
27053
|
+
var NON_WORD_RE = /[^\p{L}\p{N}]+/gu;
|
|
27054
|
+
function trackerKey(name) {
|
|
27055
|
+
return name.toLowerCase().replace(POSSESSIVE_RE, "").replace(NON_WORD_RE, " ").trim().replace(ARTICLE_RE, "").trim();
|
|
27056
|
+
}
|
|
27057
|
+
function numberProblem(field, label) {
|
|
27058
|
+
if (field.kind === "clock") {
|
|
27059
|
+
const size = field.max ?? 0;
|
|
27060
|
+
if (size < TRACKER_LIMITS.clockMin || size > TRACKER_LIMITS.clockMax) {
|
|
27061
|
+
return `${label} is a clock of ${TRACKER_LIMITS.clockMin} to ${TRACKER_LIMITS.clockMax} segments`;
|
|
27062
|
+
}
|
|
27063
|
+
}
|
|
27064
|
+
if (field.kind === "scale") {
|
|
27065
|
+
if (field.min == null || field.max == null || field.min >= field.max) {
|
|
27066
|
+
return `${label} is a tug of war: give it a low end below its high end`;
|
|
27067
|
+
}
|
|
27068
|
+
if (field.max - field.min + 1 > TRACKER_LIMITS.scaleSteps) {
|
|
27069
|
+
return `${label} spans at most ${TRACKER_LIMITS.scaleSteps} steps`;
|
|
27070
|
+
}
|
|
27071
|
+
}
|
|
27072
|
+
if (field.min != null && field.max != null && field.min > field.max) {
|
|
27073
|
+
return `${label} has a lowest value above its highest`;
|
|
27074
|
+
}
|
|
27075
|
+
if (field.min != null && field.value < field.min || field.max != null && field.value > field.max) {
|
|
27076
|
+
return `${label} starts outside its range`;
|
|
27077
|
+
}
|
|
27078
|
+
return null;
|
|
27079
|
+
}
|
|
27080
|
+
function fieldsProblem(fields, where) {
|
|
27081
|
+
const labels = fields.map((field) => field.label?.trim() ?? "");
|
|
27082
|
+
if (fields.length > 1 && labels.some((label) => !label)) {
|
|
27083
|
+
return `${where} has more than one field, so each needs a label`;
|
|
27084
|
+
}
|
|
27085
|
+
const keys = labels.filter(Boolean).map(trackerKey);
|
|
27086
|
+
if (new Set(keys).size !== keys.length) {
|
|
27087
|
+
return `${where} has two fields with the same label`;
|
|
27088
|
+
}
|
|
27089
|
+
for (const [index, field] of fields.entries()) {
|
|
27090
|
+
const label = labels[index] ? `"${labels[index]}"` : where;
|
|
27091
|
+
const problem = isNumberField(field) ? numberProblem(field, label) : null;
|
|
27092
|
+
if (problem) {
|
|
27093
|
+
return problem;
|
|
27094
|
+
}
|
|
27095
|
+
if ((field.kind === "records" || field.kind === "links") && field.each?.length) {
|
|
27096
|
+
const inner = fieldsProblem(field.each, `What each ${field.kind === "links" ? "link" : "record"} of ${label} starts with`);
|
|
27097
|
+
if (inner) {
|
|
27098
|
+
return inner;
|
|
27099
|
+
}
|
|
27100
|
+
}
|
|
27101
|
+
}
|
|
27102
|
+
return null;
|
|
27103
|
+
}
|
|
27104
|
+
var QUOTE_RE = /"/;
|
|
27105
|
+
function needsStartTrigger(definition) {
|
|
27106
|
+
return definition.starts === "when-started" && !definition.trigger?.trim() && !QUOTE_RE.test(definition.when ?? "");
|
|
27107
|
+
}
|
|
27108
|
+
function blankStartingName(definition) {
|
|
27109
|
+
for (const field of definition.start.fields) {
|
|
27110
|
+
if (field.kind === "links" && field.entries.some((link) => link.between.some((end) => !end.name.trim()))) {
|
|
27111
|
+
return "a name on each side of every pair it starts with";
|
|
27112
|
+
}
|
|
27113
|
+
const names = field.kind === "records" && field.entries.map((entry) => entry.ref.name) || field.kind === "order" && field.entries.map((entry) => entry.name) || field.kind === "list" && field.items.map((item) => item.text) || [];
|
|
27114
|
+
if (names.some((each) => !each.trim())) {
|
|
27115
|
+
return "a name for everyone and everything it starts with";
|
|
27116
|
+
}
|
|
27117
|
+
}
|
|
27118
|
+
return null;
|
|
27119
|
+
}
|
|
27120
|
+
function updatesEveryTurn(definition) {
|
|
27121
|
+
return definition.updates === "every-turn";
|
|
27122
|
+
}
|
|
27123
|
+
function trackerDefinitionProblem(definition) {
|
|
27124
|
+
const name = definition.start.name.trim();
|
|
27125
|
+
const tracker = name ? `The tracker "${name}"` : "A tracker";
|
|
27126
|
+
if (!name) {
|
|
27127
|
+
return "A tracker needs a name.";
|
|
27128
|
+
}
|
|
27129
|
+
if (definition.start.lasts === "always" && definition.starts !== "game-start") {
|
|
27130
|
+
return `${tracker} never goes away, so it is on the board from the game's start: its "starts" is "game-start".`;
|
|
27131
|
+
}
|
|
27132
|
+
if (needsStartTrigger(definition)) {
|
|
27133
|
+
return `${tracker} starts on a trigger, so it needs to say what starts it.`;
|
|
27134
|
+
}
|
|
27135
|
+
if (updatesEveryTurn(definition)) {
|
|
27136
|
+
if (definition.when?.trim() || definition.watch) {
|
|
27137
|
+
return `${tracker} updates every turn, so it has no update trigger: leave out "when" and "watch".`;
|
|
27138
|
+
}
|
|
27139
|
+
} else if (!definition.when?.trim()) {
|
|
27140
|
+
return `${tracker} needs to say what updates it.`;
|
|
27141
|
+
}
|
|
27142
|
+
if (definition.watch && !definition.watch.question.trim()) {
|
|
27143
|
+
return `${tracker} updates on a trigger, so it needs the trigger to check for.`;
|
|
27144
|
+
}
|
|
27145
|
+
const problem = fieldsProblem(definition.start.fields, tracker);
|
|
27146
|
+
if (problem) {
|
|
27147
|
+
return `${problem}.`;
|
|
27148
|
+
}
|
|
27149
|
+
const blank = blankStartingName(definition);
|
|
27150
|
+
return blank ? `${tracker} needs ${blank}.` : null;
|
|
27151
|
+
}
|
|
27152
|
+
|
|
26814
27153
|
// ../shared/src/platform-instructions.ts
|
|
26815
27154
|
var PLATFORM_INSTRUCTIONS = [
|
|
26816
27155
|
{
|
|
@@ -26867,12 +27206,150 @@ End each reply on something concrete in front of the player's character that the
|
|
|
26867
27206
|
},
|
|
26868
27207
|
{
|
|
26869
27208
|
id: "combat-tracker",
|
|
26870
|
-
revision:
|
|
27209
|
+
revision: 6,
|
|
26871
27210
|
instruction: {
|
|
26872
27211
|
name: "Combat tracker",
|
|
26873
27212
|
when: "always",
|
|
26874
27213
|
section: "Rules",
|
|
26875
|
-
body: `When
|
|
27214
|
+
body: `When the fight is over (every foe down, fled or surrendered, or the fighting stops), end it: "<its name>": "End".`,
|
|
27215
|
+
tracker: {
|
|
27216
|
+
key: "fight",
|
|
27217
|
+
start: {
|
|
27218
|
+
name: "Combat",
|
|
27219
|
+
fields: [
|
|
27220
|
+
{
|
|
27221
|
+
kind: "order",
|
|
27222
|
+
entries: [],
|
|
27223
|
+
round: 1,
|
|
27224
|
+
show: ["HP", "Conditions"]
|
|
27225
|
+
}
|
|
27226
|
+
]
|
|
27227
|
+
},
|
|
27228
|
+
starts: "when-started",
|
|
27229
|
+
trigger: "a fight begins",
|
|
27230
|
+
when: "a fighter's turn ends, or someone joins or leaves the fight"
|
|
27231
|
+
}
|
|
27232
|
+
}
|
|
27233
|
+
},
|
|
27234
|
+
{
|
|
27235
|
+
id: "objectives-tracker",
|
|
27236
|
+
revision: 2,
|
|
27237
|
+
instruction: {
|
|
27238
|
+
name: "Objectives",
|
|
27239
|
+
when: "always",
|
|
27240
|
+
section: "Rules",
|
|
27241
|
+
body: `The party's objectives are one list for the whole game: what they are working toward beyond this scene. Add one when the story gives the party one (an order, a job or request taken on, a goal of their own that outlasts the scene), worded as the party knows it then, short, one goal to an objective: "Objectives": "Add: Find who opened the lid". Mark it done or failed when the story settles it: "Objectives": "Find who opened the lid: done". Never write a quest's later steps ahead of the story: they come as objectives of their own. One that runs over time (keep watch, log every contact) is done only when its end comes.`,
|
|
27242
|
+
tracker: {
|
|
27243
|
+
key: "objectives",
|
|
27244
|
+
start: {
|
|
27245
|
+
name: "Objectives",
|
|
27246
|
+
lasts: "game",
|
|
27247
|
+
fields: [{ kind: "list", items: [] }]
|
|
27248
|
+
},
|
|
27249
|
+
starts: "first-change",
|
|
27250
|
+
when: "the story gives the party an objective (an order, a job or request they take on, a goal of their own that outlasts the scene)",
|
|
27251
|
+
watch: {
|
|
27252
|
+
question: "In `earlier`, `player_turn` or `narration`, has the party been given an objective, something they are now working toward beyond this scene, that {tracker} does not hold under any wording?",
|
|
27253
|
+
yes: 'Yes: an order or an assignment handed to them (they act on it or do not refuse it), a request or a job they take on, or a goal they set themselves that will outlast this scene ("Find what woke the Sleeper", "Get the ledger to the Abbot", "Learn who signed the panel"), and nothing in {tracker} says it.',
|
|
27254
|
+
no: "No: {tracker} already holds it, however worded; or it is only floated, refused or still being weighed; or it is only what this scene is for, the next move (go to the archives, ask the clerk, cross the yard), or something done in the same moment.",
|
|
27255
|
+
remind: `the story gave the party an objective the board's Objectives list does not hold (add it as the party knows it now, short, one goal to an objective: "Objectives": "Add: <the objective>")`
|
|
27256
|
+
}
|
|
27257
|
+
}
|
|
27258
|
+
}
|
|
27259
|
+
},
|
|
27260
|
+
{
|
|
27261
|
+
id: "party-tracker",
|
|
27262
|
+
revision: 2,
|
|
27263
|
+
instruction: {
|
|
27264
|
+
name: "Party",
|
|
27265
|
+
when: "always",
|
|
27266
|
+
section: "Rules",
|
|
27267
|
+
body: `The Party is everyone who travels and acts with the player's character as one of them: the player's character, who is always in it, and the companions who have joined. Add someone when they join ("Party": "Add Mira"), and take them off when they leave, part ways for good or die ("Party": "Remove Mira"). Someone who only helps for a while (a guide for the afternoon, a guard who fights beside them once) is not in it unless the story has them join.`,
|
|
27268
|
+
tracker: {
|
|
27269
|
+
key: "party",
|
|
27270
|
+
start: {
|
|
27271
|
+
name: "Party",
|
|
27272
|
+
lasts: "game",
|
|
27273
|
+
fields: [
|
|
27274
|
+
{
|
|
27275
|
+
kind: "records",
|
|
27276
|
+
entries: [],
|
|
27277
|
+
show: ["HP", "Conditions"],
|
|
27278
|
+
display: "roster"
|
|
27279
|
+
}
|
|
27280
|
+
]
|
|
27281
|
+
},
|
|
27282
|
+
starts: "game-start",
|
|
27283
|
+
when: "someone joins the party or leaves it",
|
|
27284
|
+
watch: {
|
|
27285
|
+
question: "In `earlier`, `player_turn` or `narration`, has someone joined the party (now travelling and acting with the player's character as one of them) or left it, that {tracker} does not show?",
|
|
27286
|
+
yes: `Yes: someone agrees to come along, is taken on, or sets out with them as one of the group ("I'll come with you", hired for the journey, rescued and staying with them); or someone {tracker} lists leaves, parts ways for good, is left behind or dies.`,
|
|
27287
|
+
no: "No: {tracker} already shows who is in the party; or someone only helps for a moment, offers to come but has not joined, or is only met, fought beside or travelled near.",
|
|
27288
|
+
remind: `someone joined or left the party and the board's Party does not show it (add or take them off: "Party": "Add <name>", "Party": "Remove <name>")`
|
|
27289
|
+
}
|
|
27290
|
+
}
|
|
27291
|
+
}
|
|
27292
|
+
},
|
|
27293
|
+
{
|
|
27294
|
+
id: "death-saves-tracker",
|
|
27295
|
+
revision: 3,
|
|
27296
|
+
instruction: {
|
|
27297
|
+
name: "Death saves",
|
|
27298
|
+
when: "always",
|
|
27299
|
+
section: "Rules",
|
|
27300
|
+
body: `Only for a world whose rules have the dying roll to stay alive. Each save is a roll on their turn: the player's for their character, yours for anyone else. Record each one as it lands ("Death saves for Sara": "Successes +1", "Death saves for Sara": "Failures +1"). When they stabilize, are healed or die, end it ("Death saves for Sara": "End").`,
|
|
27301
|
+
tracker: {
|
|
27302
|
+
key: "death-saves",
|
|
27303
|
+
start: {
|
|
27304
|
+
name: "Death saves",
|
|
27305
|
+
fields: [
|
|
27306
|
+
{
|
|
27307
|
+
kind: "count",
|
|
27308
|
+
label: "Successes",
|
|
27309
|
+
value: 0,
|
|
27310
|
+
min: 0,
|
|
27311
|
+
max: 3,
|
|
27312
|
+
tone: "good",
|
|
27313
|
+
display: "pips",
|
|
27314
|
+
at: { "3": "Stable" }
|
|
27315
|
+
},
|
|
27316
|
+
{
|
|
27317
|
+
kind: "count",
|
|
27318
|
+
label: "Failures",
|
|
27319
|
+
value: 0,
|
|
27320
|
+
min: 0,
|
|
27321
|
+
max: 3,
|
|
27322
|
+
tone: "bad",
|
|
27323
|
+
display: "pips",
|
|
27324
|
+
at: { "3": "Dies" }
|
|
27325
|
+
}
|
|
27326
|
+
]
|
|
27327
|
+
},
|
|
27328
|
+
starts: "when-started",
|
|
27329
|
+
trigger: "someone falls dying, and the world's rules have them roll to stay alive",
|
|
27330
|
+
perPerson: true,
|
|
27331
|
+
when: "they make a death save"
|
|
27332
|
+
}
|
|
27333
|
+
}
|
|
27334
|
+
},
|
|
27335
|
+
{
|
|
27336
|
+
id: "time-tracker",
|
|
27337
|
+
revision: 3,
|
|
27338
|
+
instruction: {
|
|
27339
|
+
name: "Time",
|
|
27340
|
+
when: "always",
|
|
27341
|
+
section: "Rules",
|
|
27342
|
+
body: `The Time tracker is the in-game time, and only you move it: nothing moves it after the turn. It is always a time someone could tell by a clock or the sky, in the world's own terms: a clock hour ("10:40 pm"), a part of the day ("dawn", "late afternoon") or how long until something ("about 2 hours until sunset"); never only how long passed ("30 minutes later": later than what?). Whenever the story takes time worth counting (a journey, a rest, a wait, a long job or talk), set it to the time it is now in the update() for that stretch of story, beside its other changes: "Time": "It is now dusk". When it shows a clock hour, a span is enough ("Time": "40 minutes pass") and the clock adds it. With nothing else to record, the time is still an update() of its own. A line or two of talk needs no record. Tell it once, as the time it is now: "It is now 12:10 am", "It is now dawn", "It is now about an hour until sunset"; never "a while later" or "30 minutes later". A rest recorded with its length ("scene": "The party rests for 1 hour") already moves the clock, so no "Time": "1 hour passes" after it. Narrate the hour, the light and what is open from the board's time, never from a guess.`,
|
|
27343
|
+
tracker: {
|
|
27344
|
+
key: "time",
|
|
27345
|
+
start: {
|
|
27346
|
+
name: "Time",
|
|
27347
|
+
lasts: "game",
|
|
27348
|
+
fields: [{ kind: "text", text: "" }]
|
|
27349
|
+
},
|
|
27350
|
+
starts: "game-start",
|
|
27351
|
+
when: "the story takes time worth counting (a journey, a rest, a wait, a long job or talk)"
|
|
27352
|
+
}
|
|
26876
27353
|
}
|
|
26877
27354
|
},
|
|
26878
27355
|
{
|
|
@@ -26898,7 +27375,7 @@ End each reply on something concrete in front of the player's character that the
|
|
|
26898
27375
|
}
|
|
26899
27376
|
];
|
|
26900
27377
|
var RESPONSE_LENGTH = "Response length";
|
|
26901
|
-
var
|
|
27378
|
+
var LONG_TRAVEL = "Long-distance travel";
|
|
26902
27379
|
var PLATFORM_OPTIONS = [
|
|
26903
27380
|
{
|
|
26904
27381
|
revision: 3,
|
|
@@ -26952,33 +27429,39 @@ var PLATFORM_OPTIONS = [
|
|
|
26952
27429
|
}
|
|
26953
27430
|
},
|
|
26954
27431
|
{
|
|
26955
|
-
revision:
|
|
27432
|
+
revision: 1,
|
|
26956
27433
|
option: {
|
|
26957
|
-
key: "platform-
|
|
26958
|
-
label:
|
|
26959
|
-
description: "
|
|
27434
|
+
key: "platform-long-distance-travel",
|
|
27435
|
+
label: LONG_TRAVEL,
|
|
27436
|
+
description: "What happens when the party travels somewhere far away.",
|
|
26960
27437
|
kind: "one",
|
|
26961
|
-
default: ["
|
|
27438
|
+
default: ["up-to-gm"],
|
|
26962
27439
|
choices: [
|
|
26963
27440
|
{
|
|
26964
|
-
key: "
|
|
26965
|
-
label: "
|
|
26966
|
-
description: "The
|
|
27441
|
+
key: "up-to-gm",
|
|
27442
|
+
label: "Up to the GM",
|
|
27443
|
+
description: "The Game Master decides when the road is worth playing.",
|
|
27444
|
+
instructions: []
|
|
27445
|
+
},
|
|
27446
|
+
{
|
|
27447
|
+
key: "fast-travel",
|
|
27448
|
+
label: "Fast travel",
|
|
27449
|
+
description: "Skip ahead to the destination, with the trip summed up.",
|
|
26967
27450
|
instructions: [
|
|
26968
27451
|
{
|
|
26969
27452
|
when: "always",
|
|
26970
|
-
body:
|
|
27453
|
+
body: `When the party travels somewhere far (a trip of hours or more), take them straight there in the same reply: record it as arriving ("Move Kael and Mira to Harrowmere"), never "set out for", sum up the trip in a sentence or two (how long it took, how the way went), and go on from the arrival. When the player set out from the map, tell the arrival the same way.`
|
|
26971
27454
|
}
|
|
26972
27455
|
]
|
|
26973
27456
|
},
|
|
26974
27457
|
{
|
|
26975
|
-
key: "
|
|
26976
|
-
label: "
|
|
26977
|
-
description: "
|
|
27458
|
+
key: "journey",
|
|
27459
|
+
label: "Journey",
|
|
27460
|
+
description: "Travel there step by step, with the road played out.",
|
|
26978
27461
|
instructions: [
|
|
26979
27462
|
{
|
|
26980
27463
|
when: "always",
|
|
26981
|
-
body:
|
|
27464
|
+
body: `When the party travels somewhere far (a trip of hours or more), play it as a journey to where the player said they are going. Set out first ("Kael and Mira set out for Harrowmere by the coast road; it takes about three days"), then tell the way in stages over several replies, for as long as the distance really takes: the road and the weather, rests and camps, people and trouble met on the way, with time passing as it would ("It is now dusk"). The party stays on the way the whole time: a camp or a rest is told with the time it takes, never a move to a camp or a stretch of road. Someone met stops the journey where they meet; when the party goes on from there, record it as "Kael and Mira set out again for Harrowmere", never as a move farther along the road. Keep heading for the place the player named unless they change course, and record the arrival ("Move Kael and Mira to Harrowmere") only once the way has been travelled. When the player set out from the map, they are already on the way: go on with the journey.`
|
|
26982
27465
|
}
|
|
26983
27466
|
]
|
|
26984
27467
|
}
|
|
@@ -26995,7 +27478,7 @@ var GAME_OPTION_LABEL_MAX_LENGTH = 60;
|
|
|
26995
27478
|
var GAME_OPTION_DESCRIPTION_MAX_LENGTH = 200;
|
|
26996
27479
|
var GAME_OPTION_KEY_MAX_LENGTH = 60;
|
|
26997
27480
|
var GAME_OPTIONS_MAX_OPTIONS = 8;
|
|
26998
|
-
var GAME_OPTIONS_READ_MAX_OPTIONS =
|
|
27481
|
+
var GAME_OPTIONS_READ_MAX_OPTIONS = 15;
|
|
26999
27482
|
var GAME_OPTION_MAX_CHOICES = 8;
|
|
27000
27483
|
var GAME_OPTIONS_MAX_INSTRUCTIONS = 200;
|
|
27001
27484
|
var GAME_OPTIONS_MAX_ALWAYS_LENGTH = 1e4;
|
|
@@ -27011,6 +27494,7 @@ var gameOptionInstructionSchema = exports_external.object({
|
|
|
27011
27494
|
when: exports_external.enum(GAME_OPTION_INSTRUCTION_WHEN),
|
|
27012
27495
|
trigger: exports_external.string().trim().max(GAME_OPTION_INSTRUCTION_NAME_MAX_LENGTH).optional(),
|
|
27013
27496
|
section: exports_external.string().trim().max(GAME_OPTION_SECTION_MAX_LENGTH).optional(),
|
|
27497
|
+
tracker: trackerDefinitionSchema.optional(),
|
|
27014
27498
|
platform: exports_external.string().max(GAME_OPTION_KEY_MAX_LENGTH).optional()
|
|
27015
27499
|
});
|
|
27016
27500
|
var gameOptionChoiceSchema = exports_external.object({
|
|
@@ -27082,6 +27566,12 @@ function sectionsOf(options) {
|
|
|
27082
27566
|
return [...new Set(names)];
|
|
27083
27567
|
}
|
|
27084
27568
|
function instructionProblem(instruction, base) {
|
|
27569
|
+
if (instruction.tracker) {
|
|
27570
|
+
const problem = trackerDefinitionProblem(instruction.tracker);
|
|
27571
|
+
if (problem) {
|
|
27572
|
+
return problem.charAt(0).toLowerCase() + problem.slice(1);
|
|
27573
|
+
}
|
|
27574
|
+
}
|
|
27085
27575
|
if (instruction.when === "situational" && !instruction.trigger?.trim()) {
|
|
27086
27576
|
return "a situational instruction needs the moment it's for.";
|
|
27087
27577
|
}
|
|
@@ -27106,7 +27596,28 @@ function gameOptionsProblem(options) {
|
|
|
27106
27596
|
}
|
|
27107
27597
|
}
|
|
27108
27598
|
}
|
|
27109
|
-
return gameOptionsStructureProblem(options) ?? gameOptionsLimitProblem(options);
|
|
27599
|
+
return trackerKeysProblem(options) ?? gameOptionsStructureProblem(options) ?? gameOptionsLimitProblem(options);
|
|
27600
|
+
}
|
|
27601
|
+
function trackerKeysProblem(options) {
|
|
27602
|
+
const lists = [
|
|
27603
|
+
options.instructions.filter((instruction) => !instruction.platform),
|
|
27604
|
+
...options.options.flatMap((option) => option.choices.map((choice) => choice.instructions))
|
|
27605
|
+
];
|
|
27606
|
+
for (const list of lists) {
|
|
27607
|
+
const seen = new Map;
|
|
27608
|
+
for (const instruction of list) {
|
|
27609
|
+
const definition = instruction.tracker;
|
|
27610
|
+
if (!definition) {
|
|
27611
|
+
continue;
|
|
27612
|
+
}
|
|
27613
|
+
const other = seen.get(definition.key);
|
|
27614
|
+
if (other !== undefined) {
|
|
27615
|
+
return `The trackers "${other}" and "${definition.start.name}" share the key "${definition.key}": rename one.`;
|
|
27616
|
+
}
|
|
27617
|
+
seen.set(definition.key, definition.start.name);
|
|
27618
|
+
}
|
|
27619
|
+
}
|
|
27620
|
+
return null;
|
|
27110
27621
|
}
|
|
27111
27622
|
function gameOptionsStructureProblem(options) {
|
|
27112
27623
|
const optionKeys = new Set;
|
|
@@ -27167,6 +27678,54 @@ var storedGameOptionsSchema = storedGameOptionsShape.superRefine((options, ctx)
|
|
|
27167
27678
|
ctx.addIssue({ code: "custom", message: problem });
|
|
27168
27679
|
}
|
|
27169
27680
|
});
|
|
27681
|
+
function droppedKeys({
|
|
27682
|
+
raw,
|
|
27683
|
+
parsed,
|
|
27684
|
+
path = ""
|
|
27685
|
+
}) {
|
|
27686
|
+
if (Array.isArray(raw) && Array.isArray(parsed)) {
|
|
27687
|
+
return raw.flatMap((item, index) => droppedKeys({
|
|
27688
|
+
raw: item,
|
|
27689
|
+
parsed: parsed[index],
|
|
27690
|
+
path: `${path}[${index}]`
|
|
27691
|
+
}));
|
|
27692
|
+
}
|
|
27693
|
+
if (typeof raw !== "object" || raw === null || typeof parsed !== "object" || parsed === null) {
|
|
27694
|
+
return [];
|
|
27695
|
+
}
|
|
27696
|
+
return Object.entries(raw).flatMap(([key, value]) => (key in parsed) ? droppedKeys({
|
|
27697
|
+
raw: value,
|
|
27698
|
+
parsed: parsed[key],
|
|
27699
|
+
path: `${path}.${key}`
|
|
27700
|
+
}) : [`${path}.${key}`]);
|
|
27701
|
+
}
|
|
27702
|
+
var INDEX_RE = /\[\d+\]/g;
|
|
27703
|
+
var LEADING_DOT_RE = /^\./;
|
|
27704
|
+
function unknownGameOptionsKeys({
|
|
27705
|
+
raw,
|
|
27706
|
+
before
|
|
27707
|
+
}) {
|
|
27708
|
+
const parsed = storedGameOptionsShape.safeParse(raw);
|
|
27709
|
+
if (!parsed.success) {
|
|
27710
|
+
return [];
|
|
27711
|
+
}
|
|
27712
|
+
const stored = before === undefined || before === null ? null : storedGameOptionsShape.safeParse(before);
|
|
27713
|
+
const old = new Set(stored?.success ? droppedKeys({ raw: before, parsed: stored.data }).map((path) => path.replace(INDEX_RE, "[]")) : []);
|
|
27714
|
+
return droppedKeys({ raw, parsed: parsed.data }).filter((path) => !old.has(path.replace(INDEX_RE, "[]"))).map((path) => path.replace(LEADING_DOT_RE, ""));
|
|
27715
|
+
}
|
|
27716
|
+
function unknownGameOptionsKeysProblem({
|
|
27717
|
+
raw,
|
|
27718
|
+
before,
|
|
27719
|
+
where
|
|
27720
|
+
}) {
|
|
27721
|
+
const unknown2 = unknownGameOptionsKeys({ raw, before });
|
|
27722
|
+
if (unknown2.length === 0) {
|
|
27723
|
+
return null;
|
|
27724
|
+
}
|
|
27725
|
+
const listed = unknown2.slice(0, 5).map((path) => `${where}.${path}`).join(", ");
|
|
27726
|
+
const more = unknown2.length > 5 ? ` and ${unknown2.length - 5} more` : "";
|
|
27727
|
+
return `${listed}${more}: the format has no ${unknown2.length === 1 ? "such key" : "such keys"} there, and it would be dropped without effect. Check the field names and where each sits (a tracker's "lasts", "name", "fields" and "hidden" go inside its "start").`;
|
|
27728
|
+
}
|
|
27170
27729
|
var resolvedGameOptionsSchema = gameOptionsShape.superRefine((options, ctx) => {
|
|
27171
27730
|
const problem = gameOptionsProblem(options);
|
|
27172
27731
|
if (problem) {
|
|
@@ -27239,7 +27798,13 @@ function withPlatform(stored) {
|
|
|
27239
27798
|
const content = override ? overrideInstruction(override) : entry.instruction;
|
|
27240
27799
|
const { section: ownSection, ...rest } = content;
|
|
27241
27800
|
const section = placedIn?.trim() || ownSection?.trim() || entry.instruction.section;
|
|
27242
|
-
|
|
27801
|
+
const tracker = rest.tracker ?? entry.instruction.tracker;
|
|
27802
|
+
return {
|
|
27803
|
+
...rest,
|
|
27804
|
+
...tracker ? { tracker } : {},
|
|
27805
|
+
...section ? { section } : {},
|
|
27806
|
+
platform: entry.id
|
|
27807
|
+
};
|
|
27243
27808
|
}
|
|
27244
27809
|
const instructions = [];
|
|
27245
27810
|
const placed = new Set;
|
|
@@ -27315,6 +27880,12 @@ function instructionSignature(instruction) {
|
|
|
27315
27880
|
instruction.trigger?.trim() ?? ""
|
|
27316
27881
|
]);
|
|
27317
27882
|
}
|
|
27883
|
+
function sameTracker(a, b) {
|
|
27884
|
+
return sameJson(a ?? null, b ?? null);
|
|
27885
|
+
}
|
|
27886
|
+
function craftTracker(instruction, entry) {
|
|
27887
|
+
return sameTracker(instruction.tracker ?? entry.instruction.tracker, entry.instruction.tracker);
|
|
27888
|
+
}
|
|
27318
27889
|
function optionSignature(option) {
|
|
27319
27890
|
return JSON.stringify([
|
|
27320
27891
|
option.label.trim(),
|
|
@@ -27340,7 +27911,7 @@ function platformInstructionStatus(instruction, stored) {
|
|
|
27340
27911
|
if (!entry) {
|
|
27341
27912
|
return null;
|
|
27342
27913
|
}
|
|
27343
|
-
if (instructionSignature(withoutPlatformTag(instruction)) === instructionSignature(entry.instruction)) {
|
|
27914
|
+
if (instructionSignature(withoutPlatformTag(instruction)) === instructionSignature(entry.instruction) && craftTracker(instruction, entry)) {
|
|
27344
27915
|
return "platform";
|
|
27345
27916
|
}
|
|
27346
27917
|
const before = parseGameOptions(stored);
|
|
@@ -27375,10 +27946,11 @@ function alwaysTextLength(instructions) {
|
|
|
27375
27946
|
return always.reduce((total, instruction) => total + instruction.body.length, 0) + [...titles].reduce((total, title) => total + title.length, 0);
|
|
27376
27947
|
}
|
|
27377
27948
|
function alwaysLength(instructions, place) {
|
|
27378
|
-
|
|
27949
|
+
const trackerLines = instructions.reduce((total, instruction) => instruction.tracker ? total + instruction.tracker.start.name.length + (instruction.tracker.when?.length ?? 0) + 4 : total, 0);
|
|
27950
|
+
return alwaysTextLength(instructions.filter((instruction) => !instruction.tracker).map((instruction) => ({
|
|
27379
27951
|
...instruction,
|
|
27380
27952
|
title: instructionTitle(instruction, place)
|
|
27381
|
-
})));
|
|
27953
|
+
}))) + trackerLines;
|
|
27382
27954
|
}
|
|
27383
27955
|
function situationalCount(instructions, _place) {
|
|
27384
27956
|
return instructions.filter((instruction) => instruction.when === "situational").length;
|
|
@@ -36444,8 +37016,52 @@ function validateFileContentAgainstSchemaVariants(contents, schema) {
|
|
|
36444
37016
|
return sharedErrors.length > 0 ? { status: "invalid", errors: sharedErrors } : { status: "valid" };
|
|
36445
37017
|
}
|
|
36446
37018
|
|
|
37019
|
+
// ../shared/src/vfs/path.ts
|
|
37020
|
+
var LEADING_SLASHES = /^\/+/;
|
|
37021
|
+
var TRAILING_SLASHES = /\/+$/;
|
|
37022
|
+
function parseVfsPath(path) {
|
|
37023
|
+
const trimmed = path.replace(LEADING_SLASHES, "").replace(TRAILING_SLASHES, "");
|
|
37024
|
+
const isDir = path.endsWith("/") || path === "/";
|
|
37025
|
+
if (!trimmed) {
|
|
37026
|
+
return { kind: "folder", segments: [] };
|
|
37027
|
+
}
|
|
37028
|
+
const parts = trimmed.split("/").map((part) => part.trim());
|
|
37029
|
+
if (parts.some((part) => part.length === 0)) {
|
|
37030
|
+
throw new Error(`Invalid path "${path}": empty path segment`);
|
|
37031
|
+
}
|
|
37032
|
+
if (parts.some((part) => part === "." || part === "..")) {
|
|
37033
|
+
throw new Error(`Invalid path "${path}": "." and ".." path segments are not allowed`);
|
|
37034
|
+
}
|
|
37035
|
+
const lastPart = parts.at(-1);
|
|
37036
|
+
if (isDir || !lastPart || !lastPart.includes(".")) {
|
|
37037
|
+
return { kind: "folder", segments: parts };
|
|
37038
|
+
}
|
|
37039
|
+
const dotIdx = lastPart.lastIndexOf(".");
|
|
37040
|
+
const finalSuffix = lastPart.slice(dotIdx + 1);
|
|
37041
|
+
const stem = lastPart.slice(0, dotIdx);
|
|
37042
|
+
const stemDotIdx = stem.lastIndexOf(".");
|
|
37043
|
+
const hasContentExtension = isContentExtension(finalSuffix) && stemDotIdx > 0;
|
|
37044
|
+
return {
|
|
37045
|
+
kind: "file",
|
|
37046
|
+
segments: parts.slice(0, -1),
|
|
37047
|
+
baseName: hasContentExtension ? stem.slice(0, stemDotIdx) : stem,
|
|
37048
|
+
extension: hasContentExtension ? stem.slice(stemDotIdx + 1) : finalSuffix,
|
|
37049
|
+
...hasContentExtension && { contentExtension: finalSuffix }
|
|
37050
|
+
};
|
|
37051
|
+
}
|
|
37052
|
+
var UNSAFE_DISK_SEGMENT_CHARS = /[\\/:*?"<>|\u0000-\u001f\u007f]/g;
|
|
37053
|
+
function sanitizeVfsSegmentForDisk(segment) {
|
|
37054
|
+
if (segment === "." || segment === ".." || segment.trim().length === 0) {
|
|
37055
|
+
return "_";
|
|
37056
|
+
}
|
|
37057
|
+
const cleaned = segment.replace(UNSAFE_DISK_SEGMENT_CHARS, "_").trim();
|
|
37058
|
+
return cleaned.length > 0 ? cleaned : "_";
|
|
37059
|
+
}
|
|
37060
|
+
function sanitizeVfsPathForDisk(path) {
|
|
37061
|
+
return path.split("/").map((segment) => segment === "" ? "" : sanitizeVfsSegmentForDisk(segment)).join("/");
|
|
37062
|
+
}
|
|
37063
|
+
|
|
36447
37064
|
// ../shared/src/files/file-reference-content.ts
|
|
36448
|
-
var FILE_REFERENCE_EXTENSION_RE = /\.[^.]+$/;
|
|
36449
37065
|
var STATE_PATH_LEADING_SLASH_RE = /^\/+/;
|
|
36450
37066
|
var STATE_PATH_ARRAY_INDEX_RE = /\[(?:\d+|\*)\]/g;
|
|
36451
37067
|
function isRecord5(value) {
|
|
@@ -36596,23 +37212,36 @@ function referenceHintsFromLayout(layout) {
|
|
|
36596
37212
|
collectLayoutReferenceHints(layout, hints);
|
|
36597
37213
|
return hints;
|
|
36598
37214
|
}
|
|
36599
|
-
function
|
|
37215
|
+
function referenceNameParts(value) {
|
|
36600
37216
|
const trimmed = value.trim();
|
|
36601
37217
|
const lastSegment = trimmed.split("/").filter(Boolean).at(-1) ?? trimmed;
|
|
36602
|
-
|
|
37218
|
+
try {
|
|
37219
|
+
const parsed = parseVfsPath(lastSegment);
|
|
37220
|
+
if (parsed.kind === "file") {
|
|
37221
|
+
return { name: parsed.baseName, type: parsed.extension };
|
|
37222
|
+
}
|
|
37223
|
+
} catch {}
|
|
37224
|
+
return { name: lastSegment, type: null };
|
|
37225
|
+
}
|
|
37226
|
+
function referenceCandidate(value) {
|
|
37227
|
+
return referenceNameParts(value).name;
|
|
36603
37228
|
}
|
|
36604
37229
|
function normalizeFileReferenceLookupValue(value) {
|
|
36605
37230
|
const candidate = referenceCandidate(value);
|
|
36606
37231
|
return generateSlug(candidate) || candidate.toLowerCase();
|
|
36607
37232
|
}
|
|
36608
37233
|
function qualifiedLookupTypeSlug(value, fileTypeSlugs) {
|
|
37234
|
+
const normalizedTargets = new Set(fileTypeSlugs.map((slug) => normalizeReferenceTargetSlug(slug)).filter(Boolean));
|
|
36609
37235
|
const segments = value.trim().split("/").filter(Boolean);
|
|
36610
|
-
if (segments.length
|
|
36611
|
-
|
|
37236
|
+
if (segments.length >= 2) {
|
|
37237
|
+
const prefix = normalizeReferenceTargetSlug(segments[0]);
|
|
37238
|
+
if (normalizedTargets.has(prefix)) {
|
|
37239
|
+
return prefix;
|
|
37240
|
+
}
|
|
36612
37241
|
}
|
|
36613
|
-
const
|
|
36614
|
-
const
|
|
36615
|
-
return normalizedTargets.has(
|
|
37242
|
+
const ending = referenceNameParts(value).type;
|
|
37243
|
+
const type = ending ? normalizeReferenceTargetSlug(ending) : "";
|
|
37244
|
+
return type && normalizedTargets.has(type) ? type : null;
|
|
36616
37245
|
}
|
|
36617
37246
|
function parseFileReferenceLookupValue(value, fileTypeSlugs = []) {
|
|
36618
37247
|
const fileTypeSlug = qualifiedLookupTypeSlug(value, fileTypeSlugs);
|
|
@@ -40168,85 +40797,6 @@ function validateSectionsBlock(sections, errors3, options) {
|
|
|
40168
40797
|
}
|
|
40169
40798
|
}
|
|
40170
40799
|
}
|
|
40171
|
-
// ../shared/src/board-trackers.ts
|
|
40172
|
-
var TRACKER_COUNT_STYLES = [
|
|
40173
|
-
"number",
|
|
40174
|
-
"bar",
|
|
40175
|
-
"pips",
|
|
40176
|
-
"boxes",
|
|
40177
|
-
"clock",
|
|
40178
|
-
"scale"
|
|
40179
|
-
];
|
|
40180
|
-
var TRACKER_TONES = ["good", "bad"];
|
|
40181
|
-
var TRACKER_ITEM_STATES = ["open", "done", "failed"];
|
|
40182
|
-
var TRACKER_LASTS = ["here", "game"];
|
|
40183
|
-
var TRACKER_LIMITS = {
|
|
40184
|
-
name: 60,
|
|
40185
|
-
label: 40,
|
|
40186
|
-
text: 160,
|
|
40187
|
-
rows: 6,
|
|
40188
|
-
items: 16,
|
|
40189
|
-
order: 20,
|
|
40190
|
-
marks: 20,
|
|
40191
|
-
clockMin: 2,
|
|
40192
|
-
clockMax: 12,
|
|
40193
|
-
scaleSteps: 21
|
|
40194
|
-
};
|
|
40195
|
-
var labelSchema = exports_external.string().trim().max(TRACKER_LIMITS.label).nullish();
|
|
40196
|
-
var countRowSchema = exports_external.object({
|
|
40197
|
-
kind: exports_external.literal("count"),
|
|
40198
|
-
label: labelSchema,
|
|
40199
|
-
value: exports_external.number(),
|
|
40200
|
-
min: exports_external.number().nullish(),
|
|
40201
|
-
max: exports_external.number().nullish(),
|
|
40202
|
-
style: exports_external.enum(TRACKER_COUNT_STYLES).nullish(),
|
|
40203
|
-
tone: exports_external.enum(TRACKER_TONES).nullish(),
|
|
40204
|
-
ends: exports_external.tuple([exports_external.string(), exports_external.string()]).nullish(),
|
|
40205
|
-
at: exports_external.record(exports_external.string(), exports_external.string()).nullish(),
|
|
40206
|
-
start: exports_external.number().nullish()
|
|
40207
|
-
});
|
|
40208
|
-
var listItemSchema = exports_external.object({
|
|
40209
|
-
text: exports_external.string().trim().min(1).max(TRACKER_LIMITS.text),
|
|
40210
|
-
state: exports_external.enum(TRACKER_ITEM_STATES),
|
|
40211
|
-
note: exports_external.string().trim().max(TRACKER_LIMITS.text).nullish()
|
|
40212
|
-
});
|
|
40213
|
-
var listRowSchema = exports_external.object({
|
|
40214
|
-
kind: exports_external.literal("list"),
|
|
40215
|
-
label: labelSchema,
|
|
40216
|
-
items: exports_external.array(listItemSchema).max(TRACKER_LIMITS.items)
|
|
40217
|
-
});
|
|
40218
|
-
var orderEntrySchema = exports_external.object({
|
|
40219
|
-
name: exports_external.string().trim().min(1),
|
|
40220
|
-
score: exports_external.number().nullish()
|
|
40221
|
-
});
|
|
40222
|
-
var orderRowSchema = exports_external.object({
|
|
40223
|
-
kind: exports_external.literal("order"),
|
|
40224
|
-
label: labelSchema,
|
|
40225
|
-
order: exports_external.array(orderEntrySchema).max(TRACKER_LIMITS.order),
|
|
40226
|
-
current: exports_external.string().nullish(),
|
|
40227
|
-
round: exports_external.number().int().nullish()
|
|
40228
|
-
});
|
|
40229
|
-
var textRowSchema = exports_external.object({
|
|
40230
|
-
kind: exports_external.literal("text"),
|
|
40231
|
-
label: labelSchema,
|
|
40232
|
-
text: exports_external.string().trim().max(TRACKER_LIMITS.text)
|
|
40233
|
-
});
|
|
40234
|
-
var trackerRowSchema = exports_external.discriminatedUnion("kind", [
|
|
40235
|
-
countRowSchema,
|
|
40236
|
-
listRowSchema,
|
|
40237
|
-
orderRowSchema,
|
|
40238
|
-
textRowSchema
|
|
40239
|
-
]);
|
|
40240
|
-
var trackerSchema = exports_external.object({
|
|
40241
|
-
name: exports_external.string().trim().min(1).max(TRACKER_LIMITS.name),
|
|
40242
|
-
rows: exports_external.array(trackerRowSchema).min(1).max(TRACKER_LIMITS.rows),
|
|
40243
|
-
for: exports_external.string().trim().min(1).nullish(),
|
|
40244
|
-
hidden: exports_external.boolean().nullish(),
|
|
40245
|
-
lasts: exports_external.enum(TRACKER_LASTS).nullish(),
|
|
40246
|
-
since: exports_external.string().nullish(),
|
|
40247
|
-
changed: exports_external.string().nullish()
|
|
40248
|
-
});
|
|
40249
|
-
|
|
40250
40800
|
// ../shared/src/scripts/board.ts
|
|
40251
40801
|
var UPDATE_KEYS = new Set([
|
|
40252
40802
|
"arrive",
|
|
@@ -41012,19 +41562,22 @@ var FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V6 = "file-designation-metadata:v6"
|
|
|
41012
41562
|
var FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V7 = "file-designation-metadata:v7";
|
|
41013
41563
|
var FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V8 = "file-designation-metadata:v8";
|
|
41014
41564
|
var FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V9 = "file-designation-metadata:v9";
|
|
41565
|
+
var FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V10 = "file-designation-metadata:v10";
|
|
41566
|
+
var FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V11 = "file-designation-metadata:v11";
|
|
41015
41567
|
var GAME_START_OPENING_MAX_LENGTH = 1e4;
|
|
41016
41568
|
var GAME_START_MAX_OPENING_BEATS = 50;
|
|
41017
41569
|
var GAME_START_MAX_CHARACTER_OPENINGS = 100;
|
|
41018
41570
|
var GAME_START_GM_NOTES_MAX_LENGTH = 40000;
|
|
41019
41571
|
var GAME_START_BOARD_LIMITS = {
|
|
41020
41572
|
people: 30,
|
|
41021
|
-
time: 80,
|
|
41022
41573
|
purpose: 300,
|
|
41023
41574
|
area: 60,
|
|
41024
41575
|
hiddenAs: 160,
|
|
41025
41576
|
note: 160,
|
|
41026
41577
|
objectives: 16,
|
|
41027
|
-
objective: 160
|
|
41578
|
+
objective: 160,
|
|
41579
|
+
trackers: 30,
|
|
41580
|
+
trackerKey: 60
|
|
41028
41581
|
};
|
|
41029
41582
|
var GM_INSTRUCTION_TRIGGER_PHASES = [
|
|
41030
41583
|
"post_player_message",
|
|
@@ -41212,6 +41765,142 @@ var MapDataV6Schema = MapDataV5Schema.extend({
|
|
|
41212
41765
|
var MapDataV8Schema = MapDataV6Schema.extend({
|
|
41213
41766
|
grid: MapGridSchema.nullable().default(null)
|
|
41214
41767
|
});
|
|
41768
|
+
var MAP_EXIT_KINDS = [
|
|
41769
|
+
"door",
|
|
41770
|
+
"gate",
|
|
41771
|
+
"stairs",
|
|
41772
|
+
"ladder",
|
|
41773
|
+
"hatch",
|
|
41774
|
+
"path",
|
|
41775
|
+
"road",
|
|
41776
|
+
"dock",
|
|
41777
|
+
"portal",
|
|
41778
|
+
"other"
|
|
41779
|
+
];
|
|
41780
|
+
var MAP_EXIT_DIRECTIONS = [
|
|
41781
|
+
"up",
|
|
41782
|
+
"down",
|
|
41783
|
+
"in",
|
|
41784
|
+
"out",
|
|
41785
|
+
"through"
|
|
41786
|
+
];
|
|
41787
|
+
var MapExitSchema = exports_external.object({
|
|
41788
|
+
kind: exports_external.enum(MAP_EXIT_KINDS).default("door"),
|
|
41789
|
+
to: exports_external.string().uuid().nullable().default(null),
|
|
41790
|
+
direction: exports_external.enum(MAP_EXIT_DIRECTIONS).optional(),
|
|
41791
|
+
arriveAt: exports_external.string().uuid().nullable().optional(),
|
|
41792
|
+
main: exports_external.boolean().optional(),
|
|
41793
|
+
locked: exports_external.boolean().optional(),
|
|
41794
|
+
hidden: exports_external.boolean().optional()
|
|
41795
|
+
});
|
|
41796
|
+
var MapAreaV10Schema = MapAreaSchema.extend({
|
|
41797
|
+
exit: MapExitSchema.optional()
|
|
41798
|
+
});
|
|
41799
|
+
var MapPointV10Schema = MapPointSchema.extend({
|
|
41800
|
+
exit: MapExitSchema.optional()
|
|
41801
|
+
});
|
|
41802
|
+
var MapDataV10Schema = MapDataV8Schema.extend({
|
|
41803
|
+
areas: exports_external.array(MapAreaV10Schema),
|
|
41804
|
+
points: exports_external.array(MapPointV10Schema)
|
|
41805
|
+
});
|
|
41806
|
+
var COMPASS_DIRECTIONS = [
|
|
41807
|
+
"north",
|
|
41808
|
+
"northeast",
|
|
41809
|
+
"east",
|
|
41810
|
+
"southeast",
|
|
41811
|
+
"south",
|
|
41812
|
+
"southwest",
|
|
41813
|
+
"west",
|
|
41814
|
+
"northwest"
|
|
41815
|
+
];
|
|
41816
|
+
var PLACE_WAY_KINDS = [
|
|
41817
|
+
...MAP_EXIT_KINDS,
|
|
41818
|
+
"bridge",
|
|
41819
|
+
"ferry",
|
|
41820
|
+
"tunnel",
|
|
41821
|
+
"ritual"
|
|
41822
|
+
];
|
|
41823
|
+
var PLACE_WAY_DIRECTIONS = [
|
|
41824
|
+
...MAP_EXIT_DIRECTIONS,
|
|
41825
|
+
...COMPASS_DIRECTIONS
|
|
41826
|
+
];
|
|
41827
|
+
var relationWords = (max) => exports_external.string().trim().min(1).max(max);
|
|
41828
|
+
var PlaceWaySchema = MapExitSchema.extend({
|
|
41829
|
+
kind: exports_external.enum(PLACE_WAY_KINDS).default("door"),
|
|
41830
|
+
direction: exports_external.enum(PLACE_WAY_DIRECTIONS).optional(),
|
|
41831
|
+
oneWay: exports_external.boolean().optional(),
|
|
41832
|
+
when: relationWords(200).optional(),
|
|
41833
|
+
requires: relationWords(200).optional(),
|
|
41834
|
+
by: exports_external.string().uuid().optional(),
|
|
41835
|
+
label: relationWords(120).optional(),
|
|
41836
|
+
distance: relationWords(60).optional(),
|
|
41837
|
+
time: relationWords(60).optional()
|
|
41838
|
+
});
|
|
41839
|
+
var MapAreaV11Schema = MapAreaSchema.extend({
|
|
41840
|
+
exit: PlaceWaySchema.optional()
|
|
41841
|
+
});
|
|
41842
|
+
var MapPointV11Schema = MapPointSchema.extend({
|
|
41843
|
+
exit: PlaceWaySchema.optional()
|
|
41844
|
+
});
|
|
41845
|
+
var MapDataV11Schema = MapDataV8Schema.extend({
|
|
41846
|
+
areas: exports_external.array(MapAreaV11Schema),
|
|
41847
|
+
points: exports_external.array(MapPointV11Schema)
|
|
41848
|
+
});
|
|
41849
|
+
var RelationIdSchema = exports_external.string().uuid();
|
|
41850
|
+
var RelationTargetSchema = exports_external.string().uuid();
|
|
41851
|
+
var InsideRelationSchema = exports_external.object({
|
|
41852
|
+
id: RelationIdSchema,
|
|
41853
|
+
type: exports_external.literal("inside"),
|
|
41854
|
+
to: RelationTargetSchema,
|
|
41855
|
+
level: exports_external.number().int().min(-100).max(500).optional()
|
|
41856
|
+
});
|
|
41857
|
+
var ConnectsRelationSchema = PlaceWaySchema.extend({
|
|
41858
|
+
id: RelationIdSchema,
|
|
41859
|
+
type: exports_external.literal("connects")
|
|
41860
|
+
});
|
|
41861
|
+
var OtherRelationSchema = exports_external.object({
|
|
41862
|
+
id: RelationIdSchema,
|
|
41863
|
+
type: exports_external.literal("other"),
|
|
41864
|
+
to: RelationTargetSchema,
|
|
41865
|
+
label: relationWords(120)
|
|
41866
|
+
});
|
|
41867
|
+
var PlaceRelationSchema = exports_external.discriminatedUnion("type", [
|
|
41868
|
+
InsideRelationSchema,
|
|
41869
|
+
ConnectsRelationSchema,
|
|
41870
|
+
OtherRelationSchema
|
|
41871
|
+
]);
|
|
41872
|
+
var MAX_PLACE_RELATIONS = 100;
|
|
41873
|
+
var TRANSPORT_KINDS = [
|
|
41874
|
+
"ship",
|
|
41875
|
+
"boat",
|
|
41876
|
+
"airship",
|
|
41877
|
+
"wagon",
|
|
41878
|
+
"caravan",
|
|
41879
|
+
"carriage",
|
|
41880
|
+
"train",
|
|
41881
|
+
"car",
|
|
41882
|
+
"bus",
|
|
41883
|
+
"plane",
|
|
41884
|
+
"mount",
|
|
41885
|
+
"other"
|
|
41886
|
+
];
|
|
41887
|
+
var TRANSPORT_MEDIUMS = [
|
|
41888
|
+
"water",
|
|
41889
|
+
"land",
|
|
41890
|
+
"air",
|
|
41891
|
+
"rail",
|
|
41892
|
+
"underground",
|
|
41893
|
+
"space"
|
|
41894
|
+
];
|
|
41895
|
+
var TransportMetadataSchema = exports_external.object({
|
|
41896
|
+
kind: exports_external.enum(TRANSPORT_KINDS),
|
|
41897
|
+
travels: exports_external.enum(TRANSPORT_MEDIUMS).optional(),
|
|
41898
|
+
speed: exports_external.object({
|
|
41899
|
+
value: exports_external.number().positive(),
|
|
41900
|
+
unit: exports_external.enum(["mph", "km/h", "knots"])
|
|
41901
|
+
}).optional(),
|
|
41902
|
+
capacity: exports_external.number().int().positive().optional()
|
|
41903
|
+
});
|
|
41215
41904
|
var GmInstructionTriggerPhaseSchema = exports_external.enum(GM_INSTRUCTION_TRIGGER_PHASES);
|
|
41216
41905
|
var GmInstructionToolEventSchema = exports_external.enum(GM_INSTRUCTION_TOOL_EVENTS);
|
|
41217
41906
|
var GmInstructionNarrativeMetadataSchema = exports_external.object({
|
|
@@ -41270,12 +41959,43 @@ var GameStartBoardPersonSchema = exports_external.object({
|
|
|
41270
41959
|
}).nullish(),
|
|
41271
41960
|
note: exports_external.string().trim().max(GAME_START_BOARD_LIMITS.note).nullish()
|
|
41272
41961
|
});
|
|
41273
|
-
var
|
|
41962
|
+
var GameStartTrackerSchema = exports_external.object({
|
|
41963
|
+
key: exports_external.string().trim().min(1).max(GAME_START_BOARD_LIMITS.trackerKey),
|
|
41964
|
+
on: exports_external.boolean(),
|
|
41965
|
+
fields: exports_external.array(trackerFieldSchema).max(TRACKER_LIMITS.fields).nullish()
|
|
41966
|
+
});
|
|
41967
|
+
function isPlainObject2(value) {
|
|
41968
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
41969
|
+
}
|
|
41970
|
+
function boardWithTimeAsTracker(value) {
|
|
41971
|
+
if (!(isPlainObject2(value) && ("time" in value))) {
|
|
41972
|
+
return value;
|
|
41973
|
+
}
|
|
41974
|
+
const { time: time3, ...rest } = value;
|
|
41975
|
+
const words = typeof time3 === "string" ? time3.trim() : "";
|
|
41976
|
+
const trackers = Array.isArray(rest.trackers) ? rest.trackers : [];
|
|
41977
|
+
const set2 = trackers.some((entry) => isPlainObject2(entry) && entry.key === TIME_TRACKER_KEY);
|
|
41978
|
+
if (!words || set2) {
|
|
41979
|
+
return rest;
|
|
41980
|
+
}
|
|
41981
|
+
return {
|
|
41982
|
+
...rest,
|
|
41983
|
+
trackers: [
|
|
41984
|
+
...trackers,
|
|
41985
|
+
{
|
|
41986
|
+
key: TIME_TRACKER_KEY,
|
|
41987
|
+
on: true,
|
|
41988
|
+
fields: [{ kind: "text", text: words }]
|
|
41989
|
+
}
|
|
41990
|
+
]
|
|
41991
|
+
};
|
|
41992
|
+
}
|
|
41993
|
+
var GameStartBoardSchema = exports_external.preprocess(boardWithTimeAsTracker, exports_external.object({
|
|
41274
41994
|
people: exports_external.array(GameStartBoardPersonSchema).max(GAME_START_BOARD_LIMITS.people).default([]),
|
|
41275
|
-
time: exports_external.string().trim().max(GAME_START_BOARD_LIMITS.time).default(""),
|
|
41276
41995
|
purpose: exports_external.string().trim().max(GAME_START_BOARD_LIMITS.purpose).default(""),
|
|
41277
|
-
objectives: exports_external.array(exports_external.string().trim().min(1).max(GAME_START_BOARD_LIMITS.objective)).max(GAME_START_BOARD_LIMITS.objectives).default([])
|
|
41278
|
-
|
|
41996
|
+
objectives: exports_external.array(exports_external.string().trim().min(1).max(GAME_START_BOARD_LIMITS.objective)).max(GAME_START_BOARD_LIMITS.objectives).default([]),
|
|
41997
|
+
trackers: exports_external.array(GameStartTrackerSchema).max(GAME_START_BOARD_LIMITS.trackers).default([])
|
|
41998
|
+
}));
|
|
41279
41999
|
function combinedOpeningLength(opening) {
|
|
41280
42000
|
if (opening.kind !== "static" || !opening.beats) {
|
|
41281
42001
|
return 0;
|
|
@@ -41294,9 +42014,9 @@ var GameStartMetadataObjectSchema = exports_external.object({
|
|
|
41294
42014
|
openingInstructions: exports_external.string().max(GAME_START_OPENING_MAX_LENGTH).default(""),
|
|
41295
42015
|
board: GameStartBoardSchema.default({
|
|
41296
42016
|
people: [],
|
|
41297
|
-
time: "",
|
|
41298
42017
|
purpose: "",
|
|
41299
|
-
objectives: []
|
|
42018
|
+
objectives: [],
|
|
42019
|
+
trackers: []
|
|
41300
42020
|
}),
|
|
41301
42021
|
gmNotes: exports_external.string().max(GAME_START_GM_NOTES_MAX_LENGTH).default("")
|
|
41302
42022
|
});
|
|
@@ -41339,6 +42059,17 @@ var GameStartMetadataSchema = GameStartMetadataObjectSchema.superRefine((metadat
|
|
|
41339
42059
|
}
|
|
41340
42060
|
seenBoardReferenceIds.add(referenceId);
|
|
41341
42061
|
});
|
|
42062
|
+
const seenTrackerKeys = new Set;
|
|
42063
|
+
metadata.board.trackers.forEach((tracker, index) => {
|
|
42064
|
+
if (seenTrackerKeys.has(tracker.key)) {
|
|
42065
|
+
ctx.addIssue({
|
|
42066
|
+
code: "custom",
|
|
42067
|
+
path: ["board", "trackers", index, "key"],
|
|
42068
|
+
message: "Each tracker can only be set once."
|
|
42069
|
+
});
|
|
42070
|
+
}
|
|
42071
|
+
seenTrackerKeys.add(tracker.key);
|
|
42072
|
+
});
|
|
41342
42073
|
});
|
|
41343
42074
|
var FileDesignationMetadataV1Schema = exports_external.object({
|
|
41344
42075
|
schemaVersionId: exports_external.literal(FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V1).default(FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V1),
|
|
@@ -41403,24 +42134,36 @@ var FileDesignationMetadataV9Schema = FileDesignationMetadataV8Schema.extend({
|
|
|
41403
42134
|
schemaVersionId: exports_external.literal(FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V9).default(FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V9),
|
|
41404
42135
|
script: ScriptMetadataSchema.optional()
|
|
41405
42136
|
});
|
|
41406
|
-
var
|
|
41407
|
-
|
|
42137
|
+
var FileDesignationMetadataV10Schema = FileDesignationMetadataV9Schema.extend({
|
|
42138
|
+
schemaVersionId: exports_external.literal(FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V10).default(FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V10),
|
|
42139
|
+
map: MapDataV10Schema.optional(),
|
|
42140
|
+
transport: TransportMetadataSchema.optional()
|
|
42141
|
+
});
|
|
42142
|
+
var FileDesignationMetadataV11Schema = FileDesignationMetadataV10Schema.extend({
|
|
42143
|
+
schemaVersionId: exports_external.literal(FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V11).default(FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V11),
|
|
42144
|
+
map: MapDataV11Schema.optional(),
|
|
42145
|
+
relations: exports_external.array(PlaceRelationSchema).max(MAX_PLACE_RELATIONS).optional()
|
|
42146
|
+
});
|
|
42147
|
+
var MapSchema = FileDesignationMetadataV11Schema.shape.map.unwrap();
|
|
42148
|
+
var LocationDesignationMetadataSchema = FileDesignationMetadataV11Schema.pick({
|
|
41408
42149
|
schemaVersionId: true,
|
|
41409
|
-
map: true
|
|
42150
|
+
map: true,
|
|
42151
|
+
transport: true,
|
|
42152
|
+
relations: true
|
|
41410
42153
|
});
|
|
41411
|
-
var CharacterDesignationMetadataSchema =
|
|
42154
|
+
var CharacterDesignationMetadataSchema = FileDesignationMetadataV11Schema.pick({
|
|
41412
42155
|
schemaVersionId: true,
|
|
41413
42156
|
playable: true
|
|
41414
42157
|
});
|
|
41415
|
-
var GmInstructionDesignationMetadataSchema =
|
|
42158
|
+
var GmInstructionDesignationMetadataSchema = FileDesignationMetadataV11Schema.pick({
|
|
41416
42159
|
schemaVersionId: true,
|
|
41417
42160
|
gmInstruction: true
|
|
41418
42161
|
});
|
|
41419
|
-
var GameStartDesignationMetadataSchema =
|
|
42162
|
+
var GameStartDesignationMetadataSchema = FileDesignationMetadataV11Schema.pick({
|
|
41420
42163
|
schemaVersionId: true,
|
|
41421
42164
|
gameStart: true
|
|
41422
42165
|
});
|
|
41423
|
-
var ScriptDesignationMetadataSchema =
|
|
42166
|
+
var ScriptDesignationMetadataSchema = FileDesignationMetadataV11Schema.pick({
|
|
41424
42167
|
schemaVersionId: true,
|
|
41425
42168
|
script: true
|
|
41426
42169
|
});
|
|
@@ -41428,10 +42171,17 @@ function upgradeFileDesignationMetadata(input) {
|
|
|
41428
42171
|
if (input === null || input === undefined) {
|
|
41429
42172
|
return null;
|
|
41430
42173
|
}
|
|
41431
|
-
|
|
41432
|
-
|
|
42174
|
+
const schemaVersionId = readSchemaVersionId(input);
|
|
42175
|
+
if (schemaVersionId === FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V11) {
|
|
42176
|
+
return FileDesignationMetadataV11Schema.parse(input);
|
|
42177
|
+
}
|
|
42178
|
+
if (schemaVersionId === FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V10) {
|
|
42179
|
+
return upgradeV10MetadataToV11(FileDesignationMetadataV10Schema.parse(input));
|
|
42180
|
+
}
|
|
42181
|
+
if (schemaVersionId === FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V9) {
|
|
42182
|
+
return upgradeV10MetadataToV11(upgradeV9MetadataToV10(FileDesignationMetadataV9Schema.parse(input)));
|
|
41433
42183
|
}
|
|
41434
|
-
return upgradeV8MetadataToV9(upgradeMetadataToV8(input));
|
|
42184
|
+
return upgradeV10MetadataToV11(upgradeV9MetadataToV10(upgradeV8MetadataToV9(upgradeMetadataToV8(input))));
|
|
41435
42185
|
}
|
|
41436
42186
|
function upgradeMetadataToV8(input) {
|
|
41437
42187
|
const schemaVersionId = readSchemaVersionId(input);
|
|
@@ -41518,6 +42268,18 @@ function upgradeV8MetadataToV9(metadata) {
|
|
|
41518
42268
|
schemaVersionId: FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V9
|
|
41519
42269
|
});
|
|
41520
42270
|
}
|
|
42271
|
+
function upgradeV9MetadataToV10(metadata) {
|
|
42272
|
+
return FileDesignationMetadataV10Schema.parse({
|
|
42273
|
+
...metadata,
|
|
42274
|
+
schemaVersionId: FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V10
|
|
42275
|
+
});
|
|
42276
|
+
}
|
|
42277
|
+
function upgradeV10MetadataToV11(metadata) {
|
|
42278
|
+
return FileDesignationMetadataV11Schema.parse({
|
|
42279
|
+
...metadata,
|
|
42280
|
+
schemaVersionId: FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V11
|
|
42281
|
+
});
|
|
42282
|
+
}
|
|
41521
42283
|
function normalizeLegacyDefaultGrid(map2) {
|
|
41522
42284
|
if (map2.grid?.mode !== "default") {
|
|
41523
42285
|
return map2;
|
|
@@ -41812,39 +42574,95 @@ interface BoardPerson {
|
|
|
41812
42574
|
readonly details: { readonly [detail: string]: string | number | boolean };
|
|
41813
42575
|
}
|
|
41814
42576
|
|
|
41815
|
-
/**
|
|
41816
|
-
type
|
|
42577
|
+
/** A file or person a tracker points at, by name; its file is filled in by the game. */
|
|
42578
|
+
type BoardTrackerRef = { name: string; file?: string | null };
|
|
42579
|
+
|
|
42580
|
+
/** A number: a count, a clock (pressure filling toward trouble) or a scale (a tug of war between two ends). */
|
|
42581
|
+
type BoardTrackerNumber = {
|
|
42582
|
+
kind: "count" | "clock" | "scale";
|
|
42583
|
+
label?: string | null;
|
|
42584
|
+
/** The GM's alone: players never see this field. */
|
|
42585
|
+
hidden?: boolean | null;
|
|
42586
|
+
value: number;
|
|
42587
|
+
min?: number | null;
|
|
42588
|
+
max?: number | null;
|
|
42589
|
+
tone?: "good" | "bad" | null;
|
|
42590
|
+
/** A scale's two ends, low first. */
|
|
42591
|
+
ends?: [string, string] | null;
|
|
42592
|
+
/** What happens when the number reaches a value: { "6": "The guards arrive" }. */
|
|
42593
|
+
at?: { [value: string]: string } | null;
|
|
42594
|
+
};
|
|
42595
|
+
|
|
42596
|
+
type BoardTrackerList = {
|
|
42597
|
+
kind: "list";
|
|
42598
|
+
label?: string | null;
|
|
42599
|
+
hidden?: boolean | null;
|
|
42600
|
+
items: {
|
|
42601
|
+
text: string;
|
|
42602
|
+
state: "open" | "done" | "failed";
|
|
42603
|
+
note?: string | null;
|
|
42604
|
+
/** Who or what the item is about. */
|
|
42605
|
+
ref?: BoardTrackerRef | null;
|
|
42606
|
+
hidden?: boolean | null;
|
|
42607
|
+
}[];
|
|
42608
|
+
};
|
|
42609
|
+
|
|
42610
|
+
type BoardTrackerText = { kind: "text"; label?: string | null; hidden?: boolean | null; text: string };
|
|
42611
|
+
|
|
42612
|
+
/**
|
|
42613
|
+
* One field of a tracker: a number, a list, a turn order, a short text, a
|
|
42614
|
+
* file or person it points at, records (one per person or file, each with
|
|
42615
|
+
* fields of its own) or links (one per pair of them, with fields between
|
|
42616
|
+
* the two). show names live values drawn beside each person a field names:
|
|
42617
|
+
* a stat the board shows ("HP") or "where".
|
|
42618
|
+
*/
|
|
42619
|
+
type BoardTrackerField =
|
|
42620
|
+
| BoardTrackerNumber
|
|
42621
|
+
| BoardTrackerList
|
|
41817
42622
|
| {
|
|
41818
|
-
kind: "
|
|
42623
|
+
kind: "order";
|
|
41819
42624
|
label?: string | null;
|
|
41820
|
-
|
|
41821
|
-
|
|
41822
|
-
|
|
41823
|
-
|
|
41824
|
-
|
|
41825
|
-
/** A scale's two ends, low first. */
|
|
41826
|
-
ends?: [string, string] | null;
|
|
41827
|
-
/** What happens when the count reaches a value: { "6": "The guards arrive" }. */
|
|
41828
|
-
at?: { [value: string]: string } | null;
|
|
42625
|
+
hidden?: boolean | null;
|
|
42626
|
+
entries: { name: string; score?: number | null }[];
|
|
42627
|
+
current?: string | null;
|
|
42628
|
+
round?: number | null;
|
|
42629
|
+
show?: string[] | null;
|
|
41829
42630
|
}
|
|
42631
|
+
| BoardTrackerText
|
|
41830
42632
|
| {
|
|
41831
|
-
kind: "
|
|
42633
|
+
kind: "ref";
|
|
41832
42634
|
label?: string | null;
|
|
41833
|
-
|
|
42635
|
+
hidden?: boolean | null;
|
|
42636
|
+
ref: BoardTrackerRef;
|
|
42637
|
+
show?: string[] | null;
|
|
41834
42638
|
}
|
|
41835
42639
|
| {
|
|
41836
|
-
kind: "
|
|
42640
|
+
kind: "records";
|
|
41837
42641
|
label?: string | null;
|
|
41838
|
-
|
|
41839
|
-
|
|
41840
|
-
|
|
42642
|
+
hidden?: boolean | null;
|
|
42643
|
+
entries: {
|
|
42644
|
+
ref: BoardTrackerRef;
|
|
42645
|
+
hidden?: boolean | null;
|
|
42646
|
+
fields: (BoardTrackerNumber | BoardTrackerList | BoardTrackerText)[];
|
|
42647
|
+
}[];
|
|
42648
|
+
show?: string[] | null;
|
|
41841
42649
|
}
|
|
41842
|
-
| {
|
|
42650
|
+
| {
|
|
42651
|
+
kind: "links";
|
|
42652
|
+
label?: string | null;
|
|
42653
|
+
hidden?: boolean | null;
|
|
42654
|
+
entries: {
|
|
42655
|
+
/** The two it links ("Kael" and "Mira"); a pair is the same either way round. */
|
|
42656
|
+
between: [BoardTrackerRef, BoardTrackerRef];
|
|
42657
|
+
hidden?: boolean | null;
|
|
42658
|
+
fields: (BoardTrackerNumber | BoardTrackerList | BoardTrackerText)[];
|
|
42659
|
+
}[];
|
|
42660
|
+
};
|
|
41843
42661
|
|
|
41844
|
-
/** A tracker on the board: a turn order, a clock,
|
|
42662
|
+
/** A tracker on the board: a turn order, a clock, the objectives, suspects, a count. */
|
|
41845
42663
|
interface BoardTracker {
|
|
41846
42664
|
name: string;
|
|
41847
|
-
|
|
42665
|
+
fields: BoardTrackerField[];
|
|
41848
42666
|
/** Someone on the board it is about. */
|
|
41849
42667
|
for?: string | null;
|
|
41850
42668
|
/** The GM's alone: players never see it. */
|
|
@@ -42798,7 +43616,7 @@ function parseMarkdown(source) {
|
|
|
42798
43616
|
} catch {
|
|
42799
43617
|
return { content: source };
|
|
42800
43618
|
}
|
|
42801
|
-
if (!
|
|
43619
|
+
if (!isPlainObject3(parsed)) {
|
|
42802
43620
|
return { content: body };
|
|
42803
43621
|
}
|
|
42804
43622
|
const file2 = { content: body };
|
|
@@ -42846,7 +43664,7 @@ function parseFrontmatterObject(source) {
|
|
|
42846
43664
|
} catch {
|
|
42847
43665
|
return null;
|
|
42848
43666
|
}
|
|
42849
|
-
return
|
|
43667
|
+
return isPlainObject3(parsed) ? parsed : null;
|
|
42850
43668
|
}
|
|
42851
43669
|
function stripLeadingNewline(s) {
|
|
42852
43670
|
if (s.startsWith(`\r
|
|
@@ -42859,7 +43677,7 @@ function stripLeadingNewline(s) {
|
|
|
42859
43677
|
}
|
|
42860
43678
|
return s;
|
|
42861
43679
|
}
|
|
42862
|
-
function
|
|
43680
|
+
function isPlainObject3(value) {
|
|
42863
43681
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
42864
43682
|
}
|
|
42865
43683
|
function coerceString(value) {
|
|
@@ -42875,19 +43693,6 @@ function coerceString(value) {
|
|
|
42875
43693
|
// src/engine/vfs-name.ts
|
|
42876
43694
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
42877
43695
|
|
|
42878
|
-
// ../shared/src/vfs/path.ts
|
|
42879
|
-
var UNSAFE_DISK_SEGMENT_CHARS = /[\\/:*?"<>|\u0000-\u001f\u007f]/g;
|
|
42880
|
-
function sanitizeVfsSegmentForDisk(segment) {
|
|
42881
|
-
if (segment === "." || segment === ".." || segment.trim().length === 0) {
|
|
42882
|
-
return "_";
|
|
42883
|
-
}
|
|
42884
|
-
const cleaned = segment.replace(UNSAFE_DISK_SEGMENT_CHARS, "_").trim();
|
|
42885
|
-
return cleaned.length > 0 ? cleaned : "_";
|
|
42886
|
-
}
|
|
42887
|
-
function sanitizeVfsPathForDisk(path) {
|
|
42888
|
-
return path.split("/").map((segment) => segment === "" ? "" : sanitizeVfsSegmentForDisk(segment)).join("/");
|
|
42889
|
-
}
|
|
42890
|
-
|
|
42891
43696
|
// ../shared/src/cli/vfs-name.ts
|
|
42892
43697
|
function parseVfsFileName(fileName) {
|
|
42893
43698
|
if (fileName.includes("/")) {
|
|
@@ -42982,10 +43787,10 @@ function deletedSidecarPath(workingPath) {
|
|
|
42982
43787
|
}
|
|
42983
43788
|
|
|
42984
43789
|
// src/engine/vfs-name.ts
|
|
42985
|
-
var
|
|
43790
|
+
var LEADING_SLASHES2 = /^\/+/;
|
|
42986
43791
|
function joinUnderWorkspaceRoot(root, vfsPath) {
|
|
42987
43792
|
const rootResolved = resolve(root);
|
|
42988
|
-
const relativePath = vfsPath.replace(
|
|
43793
|
+
const relativePath = vfsPath.replace(LEADING_SLASHES2, "");
|
|
42989
43794
|
if (relativePath.length === 0 || relativePath.split("/").some((segment) => segment === "." || segment === "..")) {
|
|
42990
43795
|
throw new Error(`path escapes workspace root: ${vfsPath}`);
|
|
42991
43796
|
}
|
|
@@ -43680,9 +44485,9 @@ async function loadBaseRecordsForMissing({
|
|
|
43680
44485
|
return records;
|
|
43681
44486
|
}
|
|
43682
44487
|
var LEADING_DOT_SLASH = /^\.\//;
|
|
43683
|
-
var
|
|
44488
|
+
var TRAILING_SLASHES2 = /\/+$/;
|
|
43684
44489
|
function normalizeVfsPath(input) {
|
|
43685
|
-
const cleaned = input.replace(LEADING_DOT_SLASH, "").replace(
|
|
44490
|
+
const cleaned = input.replace(LEADING_DOT_SLASH, "").replace(TRAILING_SLASHES2, "");
|
|
43686
44491
|
return cleaned.startsWith("/") ? cleaned : `/${cleaned}`;
|
|
43687
44492
|
}
|
|
43688
44493
|
function toIsoTimestamp(value) {
|
|
@@ -44151,6 +44956,17 @@ function checkGameOptions(workspace) {
|
|
|
44151
44956
|
detail: "shape errors are reported under project-settings"
|
|
44152
44957
|
};
|
|
44153
44958
|
}
|
|
44959
|
+
const unknown2 = unknownGameOptionsKeysProblem({
|
|
44960
|
+
raw: value,
|
|
44961
|
+
where: "settings.gameOptions"
|
|
44962
|
+
});
|
|
44963
|
+
if (unknown2) {
|
|
44964
|
+
return {
|
|
44965
|
+
...base,
|
|
44966
|
+
status: "warn",
|
|
44967
|
+
detail: `${unknown2} Push drops them, so they do nothing (format: .craft/instructions/upgrading-to-gm-harness-2.md)`
|
|
44968
|
+
};
|
|
44969
|
+
}
|
|
44154
44970
|
const parsed = gameOptionsSchema.safeParse(value);
|
|
44155
44971
|
if (!parsed.success) {
|
|
44156
44972
|
const issue3 = parsed.error.issues[0];
|
|
@@ -44929,7 +45745,7 @@ var credentialsFileSchema = exports_external.object({
|
|
|
44929
45745
|
version: exports_external.literal(1),
|
|
44930
45746
|
remotes: exports_external.record(exports_external.string(), storedCredentialsSchema)
|
|
44931
45747
|
}).strict();
|
|
44932
|
-
var
|
|
45748
|
+
var TRAILING_SLASHES3 = /\/+$/;
|
|
44933
45749
|
function credentialsPath() {
|
|
44934
45750
|
const dir = process.env.CRAFT_CONFIG_DIR ?? join4(homedir(), ".config", "craft");
|
|
44935
45751
|
return join4(dir, "credentials.json");
|
|
@@ -44938,7 +45754,7 @@ function normalizeRemoteKey(remote) {
|
|
|
44938
45754
|
try {
|
|
44939
45755
|
return new URL(remote).origin;
|
|
44940
45756
|
} catch {
|
|
44941
|
-
return remote.replace(
|
|
45757
|
+
return remote.replace(TRAILING_SLASHES3, "");
|
|
44942
45758
|
}
|
|
44943
45759
|
}
|
|
44944
45760
|
async function readCredentialsFile() {
|
|
@@ -44993,7 +45809,7 @@ async function deleteStoredCredentials(remote) {
|
|
|
44993
45809
|
|
|
44994
45810
|
// src/auth/oauth.ts
|
|
44995
45811
|
import { createHash as createHash2, randomBytes as randomBytes3 } from "node:crypto";
|
|
44996
|
-
var
|
|
45812
|
+
var TRAILING_SLASHES4 = /\/+$/;
|
|
44997
45813
|
async function fetchJson(fetchImpl, url2, init) {
|
|
44998
45814
|
let response;
|
|
44999
45815
|
try {
|
|
@@ -45011,7 +45827,7 @@ function isRecord15(value) {
|
|
|
45011
45827
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
45012
45828
|
}
|
|
45013
45829
|
function authServerMetadataUrls(issuer) {
|
|
45014
|
-
const base = issuer.replace(
|
|
45830
|
+
const base = issuer.replace(TRAILING_SLASHES4, "");
|
|
45015
45831
|
const url2 = new URL(base);
|
|
45016
45832
|
const suffixForm = `${base}/.well-known/oauth-authorization-server`;
|
|
45017
45833
|
const insertionForm = `${url2.origin}/.well-known/oauth-authorization-server${url2.pathname === "/" ? "" : url2.pathname}`;
|
|
@@ -45021,7 +45837,7 @@ async function discoverAuthorization({
|
|
|
45021
45837
|
remote,
|
|
45022
45838
|
fetchImpl = fetch
|
|
45023
45839
|
}) {
|
|
45024
|
-
const origin = remote.replace(
|
|
45840
|
+
const origin = remote.replace(TRAILING_SLASHES4, "");
|
|
45025
45841
|
const prmUrl = `${origin}/.well-known/oauth-protected-resource/mcp`;
|
|
45026
45842
|
const prm = await fetchJson(fetchImpl, prmUrl);
|
|
45027
45843
|
if (!(prm.ok && isRecord15(prm.json))) {
|
|
@@ -45379,7 +46195,7 @@ async function refreshWorkspaceScriptTypes({
|
|
|
45379
46195
|
// package.json
|
|
45380
46196
|
var package_default = {
|
|
45381
46197
|
name: "@craftrpgs/cli",
|
|
45382
|
-
version: "0.2.0-alpha.
|
|
46198
|
+
version: "0.2.0-alpha.2",
|
|
45383
46199
|
description: "Sync Craft projects with a local folder — clone, edit with any coding agent, push back.",
|
|
45384
46200
|
license: "UNLICENSED",
|
|
45385
46201
|
homepage: "https://craftrpgs.com",
|
|
@@ -45400,14 +46216,14 @@ var package_default = {
|
|
|
45400
46216
|
scripts: {
|
|
45401
46217
|
build: "bun run scripts/build.ts",
|
|
45402
46218
|
prepack: "bun run build",
|
|
45403
|
-
typecheck: "
|
|
46219
|
+
typecheck: "bun check"
|
|
45404
46220
|
},
|
|
45405
46221
|
devDependencies: {
|
|
45406
46222
|
"@craft/flow": "workspace:*",
|
|
45407
46223
|
"@craft/shared": "workspace:*",
|
|
45408
46224
|
fflate: "^0.8.3",
|
|
45409
46225
|
typescript: "7.0.2",
|
|
45410
|
-
uuid: "11.1.
|
|
46226
|
+
uuid: "11.1.1",
|
|
45411
46227
|
zod: "4.3.6"
|
|
45412
46228
|
}
|
|
45413
46229
|
};
|
|
@@ -45416,7 +46232,7 @@ var package_default = {
|
|
|
45416
46232
|
var CLI_VERSION = package_default.version;
|
|
45417
46233
|
|
|
45418
46234
|
// src/http.ts
|
|
45419
|
-
var
|
|
46235
|
+
var TRAILING_SLASHES5 = /\/+$/;
|
|
45420
46236
|
var MAX_CONTRACT_ISSUES_SHOWN = 3;
|
|
45421
46237
|
function formatContractIssues(issues) {
|
|
45422
46238
|
if (!issues || issues.length === 0) {
|
|
@@ -45439,6 +46255,17 @@ var cliMetaWriteResponseSchema = exports_external.object({
|
|
|
45439
46255
|
version: exports_external.object({ id: exports_external.string().min(1) }),
|
|
45440
46256
|
linkStateChanged: exports_external.boolean().optional()
|
|
45441
46257
|
});
|
|
46258
|
+
var cliPlacePairWriteResponseSchema = exports_external.object({
|
|
46259
|
+
changedFileIds: exports_external.array(exports_external.string().min(1))
|
|
46260
|
+
});
|
|
46261
|
+
var cliLinkedLocationsResponseSchema = exports_external.object({
|
|
46262
|
+
parent: exports_external.object({ referenceId: exports_external.string() }).nullable(),
|
|
46263
|
+
places: exports_external.array(exports_external.object({
|
|
46264
|
+
referenceId: exports_external.string(),
|
|
46265
|
+
name: exports_external.string(),
|
|
46266
|
+
pair: exports_external.record(exports_external.string(), exports_external.unknown())
|
|
46267
|
+
}))
|
|
46268
|
+
});
|
|
45442
46269
|
var cliFileMetaResponseSchema = exports_external.object({
|
|
45443
46270
|
currentVersion: exports_external.object({
|
|
45444
46271
|
id: exports_external.string().min(1),
|
|
@@ -45541,8 +46368,8 @@ function createCliClient({
|
|
|
45541
46368
|
auth,
|
|
45542
46369
|
fetchImpl = fetch
|
|
45543
46370
|
}) {
|
|
45544
|
-
const base = remote.replace(
|
|
45545
|
-
const appBase = (appRemote ?? remote).replace(
|
|
46371
|
+
const base = remote.replace(TRAILING_SLASHES5, "");
|
|
46372
|
+
const appBase = (appRemote ?? remote).replace(TRAILING_SLASHES5, "");
|
|
45546
46373
|
async function requestWithToken(url2, token, init) {
|
|
45547
46374
|
try {
|
|
45548
46375
|
return await fetchImpl(url2, {
|
|
@@ -45829,6 +46656,40 @@ function createCliClient({
|
|
|
45829
46656
|
async putGmInstruction({ fileId, gmInstruction }) {
|
|
45830
46657
|
return await putMetadata(`/api/files/${fileId}/gm-instruction`, { gmInstruction }, "GM instruction trigger save");
|
|
45831
46658
|
},
|
|
46659
|
+
async fetchLinkedLocations({ fileId }) {
|
|
46660
|
+
const response = await request(`/api/files/${fileId}/relations`);
|
|
46661
|
+
if (response.status === 404) {
|
|
46662
|
+
return null;
|
|
46663
|
+
}
|
|
46664
|
+
if (!response.ok) {
|
|
46665
|
+
const { code, message, details } = await readErrorBody(response);
|
|
46666
|
+
throw new CliApiError({
|
|
46667
|
+
message,
|
|
46668
|
+
code,
|
|
46669
|
+
status: response.status,
|
|
46670
|
+
exitCode: 1,
|
|
46671
|
+
details
|
|
46672
|
+
});
|
|
46673
|
+
}
|
|
46674
|
+
return await parseJson(response, cliLinkedLocationsResponseSchema, "linked locations");
|
|
46675
|
+
},
|
|
46676
|
+
async putFileTransport({ fileId, transport }) {
|
|
46677
|
+
return await putMetadata(`/api/files/${fileId}/transport`, { transport }, "transport save");
|
|
46678
|
+
},
|
|
46679
|
+
async putPlacePair({ fileId, otherFileId, pair }) {
|
|
46680
|
+
const response = await request(`/api/files/${fileId}/relations/${otherFileId}`, pair === null ? { method: "DELETE" } : { method: "PUT", body: JSON.stringify(pair) });
|
|
46681
|
+
if (!response.ok) {
|
|
46682
|
+
const { code, message, details } = await readErrorBody(response);
|
|
46683
|
+
throw new CliApiError({
|
|
46684
|
+
message,
|
|
46685
|
+
code,
|
|
46686
|
+
status: response.status,
|
|
46687
|
+
exitCode: response.status === 400 || response.status === 422 ? 2 : 1,
|
|
46688
|
+
details
|
|
46689
|
+
});
|
|
46690
|
+
}
|
|
46691
|
+
return await parseJson(response, cliPlacePairWriteResponseSchema, "linked location save");
|
|
46692
|
+
},
|
|
45832
46693
|
async putDesignationFlag({ fileId, flag, value }) {
|
|
45833
46694
|
return await putMetadata(`/api/files/${fileId}/designation`, { flag, value }, "designation flag save");
|
|
45834
46695
|
},
|
|
@@ -48549,11 +49410,66 @@ async function runLogout(argv) {
|
|
|
48549
49410
|
|
|
48550
49411
|
// src/commands/map.ts
|
|
48551
49412
|
import { readFile as readFile11 } from "node:fs/promises";
|
|
49413
|
+
|
|
49414
|
+
// src/commands/place-metadata.ts
|
|
49415
|
+
function pathOrId(ledger, referenceId) {
|
|
49416
|
+
return referenceIdToPath({ ledger, referenceId }) ?? referenceId;
|
|
49417
|
+
}
|
|
49418
|
+
function exitForOutput(ledger, exit) {
|
|
49419
|
+
return {
|
|
49420
|
+
...exit,
|
|
49421
|
+
...typeof exit.to === "string" ? { to: pathOrId(ledger, exit.to) } : {},
|
|
49422
|
+
...typeof exit.by === "string" ? { by: pathOrId(ledger, exit.by) } : {}
|
|
49423
|
+
};
|
|
49424
|
+
}
|
|
49425
|
+
function relationsForOutput(ledger, relations) {
|
|
49426
|
+
return relations.map((relation) => exitForOutput(ledger, relation));
|
|
49427
|
+
}
|
|
49428
|
+
function listMapExitsForOutput(ledger, map2) {
|
|
49429
|
+
return [...map2.areas, ...map2.points].flatMap((element) => element.exit ? [
|
|
49430
|
+
{
|
|
49431
|
+
element: element.label ?? element.id,
|
|
49432
|
+
...exitForOutput(ledger, element.exit)
|
|
49433
|
+
}
|
|
49434
|
+
] : []);
|
|
49435
|
+
}
|
|
49436
|
+
function resolveExitInput({
|
|
49437
|
+
ledger,
|
|
49438
|
+
exit,
|
|
49439
|
+
where,
|
|
49440
|
+
failures
|
|
49441
|
+
}) {
|
|
49442
|
+
if (typeof exit !== "object" || exit === null || Array.isArray(exit)) {
|
|
49443
|
+
return exit;
|
|
49444
|
+
}
|
|
49445
|
+
const record2 = { ...exit };
|
|
49446
|
+
for (const key of ["to", "by"]) {
|
|
49447
|
+
const value = record2[key];
|
|
49448
|
+
if (typeof value !== "string") {
|
|
49449
|
+
continue;
|
|
49450
|
+
}
|
|
49451
|
+
const resolution = resolveLink({ ledger, link: value });
|
|
49452
|
+
if (resolution.status === "resolved") {
|
|
49453
|
+
record2[key] = resolution.referenceId;
|
|
49454
|
+
} else {
|
|
49455
|
+
failures.push({
|
|
49456
|
+
where: `${where}.${key}`,
|
|
49457
|
+
link: value,
|
|
49458
|
+
reason: resolution.status
|
|
49459
|
+
});
|
|
49460
|
+
}
|
|
49461
|
+
}
|
|
49462
|
+
return record2;
|
|
49463
|
+
}
|
|
49464
|
+
|
|
49465
|
+
// src/commands/map.ts
|
|
48552
49466
|
var MAP_USAGE = `Usage: craft map get <location-path> [--json]
|
|
48553
49467
|
` + ` craft map set <location-path> --file <map.json>
|
|
48554
49468
|
` + `
|
|
48555
|
-
` + "The map JSON is the stored MapData shape; in `set` input, areas/points may\n" + 'use `"link": "<workspace path | slug.type | referenceId>"` instead of\n' + `fileReferenceId, and element ids are minted when omitted.
|
|
48556
|
-
` + "
|
|
49469
|
+
` + "The map JSON is the stored MapData shape; in `set` input, areas/points may\n" + 'use `"link": "<workspace path | slug.type | referenceId>"` instead of\n' + `fileReferenceId, and element ids are minted when omitted. An element's
|
|
49470
|
+
` + '`exit` (a door, stairs, a road to another location) takes `"to"` as a\n' + "workspace path too; no `to` is the way out to the parent map. `get` prints\n" + `links and exit targets as workspace paths. Read
|
|
49471
|
+
` + `.craft/instructions/authoring-maps.md for the format and
|
|
49472
|
+
` + ".craft/instructions/linking-locations.md for exits.";
|
|
48557
49473
|
async function runMap(argv) {
|
|
48558
49474
|
const [subcommand, ...rest] = argv;
|
|
48559
49475
|
if (subcommand === "get") {
|
|
@@ -48575,16 +49491,22 @@ function hydrateMapForOutput(workspace, map2) {
|
|
|
48575
49491
|
if (typeof element !== "object" || element === null) {
|
|
48576
49492
|
return element;
|
|
48577
49493
|
}
|
|
48578
|
-
|
|
49494
|
+
let record2 = element;
|
|
49495
|
+
if (typeof record2.exit === "object" && record2.exit !== null) {
|
|
49496
|
+
record2 = {
|
|
49497
|
+
...record2,
|
|
49498
|
+
exit: exitForOutput(workspace.ledger, record2.exit)
|
|
49499
|
+
};
|
|
49500
|
+
}
|
|
48579
49501
|
if (typeof record2.fileReferenceId !== "string") {
|
|
48580
|
-
return
|
|
49502
|
+
return record2;
|
|
48581
49503
|
}
|
|
48582
49504
|
const path = referenceIdToPath({
|
|
48583
49505
|
ledger: workspace.ledger,
|
|
48584
49506
|
referenceId: record2.fileReferenceId
|
|
48585
49507
|
});
|
|
48586
49508
|
if (!path) {
|
|
48587
|
-
return
|
|
49509
|
+
return record2;
|
|
48588
49510
|
}
|
|
48589
49511
|
const { fileReferenceId: _dropped, ...rest } = record2;
|
|
48590
49512
|
return { ...rest, link: path };
|
|
@@ -48677,6 +49599,14 @@ function normalizeMapInput(workspace, raw) {
|
|
|
48677
49599
|
if (record2.fileReferenceId === undefined) {
|
|
48678
49600
|
record2.fileReferenceId = null;
|
|
48679
49601
|
}
|
|
49602
|
+
if (record2.exit !== undefined) {
|
|
49603
|
+
record2.exit = resolveExitInput({
|
|
49604
|
+
ledger: workspace.ledger,
|
|
49605
|
+
exit: record2.exit,
|
|
49606
|
+
where: `${kindLabel2}[${index}].exit`,
|
|
49607
|
+
failures
|
|
49608
|
+
});
|
|
49609
|
+
}
|
|
48680
49610
|
for (const [key, value] of Object.entries(defaults)) {
|
|
48681
49611
|
if (record2[key] === undefined) {
|
|
48682
49612
|
record2[key] = value;
|
|
@@ -48945,13 +49875,75 @@ function translateGameStartAuthoringInput({
|
|
|
48945
49875
|
return input;
|
|
48946
49876
|
}
|
|
48947
49877
|
|
|
49878
|
+
// ../shared/src/place-pairs.ts
|
|
49879
|
+
var PLACE_PAIR_KINDS = [
|
|
49880
|
+
"same_place",
|
|
49881
|
+
"contains",
|
|
49882
|
+
"part_of",
|
|
49883
|
+
"separate",
|
|
49884
|
+
"other"
|
|
49885
|
+
];
|
|
49886
|
+
var PairWaySchema = PlaceWaySchema.omit({
|
|
49887
|
+
to: true,
|
|
49888
|
+
arriveAt: true,
|
|
49889
|
+
main: true,
|
|
49890
|
+
oneWay: true
|
|
49891
|
+
}).extend({
|
|
49892
|
+
oneWay: exports_external.enum(["there", "back"]).optional()
|
|
49893
|
+
});
|
|
49894
|
+
var PairWordsSchema = exports_external.object({
|
|
49895
|
+
text: exports_external.string().trim().min(1).max(120),
|
|
49896
|
+
first: exports_external.enum(["a", "b"])
|
|
49897
|
+
});
|
|
49898
|
+
var PlacePairSchema = exports_external.object({
|
|
49899
|
+
kind: exports_external.enum(PLACE_PAIR_KINDS),
|
|
49900
|
+
parentRef: exports_external.string().uuid().optional(),
|
|
49901
|
+
way: PairWaySchema.nullable(),
|
|
49902
|
+
words: PairWordsSchema.nullable()
|
|
49903
|
+
});
|
|
49904
|
+
function placePairProblem({
|
|
49905
|
+
pair,
|
|
49906
|
+
aRef,
|
|
49907
|
+
bRef
|
|
49908
|
+
}) {
|
|
49909
|
+
if (aRef === bRef) {
|
|
49910
|
+
return "A location can't be linked to itself.";
|
|
49911
|
+
}
|
|
49912
|
+
if (pair.kind === "same_place") {
|
|
49913
|
+
if (!pair.parentRef) {
|
|
49914
|
+
return "Pick the location they're both in.";
|
|
49915
|
+
}
|
|
49916
|
+
if (pair.parentRef === aRef || pair.parentRef === bRef) {
|
|
49917
|
+
return "A location can't be inside itself.";
|
|
49918
|
+
}
|
|
49919
|
+
} else if (pair.parentRef) {
|
|
49920
|
+
return "Only locations in one location name the location they're in.";
|
|
49921
|
+
}
|
|
49922
|
+
if (pair.kind === "separate" && !pair.way) {
|
|
49923
|
+
return "Separate locations need a way between them.";
|
|
49924
|
+
}
|
|
49925
|
+
if (pair.kind === "other" && !pair.words) {
|
|
49926
|
+
return "Say how they relate.";
|
|
49927
|
+
}
|
|
49928
|
+
if (pair.kind !== "other" && pair.words) {
|
|
49929
|
+
return "Only something else has words of its own.";
|
|
49930
|
+
}
|
|
49931
|
+
return null;
|
|
49932
|
+
}
|
|
49933
|
+
|
|
48948
49934
|
// src/commands/meta.ts
|
|
49935
|
+
var PAIR_KIND_FIELD_RE = /^kind/;
|
|
48949
49936
|
var META_USAGE = `Usage: craft meta get <path> [--json]
|
|
48950
49937
|
` + ` craft meta set-game-start <game-start-path> --file <json>
|
|
48951
49938
|
` + ` craft meta set-gm-trigger <gm-instruction-path> (--file <json> | --clear)
|
|
48952
49939
|
` + ` craft meta set-playable <character-path> <true|false>
|
|
49940
|
+
` + ` craft meta set-transport <location-path> (--file <json> | --clear)
|
|
49941
|
+
` + ` craft meta link <location-path> <other-location-path> --file <json>
|
|
49942
|
+
` + ` craft meta unlink <location-path> <other-location-path>
|
|
48953
49943
|
` + `
|
|
48954
|
-
` +
|
|
49944
|
+
` + `See .craft/instructions/making-it-playable.md for game starts, triggers and
|
|
49945
|
+
` + `playable, and .craft/instructions/linking-locations.md for transports and
|
|
49946
|
+
` + "linked locations. `get` on a location shows its transport, its linked\n" + "locations (read from both ends) and its map's exits, with workspace paths.";
|
|
48955
49947
|
async function runMeta(argv) {
|
|
48956
49948
|
const [subcommand, ...rest] = argv;
|
|
48957
49949
|
if (subcommand === "get") {
|
|
@@ -48966,6 +49958,15 @@ async function runMeta(argv) {
|
|
|
48966
49958
|
if (subcommand === "set-playable") {
|
|
48967
49959
|
return await runSetPlayable(rest);
|
|
48968
49960
|
}
|
|
49961
|
+
if (subcommand === "set-transport") {
|
|
49962
|
+
return await runSetTransport(rest);
|
|
49963
|
+
}
|
|
49964
|
+
if (subcommand === "link") {
|
|
49965
|
+
return await runLink(rest, "link");
|
|
49966
|
+
}
|
|
49967
|
+
if (subcommand === "unlink") {
|
|
49968
|
+
return await runLink(rest, "unlink");
|
|
49969
|
+
}
|
|
48969
49970
|
throw new CliError(META_USAGE, 2);
|
|
48970
49971
|
}
|
|
48971
49972
|
async function readJsonFile2(inputPath) {
|
|
@@ -49028,6 +50029,8 @@ async function runMetaGet(argv) {
|
|
|
49028
50029
|
if (gameStart) {
|
|
49029
50030
|
gameStart.startingLocationFileReferenceId = undefined;
|
|
49030
50031
|
}
|
|
50032
|
+
const isLocation = meta3.fileType?.designation === "location" || Boolean(parsed?.map || parsed?.transport || parsed?.relations);
|
|
50033
|
+
const linkedLocations = isLocation ? await linkedLocationsForOutput(workspace, client, entry.fileId) : undefined;
|
|
49031
50034
|
printJson({
|
|
49032
50035
|
path,
|
|
49033
50036
|
fileType: entry.fileType,
|
|
@@ -49039,11 +50042,38 @@ async function runMetaGet(argv) {
|
|
|
49039
50042
|
areas: parsed.map.areas.length,
|
|
49040
50043
|
points: parsed.map.points.length,
|
|
49041
50044
|
useParentMap: parsed.map.useParentMap ?? false,
|
|
50045
|
+
exits: listMapExitsForOutput(workspace.ledger, parsed.map),
|
|
49042
50046
|
hint: "Full map via `craft map get`."
|
|
49043
|
-
} : null
|
|
50047
|
+
} : null,
|
|
50048
|
+
...isLocation ? {
|
|
50049
|
+
transport: parsed?.transport ?? null,
|
|
50050
|
+
relations: relationsForOutput(workspace.ledger, parsed?.relations ?? []),
|
|
50051
|
+
...linkedLocations === undefined ? {} : { linkedLocations }
|
|
50052
|
+
} : {}
|
|
49044
50053
|
});
|
|
49045
50054
|
return 0;
|
|
49046
50055
|
}
|
|
50056
|
+
async function linkedLocationsForOutput(workspace, client, fileId) {
|
|
50057
|
+
const linked = await client.fetchLinkedLocations({ fileId });
|
|
50058
|
+
if (!linked) {
|
|
50059
|
+
return;
|
|
50060
|
+
}
|
|
50061
|
+
return linked.places.map((place) => {
|
|
50062
|
+
const pair = place.pair;
|
|
50063
|
+
return {
|
|
50064
|
+
location: pathForReference(workspace, place.referenceId) ?? place.name,
|
|
50065
|
+
relation: pair.kind,
|
|
50066
|
+
...pair.parentRef ? { in: pathForReference(workspace, pair.parentRef) } : {},
|
|
50067
|
+
...pair.way ? {
|
|
50068
|
+
way: exitForOutput(workspace.ledger, pair.way)
|
|
50069
|
+
} : {},
|
|
50070
|
+
...pair.words ? {
|
|
50071
|
+
words: pair.words.text,
|
|
50072
|
+
wordsFirst: pair.words.first === "a" ? "location" : "other"
|
|
50073
|
+
} : {}
|
|
50074
|
+
};
|
|
50075
|
+
});
|
|
50076
|
+
}
|
|
49047
50077
|
function projectOpeningForOutput(workspace, opening) {
|
|
49048
50078
|
if (opening.kind !== "static" || !opening.beats?.length) {
|
|
49049
50079
|
return opening;
|
|
@@ -49220,6 +50250,159 @@ async function runSetPlayable(argv) {
|
|
|
49220
50250
|
printLines([`Set playable: ${value} on ${path}.`]);
|
|
49221
50251
|
return 0;
|
|
49222
50252
|
}
|
|
50253
|
+
async function runSetTransport(argv) {
|
|
50254
|
+
const args = parseCommandArgs({
|
|
50255
|
+
argv,
|
|
50256
|
+
flags: {
|
|
50257
|
+
file: { type: "string" },
|
|
50258
|
+
clear: { type: "boolean" },
|
|
50259
|
+
json: { type: "boolean" }
|
|
50260
|
+
},
|
|
50261
|
+
maxPositionals: 1
|
|
50262
|
+
});
|
|
50263
|
+
const path = args.positionals[0];
|
|
50264
|
+
const inputPath = stringFlag(args, "file");
|
|
50265
|
+
const clear = args.booleans.has("clear");
|
|
50266
|
+
const exactlyOneMode = clear ? !inputPath : Boolean(inputPath);
|
|
50267
|
+
if (!(path && exactlyOneMode)) {
|
|
50268
|
+
throw new CliError(META_USAGE, 2);
|
|
50269
|
+
}
|
|
50270
|
+
let transport = null;
|
|
50271
|
+
if (inputPath) {
|
|
50272
|
+
const parsed = LocationDesignationMetadataSchema.shape.transport.unwrap().safeParse(await readJsonFile2(inputPath));
|
|
50273
|
+
if (!parsed.success) {
|
|
50274
|
+
throw new CliError(`The transport JSON failed validation:
|
|
50275
|
+
` + parsed.error.issues.map((issue3) => ` ${issue3.path.join(".") || "(root)"}: ${issue3.message}`).join(`
|
|
50276
|
+
`), 2);
|
|
50277
|
+
}
|
|
50278
|
+
transport = parsed.data;
|
|
50279
|
+
}
|
|
50280
|
+
const workspace = await loadWorkspaceContext();
|
|
50281
|
+
const entry = resolveTrackedEntry({ ledger: workspace.ledger, input: path });
|
|
50282
|
+
const client = await workspaceClient(workspace);
|
|
50283
|
+
const result = await client.putFileTransport({
|
|
50284
|
+
fileId: entry.fileId,
|
|
50285
|
+
transport
|
|
50286
|
+
});
|
|
50287
|
+
await freshenLedgerAfterMetaWrite({
|
|
50288
|
+
root: workspace.root,
|
|
50289
|
+
ledger: workspace.ledger,
|
|
50290
|
+
fileId: entry.fileId,
|
|
50291
|
+
versionId: result.version.id
|
|
50292
|
+
});
|
|
50293
|
+
if (args.booleans.has("json")) {
|
|
50294
|
+
printJson({ path, saved: true, transport });
|
|
50295
|
+
return 0;
|
|
50296
|
+
}
|
|
50297
|
+
printLines([
|
|
50298
|
+
transport ? `Saved transport on ${path}: ${transport.kind}${transport.travels ? ` (${transport.travels})` : ""}.` : `Cleared transport on ${path}.`
|
|
50299
|
+
]);
|
|
50300
|
+
return 0;
|
|
50301
|
+
}
|
|
50302
|
+
function translatePlacePairInput({
|
|
50303
|
+
workspace,
|
|
50304
|
+
raw,
|
|
50305
|
+
aRef,
|
|
50306
|
+
bRef
|
|
50307
|
+
}) {
|
|
50308
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
50309
|
+
throw new CliError("The link JSON must be a single object.", 2);
|
|
50310
|
+
}
|
|
50311
|
+
const input = raw;
|
|
50312
|
+
const failures = [];
|
|
50313
|
+
let parentRef;
|
|
50314
|
+
if (typeof input.in === "string") {
|
|
50315
|
+
const resolution = resolveLink({
|
|
50316
|
+
ledger: workspace.ledger,
|
|
50317
|
+
link: input.in
|
|
50318
|
+
});
|
|
50319
|
+
if (resolution.status === "resolved") {
|
|
50320
|
+
parentRef = resolution.referenceId;
|
|
50321
|
+
} else {
|
|
50322
|
+
failures.push({ where: "in", link: input.in, reason: resolution.status });
|
|
50323
|
+
}
|
|
50324
|
+
}
|
|
50325
|
+
const way = input.way === undefined || input.way === null ? null : resolveExitInput({
|
|
50326
|
+
ledger: workspace.ledger,
|
|
50327
|
+
exit: input.way,
|
|
50328
|
+
where: "way",
|
|
50329
|
+
failures
|
|
50330
|
+
});
|
|
50331
|
+
if (failures.length > 0) {
|
|
50332
|
+
throw new CliError(`Nothing was saved — some links do not resolve to pushed files:
|
|
50333
|
+
` + failures.map((failure2) => ` ${failure2.where}: "${failure2.link}"` + `${failure2.reason === "unpushed" ? " (tracked but not pushed yet)" : " (no tracked file matches)"}`).join(`
|
|
50334
|
+
`), 2);
|
|
50335
|
+
}
|
|
50336
|
+
const parsedWay = way === null ? null : PairWaySchema.safeParse(way);
|
|
50337
|
+
const parsed = PlacePairSchema.safeParse({
|
|
50338
|
+
kind: input.relation,
|
|
50339
|
+
...parentRef ? { parentRef } : {},
|
|
50340
|
+
way: parsedWay?.success ? parsedWay.data : way,
|
|
50341
|
+
words: typeof input.words === "string" ? { text: input.words, first: input.wordsFirst === "other" ? "b" : "a" } : null
|
|
50342
|
+
});
|
|
50343
|
+
if (!parsed.success) {
|
|
50344
|
+
throw new CliError(`The link JSON failed validation:
|
|
50345
|
+
` + parsed.error.issues.map((issue3) => {
|
|
50346
|
+
const field = issue3.path.join(".").replace(PAIR_KIND_FIELD_RE, "relation");
|
|
50347
|
+
return ` ${field || "(root)"}: ${issue3.message}`;
|
|
50348
|
+
}).join(`
|
|
50349
|
+
`), 2);
|
|
50350
|
+
}
|
|
50351
|
+
const problem = placePairProblem({ pair: parsed.data, aRef, bRef });
|
|
50352
|
+
if (problem) {
|
|
50353
|
+
throw new CliError(problem, 2);
|
|
50354
|
+
}
|
|
50355
|
+
return parsed.data;
|
|
50356
|
+
}
|
|
50357
|
+
async function runLink(argv, mode) {
|
|
50358
|
+
const args = parseCommandArgs({
|
|
50359
|
+
argv,
|
|
50360
|
+
flags: { file: { type: "string" }, json: { type: "boolean" } },
|
|
50361
|
+
maxPositionals: 2
|
|
50362
|
+
});
|
|
50363
|
+
const [path, otherPath] = args.positionals;
|
|
50364
|
+
const inputPath = stringFlag(args, "file");
|
|
50365
|
+
if (!(path && otherPath) || mode === "link" !== Boolean(inputPath)) {
|
|
50366
|
+
throw new CliError(META_USAGE, 2);
|
|
50367
|
+
}
|
|
50368
|
+
const workspace = await loadWorkspaceContext();
|
|
50369
|
+
const entry = resolveTrackedEntry({ ledger: workspace.ledger, input: path });
|
|
50370
|
+
const other = resolveTrackedEntry({
|
|
50371
|
+
ledger: workspace.ledger,
|
|
50372
|
+
input: otherPath
|
|
50373
|
+
});
|
|
50374
|
+
const pair = inputPath ? translatePlacePairInput({
|
|
50375
|
+
workspace,
|
|
50376
|
+
raw: await readJsonFile2(inputPath),
|
|
50377
|
+
aRef: entry.referenceId,
|
|
50378
|
+
bRef: other.referenceId
|
|
50379
|
+
}) : null;
|
|
50380
|
+
const client = await workspaceClient(workspace);
|
|
50381
|
+
const { changedFileIds } = await client.putPlacePair({
|
|
50382
|
+
fileId: entry.fileId,
|
|
50383
|
+
otherFileId: other.fileId,
|
|
50384
|
+
pair
|
|
50385
|
+
});
|
|
50386
|
+
for (const fileId of changedFileIds) {
|
|
50387
|
+
const meta3 = await client.fetchFileMeta({ fileId });
|
|
50388
|
+
await freshenLedgerAfterMetaWrite({
|
|
50389
|
+
root: workspace.root,
|
|
50390
|
+
ledger: workspace.ledger,
|
|
50391
|
+
fileId,
|
|
50392
|
+
versionId: meta3.currentVersion.id
|
|
50393
|
+
});
|
|
50394
|
+
}
|
|
50395
|
+
if (args.booleans.has("json")) {
|
|
50396
|
+
printJson({ path, other: otherPath, saved: true, pair, changedFileIds });
|
|
50397
|
+
return 0;
|
|
50398
|
+
}
|
|
50399
|
+
const changed = changedFileIds.length === 0 ? "nothing changed" : `${changedFileIds.length} location${changedFileIds.length === 1 ? "" : "s"} changed`;
|
|
50400
|
+
printLines([
|
|
50401
|
+
mode === "link" ? `Linked ${path} and ${otherPath} (${pair?.kind}): ${changed}.` : `Unlinked ${path} and ${otherPath}: ${changed}.`,
|
|
50402
|
+
"See where each location now sits with `craft meta get`."
|
|
50403
|
+
]);
|
|
50404
|
+
return 0;
|
|
50405
|
+
}
|
|
49223
50406
|
|
|
49224
50407
|
// src/commands/mv.ts
|
|
49225
50408
|
import { existsSync as existsSync8 } from "node:fs";
|
|
@@ -56055,9 +57238,12 @@ var HELP_COMMAND_LINES = ` clone <projectIdOrUrl> [dir] Download a project as
|
|
|
56055
57238
|
map get|set <location-path> Read/author the interactive map stored on a
|
|
56056
57239
|
location file (see "craft help map")
|
|
56057
57240
|
meta get <path> Show a file's designation metadata (game start
|
|
56058
|
-
config, GM triggers, playable flag, map summary
|
|
57241
|
+
config, GM triggers, playable flag, map summary,
|
|
57242
|
+
a location's transport and linked locations)
|
|
56059
57243
|
meta set-game-start|set-gm-trigger|set-playable
|
|
56060
57244
|
Configure playability metadata (see "craft help meta")
|
|
57245
|
+
meta set-transport|link|unlink Make a location a ship/caravan, or link two
|
|
57246
|
+
locations (see "craft help meta")
|
|
56061
57247
|
vn list|create|pull|check|push|publish|assets
|
|
56062
57248
|
Write visual novels: pull a story to a JSON
|
|
56063
57249
|
file, edit it, push it back, publish it
|
|
@@ -56389,6 +57575,10 @@ Authoring format = the stored MapData shape with conveniences:
|
|
|
56389
57575
|
- element "id"s are minted when omitted; point style defaults are applied.
|
|
56390
57576
|
- required: "aspectRatio" matching the background image (4:3 for a default
|
|
56391
57577
|
landscape-4-3 generation). background/scale/grid default to null.
|
|
57578
|
+
- an area/point may carry "exit": a way to another location, e.g.
|
|
57579
|
+
{"kind":"stairs","to":"Places/The Inn Upstairs.location.json","direction":"up"}.
|
|
57580
|
+
"to" takes a workspace path (or slug.type / referenceId); leave it out for
|
|
57581
|
+
the way out to the parent map. \`get\` prints links and exit "to"s as paths.
|
|
56392
57582
|
|
|
56393
57583
|
The background image URL must be Craft-hosted \u2014 generate one with
|
|
56394
57584
|
\`craft image generate\` (or upload with \`craft image upload\`) and paste
|
|
@@ -56440,6 +57630,9 @@ chapters, where cutouts stand (desktop and phone), and pictures on pages.
|
|
|
56440
57630
|
craft meta set-game-start <game-start-path> --file <json>
|
|
56441
57631
|
craft meta set-gm-trigger <gm-instruction-path> (--file <json> | --clear)
|
|
56442
57632
|
craft meta set-playable <character-path> <true|false>
|
|
57633
|
+
craft meta set-transport <location-path> (--file <json> | --clear)
|
|
57634
|
+
craft meta link <location-path> <other-location-path> --file <json>
|
|
57635
|
+
craft meta unlink <location-path> <other-location-path>
|
|
56443
57636
|
|
|
56444
57637
|
Designation metadata is app-managed and lives outside the working tree;
|
|
56445
57638
|
these commands write the server directly (the target file must be pushed).
|
|
@@ -56450,10 +57643,13 @@ set-game-start JSON: { "startingLocation": "<path|slug.location|null>",
|
|
|
56450
57643
|
"openingInstructions": "...", "displayOrder": 0,
|
|
56451
57644
|
"board": { "people": [{ "character": "<path|slug.character>", "side": "ally|enemy|neutral",
|
|
56452
57645
|
"area": "...", "hidden": {"how":"unseen|sensed|disguised","as":"..."}, "note": "..." }],
|
|
56453
|
-
"
|
|
57646
|
+
"purpose": "...", "objectives": ["..."],
|
|
57647
|
+
"trackers": [{ "key": "time", "on": true, "fields": [{ "kind": "text", "text": "Dusk" }] },
|
|
57648
|
+
{ "key": "<tracker key>", "on": true, "fields": [{ "kind": "count", "value": 2 }] }] },
|
|
56454
57649
|
"gmNotes": "..." }
|
|
56455
57650
|
(board and gmNotes are read by GM Harness V2: the opening board and the facts it
|
|
56456
|
-
reads on its first turn instead of looking them up
|
|
57651
|
+
reads on its first turn instead of looking them up; the time it opens at is the
|
|
57652
|
+
Time tracker's, key "time")
|
|
56457
57653
|
|
|
56458
57654
|
set-gm-trigger JSON (triggered GM rules; name \u2264120 / body \u2264500 chars):
|
|
56459
57655
|
{"triggerKind":"narrative","triggerPhases":["post_player_message"]}
|
|
@@ -56461,6 +57657,23 @@ set-gm-trigger JSON (triggered GM rules; name \u2264120 / body \u2264500 chars):
|
|
|
56461
57657
|
|
|
56462
57658
|
Read .craft/instructions/making-it-playable.md for how these fit together.
|
|
56463
57659
|
|
|
57660
|
+
set-transport JSON (a location that carries people; its deck plan is its map):
|
|
57661
|
+
{"kind":"ship","travels":"water","speed":{"value":6,"unit":"knots"},"capacity":40}
|
|
57662
|
+
kind: ship|boat|airship|wagon|caravan|carriage|train|car|bus|plane|mount|other
|
|
57663
|
+
|
|
57664
|
+
link JSON \u2014 how <location> relates to <other>, the shape \`meta get\` prints
|
|
57665
|
+
under linkedLocations (it replaces whatever lay between the two):
|
|
57666
|
+
{"relation":"part_of","way":{"kind":"stairs","direction":"up"}}
|
|
57667
|
+
relation: same_place (both parts of "in": "<path>") | contains (other is part
|
|
57668
|
+
of location) | part_of (location is part of other) | separate (needs "way") |
|
|
57669
|
+
other (needs "words": "is the Shadowfell reflection of", "wordsFirst":
|
|
57670
|
+
"location"|"other"). way: {kind, direction from location, oneWay:
|
|
57671
|
+
"there"|"back", locked, hidden, when, requires, label, distance, time}.
|
|
57672
|
+
A location is inside at most one place and never inside itself; linking can
|
|
57673
|
+
move a place in or out of another. unlink removes everything between them.
|
|
57674
|
+
Read .craft/instructions/linking-locations.md for exits, transports and
|
|
57675
|
+
linked locations.
|
|
57676
|
+
|
|
56464
57677
|
upload: send a local image file (max 10MB; 50 per 24h, 1,000 per 24h for
|
|
56465
57678
|
subscribers) to the project's media
|
|
56466
57679
|
storage; the server re-encodes it to webp and returns the same pasteable
|