@cimplify/cli 0.15.2 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +2 -2
- package/dist/add-SXC6RUPQ.mjs +140 -0
- package/dist/{add-KRIIKAS4.mjs → add-XQVQLD3F.mjs} +4 -4
- package/dist/ai-NQH5L4I6.mjs +17 -0
- package/dist/{assets-CWB72PZG.mjs → assets-FZGVOQXH.mjs} +12 -7
- package/dist/{auth-step-up-Z6KOTTV7.mjs → auth-step-up-67LCPDNL.mjs} +5 -5
- package/dist/build-J4ZUIOPX.mjs +625 -0
- package/dist/{cancel-ZHVVILOU.mjs → cancel-VGJHZJ4J.mjs} +4 -4
- package/dist/capture-RAJ2B3BH.mjs +69 -0
- package/dist/chunk-25ZPFHLF.mjs +51 -0
- package/dist/chunk-47ZXSY47.mjs +52 -0
- package/dist/{chunk-NK6VC264.mjs → chunk-54J3XR4T.mjs} +2 -2
- package/dist/chunk-5HP2INMY.mjs +10631 -0
- package/dist/chunk-7JR6JCFF.mjs +32 -0
- package/dist/chunk-7PQL7E7K.mjs +68 -0
- package/dist/chunk-AKACUJ3W.mjs +81 -0
- package/dist/chunk-BYNE553A.mjs +2945 -0
- package/dist/{chunk-NYXA6X56.mjs → chunk-CLQYPTJ2.mjs} +31 -11
- package/dist/chunk-CNZAOVSK.mjs +104 -0
- package/dist/chunk-CPBESPWK.mjs +5576 -0
- package/dist/{chunk-GRR5U2M5.mjs → chunk-DYWUPHV3.mjs} +11 -7
- package/dist/chunk-EAV2P4N3.mjs +1941 -0
- package/dist/{chunk-KN2YSKXZ.mjs → chunk-EDZSOL5O.mjs} +14 -6
- package/dist/chunk-EGQIPYUN.mjs +696 -0
- package/dist/{chunk-DMLQBAGC.mjs → chunk-EOO4UOQD.mjs} +1 -1
- package/dist/chunk-EYEMPIMP.mjs +74 -0
- package/dist/chunk-G2ZEFSQJ.mjs +46 -0
- package/dist/{chunk-TH4XENKH.mjs → chunk-I4HNHPEM.mjs} +1 -1
- package/dist/chunk-JEVSFNPT.mjs +363 -0
- package/dist/chunk-JKECUAAV.mjs +2169 -0
- package/dist/chunk-KMHORI4Y.mjs +638 -0
- package/dist/chunk-KNPYBZ6B.mjs +502 -0
- package/dist/{chunk-3QUK5BPY.mjs → chunk-KOD4GBQ5.mjs} +8 -1
- package/dist/chunk-KQNZKV6E.mjs +535 -0
- package/dist/{chunk-MXYUAJEW.mjs → chunk-LLRUOIMU.mjs} +5 -2
- package/dist/{chunk-F2UI225N.mjs → chunk-LNMITJ4Z.mjs} +6 -6
- package/dist/chunk-MEM3MDV4.mjs +6463 -0
- package/dist/chunk-NY4YT7O6.mjs +84 -0
- package/dist/{chunk-A6WHBSXZ.mjs → chunk-Q6CF4C3E.mjs} +15 -13
- package/dist/chunk-RP66A52P.mjs +86 -0
- package/dist/{chunk-I2UP7YTG.mjs → chunk-SAXGWS2K.mjs} +1 -1
- package/dist/chunk-SCKQ4XSD.mjs +41 -0
- package/dist/chunk-SF44L2OD.mjs +1152 -0
- package/dist/chunk-SGT5IJVW.mjs +28 -0
- package/dist/{chunk-WEAB6HHQ.mjs → chunk-SS4JTMIV.mjs} +8 -8
- package/dist/chunk-T4MW3PD4.mjs +1456 -0
- package/dist/chunk-THATLMEX.mjs +1227 -0
- package/dist/{chunk-MLE4MVYC.mjs → chunk-TT4YI33G.mjs} +2 -2
- package/dist/chunk-TYV2SVFN.mjs +101 -0
- package/dist/chunk-VHK7WBO3.mjs +189 -0
- package/dist/chunk-VSA44UKR.mjs +391 -0
- package/dist/chunk-XL54SQJ3.mjs +42 -0
- package/dist/{chunk-5KYR6WJB.mjs → chunk-XQAHREKO.mjs} +17 -1
- package/dist/chunk-ZUCDXRFI.mjs +2011 -0
- package/dist/create-4S2EAUR3.mjs +10 -0
- package/dist/credits-NJOAOPPW.mjs +33 -0
- package/dist/{deploy-JE6TUYCL.mjs → deploy-26Y4UGZZ.mjs} +14 -14
- package/dist/dev-MOYURRWQ.mjs +401 -0
- package/dist/dev-WL43Q7VD.mjs +8 -0
- package/dist/dispatcher.mjs +131 -30
- package/dist/{doctor-HFB4IBYK.mjs → doctor-E6ETD2GJ.mjs} +12 -19
- package/dist/{domains-BZKGIDF4.mjs → domains-LOTQMVOR.mjs} +6 -6
- package/dist/engine-runtime-fetch-CIE7S7PB.mjs +10 -0
- package/dist/{env-FZNXKLGL.mjs → env-3PDFC5XF.mjs} +7 -7
- package/dist/env-MOTYOPM5.mjs +19 -0
- package/dist/explain-K5QEVWCD.mjs +251 -0
- package/dist/{inspect-X3M7WSTL.mjs → inspect-Q3E2DHYY.mjs} +3 -3
- package/dist/inspect-SJ4WBAXO.mjs +86 -0
- package/dist/introspect-7F34YN5V.mjs +9 -0
- package/dist/kernel-DEGWMHFL.mjs +662 -0
- package/dist/kernel-plugin.js +6862 -0
- package/dist/keys-W5SN3DPK.mjs +71 -0
- package/dist/lines-XGJYTQ7P.mjs +56 -0
- package/dist/{link-4NM2QMI5.mjs → link-NKUAVNM2.mjs} +6 -5
- package/dist/{lint-DDZ7HUQN.mjs → lint-G4YPQSSO.mjs} +2 -2
- package/dist/{list-2MFOQKKQ.mjs → list-TDR7K2JB.mjs} +3 -3
- package/dist/lock-EJPW2LM3.mjs +57 -0
- package/dist/{login-RLMDEWAL.mjs → login-AYLU7JU7.mjs} +10 -8
- package/dist/{logout-Z57X6HPE.mjs → logout-3M6VMHLH.mjs} +2 -2
- package/dist/{logs-36BLYJHJ.mjs → logs-4EL5SOXN.mjs} +5 -5
- package/dist/logs-4QQGG2XY.mjs +44 -0
- package/dist/mcp-4R2R6E5M.mjs +1077 -0
- package/dist/{mock-EVER34IX.mjs → mock-AK24GSP2.mjs} +2 -2
- package/dist/outdated-JEKCVLTG.mjs +58 -0
- package/dist/port-SBZ7X3EH.mjs +2408 -0
- package/dist/{projects-4XRNHJAH.mjs → projects-7JKTC5WJ.mjs} +9 -7
- package/dist/publish-QQCX47HK.mjs +20 -0
- package/dist/remix-NLLYD3BI.mjs +125 -0
- package/dist/repo-Z4G4EW3N.mjs +9 -0
- package/dist/report-M7F4ZYP7.mjs +2 -0
- package/dist/rollback-2WQ2CW7T.mjs +43 -0
- package/dist/{rollback-WCRWXSE3.mjs → rollback-ZGU32HGT.mjs} +6 -6
- package/dist/scene-HXARHUM7.mjs +324 -0
- package/dist/{status-VA326DWD.mjs → status-C3ZSLZ7Q.mjs} +5 -5
- package/dist/status-WEQOC25K.mjs +18 -0
- package/dist/test-ZPBWV4VX.mjs +233 -0
- package/dist/testers-A3LMXOJJ.mjs +49 -0
- package/dist/{unlink-H3BO3DOQ.mjs → unlink-4G4FE4RJ.mjs} +2 -2
- package/dist/{update-YYOTN7MG.mjs → update-GHKKM5FS.mjs} +4 -4
- package/dist/{whoami-CQPPARRS.mjs → whoami-3QQN6G2A.mjs} +3 -3
- package/dist/workshop-CD74D2LK.mjs +1703 -0
- package/package.json +11 -7
- package/templates/storefront-auto/app/products/[slug]/page.tsx +1 -1
- package/templates/storefront-auto/package.json +1 -1
- package/templates/storefront-bakery/package.json +1 -1
- package/templates/storefront-fashion/app/products/[slug]/page.tsx +1 -1
- package/templates/storefront-fashion/package.json +1 -1
- package/templates/storefront-grocery/package.json +1 -1
- package/templates/storefront-pharmacy/app/products/[slug]/page.tsx +1 -1
- package/templates/storefront-pharmacy/package.json +1 -1
- package/templates/storefront-restaurant/package.json +1 -1
- package/templates/storefront-retail/app/products/[slug]/page.tsx +1 -1
- package/templates/storefront-retail/package.json +1 -1
- package/templates/storefront-services/package.json +1 -1
- package/dist/chunk-ARIDYKLO.mjs +0 -6463
- package/dist/dev-V7JSUVOI.mjs +0 -8
- package/dist/explain-O5AC73EE.mjs +0 -251
- package/dist/introspect-2NGESQZE.mjs +0 -8
- package/dist/repo-X3KZ5R64.mjs +0 -9
|
@@ -0,0 +1,1456 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { quoteTomlString, parseToml, TomlSyntaxError } from './chunk-KQNZKV6E.mjs';
|
|
3
|
+
import { PINNED_TYPESCRIPT_VERSION, PINNED_SDK_VERSION, SDK_PACKAGE, findLibrary } from './chunk-AKACUJ3W.mjs';
|
|
4
|
+
import { compareStrings } from './chunk-25ZPFHLF.mjs';
|
|
5
|
+
import { diagnostic } from './chunk-SGT5IJVW.mjs';
|
|
6
|
+
import { CTX_CONTRACT_FINGERPRINT, CTX_CONTRACT_VERSION, CTX_MIN_SUPPORTED_CONTRACT_VERSION, CTX_COMPATIBLE_FINGERPRINTS, CTX_CAPABILITIES, CTX_LIMITS, ENGINE_FEATURE_NAMES, ENGINE_ARENA_BASELINE_BYTES, ENGINE_ARENA_MAX_CEILING_BYTES, ENGINE_ARENA_CHUNK_BYTES } from '@cimplify/sdk/interactives';
|
|
7
|
+
import { realMoneyIn, REAL_MONEY_SUGGESTION, inWorldRefusal } from '@cimplify/sdk/game/kit';
|
|
8
|
+
|
|
9
|
+
var LOCKFILE = "cimplify.lock";
|
|
10
|
+
var LOCK_VERSION = 1;
|
|
11
|
+
var HEX_DIGEST = /^[0-9a-f]{64}$/;
|
|
12
|
+
var ITEM_REFERENCE = /^[a-z0-9][a-z0-9_-]{0,63}\/[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
13
|
+
var CONTENT_VERSION = /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/;
|
|
14
|
+
function isItemReference(raw) {
|
|
15
|
+
return ITEM_REFERENCE.test(raw);
|
|
16
|
+
}
|
|
17
|
+
function sortContent(content) {
|
|
18
|
+
return content.slice().sort((a, b) => compareStrings(a.item, b.item));
|
|
19
|
+
}
|
|
20
|
+
function withContent(lock, content) {
|
|
21
|
+
const replaced = new Map((lock.content ?? []).map((entry) => [entry.item, entry]));
|
|
22
|
+
for (const entry of content) replaced.set(entry.item, entry);
|
|
23
|
+
return { ...lock, content: sortContent([...replaced.values()]) };
|
|
24
|
+
}
|
|
25
|
+
var FINGERPRINT = /^[0-9a-f]{16}$/;
|
|
26
|
+
function currentLockfile(libraries = []) {
|
|
27
|
+
return {
|
|
28
|
+
lockVersion: LOCK_VERSION,
|
|
29
|
+
sdk: PINNED_SDK_VERSION,
|
|
30
|
+
ctxContractVersion: CTX_CONTRACT_VERSION,
|
|
31
|
+
ctxFingerprint: CTX_CONTRACT_FINGERPRINT,
|
|
32
|
+
typescript: PINNED_TYPESCRIPT_VERSION,
|
|
33
|
+
libraries: sortLibraries(libraries)
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function sortLibraries(libraries) {
|
|
37
|
+
return libraries.slice().sort((a, b) => compareStrings(a.name, b.name));
|
|
38
|
+
}
|
|
39
|
+
function serializeLockfile(lock) {
|
|
40
|
+
let out = `lock_version = ${lock.lockVersion}
|
|
41
|
+
|
|
42
|
+
[sdk]
|
|
43
|
+
version = ${quoteTomlString(lock.sdk)}
|
|
44
|
+
|
|
45
|
+
`;
|
|
46
|
+
out += `[ctx]
|
|
47
|
+
contract_version = ${lock.ctxContractVersion}
|
|
48
|
+
fingerprint = ${quoteTomlString(lock.ctxFingerprint)}
|
|
49
|
+
|
|
50
|
+
`;
|
|
51
|
+
out += `[toolchain]
|
|
52
|
+
typescript = ${quoteTomlString(lock.typescript)}
|
|
53
|
+
`;
|
|
54
|
+
for (const library of sortLibraries(lock.libraries)) {
|
|
55
|
+
out += `
|
|
56
|
+
[[libraries]]
|
|
57
|
+
name = ${quoteTomlString(library.name)}
|
|
58
|
+
version = ${quoteTomlString(library.version)}
|
|
59
|
+
`;
|
|
60
|
+
}
|
|
61
|
+
for (const entry of sortContent(lock.content ?? [])) {
|
|
62
|
+
out += `
|
|
63
|
+
[[content]]
|
|
64
|
+
item = ${quoteTomlString(entry.item)}
|
|
65
|
+
version = ${quoteTomlString(entry.version)}
|
|
66
|
+
version_digest = ${quoteTomlString(entry.versionDigest)}
|
|
67
|
+
licence = ${quoteTomlString(entry.licence)}
|
|
68
|
+
terms = ${quoteTomlString(entry.terms)}
|
|
69
|
+
`;
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
function str(table, key) {
|
|
74
|
+
const v = table?.entries.get(key);
|
|
75
|
+
return v !== void 0 && v.kind === "string" ? v.value : null;
|
|
76
|
+
}
|
|
77
|
+
function int(table, key) {
|
|
78
|
+
const v = table?.entries.get(key);
|
|
79
|
+
return v !== void 0 && v.kind === "integer" ? v.value : null;
|
|
80
|
+
}
|
|
81
|
+
function sub(root, key) {
|
|
82
|
+
const v = root.entries.get(key);
|
|
83
|
+
return v !== void 0 && v.kind === "table" ? v : null;
|
|
84
|
+
}
|
|
85
|
+
function parseLockfile(source, file = LOCKFILE) {
|
|
86
|
+
let root;
|
|
87
|
+
try {
|
|
88
|
+
root = parseToml(source);
|
|
89
|
+
} catch (err) {
|
|
90
|
+
if (err instanceof TomlSyntaxError) {
|
|
91
|
+
return { lockfile: null, diagnostics: [diagnostic("lock.syntax", err.reason, file, err)] };
|
|
92
|
+
}
|
|
93
|
+
throw err;
|
|
94
|
+
}
|
|
95
|
+
const diagnostics = [];
|
|
96
|
+
const fail = (code, message, at2 = root.at) => {
|
|
97
|
+
diagnostics.push(diagnostic(code, message, file, at2));
|
|
98
|
+
};
|
|
99
|
+
const lockVersion = int(root, "lock_version");
|
|
100
|
+
if (lockVersion !== LOCK_VERSION) fail("lock.version", `lock_version must be ${LOCK_VERSION}`, root.entries.get("lock_version")?.at);
|
|
101
|
+
const sdkTable = sub(root, "sdk");
|
|
102
|
+
const ctxTable = sub(root, "ctx");
|
|
103
|
+
const toolchain = sub(root, "toolchain");
|
|
104
|
+
const sdk = str(sdkTable, "version");
|
|
105
|
+
const contract = int(ctxTable, "contract_version");
|
|
106
|
+
const fingerprint = str(ctxTable, "fingerprint");
|
|
107
|
+
const typescript = str(toolchain, "typescript");
|
|
108
|
+
if (sdk === null) fail("lock.missing", "[sdk] version is missing", sdkTable?.at);
|
|
109
|
+
if (contract === null) fail("lock.missing", "[ctx] contract_version is missing", ctxTable?.at);
|
|
110
|
+
if (fingerprint === null || !FINGERPRINT.test(fingerprint)) fail("lock.missing", "[ctx] fingerprint must be 16 hex digits", ctxTable?.at);
|
|
111
|
+
if (typescript === null) fail("lock.missing", "[toolchain] typescript is missing", toolchain?.at);
|
|
112
|
+
const libraries = [];
|
|
113
|
+
const seen = /* @__PURE__ */ new Set();
|
|
114
|
+
const libs = root.entries.get("libraries");
|
|
115
|
+
if (libs !== void 0) {
|
|
116
|
+
if (libs.kind !== "array" || !libs.ofTables) {
|
|
117
|
+
fail("lock.type", "libraries must be written as [[libraries]] tables", libs.at);
|
|
118
|
+
} else {
|
|
119
|
+
for (const item of libs.items) {
|
|
120
|
+
const t = item.kind === "table" ? item : null;
|
|
121
|
+
const name = str(t, "name");
|
|
122
|
+
const version = str(t, "version");
|
|
123
|
+
if (name === null || version === null) {
|
|
124
|
+
fail("lock.missing", "each [[libraries]] needs a name and a version", item.at);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (seen.has(name)) {
|
|
128
|
+
fail("lock.duplicate", `library '${name}' is locked twice`, item.at);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
seen.add(name);
|
|
132
|
+
libraries.push({ name, version });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const content = [];
|
|
137
|
+
const locked = root.entries.get("content");
|
|
138
|
+
if (locked !== void 0) {
|
|
139
|
+
if (locked.kind !== "array" || !locked.ofTables) {
|
|
140
|
+
fail("lock.type", "content must be written as [[content]] tables", locked.at);
|
|
141
|
+
} else {
|
|
142
|
+
const items = /* @__PURE__ */ new Set();
|
|
143
|
+
for (const entry of locked.items) {
|
|
144
|
+
const t = entry.kind === "table" ? entry : null;
|
|
145
|
+
const item = str(t, "item");
|
|
146
|
+
const version = str(t, "version");
|
|
147
|
+
const versionDigest = str(t, "version_digest");
|
|
148
|
+
const licence = str(t, "licence");
|
|
149
|
+
const terms = str(t, "terms");
|
|
150
|
+
if (item === null || version === null || versionDigest === null || licence === null || terms === null) {
|
|
151
|
+
fail("lock.missing", "each [[content]] needs item, version, version_digest, licence and terms", entry.at);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (!isItemReference(item) || !CONTENT_VERSION.test(version) || !HEX_DIGEST.test(versionDigest) || !HEX_DIGEST.test(terms)) {
|
|
155
|
+
fail("lock.content", `the [[content]] entry for '${item}' is malformed`, entry.at);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (items.has(item)) {
|
|
159
|
+
fail("lock.duplicate", `content '${item}' is locked twice`, entry.at);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
items.add(item);
|
|
163
|
+
content.push({ item, version, versionDigest, licence, terms });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (diagnostics.length > 0 || sdk === null || contract === null || fingerprint === null || typescript === null) {
|
|
168
|
+
return { lockfile: null, diagnostics };
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
lockfile: {
|
|
172
|
+
lockVersion: LOCK_VERSION,
|
|
173
|
+
sdk,
|
|
174
|
+
ctxContractVersion: contract,
|
|
175
|
+
ctxFingerprint: fingerprint,
|
|
176
|
+
typescript,
|
|
177
|
+
libraries,
|
|
178
|
+
...content.length === 0 ? {} : { content: sortContent(content) }
|
|
179
|
+
},
|
|
180
|
+
diagnostics
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function checkLockfile(lock, file = LOCKFILE) {
|
|
184
|
+
const out = [];
|
|
185
|
+
if (lock.sdk !== PINNED_SDK_VERSION) {
|
|
186
|
+
out.push(diagnostic("lock.sdk", `the lockfile pins ${SDK_PACKAGE} ${lock.sdk}; this build service supports ${PINNED_SDK_VERSION}`, file, null));
|
|
187
|
+
}
|
|
188
|
+
if (lock.ctxContractVersion < CTX_MIN_SUPPORTED_CONTRACT_VERSION || lock.ctxContractVersion > CTX_CONTRACT_VERSION) {
|
|
189
|
+
out.push(diagnostic("lock.ctx", `ctx contract ${lock.ctxContractVersion} is outside the supported window ${CTX_MIN_SUPPORTED_CONTRACT_VERSION} to ${CTX_CONTRACT_VERSION}`, file, null));
|
|
190
|
+
} else if (lockedMissingOperations(lock) === null) {
|
|
191
|
+
out.push(
|
|
192
|
+
diagnostic(
|
|
193
|
+
"lock.ctx",
|
|
194
|
+
`ctx fingerprint ${lock.ctxFingerprint} is not a known fingerprint of contract ${lock.ctxContractVersion} (current ${CTX_CONTRACT_FINGERPRINT}); run cimplify lock --update`,
|
|
195
|
+
file,
|
|
196
|
+
null
|
|
197
|
+
)
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
if (lock.typescript !== PINNED_TYPESCRIPT_VERSION) {
|
|
201
|
+
out.push(diagnostic("lock.toolchain", `the lockfile pins TypeScript ${lock.typescript}; the pinned toolchain is ${PINNED_TYPESCRIPT_VERSION}`, file, null));
|
|
202
|
+
}
|
|
203
|
+
for (const library of lock.libraries) {
|
|
204
|
+
const allowed = findLibrary(library.name);
|
|
205
|
+
if (allowed === null || allowed.name === SDK_PACKAGE) {
|
|
206
|
+
out.push(diagnostic("lock.library", `'${library.name}' is not on the reviewed library allowlist`, file, null));
|
|
207
|
+
} else if (allowed.version !== library.version) {
|
|
208
|
+
out.push(diagnostic("lock.library", `'${library.name}' is locked at ${library.version}; the reviewed version is ${allowed.version}`, file, null));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return out;
|
|
212
|
+
}
|
|
213
|
+
function lockedMissingOperations(lock) {
|
|
214
|
+
if (lock.ctxContractVersion === CTX_CONTRACT_VERSION && lock.ctxFingerprint === CTX_CONTRACT_FINGERPRINT) return /* @__PURE__ */ new Set();
|
|
215
|
+
if (lock.ctxContractVersion !== CTX_CONTRACT_VERSION) return /* @__PURE__ */ new Set();
|
|
216
|
+
const known = CTX_COMPATIBLE_FINGERPRINTS.find(
|
|
217
|
+
(entry) => entry.contract_version === lock.ctxContractVersion && entry.fingerprint === lock.ctxFingerprint
|
|
218
|
+
);
|
|
219
|
+
return known === void 0 ? null : new Set(known.missing_operations);
|
|
220
|
+
}
|
|
221
|
+
function withCurrentContract(lock) {
|
|
222
|
+
if (lock.ctxContractVersion === CTX_CONTRACT_VERSION && lock.ctxFingerprint === CTX_CONTRACT_FINGERPRINT) return lock;
|
|
223
|
+
return { ...lock, ctxContractVersion: CTX_CONTRACT_VERSION, ctxFingerprint: CTX_CONTRACT_FINGERPRINT };
|
|
224
|
+
}
|
|
225
|
+
function lockedVersion(lock, name) {
|
|
226
|
+
if (name === SDK_PACKAGE) return lock.sdk;
|
|
227
|
+
for (const library of lock.libraries) if (library.name === name) return library.version;
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
function withLibrary(lock, library) {
|
|
231
|
+
if (library.name === SDK_PACKAGE || lockedVersion(lock, library.name) !== null) return lock;
|
|
232
|
+
return { ...lock, libraries: sortLibraries([...lock.libraries, library]) };
|
|
233
|
+
}
|
|
234
|
+
var MANIFEST_FILE = "interactive.toml";
|
|
235
|
+
var CATEGORIES = ["games", "everyday"];
|
|
236
|
+
var PROFILE_KINDS = ["native", "canvas"];
|
|
237
|
+
var ENGINE_PROFILES = ["godot", "wasm"];
|
|
238
|
+
var GAME_PROFILES = ["game_api", "direct_engine:three", "direct_engine:phaser", "wasm", "godot"];
|
|
239
|
+
var ENGINE_GAME_PROFILES = ["wasm", "godot"];
|
|
240
|
+
var WASM_LANGUAGES = ["c", "cpp", "rust"];
|
|
241
|
+
var THREAD_MODES = ["single", "isolated"];
|
|
242
|
+
var GODOT_RUNTIME_KINDS = ["full", "3d", "2d"];
|
|
243
|
+
var ENGINE_ENTRY = "src/cimplify.engine.ts";
|
|
244
|
+
var DEVICE_TIERS = ["low", "mid", "high"];
|
|
245
|
+
var ENTRY_KINDS = ["client", "room", "worker"];
|
|
246
|
+
var CHANNEL_DELIVERIES = ["reliable", "latest", "unordered"];
|
|
247
|
+
var DATA_USES = ["none", "anonymous", "personal"];
|
|
248
|
+
var CURRENCY_CLASSES = ["soft", "premium"];
|
|
249
|
+
var OFFER_GRANT_KINDS = ["item", "currency", "unlock", "effect"];
|
|
250
|
+
var RULE_KINDS = ["faucet", "sink"];
|
|
251
|
+
var VIOLENCE = ["none", "cartoon", "realistic", "graphic"];
|
|
252
|
+
var SEXUALITY = ["none", "suggestive", "explicit"];
|
|
253
|
+
var LANGUAGE = ["none", "mild", "strong"];
|
|
254
|
+
var FEAR = ["none", "mild", "intense"];
|
|
255
|
+
var DEFAULT_KERNELS = { lower: true, exactMath: false, noLower: [] };
|
|
256
|
+
var TOP_LEVEL = ["interactive", "profile", "entries", "capabilities", "providers", "economy", "data", "ai", "rating", "delivery", "engine", "content", "credits", "world", "kernels"];
|
|
257
|
+
var CONTENT_KEYS = ["item", "version", "group"];
|
|
258
|
+
var CREDIT_KEYS = ["title", "authors", "licence", "licence_url", "source_url", "modified"];
|
|
259
|
+
var CONTENT_ITEM = /^[a-z0-9][a-z0-9_-]{0,63}\/[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
260
|
+
var CONTENT_RANGE = /^(\*|[=^~]?(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*)){0,2})$/;
|
|
261
|
+
var MAX_CREDIT_TEXT = 512;
|
|
262
|
+
var MAX_CREDIT_AUTHORS = 16;
|
|
263
|
+
var WASM_ENGINE_KEYS = ["language", "sources", "includes", "crate", "threads", "memory", "max_memory", "packs"];
|
|
264
|
+
var GODOT_ENGINE_KEYS = ["project", "threads", "runtime", "packs"];
|
|
265
|
+
var PACK_KEYS = ["name", "files"];
|
|
266
|
+
var NATIVE_DIR = "native/";
|
|
267
|
+
var C_SOURCE = /\.c$/;
|
|
268
|
+
var CPP_SOURCE = /\.(cc|cpp|cxx)$/;
|
|
269
|
+
var ENGINE_PATH = /^[A-Za-z0-9_][A-Za-z0-9_./-]*$/;
|
|
270
|
+
var GODOT_RESOURCE = /^res:\/\/[A-Za-z0-9_./*-]+$/;
|
|
271
|
+
var TRAILING_SLASHES = /\/+$/;
|
|
272
|
+
var WASM_PAGE_BYTES = 64 * 1024;
|
|
273
|
+
var DEFAULT_WASM_MEMORY = 32 * 1024 * 1024;
|
|
274
|
+
var DEFAULT_WASM_MAX_MEMORY = 256 * 1024 * 1024;
|
|
275
|
+
var MAX_WASM_MEMORY = 2 * 1024 * 1024 * 1024;
|
|
276
|
+
var MAX_ENGINE_PACKS = 16;
|
|
277
|
+
var DATA_KEYS = ["version", "tables", "statements"];
|
|
278
|
+
var TABLE_KEYS = ["name", "columns", "primary_key", "indexes", "row_scope", "mirror"];
|
|
279
|
+
var COLUMN_KEYS = ["name", "type", "nullable", "classification"];
|
|
280
|
+
var INDEX_KEYS = ["name", "columns", "unique"];
|
|
281
|
+
var STATEMENT_KEYS = ["name", "sql", "access", "params", "owner_param", "shared_write"];
|
|
282
|
+
var COLUMN_TYPES = ["text", "integer", "real", "boolean", "timestamp", "json", "blob_ref"];
|
|
283
|
+
var ROW_SCOPES = ["owner", "interactive", "shared"];
|
|
284
|
+
var MIRRORS = ["server_only", "device_mirror"];
|
|
285
|
+
var CLASSIFICATIONS = ["public", "interactive", "personal"];
|
|
286
|
+
var STATEMENT_ACCESS = ["read", "write"];
|
|
287
|
+
var DATA_IDENTIFIER = /^[a-z][a-z0-9_]{0,62}$/;
|
|
288
|
+
var RESERVED_DATA_PREFIX = "ix_";
|
|
289
|
+
var MAX_SCHEMA_VERSION = 4294967295;
|
|
290
|
+
var IDENTITY_KEYS = ["id", "name", "description", "category", "publisher"];
|
|
291
|
+
var PROFILE_KEYS = ["kind", "game", "engine_features", "text_dictionaries", "engine_memory_max"];
|
|
292
|
+
var ENTRY_KEYS = ["client", "room", "worker", "tick", "conformance", "channels"];
|
|
293
|
+
var CHANNEL_KEYS = ["name", "schema", "from", "to", "delivery"];
|
|
294
|
+
var PROVIDER_KEYS = ["id", "destination", "data_use", "purpose"];
|
|
295
|
+
var ECONOMY_KEYS = ["currencies", "items", "odds", "offers", "rules"];
|
|
296
|
+
var CURRENCY_KEYS = ["id", "name", "class", "in_world"];
|
|
297
|
+
var ITEM_KEYS = ["id", "name", "currency", "price", "random"];
|
|
298
|
+
var ODDS_KEYS = ["item", "outcomes"];
|
|
299
|
+
var OUTCOME_KEYS = ["value", "parts_per_million"];
|
|
300
|
+
var OFFER_KEYS = ["id", "name", "price_cimples", "grants", "odds"];
|
|
301
|
+
var OFFER_GRANT_KEYS = ["kind", "id", "quantity"];
|
|
302
|
+
var OFFER_ODDS_KEYS = ["label", "parts_per_million", "grants"];
|
|
303
|
+
var RULE_KEYS = ["id", "kind", "currency", "item", "max_quantity"];
|
|
304
|
+
var MAX_OFFER_CIMPLES = 1e6;
|
|
305
|
+
var MAX_OFFER_GRANTS = 8;
|
|
306
|
+
var DELIVERY_KEYS = ["min_device_tier", "download_size", "groups", "tier_groups", "parts", "progressive", "keep_on_device", "weave", "ranged", "repair_percent"];
|
|
307
|
+
var MAX_REPAIR_PERCENT = 100;
|
|
308
|
+
var GROUP_KEYS = ["name", "before_play", "assets"];
|
|
309
|
+
var RATING_KEYS = ["violence", "sexuality", "language", "fear", "simulated_gambling"];
|
|
310
|
+
var IDENTIFIER = /^[a-z][a-z0-9_-]{0,62}$/;
|
|
311
|
+
var INTERACTIVE_ID = /^ixr_[A-Za-z0-9]{8,40}$/;
|
|
312
|
+
var SOURCE_ENTRY = /^src\/[A-Za-z0-9_./-]+\.(ts|mts|js|mjs)$/;
|
|
313
|
+
var SIZE_TEXT = /^(\d+(?:\.\d+)?)\s*(B|KB|KiB|MB|MiB|GB|GiB)$/;
|
|
314
|
+
var NAME_MAX = 64;
|
|
315
|
+
var DESCRIPTION_MAX = 500;
|
|
316
|
+
var PURPOSE_MAX = 200;
|
|
317
|
+
var MAX_DOWNLOAD_BYTES = 2 * 1024 * 1024 * 1024;
|
|
318
|
+
var MAX_KEPT_DOWNLOAD_BYTES = 16 * 1024 * 1024 * 1024;
|
|
319
|
+
var PROVIDER_SCHEME = "https:";
|
|
320
|
+
var SIZE_UNITS = {
|
|
321
|
+
B: 1,
|
|
322
|
+
KB: 1e3,
|
|
323
|
+
KiB: 1024,
|
|
324
|
+
MB: 1e3 * 1e3,
|
|
325
|
+
MiB: 1024 * 1024,
|
|
326
|
+
GB: 1e3 * 1e3 * 1e3,
|
|
327
|
+
GiB: 1024 * 1024 * 1024
|
|
328
|
+
};
|
|
329
|
+
var Reader = class {
|
|
330
|
+
diagnostics = [];
|
|
331
|
+
file;
|
|
332
|
+
constructor(file) {
|
|
333
|
+
this.file = file;
|
|
334
|
+
}
|
|
335
|
+
error(code, message, at2) {
|
|
336
|
+
this.diagnostics.push(diagnostic(code, message, this.file, at2));
|
|
337
|
+
}
|
|
338
|
+
keys(table, path, allowed) {
|
|
339
|
+
for (const [key, value] of table.entries) {
|
|
340
|
+
if (allowed.indexOf(key) >= 0) continue;
|
|
341
|
+
const where = path === "" ? "at the top level" : `in [${path}]`;
|
|
342
|
+
this.error("manifest.unknown_key", `unknown key '${key}' ${where}; expected one of ${allowed.join(", ")}`, table.keyAt.get(key) ?? value.at);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
table(parent, key, path, required) {
|
|
346
|
+
const value = parent.entries.get(key);
|
|
347
|
+
const full = join(path, key);
|
|
348
|
+
if (value === void 0) {
|
|
349
|
+
if (required) this.error("manifest.missing", `missing required table [${full}]`, parent.at);
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
if (value.kind !== "table") {
|
|
353
|
+
this.error("manifest.type", `'${full}' must be a table, found ${kindName(value)}`, value.at);
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
return value;
|
|
357
|
+
}
|
|
358
|
+
tables(parent, key, path) {
|
|
359
|
+
const value = parent.entries.get(key);
|
|
360
|
+
if (value === void 0) return [];
|
|
361
|
+
const full = join(path, key);
|
|
362
|
+
if (value.kind !== "array") {
|
|
363
|
+
this.error("manifest.type", `'${full}' must be an array of tables ([[${full}]]), found ${kindName(value)}`, value.at);
|
|
364
|
+
return [];
|
|
365
|
+
}
|
|
366
|
+
const out = [];
|
|
367
|
+
for (const item of value.items) {
|
|
368
|
+
if (item.kind !== "table") {
|
|
369
|
+
this.error("manifest.type", `each '${full}' entry must be a table, found ${kindName(item)}`, item.at);
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
out.push(item);
|
|
373
|
+
}
|
|
374
|
+
return out;
|
|
375
|
+
}
|
|
376
|
+
string(table, key, path, required, max = Number.MAX_SAFE_INTEGER) {
|
|
377
|
+
const value = table.entries.get(key);
|
|
378
|
+
const full = join(path, key);
|
|
379
|
+
if (value === void 0) {
|
|
380
|
+
if (required) this.error("manifest.missing", `missing required key '${full}'`, table.at);
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
if (value.kind !== "string") {
|
|
384
|
+
this.error("manifest.type", `'${full}' must be a string, found ${kindName(value)}`, value.at);
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
if (required && value.value.trim().length === 0) {
|
|
388
|
+
this.error("manifest.empty", `'${full}' must not be empty`, value.at);
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
if (value.value.length > max) {
|
|
392
|
+
this.error("manifest.too_long", `'${full}' is ${value.value.length} characters; the limit is ${max}`, value.at);
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
return value.value;
|
|
396
|
+
}
|
|
397
|
+
oneOf(table, key, path, options, required = true) {
|
|
398
|
+
const raw = this.string(table, key, path, required);
|
|
399
|
+
if (raw === null) return null;
|
|
400
|
+
if (options.indexOf(raw) < 0) {
|
|
401
|
+
this.error("manifest.value", `'${join(path, key)}' is '${raw}'; expected one of ${options.join(", ")}`, at(table, key));
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
return raw;
|
|
405
|
+
}
|
|
406
|
+
boolean(table, key, path, fallback) {
|
|
407
|
+
const value = table.entries.get(key);
|
|
408
|
+
if (value === void 0) {
|
|
409
|
+
if (fallback === null) this.error("manifest.missing", `missing required key '${join(path, key)}'`, table.at);
|
|
410
|
+
return fallback;
|
|
411
|
+
}
|
|
412
|
+
if (value.kind !== "boolean") {
|
|
413
|
+
this.error("manifest.type", `'${join(path, key)}' must be true or false, found ${kindName(value)}`, value.at);
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
return value.value;
|
|
417
|
+
}
|
|
418
|
+
integer(table, key, path, required, min, max) {
|
|
419
|
+
const value = table.entries.get(key);
|
|
420
|
+
const full = join(path, key);
|
|
421
|
+
if (value === void 0) {
|
|
422
|
+
if (required) this.error("manifest.missing", `missing required key '${full}'`, table.at);
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
if (value.kind !== "integer") {
|
|
426
|
+
this.error("manifest.type", `'${full}' must be an integer, found ${kindName(value)}`, value.at);
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
if (value.value < min || value.value > max) {
|
|
430
|
+
this.error("manifest.range", `'${full}' is ${value.value}; expected ${min} to ${max}`, value.at);
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
return value.value;
|
|
434
|
+
}
|
|
435
|
+
strings(table, key, path, required) {
|
|
436
|
+
const value = table.entries.get(key);
|
|
437
|
+
const full = join(path, key);
|
|
438
|
+
if (value === void 0) {
|
|
439
|
+
if (required) this.error("manifest.missing", `missing required key '${full}'`, table.at);
|
|
440
|
+
return [];
|
|
441
|
+
}
|
|
442
|
+
if (value.kind !== "array" || value.ofTables) {
|
|
443
|
+
this.error("manifest.type", `'${full}' must be an array of strings, found ${kindName(value)}`, value.at);
|
|
444
|
+
return [];
|
|
445
|
+
}
|
|
446
|
+
const out = [];
|
|
447
|
+
for (const item of value.items) {
|
|
448
|
+
if (item.kind !== "string") {
|
|
449
|
+
this.error("manifest.type", `each '${full}' entry must be a string, found ${kindName(item)}`, item.at);
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
out.push(item.value);
|
|
453
|
+
}
|
|
454
|
+
return out;
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
function join(path, key) {
|
|
458
|
+
return path === "" ? key : `${path}.${key}`;
|
|
459
|
+
}
|
|
460
|
+
function at(table, key) {
|
|
461
|
+
return table.entries.get(key)?.at ?? table.at;
|
|
462
|
+
}
|
|
463
|
+
function kindName(value) {
|
|
464
|
+
if (value.kind === "array") return value.ofTables ? "an array of tables" : "an array";
|
|
465
|
+
if (value.kind === "table") return "a table";
|
|
466
|
+
if (value.kind === "integer") return "an integer";
|
|
467
|
+
return `a ${value.kind}`;
|
|
468
|
+
}
|
|
469
|
+
function parseByteSize(text) {
|
|
470
|
+
const match = SIZE_TEXT.exec(text.trim());
|
|
471
|
+
if (!match) return null;
|
|
472
|
+
const bytes = Math.round(Number(match[1]) * SIZE_UNITS[match[2]]);
|
|
473
|
+
return Number.isSafeInteger(bytes) ? bytes : null;
|
|
474
|
+
}
|
|
475
|
+
function readIdentity(r, root) {
|
|
476
|
+
const t = r.table(root, "interactive", "", true);
|
|
477
|
+
if (!t) return null;
|
|
478
|
+
r.keys(t, "interactive", IDENTITY_KEYS);
|
|
479
|
+
const id = r.string(t, "id", "interactive", false);
|
|
480
|
+
if (id !== null && !INTERACTIVE_ID.test(id)) {
|
|
481
|
+
r.error("manifest.value", `'interactive.id' is '${id}'; ids are assigned by Cimplify on first publish and look like ixr_<letters and digits>`, at(t, "id"));
|
|
482
|
+
}
|
|
483
|
+
const name = r.string(t, "name", "interactive", true, NAME_MAX);
|
|
484
|
+
const description = r.string(t, "description", "interactive", true, DESCRIPTION_MAX);
|
|
485
|
+
const category = r.oneOf(t, "category", "interactive", CATEGORIES);
|
|
486
|
+
const publisher = r.string(t, "publisher", "interactive", false);
|
|
487
|
+
if (name === null || description === null || category === null) return null;
|
|
488
|
+
return { id, name, description, category, publisher };
|
|
489
|
+
}
|
|
490
|
+
function readProfile(r, root) {
|
|
491
|
+
const t = r.table(root, "profile", "", true);
|
|
492
|
+
if (!t) return null;
|
|
493
|
+
r.keys(t, "profile", PROFILE_KEYS);
|
|
494
|
+
const kindRaw = r.string(t, "kind", "profile", true);
|
|
495
|
+
if (kindRaw === null) return null;
|
|
496
|
+
if (ENGINE_PROFILES.indexOf(kindRaw) >= 0) {
|
|
497
|
+
r.error("manifest.value", `engine games run in the canvas profile; set profile.kind = "canvas" and profile.game = "${kindRaw}"`, at(t, "kind"));
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
if (PROFILE_KINDS.indexOf(kindRaw) < 0) {
|
|
501
|
+
r.error("manifest.value", `'profile.kind' is '${kindRaw}'; expected one of ${PROFILE_KINDS.join(", ")}`, at(t, "kind"));
|
|
502
|
+
return null;
|
|
503
|
+
}
|
|
504
|
+
const kind = kindRaw;
|
|
505
|
+
const engineFeatures = readEngineFeatures(r, t);
|
|
506
|
+
const textDictionaries = r.boolean(t, "text_dictionaries", "profile", false) === true ? { textDictionaries: true } : {};
|
|
507
|
+
const engineMemoryMax = readEngineMemoryMax(r, t);
|
|
508
|
+
const extras = engineMemoryMax === null ? textDictionaries : { ...textDictionaries, engineMemoryMax };
|
|
509
|
+
if (!t.entries.has("game")) return { kind, game: null, engineFeatures, ...extras };
|
|
510
|
+
const game = r.oneOf(t, "game", "profile", GAME_PROFILES);
|
|
511
|
+
if (game === null) return null;
|
|
512
|
+
if (kind !== "canvas" && game !== "game_api") {
|
|
513
|
+
r.error("manifest.value", `'profile.game' = "${game}" requires profile.kind = "canvas"; native games use game_api`, at(t, "game"));
|
|
514
|
+
return null;
|
|
515
|
+
}
|
|
516
|
+
return { kind, game, engineFeatures, ...extras };
|
|
517
|
+
}
|
|
518
|
+
function readEngineMemoryMax(r, t) {
|
|
519
|
+
const value = t.entries.get("engine_memory_max");
|
|
520
|
+
if (value === void 0) return null;
|
|
521
|
+
const bytes = value.kind === "integer" ? value.value : value.kind === "string" ? parseByteSize(value.value) : null;
|
|
522
|
+
if (bytes === null || bytes < ENGINE_ARENA_BASELINE_BYTES || bytes > ENGINE_ARENA_MAX_CEILING_BYTES || bytes % ENGINE_ARENA_CHUNK_BYTES !== 0) {
|
|
523
|
+
r.error("manifest.value", `'profile.engine_memory_max' is a whole number of 16 MiB chunks between 512 MiB and 1968 MiB, like "1 GiB"`, value.at);
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
return bytes;
|
|
527
|
+
}
|
|
528
|
+
function readEngineFeatures(r, t) {
|
|
529
|
+
if (!t.entries.has("engine_features")) return null;
|
|
530
|
+
const declared = r.strings(t, "engine_features", "profile", true);
|
|
531
|
+
const known = ENGINE_FEATURE_NAMES;
|
|
532
|
+
const unknown = declared.find((name) => known.indexOf(name) < 0);
|
|
533
|
+
if (unknown !== void 0) {
|
|
534
|
+
r.error("manifest.value", `'profile.engine_features' names '${unknown}'; expected some of ${ENGINE_FEATURE_NAMES.join(", ")}`, at(t, "engine_features"));
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
return ENGINE_FEATURE_NAMES.filter((name) => declared.indexOf(name) >= 0);
|
|
538
|
+
}
|
|
539
|
+
function readEntry(r, t, key, required) {
|
|
540
|
+
const value = r.string(t, key, "entries", required);
|
|
541
|
+
if (value === null) return null;
|
|
542
|
+
if (!SOURCE_ENTRY.test(value) || value.split("/").indexOf("..") >= 0) {
|
|
543
|
+
r.error("manifest.value", `'entries.${key}' is '${value}'; entries are TypeScript or JavaScript files under src/`, at(t, key));
|
|
544
|
+
return null;
|
|
545
|
+
}
|
|
546
|
+
return value;
|
|
547
|
+
}
|
|
548
|
+
function isEngineGame(game) {
|
|
549
|
+
return game !== null && ENGINE_GAME_PROFILES.indexOf(game) >= 0;
|
|
550
|
+
}
|
|
551
|
+
function readEntries(r, root, profile) {
|
|
552
|
+
const engine = profile !== null && isEngineGame(profile.game);
|
|
553
|
+
if (engine && !root.entries.has("entries")) return { client: ENGINE_ENTRY, room: null, worker: null, tick: null, conformance: null, channels: [] };
|
|
554
|
+
const t = r.table(root, "entries", "", true);
|
|
555
|
+
if (!t) return null;
|
|
556
|
+
r.keys(t, "entries", ENTRY_KEYS);
|
|
557
|
+
if (engine && t.entries.has("client")) {
|
|
558
|
+
r.error("manifest.value", "an engine game's client entry is built by Cimplify from [engine]; remove entries.client", at(t, "client"));
|
|
559
|
+
}
|
|
560
|
+
const client = engine ? ENGINE_ENTRY : readEntry(r, t, "client", true);
|
|
561
|
+
const room = readEntry(r, t, "room", false);
|
|
562
|
+
const worker = readEntry(r, t, "worker", false);
|
|
563
|
+
const tick = readEntry(r, t, "tick", false);
|
|
564
|
+
if (tick !== null && tick === client) {
|
|
565
|
+
r.error("manifest.value", "'entries.tick' is the authoritative room bundle; give it its own module and import shared rules from it in the client", at(t, "tick"));
|
|
566
|
+
}
|
|
567
|
+
const conformance = readEntry(r, t, "conformance", false);
|
|
568
|
+
if (conformance !== null && profile !== null && profile.game === null) {
|
|
569
|
+
r.error("manifest.value", "'entries.conformance' is only used by game profiles; set profile.game or remove it", at(t, "conformance"));
|
|
570
|
+
}
|
|
571
|
+
const declared = { client: true, room: room !== null, worker: worker !== null };
|
|
572
|
+
const channels = [];
|
|
573
|
+
const names = /* @__PURE__ */ new Set();
|
|
574
|
+
for (const c of r.tables(t, "channels", "entries")) {
|
|
575
|
+
r.keys(c, "entries.channels", CHANNEL_KEYS);
|
|
576
|
+
const name = r.string(c, "name", "entries.channels", true);
|
|
577
|
+
const schema = r.string(c, "schema", "entries.channels", true);
|
|
578
|
+
const from = r.oneOf(c, "from", "entries.channels", ENTRY_KINDS);
|
|
579
|
+
const to = r.oneOf(c, "to", "entries.channels", ENTRY_KINDS);
|
|
580
|
+
const delivery = r.oneOf(c, "delivery", "entries.channels", CHANNEL_DELIVERIES);
|
|
581
|
+
if (name === null || schema === null || from === null || to === null || delivery === null) continue;
|
|
582
|
+
if (!IDENTIFIER.test(name)) {
|
|
583
|
+
r.error("manifest.value", `channel name '${name}' must be lowercase letters, digits, '-' or '_'`, at(c, "name"));
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
if (names.has(name)) {
|
|
587
|
+
r.error("manifest.duplicate", `channel '${name}' is declared twice`, at(c, "name"));
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
if (!declared[from] || !declared[to]) {
|
|
591
|
+
const missing = !declared[from] ? from : to;
|
|
592
|
+
r.error("manifest.value", `channel '${name}' uses the ${missing} entry, but entries.${missing} is not declared`, c.at);
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
if (from === to) {
|
|
596
|
+
r.error("manifest.value", `channel '${name}' must connect two different entries`, at(c, "to"));
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
names.add(name);
|
|
600
|
+
channels.push({ name, schema, from, to, delivery });
|
|
601
|
+
}
|
|
602
|
+
if (client === null) return null;
|
|
603
|
+
return { client, room, worker, tick, conformance, channels };
|
|
604
|
+
}
|
|
605
|
+
function readCapabilities(r, root) {
|
|
606
|
+
const t = r.table(root, "capabilities", "", false);
|
|
607
|
+
if (!t) return [];
|
|
608
|
+
const out = [];
|
|
609
|
+
for (const [name, value] of t.entries) {
|
|
610
|
+
if (!Object.prototype.hasOwnProperty.call(CTX_CAPABILITIES, name)) {
|
|
611
|
+
r.error("manifest.capability", `'${name}' is not a ctx capability; see the ctx reference for the list`, t.keyAt.get(name) ?? value.at);
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
if (value.kind !== "string") {
|
|
615
|
+
r.error("manifest.type", `capability '${name}' must be a purpose string shown to the player, found ${kindName(value)}`, value.at);
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
if (value.value.trim().length === 0 || value.value.length > PURPOSE_MAX) {
|
|
619
|
+
r.error("manifest.value", `capability '${name}' needs a purpose of 1 to ${PURPOSE_MAX} characters`, value.at);
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
out.push({ name, purpose: value.value });
|
|
623
|
+
}
|
|
624
|
+
out.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
625
|
+
return out;
|
|
626
|
+
}
|
|
627
|
+
function readProviders(r, root) {
|
|
628
|
+
const out = [];
|
|
629
|
+
const ids = /* @__PURE__ */ new Set();
|
|
630
|
+
for (const p of r.tables(root, "providers", "")) {
|
|
631
|
+
r.keys(p, "providers", PROVIDER_KEYS);
|
|
632
|
+
const id = r.string(p, "id", "providers", true);
|
|
633
|
+
const destination = r.string(p, "destination", "providers", true);
|
|
634
|
+
const dataUse = r.oneOf(p, "data_use", "providers", DATA_USES);
|
|
635
|
+
const purpose = r.string(p, "purpose", "providers", true, PURPOSE_MAX);
|
|
636
|
+
if (id === null || destination === null || dataUse === null || purpose === null) continue;
|
|
637
|
+
if (!IDENTIFIER.test(id)) {
|
|
638
|
+
r.error("manifest.value", `provider id '${id}' must be lowercase letters, digits, '-' or '_'`, at(p, "id"));
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
if (ids.has(id)) {
|
|
642
|
+
r.error("manifest.duplicate", `provider '${id}' is declared twice`, at(p, "id"));
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
645
|
+
let url;
|
|
646
|
+
try {
|
|
647
|
+
url = new URL(destination);
|
|
648
|
+
} catch {
|
|
649
|
+
r.error("manifest.value", `provider '${id}' destination '${destination}' is not a URL`, at(p, "destination"));
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
if (url.protocol !== PROVIDER_SCHEME || url.username !== "" || url.password !== "") {
|
|
653
|
+
r.error("manifest.value", `provider '${id}' destination must be an https origin without credentials`, at(p, "destination"));
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
ids.add(id);
|
|
657
|
+
out.push({ id, destination: url.origin, dataUse, purpose });
|
|
658
|
+
}
|
|
659
|
+
return out;
|
|
660
|
+
}
|
|
661
|
+
function readEconomy(r, root, context) {
|
|
662
|
+
const t = r.table(root, "economy", "", false);
|
|
663
|
+
if (!t) return { currencies: [], items: [], odds: [], offers: [], rules: [] };
|
|
664
|
+
r.keys(t, "economy", ECONOMY_KEYS);
|
|
665
|
+
const currencies = [];
|
|
666
|
+
const currencyIds = /* @__PURE__ */ new Set();
|
|
667
|
+
for (const c of r.tables(t, "currencies", "economy")) {
|
|
668
|
+
r.keys(c, "economy.currencies", CURRENCY_KEYS);
|
|
669
|
+
const id = r.string(c, "id", "economy.currencies", true);
|
|
670
|
+
const name = r.string(c, "name", "economy.currencies", true, NAME_MAX);
|
|
671
|
+
const cls = r.oneOf(c, "class", "economy.currencies", CURRENCY_CLASSES);
|
|
672
|
+
const inWorld = r.boolean(c, "in_world", "economy.currencies", false) ?? false;
|
|
673
|
+
if (id === null || name === null || cls === null) continue;
|
|
674
|
+
if (inWorld && cls !== "soft") r.error("manifest.money", `currency '${id}' is in_world, which only a soft currency may be; premium value is bought with Cimples and must never look like real money`, at(c, "in_world"));
|
|
675
|
+
const exempt = inWorld && cls === "soft";
|
|
676
|
+
const named = exempt ? null : realMoneyIn(name);
|
|
677
|
+
const money = named ?? (exempt ? null : realMoneyIn(id));
|
|
678
|
+
if (money !== null) r.error("manifest.money", `currency '${id}' uses '${money.found}'; ${REAL_MONEY_SUGGESTION}`, at(c, named !== null ? "name" : "id"));
|
|
679
|
+
const refusal = exempt && context !== null ? inWorldRefusal(id, context) : null;
|
|
680
|
+
if (refusal !== null) r.error("manifest.money", refusal, at(c, "in_world"));
|
|
681
|
+
if (!IDENTIFIER.test(id) || currencyIds.has(id)) {
|
|
682
|
+
r.error(currencyIds.has(id) ? "manifest.duplicate" : "manifest.value", `currency id '${id}' must be unique lowercase letters, digits, '-' or '_'`, at(c, "id"));
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
currencyIds.add(id);
|
|
686
|
+
currencies.push({ id, name, class: cls, inWorld });
|
|
687
|
+
}
|
|
688
|
+
const items = [];
|
|
689
|
+
const itemIds = /* @__PURE__ */ new Set();
|
|
690
|
+
for (const it of r.tables(t, "items", "economy")) {
|
|
691
|
+
r.keys(it, "economy.items", ITEM_KEYS);
|
|
692
|
+
const id = r.string(it, "id", "economy.items", true);
|
|
693
|
+
const name = r.string(it, "name", "economy.items", true, NAME_MAX);
|
|
694
|
+
const currency = r.string(it, "currency", "economy.items", false);
|
|
695
|
+
const price = r.integer(it, "price", "economy.items", false, 0, Number.MAX_SAFE_INTEGER);
|
|
696
|
+
const random = r.boolean(it, "random", "economy.items", false) ?? false;
|
|
697
|
+
if (id === null || name === null) continue;
|
|
698
|
+
if (!IDENTIFIER.test(id) || itemIds.has(id)) {
|
|
699
|
+
r.error(itemIds.has(id) ? "manifest.duplicate" : "manifest.value", `item id '${id}' must be unique lowercase letters, digits, '-' or '_'`, at(it, "id"));
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
if (currency === null !== (price === null)) {
|
|
703
|
+
r.error("manifest.value", `item '${id}' needs both currency and price, or neither`, it.at);
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
if (currency !== null && !currencyIds.has(currency)) {
|
|
707
|
+
r.error("manifest.reference", `item '${id}' is priced in '${currency}', which is not a declared currency`, at(it, "currency"));
|
|
708
|
+
continue;
|
|
709
|
+
}
|
|
710
|
+
itemIds.add(id);
|
|
711
|
+
items.push({ id, name, currency, price, random });
|
|
712
|
+
}
|
|
713
|
+
const odds = [];
|
|
714
|
+
const total = CTX_LIMITS.odds_parts_per_million;
|
|
715
|
+
for (const o of r.tables(t, "odds", "economy")) {
|
|
716
|
+
r.keys(o, "economy.odds", ODDS_KEYS);
|
|
717
|
+
const item = r.string(o, "item", "economy.odds", true);
|
|
718
|
+
if (item !== null && !itemIds.has(item)) {
|
|
719
|
+
r.error("manifest.reference", `odds are disclosed for '${item}', which is not a declared item`, at(o, "item"));
|
|
720
|
+
}
|
|
721
|
+
const outcomes = [];
|
|
722
|
+
let sum = 0;
|
|
723
|
+
for (const out of r.tables(o, "outcomes", "economy.odds")) {
|
|
724
|
+
r.keys(out, "economy.odds.outcomes", OUTCOME_KEYS);
|
|
725
|
+
const reward = r.string(out, "value", "economy.odds.outcomes", true);
|
|
726
|
+
const ppm = r.integer(out, "parts_per_million", "economy.odds.outcomes", true, 1, total);
|
|
727
|
+
if (reward === null || ppm === null) continue;
|
|
728
|
+
if (!itemIds.has(reward)) {
|
|
729
|
+
r.error("manifest.reference", `odds outcome '${reward}' is not a declared item`, at(out, "value"));
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
sum += ppm;
|
|
733
|
+
outcomes.push({ value: reward, partsPerMillion: ppm });
|
|
734
|
+
}
|
|
735
|
+
if (outcomes.length === 0) {
|
|
736
|
+
r.error("manifest.missing", `odds for '${item ?? "?"}' need at least one [[economy.odds.outcomes]]`, o.at);
|
|
737
|
+
continue;
|
|
738
|
+
}
|
|
739
|
+
if (sum !== total) {
|
|
740
|
+
r.error("manifest.odds", `odds for '${item ?? "?"}' add up to ${sum} parts per million; they must add up to exactly ${total}`, o.at);
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
if (item !== null) odds.push({ item, outcomes });
|
|
744
|
+
}
|
|
745
|
+
const offers = readOffers(r, t, currencyIds, itemIds);
|
|
746
|
+
const offered = new Set(offers.flatMap((o) => [...o.grants, ...o.odds.flatMap((x) => x.grants)]).filter((g) => g.kind === "item").map((g) => g.id));
|
|
747
|
+
const rules = readRules(r, t, currencies, items, offered);
|
|
748
|
+
return { currencies, items, odds, offers, rules };
|
|
749
|
+
}
|
|
750
|
+
function readGrants(r, owner, path, currencyIds, itemIds) {
|
|
751
|
+
const grants = [];
|
|
752
|
+
let valid = true;
|
|
753
|
+
for (const g of r.tables(owner, "grants", path)) {
|
|
754
|
+
const at_ = `${path}.grants`;
|
|
755
|
+
r.keys(g, at_, OFFER_GRANT_KEYS);
|
|
756
|
+
const kind = r.oneOf(g, "kind", at_, OFFER_GRANT_KINDS);
|
|
757
|
+
const id = r.string(g, "id", at_, true);
|
|
758
|
+
const quantity = r.integer(g, "quantity", at_, false, 1, Number.MAX_SAFE_INTEGER) ?? 1;
|
|
759
|
+
if (kind === null || id === null) {
|
|
760
|
+
valid = false;
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
if (kind === "currency" && !currencyIds.has(id)) {
|
|
764
|
+
r.error("manifest.reference", `offer grant '${id}' is not a declared currency`, at(g, "id"));
|
|
765
|
+
valid = false;
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
if (kind === "item" && !itemIds.has(id)) {
|
|
769
|
+
r.error("manifest.reference", `offer grant '${id}' is not a declared item`, at(g, "id"));
|
|
770
|
+
valid = false;
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
if ((kind === "unlock" || kind === "effect") && !IDENTIFIER.test(id)) {
|
|
774
|
+
r.error("manifest.value", `${kind} id '${id}' must be lowercase letters, digits, '-' or '_'`, at(g, "id"));
|
|
775
|
+
valid = false;
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
grants.push({ kind, id, quantity: kind === "unlock" || kind === "effect" ? 1 : quantity });
|
|
779
|
+
}
|
|
780
|
+
if (grants.length > MAX_OFFER_GRANTS) {
|
|
781
|
+
r.error("manifest.value", `an offer grants at most ${MAX_OFFER_GRANTS} holdings`, owner.at);
|
|
782
|
+
return null;
|
|
783
|
+
}
|
|
784
|
+
return valid ? grants : null;
|
|
785
|
+
}
|
|
786
|
+
function readOffers(r, t, currencyIds, itemIds) {
|
|
787
|
+
const offers = [];
|
|
788
|
+
const offerIds = /* @__PURE__ */ new Set();
|
|
789
|
+
const total = CTX_LIMITS.odds_parts_per_million;
|
|
790
|
+
for (const o of r.tables(t, "offers", "economy")) {
|
|
791
|
+
r.keys(o, "economy.offers", OFFER_KEYS);
|
|
792
|
+
const id = r.string(o, "id", "economy.offers", true);
|
|
793
|
+
const name = r.string(o, "name", "economy.offers", true, NAME_MAX);
|
|
794
|
+
const priceCimples = r.integer(o, "price_cimples", "economy.offers", true, 1, MAX_OFFER_CIMPLES);
|
|
795
|
+
if (id === null || name === null || priceCimples === null) continue;
|
|
796
|
+
if (!IDENTIFIER.test(id) || offerIds.has(id)) {
|
|
797
|
+
r.error(offerIds.has(id) ? "manifest.duplicate" : "manifest.value", `offer id '${id}' must be unique lowercase letters, digits, '-' or '_'`, at(o, "id"));
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
const grants = readGrants(r, o, "economy.offers", currencyIds, itemIds);
|
|
801
|
+
const odds = [];
|
|
802
|
+
let sum = 0;
|
|
803
|
+
let oddsValid = true;
|
|
804
|
+
for (const x of r.tables(o, "odds", "economy.offers")) {
|
|
805
|
+
r.keys(x, "economy.offers.odds", OFFER_ODDS_KEYS);
|
|
806
|
+
const label = r.string(x, "label", "economy.offers.odds", true, NAME_MAX);
|
|
807
|
+
const ppm = r.integer(x, "parts_per_million", "economy.offers.odds", true, 1, total);
|
|
808
|
+
const won = readGrants(r, x, "economy.offers.odds", currencyIds, itemIds);
|
|
809
|
+
if (label === null || ppm === null || won === null || won.length === 0) {
|
|
810
|
+
oddsValid = false;
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
sum += ppm;
|
|
814
|
+
odds.push({ label, partsPerMillion: ppm, grants: won });
|
|
815
|
+
}
|
|
816
|
+
if (grants === null || !oddsValid) continue;
|
|
817
|
+
if (grants.length === 0 === (odds.length === 0)) {
|
|
818
|
+
r.error("manifest.value", `offer '${id}' needs [[economy.offers.grants]] or [[economy.offers.odds]], not both and not neither`, o.at);
|
|
819
|
+
continue;
|
|
820
|
+
}
|
|
821
|
+
if (odds.length > 0 && sum !== total) {
|
|
822
|
+
r.error("manifest.odds", `odds for offer '${id}' add up to ${sum} parts per million; they must add up to exactly ${total}`, o.at);
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
offerIds.add(id);
|
|
826
|
+
offers.push({ id, name, priceCimples, grants, odds });
|
|
827
|
+
}
|
|
828
|
+
return offers;
|
|
829
|
+
}
|
|
830
|
+
function readRules(r, t, currencies, items, offered) {
|
|
831
|
+
const rules = [];
|
|
832
|
+
const ruleIds = /* @__PURE__ */ new Set();
|
|
833
|
+
for (const rule of r.tables(t, "rules", "economy")) {
|
|
834
|
+
r.keys(rule, "economy.rules", RULE_KEYS);
|
|
835
|
+
const id = r.string(rule, "id", "economy.rules", true);
|
|
836
|
+
const kind = r.oneOf(rule, "kind", "economy.rules", RULE_KINDS);
|
|
837
|
+
const currency = r.string(rule, "currency", "economy.rules", false);
|
|
838
|
+
const item = r.string(rule, "item", "economy.rules", false);
|
|
839
|
+
const maxQuantity = r.integer(rule, "max_quantity", "economy.rules", true, 1, Number.MAX_SAFE_INTEGER);
|
|
840
|
+
if (id === null || kind === null || maxQuantity === null) continue;
|
|
841
|
+
if (!IDENTIFIER.test(id) || ruleIds.has(id)) {
|
|
842
|
+
r.error(ruleIds.has(id) ? "manifest.duplicate" : "manifest.value", `rule id '${id}' must be unique lowercase letters, digits, '-' or '_'`, at(rule, "id"));
|
|
843
|
+
continue;
|
|
844
|
+
}
|
|
845
|
+
if (currency === null === (item === null)) {
|
|
846
|
+
r.error("manifest.value", `rule '${id}' names exactly one currency or item`, rule.at);
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
const soft = currency !== null ? currencies.some((c) => c.id === currency && c.class === "soft") : items.some((i) => i.id === item && !offered.has(i.id) && (i.currency === null || currencies.some((c) => c.id === i.currency && c.class === "soft")));
|
|
850
|
+
if (!soft) {
|
|
851
|
+
r.error("manifest.money", `rule '${id}' may only move a declared soft currency or soft item`, rule.at);
|
|
852
|
+
continue;
|
|
853
|
+
}
|
|
854
|
+
ruleIds.add(id);
|
|
855
|
+
rules.push({ id, kind, currency, item, maxQuantity });
|
|
856
|
+
}
|
|
857
|
+
return rules;
|
|
858
|
+
}
|
|
859
|
+
function dataName(r, table, path, what, taken) {
|
|
860
|
+
const name = r.string(table, "name", path, true);
|
|
861
|
+
if (name === null) return null;
|
|
862
|
+
if (!DATA_IDENTIFIER.test(name) || name.startsWith(RESERVED_DATA_PREFIX) || taken.has(name)) {
|
|
863
|
+
r.error(
|
|
864
|
+
taken.has(name) ? "manifest.duplicate" : "manifest.value",
|
|
865
|
+
`${what} '${name}' must be unique lowercase letters, digits and '_', starting with a letter and not with '${RESERVED_DATA_PREFIX}'`,
|
|
866
|
+
at(table, "name")
|
|
867
|
+
);
|
|
868
|
+
return null;
|
|
869
|
+
}
|
|
870
|
+
taken.add(name);
|
|
871
|
+
return name;
|
|
872
|
+
}
|
|
873
|
+
function readTable(r, t, taken) {
|
|
874
|
+
const path = "data.tables";
|
|
875
|
+
r.keys(t, path, TABLE_KEYS);
|
|
876
|
+
const name = dataName(r, t, path, "table", taken);
|
|
877
|
+
const columns = [];
|
|
878
|
+
const columnNames = /* @__PURE__ */ new Set();
|
|
879
|
+
for (const c of r.tables(t, "columns", path)) {
|
|
880
|
+
const columnPath = `${path}.columns`;
|
|
881
|
+
r.keys(c, columnPath, COLUMN_KEYS);
|
|
882
|
+
const column = dataName(r, c, columnPath, "column", columnNames);
|
|
883
|
+
const columnType = r.oneOf(c, "type", columnPath, COLUMN_TYPES);
|
|
884
|
+
const nullable = r.boolean(c, "nullable", columnPath, false);
|
|
885
|
+
const classification = r.oneOf(c, "classification", columnPath, CLASSIFICATIONS, false) ?? "interactive";
|
|
886
|
+
if (column === null || columnType === null || nullable === null) continue;
|
|
887
|
+
columns.push({ name: column, columnType, nullable, classification });
|
|
888
|
+
}
|
|
889
|
+
const primaryKey = r.strings(t, "primary_key", path, true);
|
|
890
|
+
for (const key of primaryKey) {
|
|
891
|
+
if (!columnNames.has(key)) r.error("manifest.reference", `primary key column '${key}' is not a column of '${name ?? "?"}'`, at(t, "primary_key"));
|
|
892
|
+
}
|
|
893
|
+
const indexes = [];
|
|
894
|
+
const indexNames = /* @__PURE__ */ new Set();
|
|
895
|
+
for (const i of r.tables(t, "indexes", path)) {
|
|
896
|
+
const indexPath = `${path}.indexes`;
|
|
897
|
+
r.keys(i, indexPath, INDEX_KEYS);
|
|
898
|
+
const index = dataName(r, i, indexPath, "index", indexNames);
|
|
899
|
+
const indexed = r.strings(i, "columns", indexPath, true);
|
|
900
|
+
const unique = r.boolean(i, "unique", indexPath, false);
|
|
901
|
+
for (const column of indexed) {
|
|
902
|
+
if (!columnNames.has(column)) r.error("manifest.reference", `index column '${column}' is not a column of '${name ?? "?"}'`, at(i, "columns"));
|
|
903
|
+
}
|
|
904
|
+
if (index === null || unique === null || indexed.length === 0) continue;
|
|
905
|
+
indexes.push({ name: index, columns: indexed, unique });
|
|
906
|
+
}
|
|
907
|
+
const rowScope = r.oneOf(t, "row_scope", path, ROW_SCOPES, false) ?? "owner";
|
|
908
|
+
const mirror = r.oneOf(t, "mirror", path, MIRRORS, false) ?? "server_only";
|
|
909
|
+
if (columns.length === 0) r.error("manifest.missing", `table '${name ?? "?"}' needs at least one [[data.tables.columns]]`, t.at);
|
|
910
|
+
if (name === null || columns.length === 0 || primaryKey.length === 0) return null;
|
|
911
|
+
return { name, columns, primaryKey, indexes, rowScope, mirror };
|
|
912
|
+
}
|
|
913
|
+
function readStatement(r, s, taken) {
|
|
914
|
+
const path = "data.statements";
|
|
915
|
+
r.keys(s, path, STATEMENT_KEYS);
|
|
916
|
+
const name = dataName(r, s, path, "statement", taken);
|
|
917
|
+
const sql = r.string(s, "sql", path, true);
|
|
918
|
+
const access = r.oneOf(s, "access", path, STATEMENT_ACCESS);
|
|
919
|
+
const params = r.integer(s, "params", path, false, 0, CTX_LIMITS.max_db_parameters) ?? 0;
|
|
920
|
+
const ownerParam = r.integer(s, "owner_param", path, false, 1, CTX_LIMITS.max_db_parameters);
|
|
921
|
+
const sharedWrite = r.boolean(s, "shared_write", path, false);
|
|
922
|
+
if (ownerParam !== null && ownerParam > params + 1) {
|
|
923
|
+
r.error("manifest.range", `statement '${name ?? "?"}' binds the owner at $${ownerParam}, past its ${params} parameters`, at(s, "owner_param"));
|
|
924
|
+
return null;
|
|
925
|
+
}
|
|
926
|
+
if (sharedWrite === true && access !== "write") {
|
|
927
|
+
r.error("manifest.value", `statement '${name ?? "?"}' is a read, so it cannot be a shared write`, at(s, "shared_write"));
|
|
928
|
+
return null;
|
|
929
|
+
}
|
|
930
|
+
if (name === null || sql === null || access === null || sharedWrite === null) return null;
|
|
931
|
+
return { name, sql, access, params, ownerParam, sharedWrite };
|
|
932
|
+
}
|
|
933
|
+
function readData(r, root) {
|
|
934
|
+
const t = r.table(root, "data", "", false);
|
|
935
|
+
if (!t) return null;
|
|
936
|
+
r.keys(t, "data", DATA_KEYS);
|
|
937
|
+
const version = r.integer(t, "version", "data", true, 1, MAX_SCHEMA_VERSION);
|
|
938
|
+
const tableNames = /* @__PURE__ */ new Set();
|
|
939
|
+
const tables = [];
|
|
940
|
+
for (const table of r.tables(t, "tables", "data")) {
|
|
941
|
+
const read = readTable(r, table, tableNames);
|
|
942
|
+
if (read !== null) tables.push(read);
|
|
943
|
+
}
|
|
944
|
+
const statementNames = /* @__PURE__ */ new Set();
|
|
945
|
+
const statements = [];
|
|
946
|
+
for (const statement of r.tables(t, "statements", "data")) {
|
|
947
|
+
const read = readStatement(r, statement, statementNames);
|
|
948
|
+
if (read !== null) statements.push(read);
|
|
949
|
+
}
|
|
950
|
+
if (version === null) return null;
|
|
951
|
+
return { version, tables, statements };
|
|
952
|
+
}
|
|
953
|
+
function readRating(r, root) {
|
|
954
|
+
const t = r.table(root, "rating", "", true);
|
|
955
|
+
if (!t) return null;
|
|
956
|
+
r.keys(t, "rating", RATING_KEYS);
|
|
957
|
+
const violence = r.oneOf(t, "violence", "rating", VIOLENCE);
|
|
958
|
+
const sexuality = r.oneOf(t, "sexuality", "rating", SEXUALITY);
|
|
959
|
+
const language = r.oneOf(t, "language", "rating", LANGUAGE);
|
|
960
|
+
const fear = r.oneOf(t, "fear", "rating", FEAR);
|
|
961
|
+
const gambling = r.boolean(t, "simulated_gambling", "rating", null);
|
|
962
|
+
if (sexuality === "explicit") {
|
|
963
|
+
r.error("manifest.prohibited", "sexually explicit content is not admitted on Cimplify", at(t, "sexuality"));
|
|
964
|
+
return null;
|
|
965
|
+
}
|
|
966
|
+
if (violence === null || sexuality === null || language === null || fear === null || gambling === null) return null;
|
|
967
|
+
return { violence, sexuality, language, fear, simulated_gambling: gambling };
|
|
968
|
+
}
|
|
969
|
+
function readDelivery(r, root) {
|
|
970
|
+
const t = r.table(root, "delivery", "", true);
|
|
971
|
+
if (!t) return null;
|
|
972
|
+
r.keys(t, "delivery", DELIVERY_KEYS);
|
|
973
|
+
const tier = r.oneOf(t, "min_device_tier", "delivery", DEVICE_TIERS);
|
|
974
|
+
const sizeValue = t.entries.get("download_size");
|
|
975
|
+
let downloadBytes = null;
|
|
976
|
+
if (sizeValue === void 0) {
|
|
977
|
+
r.error("manifest.missing", "missing required key 'delivery.download_size'", t.at);
|
|
978
|
+
} else if (sizeValue.kind === "integer") {
|
|
979
|
+
downloadBytes = sizeValue.value;
|
|
980
|
+
} else if (sizeValue.kind === "string") {
|
|
981
|
+
downloadBytes = parseByteSize(sizeValue.value);
|
|
982
|
+
if (downloadBytes === null) {
|
|
983
|
+
r.error("manifest.value", `'delivery.download_size' is '${sizeValue.value}'; write bytes or a size like "12 MiB"`, sizeValue.at);
|
|
984
|
+
}
|
|
985
|
+
} else {
|
|
986
|
+
r.error("manifest.type", `'delivery.download_size' must be bytes or a size string, found ${kindName(sizeValue)}`, sizeValue.at);
|
|
987
|
+
}
|
|
988
|
+
const keepOnDevice = r.boolean(t, "keep_on_device", "delivery", false) ?? false;
|
|
989
|
+
const maxDownload = keepOnDevice ? MAX_KEPT_DOWNLOAD_BYTES : MAX_DOWNLOAD_BYTES;
|
|
990
|
+
if (downloadBytes !== null && (downloadBytes <= 0 || downloadBytes > maxDownload)) {
|
|
991
|
+
r.error("manifest.range", `'delivery.download_size' must be between 1 byte and ${maxDownload} bytes`, sizeValue?.at ?? t.at);
|
|
992
|
+
downloadBytes = null;
|
|
993
|
+
}
|
|
994
|
+
const groups = [];
|
|
995
|
+
const names = /* @__PURE__ */ new Set();
|
|
996
|
+
let streamed = false;
|
|
997
|
+
for (const g of r.tables(t, "groups", "delivery")) {
|
|
998
|
+
r.keys(g, "delivery.groups", GROUP_KEYS);
|
|
999
|
+
const name = r.string(g, "name", "delivery.groups", true);
|
|
1000
|
+
const beforePlay = r.boolean(g, "before_play", "delivery.groups", false);
|
|
1001
|
+
const assets = r.strings(g, "assets", "delivery.groups", true);
|
|
1002
|
+
if (name === null || beforePlay === null) continue;
|
|
1003
|
+
if (!IDENTIFIER.test(name) || names.has(name)) {
|
|
1004
|
+
r.error(names.has(name) ? "manifest.duplicate" : "manifest.value", `asset group '${name}' must be a unique lowercase name`, at(g, "name"));
|
|
1005
|
+
continue;
|
|
1006
|
+
}
|
|
1007
|
+
if (beforePlay && streamed) {
|
|
1008
|
+
r.error("manifest.order", `asset group '${name}' must arrive before play, so it must come before every streamed group`, g.at);
|
|
1009
|
+
continue;
|
|
1010
|
+
}
|
|
1011
|
+
if (!beforePlay) streamed = true;
|
|
1012
|
+
for (const pattern of assets) {
|
|
1013
|
+
if (!pattern.startsWith("assets/") || pattern.split("/").indexOf("..") >= 0) {
|
|
1014
|
+
r.error("manifest.value", `asset pattern '${pattern}' in group '${name}' must start with assets/`, at(g, "assets"));
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
names.add(name);
|
|
1018
|
+
groups.push({ name, beforePlay, assets });
|
|
1019
|
+
}
|
|
1020
|
+
const tierGroups = r.boolean(t, "tier_groups", "delivery", false) ?? false;
|
|
1021
|
+
const parts = r.boolean(t, "parts", "delivery", false) ?? false;
|
|
1022
|
+
const progressive = r.boolean(t, "progressive", "delivery", false) ?? false;
|
|
1023
|
+
const weave = r.boolean(t, "weave", "delivery", false) ?? false;
|
|
1024
|
+
const ranged = r.boolean(t, "ranged", "delivery", false) ?? false;
|
|
1025
|
+
const repairPercent = r.integer(t, "repair_percent", "delivery", false, 1, MAX_REPAIR_PERCENT);
|
|
1026
|
+
if (repairPercent !== null && !ranged) {
|
|
1027
|
+
r.error("manifest.value", "delivery.repair_percent needs delivery.ranged = true, because repair files cover zone files", at(t, "repair_percent"));
|
|
1028
|
+
}
|
|
1029
|
+
if (tier === null || downloadBytes === null) return null;
|
|
1030
|
+
return {
|
|
1031
|
+
minDeviceTier: tier,
|
|
1032
|
+
downloadBytes,
|
|
1033
|
+
groups,
|
|
1034
|
+
...tierGroups ? { tierGroups } : {},
|
|
1035
|
+
...parts ? { parts } : {},
|
|
1036
|
+
...progressive ? { progressive } : {},
|
|
1037
|
+
...keepOnDevice ? { keepOnDevice } : {},
|
|
1038
|
+
...weave ? { weave } : {},
|
|
1039
|
+
...ranged ? { ranged } : {},
|
|
1040
|
+
...repairPercent !== null && ranged ? { repairPercent } : {}
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
function readSize(r, t, key, path, fallback) {
|
|
1044
|
+
const value = t.entries.get(key);
|
|
1045
|
+
if (value === void 0) return fallback;
|
|
1046
|
+
const bytes = value.kind === "integer" ? value.value : value.kind === "string" ? parseByteSize(value.value) : null;
|
|
1047
|
+
if (bytes === null || bytes < WASM_PAGE_BYTES || bytes > MAX_WASM_MEMORY || bytes % WASM_PAGE_BYTES !== 0) {
|
|
1048
|
+
r.error("manifest.value", `'${join(path, key)}' is a whole number of 64 KiB pages between 64 KiB and 2 GiB, like "32 MiB"`, value.at);
|
|
1049
|
+
return null;
|
|
1050
|
+
}
|
|
1051
|
+
return bytes;
|
|
1052
|
+
}
|
|
1053
|
+
function enginePath(r, t, key, path, value, prefix) {
|
|
1054
|
+
if (!ENGINE_PATH.test(value) || value.split("/").indexOf("..") >= 0 || !value.startsWith(prefix)) {
|
|
1055
|
+
r.error("manifest.value", `'${join(path, key)}' entry '${value}' must be a relative path under ${prefix}`, at(t, key));
|
|
1056
|
+
return false;
|
|
1057
|
+
}
|
|
1058
|
+
return true;
|
|
1059
|
+
}
|
|
1060
|
+
function readPacks(r, t, path, accept) {
|
|
1061
|
+
const packs = [];
|
|
1062
|
+
const names = /* @__PURE__ */ new Set();
|
|
1063
|
+
const packPath = join(path, "packs");
|
|
1064
|
+
for (const p of r.tables(t, "packs", path)) {
|
|
1065
|
+
r.keys(p, packPath, PACK_KEYS);
|
|
1066
|
+
const name = r.string(p, "name", packPath, true);
|
|
1067
|
+
const files = r.strings(p, "files", packPath, true);
|
|
1068
|
+
if (name === null) continue;
|
|
1069
|
+
if (!IDENTIFIER.test(name) || names.has(name)) {
|
|
1070
|
+
r.error(names.has(name) ? "manifest.duplicate" : "manifest.value", `pack '${name}' must be a unique lowercase name`, at(p, "name"));
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
1073
|
+
if (files.length === 0 || !files.every(accept)) {
|
|
1074
|
+
r.error("manifest.value", `pack '${name}' lists no files or a file outside the engine project`, at(p, "files"));
|
|
1075
|
+
continue;
|
|
1076
|
+
}
|
|
1077
|
+
names.add(name);
|
|
1078
|
+
packs.push({ name, files });
|
|
1079
|
+
}
|
|
1080
|
+
if (packs.length > MAX_ENGINE_PACKS) r.error("manifest.value", `an engine game streams at most ${MAX_ENGINE_PACKS} packs`, t.at);
|
|
1081
|
+
return packs;
|
|
1082
|
+
}
|
|
1083
|
+
function readWasmEngine(r, t) {
|
|
1084
|
+
const path = "engine";
|
|
1085
|
+
r.keys(t, path, WASM_ENGINE_KEYS);
|
|
1086
|
+
const language = r.oneOf(t, "language", path, WASM_LANGUAGES);
|
|
1087
|
+
const threads = r.oneOf(t, "threads", path, THREAD_MODES, false) ?? "single";
|
|
1088
|
+
const memoryBytes = readSize(r, t, "memory", path, DEFAULT_WASM_MEMORY);
|
|
1089
|
+
const maxMemoryBytes = readSize(r, t, "max_memory", path, DEFAULT_WASM_MAX_MEMORY);
|
|
1090
|
+
const includes = r.strings(t, "includes", path, false).filter((dir) => enginePath(r, t, "includes", path, dir, NATIVE_DIR));
|
|
1091
|
+
const packs = readPacks(r, t, path, (file) => ENGINE_PATH.test(file) && file.startsWith(NATIVE_DIR) && file.split("/").indexOf("..") < 0);
|
|
1092
|
+
if (language === null || memoryBytes === null || maxMemoryBytes === null) return null;
|
|
1093
|
+
if (maxMemoryBytes < memoryBytes) r.error("manifest.range", "'engine.max_memory' must be at least 'engine.memory'", at(t, "max_memory"));
|
|
1094
|
+
if (language === "rust") {
|
|
1095
|
+
const crate = r.string(t, "crate", path, true);
|
|
1096
|
+
if (t.entries.has("sources")) r.error("manifest.value", "a Rust game names its crate directory, not 'engine.sources'", at(t, "sources"));
|
|
1097
|
+
if (threads === "isolated") r.error("manifest.value", 'threaded Rust needs the standard library rebuilt with atomics, which the pinned stable toolchain does not do; use threads = "single"', at(t, "threads"));
|
|
1098
|
+
if (crate === null || !enginePath(r, t, "crate", path, crate, NATIVE_DIR)) return null;
|
|
1099
|
+
return { kind: "wasm", language, sources: [], includes, crate, threads, memoryBytes, maxMemoryBytes, packs };
|
|
1100
|
+
}
|
|
1101
|
+
if (t.entries.has("crate")) r.error("manifest.value", "'engine.crate' is for Rust games", at(t, "crate"));
|
|
1102
|
+
const pattern = language === "c" ? C_SOURCE : CPP_SOURCE;
|
|
1103
|
+
const sources = r.strings(t, "sources", path, true).filter((source) => enginePath(r, t, "sources", path, source, NATIVE_DIR));
|
|
1104
|
+
if (sources.length === 0) return null;
|
|
1105
|
+
const foreign = sources.find((source) => !pattern.test(source) && !C_SOURCE.test(source));
|
|
1106
|
+
if (foreign !== void 0) {
|
|
1107
|
+
r.error("manifest.value", `'${foreign}' is not a ${language === "c" ? "C" : "C or C++"} translation unit`, at(t, "sources"));
|
|
1108
|
+
return null;
|
|
1109
|
+
}
|
|
1110
|
+
return { kind: "wasm", language, sources, includes, crate: null, threads, memoryBytes, maxMemoryBytes, packs };
|
|
1111
|
+
}
|
|
1112
|
+
function readGodotEngine(r, t) {
|
|
1113
|
+
const path = "engine";
|
|
1114
|
+
r.keys(t, path, GODOT_ENGINE_KEYS);
|
|
1115
|
+
const project = r.string(t, "project", path, true);
|
|
1116
|
+
const threads = r.oneOf(t, "threads", path, THREAD_MODES, false) ?? "single";
|
|
1117
|
+
const runtime = r.oneOf(t, "runtime", path, GODOT_RUNTIME_KINDS, false) ?? "full";
|
|
1118
|
+
const packs = readPacks(r, t, path, (pattern) => GODOT_RESOURCE.test(pattern) && pattern.split("/").indexOf("..") < 0);
|
|
1119
|
+
if (runtime === "2d" && threads === "isolated") {
|
|
1120
|
+
r.error("manifest.value", `the 2D Godot runtime is single-threaded; use threads = "single", or runtime = "3d" or "full"`, at(t, "runtime"));
|
|
1121
|
+
return null;
|
|
1122
|
+
}
|
|
1123
|
+
if (project === null || !ENGINE_PATH.test(project) || project.split("/").indexOf("..") >= 0) {
|
|
1124
|
+
if (project !== null) r.error("manifest.value", `'engine.project' is '${project}'; name the directory that holds project.godot`, at(t, "project"));
|
|
1125
|
+
return null;
|
|
1126
|
+
}
|
|
1127
|
+
return { kind: "godot", project: project.replace(TRAILING_SLASHES, ""), threads, runtime, packs };
|
|
1128
|
+
}
|
|
1129
|
+
function readEngine(r, root, profile) {
|
|
1130
|
+
const game = profile?.game ?? null;
|
|
1131
|
+
if (!isEngineGame(game)) {
|
|
1132
|
+
if (root.entries.has("engine")) r.error("manifest.value", '[engine] is read only for profile.game = "wasm" or "godot"', root.entries.get("engine")?.at ?? root.at);
|
|
1133
|
+
return null;
|
|
1134
|
+
}
|
|
1135
|
+
const t = r.table(root, "engine", "", true);
|
|
1136
|
+
if (!t) return null;
|
|
1137
|
+
return game === "wasm" ? readWasmEngine(r, t) : readGodotEngine(r, t);
|
|
1138
|
+
}
|
|
1139
|
+
var AI_KEYS = ["payer", "languages", "per_player_daily_units", "per_session_units", "per_interactive_monthly_units", "characters", "prompts", "schemas", "actions", "styles"];
|
|
1140
|
+
var AI_CHARACTER_KEYS = ["name", "persona", "voice", "memory", "quality", "actions", "companion", "slots"];
|
|
1141
|
+
var AI_PROMPT_KEYS = ["name", "template", "output", "schema", "set", "quality", "cache", "player_text", "slots"];
|
|
1142
|
+
var AI_SLOT_KEYS = ["name", "kind", "max_chars", "min", "max", "values", "items"];
|
|
1143
|
+
var AI_SCHEMA_KEYS = ["name", "schema"];
|
|
1144
|
+
var AI_ACTION_SET_KEYS = ["set", "actions"];
|
|
1145
|
+
var AI_ACTION_KEYS = ["name", "description", "args"];
|
|
1146
|
+
var AI_STYLE_KEYS = ["name", "description", "reference"];
|
|
1147
|
+
var AI_PAYERS = ["creator", "player", "sponsor"];
|
|
1148
|
+
var AI_MEMORY = ["none", "session", "persistent"];
|
|
1149
|
+
var AI_QUALITY = ["fast", "standard", "best"];
|
|
1150
|
+
var AI_CACHE = ["none", "personal", "interactive"];
|
|
1151
|
+
var AI_OUTPUTS = ["text", "structured", "actions"];
|
|
1152
|
+
var AI_SLOT_KINDS = ["text", "integer", "number", "choice"];
|
|
1153
|
+
var AI_ARG_KINDS = ["integer", "number", "text", "choice", "item"];
|
|
1154
|
+
var AI_NAME = /^[a-z][a-z0-9_]{0,47}$/;
|
|
1155
|
+
var AI_TEXT_MAX = 8e3;
|
|
1156
|
+
var AI_SLOT_TEXT_MAX = 200;
|
|
1157
|
+
var AI_UNITS_MAX = 1e9;
|
|
1158
|
+
var AI_DEFAULT_CAPS = [
|
|
1159
|
+
["per_player_daily_units", 5e3],
|
|
1160
|
+
["per_session_units", 3e3],
|
|
1161
|
+
["per_interactive_monthly_units", 1e5]
|
|
1162
|
+
];
|
|
1163
|
+
function tomlJson(value) {
|
|
1164
|
+
if (value.kind === "array") return value.items.map(tomlJson);
|
|
1165
|
+
if (value.kind === "table") {
|
|
1166
|
+
const out = {};
|
|
1167
|
+
for (const [key, item] of value.entries) out[key] = tomlJson(item);
|
|
1168
|
+
return out;
|
|
1169
|
+
}
|
|
1170
|
+
return value.value;
|
|
1171
|
+
}
|
|
1172
|
+
function aiName(r, table, key, path, names) {
|
|
1173
|
+
const name = r.string(table, key, path, true, 48);
|
|
1174
|
+
if (name === null) return null;
|
|
1175
|
+
if (!AI_NAME.test(name)) {
|
|
1176
|
+
r.error("manifest.value", `'${join(path, key)}' must be lowercase letters, digits and underscores, starting with a letter`, at(table, key));
|
|
1177
|
+
return null;
|
|
1178
|
+
}
|
|
1179
|
+
if (names !== null) {
|
|
1180
|
+
if (names.has(name)) r.error("manifest.duplicate", `${path} '${name}' is declared twice`, at(table, key));
|
|
1181
|
+
names.add(name);
|
|
1182
|
+
}
|
|
1183
|
+
return name;
|
|
1184
|
+
}
|
|
1185
|
+
function readAiSlots(r, parent, path, kinds) {
|
|
1186
|
+
const out = [];
|
|
1187
|
+
const names = /* @__PURE__ */ new Set();
|
|
1188
|
+
for (const s of r.tables(parent, path === "args" ? "args" : "slots", path)) {
|
|
1189
|
+
r.keys(s, path, AI_SLOT_KEYS);
|
|
1190
|
+
const name = aiName(r, s, "name", path, names);
|
|
1191
|
+
const kind = r.oneOf(s, "kind", path, kinds);
|
|
1192
|
+
if (name === null || kind === null) continue;
|
|
1193
|
+
if (kind === "text") {
|
|
1194
|
+
const max = r.integer(s, "max_chars", path, true, 1, AI_SLOT_TEXT_MAX);
|
|
1195
|
+
if (max !== null) out.push({ name, kind, max_chars: max });
|
|
1196
|
+
} else if (kind === "integer" || kind === "number") {
|
|
1197
|
+
const min = r.integer(s, "min", path, true, -Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER);
|
|
1198
|
+
const max = r.integer(s, "max", path, true, -Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER);
|
|
1199
|
+
if (min !== null && max !== null) {
|
|
1200
|
+
if (min > max) r.error("manifest.range", `${path} '${name}' has min above max`, at(s, "min"));
|
|
1201
|
+
else out.push({ name, kind, min, max });
|
|
1202
|
+
}
|
|
1203
|
+
} else {
|
|
1204
|
+
const key = kind === "item" ? "items" : "values";
|
|
1205
|
+
const values = r.strings(s, key, path, true);
|
|
1206
|
+
if (values.length === 0) r.error("manifest.missing", `${path} '${name}' needs at least one of ${key}`, s.at);
|
|
1207
|
+
else out.push({ name, kind, [key]: values });
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
return out;
|
|
1211
|
+
}
|
|
1212
|
+
var WORLD_SIMULATIONS = ["build", "frontier", "genesis"];
|
|
1213
|
+
var WORLD_KEYS = ["simulation"];
|
|
1214
|
+
var KERNELS_KEYS = ["lower", "exact_math", "no_lower"];
|
|
1215
|
+
var NO_LOWER_ENTRY = /^[^#\s]+(#[A-Za-z_$][A-Za-z0-9_$]*)?$/;
|
|
1216
|
+
function readKernels(r, root) {
|
|
1217
|
+
const t = r.table(root, "kernels", "", false);
|
|
1218
|
+
if (!t) return null;
|
|
1219
|
+
r.keys(t, "kernels", KERNELS_KEYS);
|
|
1220
|
+
const lower = r.boolean(t, "lower", "kernels", DEFAULT_KERNELS.lower) ?? DEFAULT_KERNELS.lower;
|
|
1221
|
+
const exactMath = r.boolean(t, "exact_math", "kernels", DEFAULT_KERNELS.exactMath) ?? DEFAULT_KERNELS.exactMath;
|
|
1222
|
+
const noLower = t.entries.has("no_lower") ? r.strings(t, "no_lower", "kernels", false) : [];
|
|
1223
|
+
for (const entry of noLower) {
|
|
1224
|
+
if (!NO_LOWER_ENTRY.test(entry)) r.error("manifest.kernels", `[kernels] no_lower entry '${entry}' must be a source path such as src/sim.ts, or a path and function such as src/sim.ts#integrate`, at(t, "no_lower"));
|
|
1225
|
+
}
|
|
1226
|
+
return { lower, exactMath, noLower };
|
|
1227
|
+
}
|
|
1228
|
+
function readWorld(r, root) {
|
|
1229
|
+
const t = r.table(root, "world", "", false);
|
|
1230
|
+
if (!t) return null;
|
|
1231
|
+
r.keys(t, "world", WORLD_KEYS);
|
|
1232
|
+
const simulation = r.oneOf(t, "simulation", "world", WORLD_SIMULATIONS);
|
|
1233
|
+
return simulation === null ? null : { simulation };
|
|
1234
|
+
}
|
|
1235
|
+
function readAi(r, root) {
|
|
1236
|
+
const t = r.table(root, "ai", "", false);
|
|
1237
|
+
if (!t) return null;
|
|
1238
|
+
r.keys(t, "ai", AI_KEYS);
|
|
1239
|
+
const payer = r.oneOf(t, "payer", "ai", AI_PAYERS);
|
|
1240
|
+
const languages = r.strings(t, "languages", "ai", true);
|
|
1241
|
+
const ai = { payer, languages };
|
|
1242
|
+
for (const [key, fallback] of AI_DEFAULT_CAPS) {
|
|
1243
|
+
ai[key] = r.integer(t, key, "ai", false, 1, AI_UNITS_MAX) ?? fallback;
|
|
1244
|
+
}
|
|
1245
|
+
if (ai.per_session_units > ai.per_player_daily_units) {
|
|
1246
|
+
r.error("manifest.range", "[ai] per_session_units cannot exceed per_player_daily_units", t.at);
|
|
1247
|
+
}
|
|
1248
|
+
const characters = [];
|
|
1249
|
+
const characterNames = /* @__PURE__ */ new Set();
|
|
1250
|
+
for (const c of r.tables(t, "characters", "ai")) {
|
|
1251
|
+
r.keys(c, "ai.characters", AI_CHARACTER_KEYS);
|
|
1252
|
+
const name = aiName(r, c, "name", "ai.characters", characterNames);
|
|
1253
|
+
const persona = r.string(c, "persona", "ai.characters", true, AI_TEXT_MAX);
|
|
1254
|
+
const memory = r.oneOf(c, "memory", "ai.characters", AI_MEMORY);
|
|
1255
|
+
const quality = r.oneOf(c, "quality", "ai.characters", AI_QUALITY);
|
|
1256
|
+
const voice = r.string(c, "voice", "ai.characters", false, 48);
|
|
1257
|
+
const actions = r.string(c, "actions", "ai.characters", false, 48);
|
|
1258
|
+
const companion = r.boolean(c, "companion", "ai.characters", false) ?? false;
|
|
1259
|
+
const slots = readAiSlots(r, c, "ai.characters.slots", AI_SLOT_KINDS);
|
|
1260
|
+
if (name === null || persona === null || memory === null || quality === null) continue;
|
|
1261
|
+
characters.push({ name, persona, voice, memory, quality, actions, companion, slots });
|
|
1262
|
+
}
|
|
1263
|
+
const prompts = [];
|
|
1264
|
+
const promptNames = /* @__PURE__ */ new Set();
|
|
1265
|
+
for (const p of r.tables(t, "prompts", "ai")) {
|
|
1266
|
+
r.keys(p, "ai.prompts", AI_PROMPT_KEYS);
|
|
1267
|
+
const name = aiName(r, p, "name", "ai.prompts", promptNames);
|
|
1268
|
+
const template = r.string(p, "template", "ai.prompts", true, AI_TEXT_MAX);
|
|
1269
|
+
const kind = r.oneOf(p, "output", "ai.prompts", AI_OUTPUTS);
|
|
1270
|
+
const quality = r.oneOf(p, "quality", "ai.prompts", AI_QUALITY);
|
|
1271
|
+
const cache = r.oneOf(p, "cache", "ai.prompts", AI_CACHE);
|
|
1272
|
+
const playerText = r.boolean(p, "player_text", "ai.prompts", false) ?? false;
|
|
1273
|
+
const slots = readAiSlots(r, p, "ai.prompts.slots", AI_SLOT_KINDS);
|
|
1274
|
+
let output = null;
|
|
1275
|
+
if (kind === "text") output = { kind };
|
|
1276
|
+
else if (kind === "structured") output = { kind, schema: r.string(p, "schema", "ai.prompts", true, 48) };
|
|
1277
|
+
else if (kind === "actions") output = { kind, set: r.string(p, "set", "ai.prompts", true, 48) };
|
|
1278
|
+
if (cache === "interactive" && playerText) {
|
|
1279
|
+
r.error("manifest.value", `prompt '${name ?? "?"}' is shared across players (cache = "interactive") so it cannot take player_text`, at(p, "cache"));
|
|
1280
|
+
continue;
|
|
1281
|
+
}
|
|
1282
|
+
if (name === null || template === null || output === null || quality === null || cache === null) continue;
|
|
1283
|
+
prompts.push({ name, template, output, quality, cache, player_text: playerText, slots });
|
|
1284
|
+
}
|
|
1285
|
+
const schemas = [];
|
|
1286
|
+
const schemaNames = /* @__PURE__ */ new Set();
|
|
1287
|
+
for (const s of r.tables(t, "schemas", "ai")) {
|
|
1288
|
+
r.keys(s, "ai.schemas", AI_SCHEMA_KEYS);
|
|
1289
|
+
const name = aiName(r, s, "name", "ai.schemas", schemaNames);
|
|
1290
|
+
const schema = r.table(s, "schema", "ai.schemas", true);
|
|
1291
|
+
if (name !== null && schema !== null) schemas.push({ name, schema: tomlJson(schema) });
|
|
1292
|
+
}
|
|
1293
|
+
const actionSets = [];
|
|
1294
|
+
const setNames = /* @__PURE__ */ new Set();
|
|
1295
|
+
for (const a of r.tables(t, "actions", "ai")) {
|
|
1296
|
+
r.keys(a, "ai.actions", AI_ACTION_SET_KEYS);
|
|
1297
|
+
const set = aiName(r, a, "set", "ai.actions", setNames);
|
|
1298
|
+
const actions = [];
|
|
1299
|
+
const actionNames = /* @__PURE__ */ new Set();
|
|
1300
|
+
for (const action of r.tables(a, "actions", "ai.actions")) {
|
|
1301
|
+
r.keys(action, "ai.actions.actions", AI_ACTION_KEYS);
|
|
1302
|
+
const name = aiName(r, action, "name", "ai.actions.actions", actionNames);
|
|
1303
|
+
const description = r.string(action, "description", "ai.actions.actions", false, 200) ?? "";
|
|
1304
|
+
const args = readAiSlots(r, action, "args", AI_ARG_KINDS);
|
|
1305
|
+
if (name !== null) actions.push({ name, description, args });
|
|
1306
|
+
}
|
|
1307
|
+
if (set !== null) actionSets.push({ set, actions });
|
|
1308
|
+
}
|
|
1309
|
+
const styles = [];
|
|
1310
|
+
const styleNames = /* @__PURE__ */ new Set();
|
|
1311
|
+
for (const s of r.tables(t, "styles", "ai")) {
|
|
1312
|
+
r.keys(s, "ai.styles", AI_STYLE_KEYS);
|
|
1313
|
+
const name = aiName(r, s, "name", "ai.styles", styleNames);
|
|
1314
|
+
const description = r.string(s, "description", "ai.styles", true, 400);
|
|
1315
|
+
const reference = r.string(s, "reference", "ai.styles", false, 200);
|
|
1316
|
+
if (name !== null && description !== null) styles.push({ name, description, reference });
|
|
1317
|
+
}
|
|
1318
|
+
if (characters.length === 0 && prompts.length === 0 && styles.length === 0) {
|
|
1319
|
+
r.error("manifest.missing", "[ai] declares no characters, prompts or styles", t.at);
|
|
1320
|
+
}
|
|
1321
|
+
ai.characters = characters;
|
|
1322
|
+
ai.prompts = prompts;
|
|
1323
|
+
ai.schemas = schemas;
|
|
1324
|
+
ai.actions = actionSets;
|
|
1325
|
+
ai.styles = styles;
|
|
1326
|
+
return ai;
|
|
1327
|
+
}
|
|
1328
|
+
function readContent(r, root, groups) {
|
|
1329
|
+
const out = [];
|
|
1330
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1331
|
+
for (const t of r.tables(root, "content", "")) {
|
|
1332
|
+
r.keys(t, "content", CONTENT_KEYS);
|
|
1333
|
+
const item = r.string(t, "item", "content", true);
|
|
1334
|
+
const version = r.string(t, "version", "content", false) ?? "*";
|
|
1335
|
+
const group = r.string(t, "group", "content", false);
|
|
1336
|
+
if (item === null) continue;
|
|
1337
|
+
if (!CONTENT_ITEM.test(item)) {
|
|
1338
|
+
r.error("manifest.value", `'content.item' is '${item}'; write a Workshop reference like cimplify/danfo-bus`, at(t, "item"));
|
|
1339
|
+
continue;
|
|
1340
|
+
}
|
|
1341
|
+
if (!CONTENT_RANGE.test(version)) {
|
|
1342
|
+
r.error("manifest.value", `'content.version' is '${version}'; write a version range like "^2.1", "~1.0", "=1.2.3" or "*"`, at(t, "version"));
|
|
1343
|
+
continue;
|
|
1344
|
+
}
|
|
1345
|
+
if (group !== null && groups.indexOf(group) < 0) {
|
|
1346
|
+
r.error("manifest.value", `'content.group' names '${group}', which is not one of the [[delivery.groups]]`, at(t, "group"));
|
|
1347
|
+
continue;
|
|
1348
|
+
}
|
|
1349
|
+
if (seen.has(item)) {
|
|
1350
|
+
r.error("manifest.duplicate", `content '${item}' is listed twice`, t.at);
|
|
1351
|
+
continue;
|
|
1352
|
+
}
|
|
1353
|
+
seen.add(item);
|
|
1354
|
+
out.push({ item, version, group });
|
|
1355
|
+
}
|
|
1356
|
+
return out;
|
|
1357
|
+
}
|
|
1358
|
+
function readCredits(r, root) {
|
|
1359
|
+
const out = [];
|
|
1360
|
+
for (const t of r.tables(root, "credits", "")) {
|
|
1361
|
+
r.keys(t, "credits", CREDIT_KEYS);
|
|
1362
|
+
const title = r.string(t, "title", "credits", true, MAX_CREDIT_TEXT);
|
|
1363
|
+
const licence = r.string(t, "licence", "credits", true, MAX_CREDIT_TEXT);
|
|
1364
|
+
const authors = r.strings(t, "authors", "credits", true);
|
|
1365
|
+
const licenceUrl = r.string(t, "licence_url", "credits", false, MAX_CREDIT_TEXT);
|
|
1366
|
+
const sourceUrl = r.string(t, "source_url", "credits", false, MAX_CREDIT_TEXT);
|
|
1367
|
+
const modified = r.boolean(t, "modified", "credits", false);
|
|
1368
|
+
if (title === null || licence === null || modified === null) continue;
|
|
1369
|
+
if (authors.length === 0 || authors.length > MAX_CREDIT_AUTHORS || authors.some((a) => a.trim().length === 0 || a.length > MAX_CREDIT_TEXT)) {
|
|
1370
|
+
r.error("manifest.value", `'credits.authors' needs 1 to ${MAX_CREDIT_AUTHORS} names`, at(t, "authors"));
|
|
1371
|
+
continue;
|
|
1372
|
+
}
|
|
1373
|
+
out.push({ title, authors, licence, licenceUrl, sourceUrl, modified });
|
|
1374
|
+
}
|
|
1375
|
+
return out;
|
|
1376
|
+
}
|
|
1377
|
+
function parseManifest(source, file = MANIFEST_FILE) {
|
|
1378
|
+
let root;
|
|
1379
|
+
try {
|
|
1380
|
+
root = parseToml(source);
|
|
1381
|
+
} catch (err) {
|
|
1382
|
+
if (err instanceof TomlSyntaxError) {
|
|
1383
|
+
return { manifest: null, diagnostics: [diagnostic("manifest.syntax", err.reason, file, err)] };
|
|
1384
|
+
}
|
|
1385
|
+
throw err;
|
|
1386
|
+
}
|
|
1387
|
+
const r = new Reader(file);
|
|
1388
|
+
r.keys(root, "", TOP_LEVEL);
|
|
1389
|
+
const identity = readIdentity(r, root);
|
|
1390
|
+
const profile = readProfile(r, root);
|
|
1391
|
+
const entries = readEntries(r, root, profile);
|
|
1392
|
+
const capabilities = readCapabilities(r, root);
|
|
1393
|
+
const providers = readProviders(r, root);
|
|
1394
|
+
const economy = readEconomy(r, root, identity === null ? null : { category: identity.category, capabilities: capabilities.map((c) => c.name) });
|
|
1395
|
+
const data = readData(r, root);
|
|
1396
|
+
const ai = readAi(r, root);
|
|
1397
|
+
const rating = readRating(r, root);
|
|
1398
|
+
const delivery = readDelivery(r, root);
|
|
1399
|
+
const engine = readEngine(r, root, profile);
|
|
1400
|
+
const content = readContent(r, root, delivery?.groups.map((g) => g.name) ?? []);
|
|
1401
|
+
const credits = readCredits(r, root);
|
|
1402
|
+
const world = readWorld(r, root);
|
|
1403
|
+
const kernels = readKernels(r, root);
|
|
1404
|
+
if (world !== null && !capabilities.some((c) => c.name === "world")) {
|
|
1405
|
+
r.error("manifest.capability", "[world] runs the world's simulation on the tick service, so [capabilities] must request world", root.entries.get("world")?.at ?? root.at);
|
|
1406
|
+
}
|
|
1407
|
+
if ((economy.offers.length > 0 || economy.currencies.some((c) => c.class === "premium")) && !capabilities.some((c) => c.name === "wallet")) {
|
|
1408
|
+
r.error("manifest.capability", "premium currencies and Cimples offers are bought through ctx.wallet, so [capabilities] must request wallet", root.entries.get("economy")?.at ?? root.at);
|
|
1409
|
+
}
|
|
1410
|
+
if (data !== null && !capabilities.some((c) => c.name === "db")) {
|
|
1411
|
+
r.error("manifest.capability", "[data] is read and written through ctx.db, so [capabilities] must request db", root.entries.get("data")?.at ?? root.at);
|
|
1412
|
+
}
|
|
1413
|
+
const requestsAi = capabilities.some((c) => c.name === "ai");
|
|
1414
|
+
if (ai !== null && !requestsAi) {
|
|
1415
|
+
r.error("manifest.capability", "[ai] is called through ctx.ai, so [capabilities] must request ai", root.entries.get("ai")?.at ?? root.at);
|
|
1416
|
+
}
|
|
1417
|
+
if (ai === null && requestsAi) {
|
|
1418
|
+
r.error("manifest.missing", "[capabilities] requests ai, so declare its characters, prompts or styles in [ai]", root.at);
|
|
1419
|
+
}
|
|
1420
|
+
if (providers.length > 0 && !capabilities.some((c) => c.name === "provider")) {
|
|
1421
|
+
r.error("manifest.capability", "[[providers]] are called through ctx.provider, so [capabilities] must request provider", root.entries.get("providers")?.at ?? root.at);
|
|
1422
|
+
}
|
|
1423
|
+
if (r.diagnostics.length > 0 || !identity || !profile || !entries || !rating || !delivery || isEngineGame(profile.game) && engine === null) {
|
|
1424
|
+
return { manifest: null, diagnostics: r.diagnostics };
|
|
1425
|
+
}
|
|
1426
|
+
return {
|
|
1427
|
+
manifest: {
|
|
1428
|
+
identity,
|
|
1429
|
+
profile,
|
|
1430
|
+
entries,
|
|
1431
|
+
capabilities,
|
|
1432
|
+
providers,
|
|
1433
|
+
economy,
|
|
1434
|
+
data,
|
|
1435
|
+
ai,
|
|
1436
|
+
rating,
|
|
1437
|
+
delivery,
|
|
1438
|
+
engine,
|
|
1439
|
+
...content.length === 0 ? {} : { content },
|
|
1440
|
+
...credits.length === 0 ? {} : { credits },
|
|
1441
|
+
...world === null ? {} : { world },
|
|
1442
|
+
...kernels === null ? {} : { kernels }
|
|
1443
|
+
},
|
|
1444
|
+
diagnostics: []
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
function assignInteractiveId(source, id) {
|
|
1448
|
+
if (!INTERACTIVE_ID.test(id)) throw new Error(`invalid interactive id ${id}`);
|
|
1449
|
+
const lines = source.split("\n");
|
|
1450
|
+
const header = lines.findIndex((l) => l.trim() === "[interactive]");
|
|
1451
|
+
if (header < 0) throw new Error("interactive.toml has no [interactive] table");
|
|
1452
|
+
lines.splice(header + 1, 0, `id = "${id}"`);
|
|
1453
|
+
return lines.join("\n");
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
export { DEFAULT_KERNELS, ENGINE_ENTRY, LOCKFILE, MANIFEST_FILE, assignInteractiveId, checkLockfile, currentLockfile, isEngineGame, isItemReference, lockedMissingOperations, lockedVersion, parseLockfile, parseManifest, serializeLockfile, withContent, withCurrentContract, withLibrary };
|