@s2script/sdk 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/clients.d.ts +36 -0
- package/contracts/README.md +37 -0
- package/contracts/workshop.d.ts +82 -0
- package/dist/cli.js +629 -91
- package/package.json +13 -3
- package/server.d.ts +22 -0
- package/timers.d.ts +39 -0
- package/unsafe.d.ts +35 -0
- package/voice.d.ts +59 -0
package/dist/cli.js
CHANGED
|
@@ -1074,8 +1074,8 @@ function unzipSync(data, opts) {
|
|
|
1074
1074
|
}
|
|
1075
1075
|
|
|
1076
1076
|
// src/build.ts
|
|
1077
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync } from "node:fs";
|
|
1078
|
-
import { resolve as resolve5, join as join6 } from "node:path";
|
|
1077
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync, rmSync as rmSync2 } from "node:fs";
|
|
1078
|
+
import { resolve as resolve5, join as join6, sep, basename } from "node:path";
|
|
1079
1079
|
|
|
1080
1080
|
// src/typecheck/typecheck.ts
|
|
1081
1081
|
import ts from "typescript";
|
|
@@ -1168,6 +1168,10 @@ function localDeclarationFiles(pluginDir) {
|
|
|
1168
1168
|
if (!existsSync3(srcDir)) return [];
|
|
1169
1169
|
return readdirSync(srcDir).filter((f) => f.endsWith(".d.ts")).map((f) => join3(srcDir, f));
|
|
1170
1170
|
}
|
|
1171
|
+
function generatedDeclarationFiles(pluginDir) {
|
|
1172
|
+
const gd = join3(pluginDir, ".s2script", "gamedata.d.ts");
|
|
1173
|
+
return existsSync3(gd) ? [gd] : [];
|
|
1174
|
+
}
|
|
1171
1175
|
function declaredModules(dtsFiles) {
|
|
1172
1176
|
const out = /* @__PURE__ */ new Set();
|
|
1173
1177
|
for (const f of dtsFiles) {
|
|
@@ -1213,11 +1217,20 @@ function typecheckPlugin(pluginDir, opts) {
|
|
|
1213
1217
|
// dirs are deleted). tsc picks the longest matching prefix, so `@s2script/sdk/*` wins for
|
|
1214
1218
|
// sdk imports.
|
|
1215
1219
|
"@s2script/sdk/*": ["sdk/*.d.ts"],
|
|
1220
|
+
// Game-package SUBPATHS (e.g. @s2script/cs2/econ -> packages/cs2/econ.d.ts). Needed because
|
|
1221
|
+
// the `@s2script/*` fallback below maps to `<pkg>/index.d.ts`, which cannot express a
|
|
1222
|
+
// subpath. tsc picks the longest matching prefix, so this wins for cs2 subpath imports.
|
|
1223
|
+
"@s2script/cs2/*": ["cs2/*.d.ts"],
|
|
1216
1224
|
"@s2script/*": ["*/index.d.ts"],
|
|
1217
1225
|
...contractPaths
|
|
1218
1226
|
}
|
|
1219
1227
|
};
|
|
1220
|
-
const rootNames = [
|
|
1228
|
+
const rootNames = [
|
|
1229
|
+
entry,
|
|
1230
|
+
join3(packagesDir, "sdk", "globals.d.ts"),
|
|
1231
|
+
...localDts,
|
|
1232
|
+
...generatedDeclarationFiles(absDir)
|
|
1233
|
+
];
|
|
1221
1234
|
const tmp = mkdtempSync(join3(tmpdir(), "s2tc-"));
|
|
1222
1235
|
try {
|
|
1223
1236
|
if (deps.length) {
|
|
@@ -1521,6 +1534,253 @@ async function lintPlugin(pluginDir, program) {
|
|
|
1521
1534
|
return { ok: errorCount === 0, output, errorCount };
|
|
1522
1535
|
}
|
|
1523
1536
|
|
|
1537
|
+
// src/gamedata/types.ts
|
|
1538
|
+
var PLATFORM = "linuxsteamrt64";
|
|
1539
|
+
var ARG_KINDS = ["bool", "int", "float", "string", "vector", "entity"];
|
|
1540
|
+
var RET_KINDS = ["void", "bool", "int", "float", "entity"];
|
|
1541
|
+
var INT_CLASS_ARGS = /* @__PURE__ */ new Set(["bool", "int", "string", "vector", "entity"]);
|
|
1542
|
+
var MAX_INT_ARGS = 5;
|
|
1543
|
+
var MAX_FLOAT_ARGS = 8;
|
|
1544
|
+
|
|
1545
|
+
// src/gamedata/validate.ts
|
|
1546
|
+
var ALLOWED_SECTIONS = /* @__PURE__ */ new Set(["signatures", "calls"]);
|
|
1547
|
+
var IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
1548
|
+
var RESERVED_CALL_NAMES = /* @__PURE__ */ new Set(["constructor", "prototype", "__proto__"]);
|
|
1549
|
+
var RESOLVE_KINDS = ["direct", "ctor-body-xref", "lea-disp"];
|
|
1550
|
+
var KNOWN_PERMISSIONS = ["engine:calls"];
|
|
1551
|
+
function validatePluginGamedata(gd, opts) {
|
|
1552
|
+
const errs = [];
|
|
1553
|
+
if (gd == null || typeof gd !== "object" || Array.isArray(gd)) return ["gamedata must be an object"];
|
|
1554
|
+
const g = gd;
|
|
1555
|
+
for (const key of Object.keys(g)) {
|
|
1556
|
+
if (!ALLOWED_SECTIONS.has(key)) {
|
|
1557
|
+
errs.push(`gamedata section '${key}' is not supported in v1 (allowed: signatures, calls)`);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
const sigs = g.signatures ?? {};
|
|
1561
|
+
if (typeof sigs !== "object" || Array.isArray(sigs)) errs.push("signatures must be an object");
|
|
1562
|
+
for (const [name, platforms] of Object.entries(sigs)) {
|
|
1563
|
+
const p = platforms?.[PLATFORM];
|
|
1564
|
+
if (!p) {
|
|
1565
|
+
errs.push(`signature '${name}': missing platform '${PLATFORM}'`);
|
|
1566
|
+
continue;
|
|
1567
|
+
}
|
|
1568
|
+
for (const f of ["module", "pattern", "resolve"]) {
|
|
1569
|
+
if (typeof p[f] !== "string" || !p[f].length) {
|
|
1570
|
+
errs.push(`signature '${name}': '${f}' must be a non-empty string`);
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
if (typeof p.resolve === "string" && p.resolve.length && !RESOLVE_KINDS.includes(p.resolve)) {
|
|
1574
|
+
errs.push(
|
|
1575
|
+
`signature '${name}': unknown resolve step ${JSON.stringify(p.resolve)} (allowed: ${RESOLVE_KINDS.join(", ")})`
|
|
1576
|
+
);
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
for (const perm of opts.permissions) {
|
|
1580
|
+
if (!KNOWN_PERMISSIONS.includes(perm)) {
|
|
1581
|
+
errs.push(`unknown permission ${JSON.stringify(perm)} (allowed: ${KNOWN_PERMISSIONS.join(", ")})`);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
const calls = g.calls ?? {};
|
|
1585
|
+
const callNames = Object.keys(calls);
|
|
1586
|
+
if (callNames.length && !opts.permissions.includes("engine:calls")) {
|
|
1587
|
+
errs.push('gamedata declares a `calls` section but the manifest does not declare permission "engine:calls"');
|
|
1588
|
+
}
|
|
1589
|
+
for (const [name, rawDecl] of Object.entries(calls)) {
|
|
1590
|
+
const where = `call '${name}'`;
|
|
1591
|
+
if (!IDENTIFIER.test(name)) {
|
|
1592
|
+
errs.push(
|
|
1593
|
+
`${where}: call name must be a plain identifier matching ${IDENTIFIER.source} \u2014 it is emitted verbatim as a TypeScript interface member in the generated .s2script/gamedata.d.ts`
|
|
1594
|
+
);
|
|
1595
|
+
continue;
|
|
1596
|
+
}
|
|
1597
|
+
if (RESERVED_CALL_NAMES.has(name)) {
|
|
1598
|
+
errs.push(`${where}: call name '${name}' is reserved`);
|
|
1599
|
+
continue;
|
|
1600
|
+
}
|
|
1601
|
+
if (rawDecl == null || typeof rawDecl !== "object") {
|
|
1602
|
+
errs.push(`${where}: must be an object`);
|
|
1603
|
+
continue;
|
|
1604
|
+
}
|
|
1605
|
+
const decl = rawDecl;
|
|
1606
|
+
const recv = decl.receiver;
|
|
1607
|
+
if (!recv || recv.kind !== "entity") {
|
|
1608
|
+
errs.push(`${where}: receiver.kind must be "entity" in v1`);
|
|
1609
|
+
} else if (recv.via !== void 0) {
|
|
1610
|
+
const via = recv.via;
|
|
1611
|
+
if (typeof via?.class !== "string" || typeof via?.field !== "string") {
|
|
1612
|
+
errs.push(`${where}: receiver.via requires both 'class' and 'field' strings`);
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
const target = decl.target;
|
|
1616
|
+
if (!target) {
|
|
1617
|
+
errs.push(`${where}: missing 'target'`);
|
|
1618
|
+
} else if (target.kind === "signature") {
|
|
1619
|
+
if (typeof target.name !== "string") errs.push(`${where}: target.name must be a string`);
|
|
1620
|
+
else if (!(target.name in sigs)) errs.push(`${where}: target.name '${target.name}' has no entry in 'signatures'`);
|
|
1621
|
+
} else if (target.kind === "vtable") {
|
|
1622
|
+
if (typeof target.class !== "string") errs.push(`${where}: vtable target requires 'class'`);
|
|
1623
|
+
const plat = target[PLATFORM];
|
|
1624
|
+
if (!plat) {
|
|
1625
|
+
errs.push(`${where}: vtable target missing platform '${PLATFORM}'`);
|
|
1626
|
+
} else {
|
|
1627
|
+
if (!Number.isInteger(plat.index) || plat.index < 0) {
|
|
1628
|
+
errs.push(`${where}: vtable index must be a non-negative integer`);
|
|
1629
|
+
}
|
|
1630
|
+
const prologue = plat.validate?.prologue;
|
|
1631
|
+
if (typeof prologue !== "string" || !prologue.length) {
|
|
1632
|
+
errs.push(`${where}: a vtable target REQUIRES validate.prologue (a bare borrowed index is never trusted)`);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
} else {
|
|
1636
|
+
errs.push(`${where}: target.kind must be "signature" or "vtable"`);
|
|
1637
|
+
}
|
|
1638
|
+
const args = decl.args;
|
|
1639
|
+
if (!Array.isArray(args)) {
|
|
1640
|
+
errs.push(`${where}: 'args' must be an array`);
|
|
1641
|
+
} else {
|
|
1642
|
+
let ints = 0, floats = 0;
|
|
1643
|
+
for (const a2 of args) {
|
|
1644
|
+
if (typeof a2 !== "string" || !ARG_KINDS.includes(a2)) {
|
|
1645
|
+
errs.push(`${where}: unknown arg kind ${JSON.stringify(a2)} (allowed: ${ARG_KINDS.join(", ")})`);
|
|
1646
|
+
continue;
|
|
1647
|
+
}
|
|
1648
|
+
if (INT_CLASS_ARGS.has(a2)) ints++;
|
|
1649
|
+
else floats++;
|
|
1650
|
+
}
|
|
1651
|
+
if (ints > MAX_INT_ARGS) {
|
|
1652
|
+
errs.push(`${where}: ${ints} integer-class args exceeds the max of ${MAX_INT_ARGS} (\`this\` occupies rdi)`);
|
|
1653
|
+
}
|
|
1654
|
+
if (floats > MAX_FLOAT_ARGS) {
|
|
1655
|
+
errs.push(`${where}: ${floats} float args exceeds the max of ${MAX_FLOAT_ARGS}`);
|
|
1656
|
+
}
|
|
1657
|
+
if (decl.argNames !== void 0) {
|
|
1658
|
+
const names = decl.argNames;
|
|
1659
|
+
if (!Array.isArray(names)) {
|
|
1660
|
+
errs.push(`${where}: 'argNames' must be an array of parameter names`);
|
|
1661
|
+
} else if (names.length !== args.length) {
|
|
1662
|
+
errs.push(
|
|
1663
|
+
`${where}: 'argNames' has ${names.length} entr${names.length === 1 ? "y" : "ies"} but 'args' has ${args.length} \u2014 they are positionally matched`
|
|
1664
|
+
);
|
|
1665
|
+
} else {
|
|
1666
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1667
|
+
for (const nm of names) {
|
|
1668
|
+
if (typeof nm !== "string" || !IDENTIFIER.test(nm)) {
|
|
1669
|
+
errs.push(
|
|
1670
|
+
`${where}: argName ${JSON.stringify(nm)} must be a plain identifier matching ${IDENTIFIER.source} \u2014 it is emitted as a parameter name in the generated .d.ts`
|
|
1671
|
+
);
|
|
1672
|
+
continue;
|
|
1673
|
+
}
|
|
1674
|
+
if (nm === "self") {
|
|
1675
|
+
errs.push(`${where}: argName 'self' is reserved (it names the receiver parameter)`);
|
|
1676
|
+
continue;
|
|
1677
|
+
}
|
|
1678
|
+
if (seen.has(nm)) errs.push(`${where}: duplicate argName ${JSON.stringify(nm)}`);
|
|
1679
|
+
seen.add(nm);
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
if (typeof decl.returns !== "string" || !RET_KINDS.includes(decl.returns)) {
|
|
1685
|
+
errs.push(`${where}: 'returns' must be one of ${RET_KINDS.join(", ")}`);
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
return errs;
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
// src/gamedata/gen-types.ts
|
|
1692
|
+
var VECTOR_TS = "{ readonly x: number; readonly y: number; readonly z: number }";
|
|
1693
|
+
var ARG_TS = {
|
|
1694
|
+
bool: "boolean",
|
|
1695
|
+
int: "number",
|
|
1696
|
+
float: "number",
|
|
1697
|
+
string: "string",
|
|
1698
|
+
vector: VECTOR_TS,
|
|
1699
|
+
entity: "EntityRef | null"
|
|
1700
|
+
};
|
|
1701
|
+
var RET_TS = {
|
|
1702
|
+
void: "void",
|
|
1703
|
+
bool: "boolean | null",
|
|
1704
|
+
int: "number | null",
|
|
1705
|
+
float: "number | null",
|
|
1706
|
+
entity: "EntityRef | null"
|
|
1707
|
+
};
|
|
1708
|
+
function generateGamedataTypes(gd) {
|
|
1709
|
+
const calls = gd.calls ?? {};
|
|
1710
|
+
const lines = Object.keys(calls).sort().map((name) => {
|
|
1711
|
+
const decl = calls[name];
|
|
1712
|
+
const names = decl.argNames;
|
|
1713
|
+
const params = [
|
|
1714
|
+
"self: EntityRef",
|
|
1715
|
+
...decl.args.map((a2, i2) => `${names?.[i2] ?? `a${i2}`}: ${ARG_TS[a2]}`)
|
|
1716
|
+
];
|
|
1717
|
+
return ` ${name}: (${params.join(", ")}) => ${RET_TS[decl.returns]};`;
|
|
1718
|
+
});
|
|
1719
|
+
const body = lines.length ? `
|
|
1720
|
+
${lines.join("\n")}
|
|
1721
|
+
` : "\n ";
|
|
1722
|
+
return [
|
|
1723
|
+
"// GENERATED by `s2s build` from the plugin's gamedata \u2014 DO NOT EDIT.",
|
|
1724
|
+
"// The EntityRef import is REQUIRED: a `declare module` augmentation resolves type names in its",
|
|
1725
|
+
"// own file scope, so an un-imported EntityRef degrades to an error skipLibCheck can swallow.",
|
|
1726
|
+
'import type { EntityRef } from "@s2script/sdk/entity";',
|
|
1727
|
+
"",
|
|
1728
|
+
'declare module "@s2script/sdk/unsafe" {',
|
|
1729
|
+
` interface EngineCalls {${body}}`,
|
|
1730
|
+
"}",
|
|
1731
|
+
""
|
|
1732
|
+
].join("\n");
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
// src/gamedata/jsonc.ts
|
|
1736
|
+
function stripJsonComments(src) {
|
|
1737
|
+
let out = "";
|
|
1738
|
+
let i2 = 0;
|
|
1739
|
+
const n3 = src.length;
|
|
1740
|
+
while (i2 < n3) {
|
|
1741
|
+
const c2 = src[i2];
|
|
1742
|
+
if (c2 === '"') {
|
|
1743
|
+
out += c2;
|
|
1744
|
+
i2++;
|
|
1745
|
+
while (i2 < n3) {
|
|
1746
|
+
const s = src[i2];
|
|
1747
|
+
if (s === "\\") {
|
|
1748
|
+
out += src.slice(i2, i2 + 2);
|
|
1749
|
+
i2 += 2;
|
|
1750
|
+
continue;
|
|
1751
|
+
}
|
|
1752
|
+
out += s;
|
|
1753
|
+
i2++;
|
|
1754
|
+
if (s === '"') break;
|
|
1755
|
+
}
|
|
1756
|
+
continue;
|
|
1757
|
+
}
|
|
1758
|
+
if (c2 === "/" && src[i2 + 1] === "/") {
|
|
1759
|
+
while (i2 < n3 && src[i2] !== "\n") {
|
|
1760
|
+
out += " ";
|
|
1761
|
+
i2++;
|
|
1762
|
+
}
|
|
1763
|
+
continue;
|
|
1764
|
+
}
|
|
1765
|
+
if (c2 === "/" && src[i2 + 1] === "*") {
|
|
1766
|
+
out += " ";
|
|
1767
|
+
i2 += 2;
|
|
1768
|
+
while (i2 < n3 && !(src[i2] === "*" && src[i2 + 1] === "/")) {
|
|
1769
|
+
out += src[i2] === "\n" ? "\n" : " ";
|
|
1770
|
+
i2++;
|
|
1771
|
+
}
|
|
1772
|
+
if (i2 < n3) {
|
|
1773
|
+
out += " ";
|
|
1774
|
+
i2 += 2;
|
|
1775
|
+
}
|
|
1776
|
+
continue;
|
|
1777
|
+
}
|
|
1778
|
+
out += c2;
|
|
1779
|
+
i2++;
|
|
1780
|
+
}
|
|
1781
|
+
return out;
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1524
1784
|
// src/build.ts
|
|
1525
1785
|
async function buildPlugin(dir, packagesDir) {
|
|
1526
1786
|
const absDir = resolve5(dir);
|
|
@@ -1533,6 +1793,39 @@ async function buildPlugin(dir, packagesDir) {
|
|
|
1533
1793
|
if (cfgErrs.length) throw new Error(`invalid s2script.config:
|
|
1534
1794
|
${cfgErrs.join("\n ")}`);
|
|
1535
1795
|
}
|
|
1796
|
+
const permissions = Array.isArray(s2.permissions) ? s2.permissions : [];
|
|
1797
|
+
let gamedata;
|
|
1798
|
+
if (typeof s2.gamedata === "string") {
|
|
1799
|
+
const gdPath = resolve5(absDir, s2.gamedata);
|
|
1800
|
+
const dirPrefix = absDir.endsWith(sep) ? absDir : absDir + sep;
|
|
1801
|
+
if (gdPath !== absDir && !gdPath.startsWith(dirPrefix)) {
|
|
1802
|
+
throw new Error(
|
|
1803
|
+
`s2script.gamedata escapes the plugin directory: ${JSON.stringify(s2.gamedata)} resolves to ${gdPath}`
|
|
1804
|
+
);
|
|
1805
|
+
}
|
|
1806
|
+
const unscoped = pkg.name.includes("/") ? pkg.name.slice(pkg.name.lastIndexOf("/") + 1) : pkg.name;
|
|
1807
|
+
const actualBase = basename(gdPath);
|
|
1808
|
+
const allowedBases = [`${unscoped}.gamedata.jsonc`, `${unscoped}.gamedata.json`];
|
|
1809
|
+
if (!allowedBases.includes(actualBase)) {
|
|
1810
|
+
throw new Error(
|
|
1811
|
+
`s2script.gamedata must be named '${unscoped}.gamedata.jsonc' (after the plugin name without its scope, like the framework's own core.gamedata.jsonc), but is '${actualBase}'`
|
|
1812
|
+
);
|
|
1813
|
+
}
|
|
1814
|
+
const raw = stripJsonComments(readFileSync4(gdPath, "utf8"));
|
|
1815
|
+
try {
|
|
1816
|
+
gamedata = JSON.parse(raw);
|
|
1817
|
+
} catch (e) {
|
|
1818
|
+
throw new Error(`invalid gamedata ${gdPath}: ${e.message}`);
|
|
1819
|
+
}
|
|
1820
|
+
const gdErrs = validatePluginGamedata(gamedata, { permissions });
|
|
1821
|
+
if (gdErrs.length) throw new Error(`invalid gamedata:
|
|
1822
|
+
${gdErrs.join("\n ")}`);
|
|
1823
|
+
const genDir = join6(absDir, ".s2script");
|
|
1824
|
+
mkdirSync(genDir, { recursive: true });
|
|
1825
|
+
writeFileSync2(join6(genDir, "gamedata.d.ts"), generateGamedataTypes(gamedata));
|
|
1826
|
+
} else {
|
|
1827
|
+
rmSync2(join6(absDir, ".s2script", "gamedata.d.ts"), { force: true });
|
|
1828
|
+
}
|
|
1536
1829
|
const tc = typecheckPlugin(absDir, packagesDir !== void 0 ? { packagesDir } : void 0);
|
|
1537
1830
|
if (!tc.ok) {
|
|
1538
1831
|
throw new Error(`typecheck failed (${tc.diagnostics.length} error(s)):
|
|
@@ -1642,6 +1935,7 @@ ${lint.output}`);
|
|
|
1642
1935
|
manifest.publishes = derivedPublishes;
|
|
1643
1936
|
}
|
|
1644
1937
|
if (config !== void 0) manifest.config = config;
|
|
1938
|
+
if (permissions.length > 0) manifest.permissions = permissions;
|
|
1645
1939
|
const compiledAgainst = {};
|
|
1646
1940
|
for (const dep of [
|
|
1647
1941
|
...Object.keys(pluginDependencies),
|
|
@@ -1655,6 +1949,7 @@ ${lint.output}`);
|
|
|
1655
1949
|
"manifest.json": Buffer.from(JSON.stringify(manifest, null, 2)),
|
|
1656
1950
|
"plugin.js": Buffer.from(pluginJs)
|
|
1657
1951
|
};
|
|
1952
|
+
if (gamedata) zipFiles["gamedata.json"] = Buffer.from(JSON.stringify(gamedata, null, 2));
|
|
1658
1953
|
if (gate.typesPath !== null && Object.keys(derivedPublishes).length > 0) {
|
|
1659
1954
|
const contract = readFileSync4(gate.typesPath);
|
|
1660
1955
|
for (const iface of Object.keys(derivedPublishes)) {
|
|
@@ -3134,7 +3429,9 @@ var TIMEOUTS_MS = {
|
|
|
3134
3429
|
// up to ~40 MiB on a slow uplink
|
|
3135
3430
|
resolve: 3e4,
|
|
3136
3431
|
meta: 3e4,
|
|
3137
|
-
|
|
3432
|
+
plan: 3e4,
|
|
3433
|
+
"types download": 12e4,
|
|
3434
|
+
"s2sp download": 12e4
|
|
3138
3435
|
};
|
|
3139
3436
|
function rootCause(err2) {
|
|
3140
3437
|
let cur = err2;
|
|
@@ -3261,6 +3558,20 @@ var RegistryClient = class {
|
|
|
3261
3558
|
const res = await this.request("meta", u4);
|
|
3262
3559
|
return this.json(res, "meta");
|
|
3263
3560
|
}
|
|
3561
|
+
async plan(name, range = "*") {
|
|
3562
|
+
const u4 = new URL(`${this.baseUrl}/api/v1/plan`);
|
|
3563
|
+
u4.searchParams.set("name", name);
|
|
3564
|
+
u4.searchParams.set("range", range);
|
|
3565
|
+
const res = await this.request("plan", u4);
|
|
3566
|
+
return this.json(res, "plan");
|
|
3567
|
+
}
|
|
3568
|
+
async downloadS2sp(name, version) {
|
|
3569
|
+
const u4 = new URL(`${this.baseUrl}/api/v1/download/s2sp`);
|
|
3570
|
+
u4.searchParams.set("name", name);
|
|
3571
|
+
u4.searchParams.set("version", version);
|
|
3572
|
+
const res = await this.request("s2sp download", u4);
|
|
3573
|
+
return Buffer.from(await res.arrayBuffer());
|
|
3574
|
+
}
|
|
3264
3575
|
};
|
|
3265
3576
|
|
|
3266
3577
|
// src/registry/deploy.ts
|
|
@@ -3493,30 +3804,217 @@ async function run3(argv2) {
|
|
|
3493
3804
|
}
|
|
3494
3805
|
}
|
|
3495
3806
|
|
|
3807
|
+
// src/commands/install.ts
|
|
3808
|
+
import { resolve as resolve11 } from "node:path";
|
|
3809
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
3810
|
+
|
|
3811
|
+
// src/registry/install.ts
|
|
3812
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
3813
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync6, renameSync, existsSync as existsSync9 } from "node:fs";
|
|
3814
|
+
import { basename as basename2, join as join10, resolve as resolve10, sep as sep2 } from "node:path";
|
|
3815
|
+
function parseManifest(input) {
|
|
3816
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
3817
|
+
throw new Error("manifest must be a JSON object");
|
|
3818
|
+
}
|
|
3819
|
+
const plugins = input.plugins;
|
|
3820
|
+
if (!plugins || typeof plugins !== "object" || Array.isArray(plugins)) {
|
|
3821
|
+
throw new Error('manifest must have a "plugins" object (name -> version range)');
|
|
3822
|
+
}
|
|
3823
|
+
const out = {};
|
|
3824
|
+
for (const [name, range] of Object.entries(plugins)) {
|
|
3825
|
+
if (typeof range !== "string") throw new Error(`range for ${name} must be a string`);
|
|
3826
|
+
out[name] = range;
|
|
3827
|
+
}
|
|
3828
|
+
return { plugins: out };
|
|
3829
|
+
}
|
|
3830
|
+
function parseSpec2(spec) {
|
|
3831
|
+
const at = spec.lastIndexOf("@");
|
|
3832
|
+
if (at > 0) return { name: spec.slice(0, at), range: spec.slice(at + 1) || "*" };
|
|
3833
|
+
return { name: spec, range: "*" };
|
|
3834
|
+
}
|
|
3835
|
+
function mergeSpecs(manifest, args) {
|
|
3836
|
+
const merged = { ...manifest.plugins };
|
|
3837
|
+
for (const a2 of args) {
|
|
3838
|
+
const { name, range } = parseSpec2(a2);
|
|
3839
|
+
merged[name] = range;
|
|
3840
|
+
}
|
|
3841
|
+
return merged;
|
|
3842
|
+
}
|
|
3843
|
+
function sha256Hex(bytes) {
|
|
3844
|
+
return createHash2("sha256").update(bytes).digest("hex");
|
|
3845
|
+
}
|
|
3846
|
+
var SAFE_FILENAME_RE = /^[A-Za-z0-9._-]+$/;
|
|
3847
|
+
function safeFilename(e) {
|
|
3848
|
+
const raw = e.filename;
|
|
3849
|
+
const base = basename2(raw);
|
|
3850
|
+
if (!base || base === "." || base === ".." || base !== raw || base.includes("/") || base.includes("\\") || base.includes("..") || !SAFE_FILENAME_RE.test(base)) {
|
|
3851
|
+
throw new Error(`refusing ${e.name}@${e.version}: unsafe filename "${raw}"`);
|
|
3852
|
+
}
|
|
3853
|
+
return base;
|
|
3854
|
+
}
|
|
3855
|
+
async function resolveMerged(client, specs) {
|
|
3856
|
+
const chosen = /* @__PURE__ */ new Map();
|
|
3857
|
+
const warnings = [];
|
|
3858
|
+
const errors = [];
|
|
3859
|
+
for (const [name, range] of Object.entries(specs)) {
|
|
3860
|
+
const plan = await client.plan(name, range);
|
|
3861
|
+
if (plan.errors.length) {
|
|
3862
|
+
errors.push(...plan.errors);
|
|
3863
|
+
continue;
|
|
3864
|
+
}
|
|
3865
|
+
warnings.push(...plan.warnings);
|
|
3866
|
+
for (const entry of plan.install) {
|
|
3867
|
+
const prior = chosen.get(entry.name);
|
|
3868
|
+
if (prior && prior.version !== entry.version) {
|
|
3869
|
+
errors.push(
|
|
3870
|
+
`version conflict for ${entry.name}: ${prior.version} vs ${entry.version} across requested plugins`
|
|
3871
|
+
);
|
|
3872
|
+
continue;
|
|
3873
|
+
}
|
|
3874
|
+
chosen.set(entry.name, entry);
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3877
|
+
return {
|
|
3878
|
+
install: [...chosen.values()].sort((a2, b) => a2.name.localeCompare(b.name)),
|
|
3879
|
+
warnings: [...new Set(warnings)],
|
|
3880
|
+
errors: [...new Set(errors)]
|
|
3881
|
+
};
|
|
3882
|
+
}
|
|
3883
|
+
async function installPlan(opts) {
|
|
3884
|
+
const log3 = opts.log ?? (() => {
|
|
3885
|
+
});
|
|
3886
|
+
const { install, warnings, errors } = await resolveMerged(opts.client, opts.specs);
|
|
3887
|
+
if (errors.length) throw new Error(`cannot resolve plugins:
|
|
3888
|
+
- ${errors.join("\n - ")}`);
|
|
3889
|
+
if (opts.reviewedOnly) {
|
|
3890
|
+
const unreviewed = install.filter((e) => e.reviewState !== "reviewed");
|
|
3891
|
+
if (unreviewed.length) {
|
|
3892
|
+
throw new Error(
|
|
3893
|
+
`--reviewed-only: refusing unreviewed plugins: ${unreviewed.map((e) => `${e.name}@${e.version}`).join(", ")}`
|
|
3894
|
+
);
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
3897
|
+
const dirResolved = resolve10(opts.dir);
|
|
3898
|
+
const targets = install.map((e) => {
|
|
3899
|
+
if (!e.sha256) {
|
|
3900
|
+
throw new Error(`refusing ${e.name}@${e.version}: registry did not provide a sha256 to verify against`);
|
|
3901
|
+
}
|
|
3902
|
+
const filename = safeFilename(e);
|
|
3903
|
+
const dest = resolve10(join10(opts.dir, filename));
|
|
3904
|
+
if (dest !== dirResolved && !dest.startsWith(dirResolved + sep2)) {
|
|
3905
|
+
throw new Error(`refusing ${e.name}@${e.version}: destination "${dest}" escapes plugins dir "${dirResolved}"`);
|
|
3906
|
+
}
|
|
3907
|
+
return { entry: e, filename, dest };
|
|
3908
|
+
});
|
|
3909
|
+
for (const w of warnings) log3(`warning: ${w}`);
|
|
3910
|
+
if (opts.dryRun) {
|
|
3911
|
+
for (const e of install) log3(`would install ${e.name}@${e.version} -> ${e.filename}`);
|
|
3912
|
+
return { written: [], skipped: [], warnings };
|
|
3913
|
+
}
|
|
3914
|
+
mkdirSync4(opts.dir, { recursive: true });
|
|
3915
|
+
const written = [];
|
|
3916
|
+
const skipped = [];
|
|
3917
|
+
for (const { entry: e, filename, dest } of targets) {
|
|
3918
|
+
if (existsSync9(dest)) {
|
|
3919
|
+
const have = sha256Hex(readFileSync10(dest));
|
|
3920
|
+
if (have === e.sha256) {
|
|
3921
|
+
skipped.push(filename);
|
|
3922
|
+
log3(`up to date ${filename}`);
|
|
3923
|
+
continue;
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
const bytes = await opts.client.downloadS2sp(e.name, e.version);
|
|
3927
|
+
const got = sha256Hex(bytes);
|
|
3928
|
+
if (got !== e.sha256) {
|
|
3929
|
+
throw new Error(`sha256 mismatch for ${e.name}@${e.version}: expected ${e.sha256}, got ${got}`);
|
|
3930
|
+
}
|
|
3931
|
+
const tmp = `${dest}.${process.pid}.tmp`;
|
|
3932
|
+
writeFileSync6(tmp, bytes);
|
|
3933
|
+
renameSync(tmp, dest);
|
|
3934
|
+
written.push(filename);
|
|
3935
|
+
log3(`installed ${e.name}@${e.version} -> ${filename}`);
|
|
3936
|
+
}
|
|
3937
|
+
return { written, skipped, warnings };
|
|
3938
|
+
}
|
|
3939
|
+
function loadManifestFile(path) {
|
|
3940
|
+
if (!existsSync9(path)) return { plugins: {} };
|
|
3941
|
+
return parseManifest(JSON.parse(readFileSync10(path, "utf8")));
|
|
3942
|
+
}
|
|
3943
|
+
|
|
3944
|
+
// src/commands/install.ts
|
|
3945
|
+
var AUTODETECT = "addons/s2script/plugins";
|
|
3946
|
+
async function run4(argv2) {
|
|
3947
|
+
const args = positionals(argv2, ["--dir", "--file", "--registry"]);
|
|
3948
|
+
const file = parseFlag(argv2, "--file") ?? "s2script-plugins.json";
|
|
3949
|
+
const registry = parseFlag(argv2, "--registry") || loadCredentials()?.registryUrl || defaultRegistryUrl();
|
|
3950
|
+
const dryRun = hasFlag(argv2, "--dry-run");
|
|
3951
|
+
const reviewedOnly = hasFlag(argv2, "--reviewed-only");
|
|
3952
|
+
let dir = parseFlag(argv2, "--dir");
|
|
3953
|
+
if (!dir) {
|
|
3954
|
+
if (existsSync10(resolve11(AUTODETECT))) dir = resolve11(AUTODETECT);
|
|
3955
|
+
else {
|
|
3956
|
+
console.error(
|
|
3957
|
+
`Usage: s2s install [name[@range]...] --dir <plugins dir>
|
|
3958
|
+
No --dir given and ./${AUTODETECT} not found. Point --dir at your addons/s2script/plugins directory.`
|
|
3959
|
+
);
|
|
3960
|
+
process.exit(1);
|
|
3961
|
+
}
|
|
3962
|
+
}
|
|
3963
|
+
const client = new RegistryClient({ baseUrl: registry });
|
|
3964
|
+
try {
|
|
3965
|
+
const manifest = loadManifestFile(resolve11(file));
|
|
3966
|
+
const specs = mergeSpecs(manifest, args);
|
|
3967
|
+
if (Object.keys(specs).length === 0) {
|
|
3968
|
+
console.error(
|
|
3969
|
+
`Nothing to install. Add plugins to ${file} or pass names: s2s install rtv@^1.0.0`
|
|
3970
|
+
);
|
|
3971
|
+
process.exit(1);
|
|
3972
|
+
}
|
|
3973
|
+
const res = await installPlan({
|
|
3974
|
+
client,
|
|
3975
|
+
specs,
|
|
3976
|
+
dir,
|
|
3977
|
+
dryRun,
|
|
3978
|
+
reviewedOnly,
|
|
3979
|
+
log: (m) => console.log(m)
|
|
3980
|
+
});
|
|
3981
|
+
if (dryRun) {
|
|
3982
|
+
console.log("dry run \u2014 nothing written");
|
|
3983
|
+
} else {
|
|
3984
|
+
console.log(
|
|
3985
|
+
`done: ${res.written.length} installed, ${res.skipped.length} up to date, in ${dir}`
|
|
3986
|
+
);
|
|
3987
|
+
}
|
|
3988
|
+
} catch (e) {
|
|
3989
|
+
console.error(String(e instanceof Error ? e.message : e));
|
|
3990
|
+
process.exit(1);
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
|
|
3496
3994
|
// src/create/create.ts
|
|
3497
3995
|
import {
|
|
3498
|
-
mkdirSync as
|
|
3499
|
-
writeFileSync as
|
|
3500
|
-
existsSync as
|
|
3996
|
+
mkdirSync as mkdirSync5,
|
|
3997
|
+
writeFileSync as writeFileSync7,
|
|
3998
|
+
existsSync as existsSync11,
|
|
3501
3999
|
readdirSync as readdirSync2,
|
|
3502
|
-
readFileSync as
|
|
4000
|
+
readFileSync as readFileSync11,
|
|
3503
4001
|
symlinkSync
|
|
3504
4002
|
} from "node:fs";
|
|
3505
|
-
import { dirname as dirname2, join as
|
|
4003
|
+
import { dirname as dirname2, join as join11, resolve as resolve12, basename as basename3 } from "node:path";
|
|
3506
4004
|
import { spawnSync } from "node:child_process";
|
|
3507
4005
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3508
4006
|
function readCliVersion() {
|
|
3509
4007
|
const here = dirname2(fileURLToPath2(import.meta.url));
|
|
3510
4008
|
const candidates = [
|
|
3511
|
-
|
|
4009
|
+
join11(here, "..", "package.json"),
|
|
3512
4010
|
// dist/cli.js → packages/sdk/package.json
|
|
3513
|
-
|
|
4011
|
+
join11(here, "..", "..", "package.json")
|
|
3514
4012
|
// src/create → packages/sdk/package.json
|
|
3515
4013
|
];
|
|
3516
4014
|
for (const p of candidates) {
|
|
3517
|
-
if (!
|
|
4015
|
+
if (!existsSync11(p)) continue;
|
|
3518
4016
|
try {
|
|
3519
|
-
const pkg = JSON.parse(
|
|
4017
|
+
const pkg = JSON.parse(readFileSync11(p, "utf8"));
|
|
3520
4018
|
if (pkg.name === "@s2script/sdk" && pkg.version) return pkg.version;
|
|
3521
4019
|
} catch {
|
|
3522
4020
|
}
|
|
@@ -3526,18 +4024,18 @@ function readCliVersion() {
|
|
|
3526
4024
|
function findLocalPackagesDir() {
|
|
3527
4025
|
const here = dirname2(fileURLToPath2(import.meta.url));
|
|
3528
4026
|
const candidates = [
|
|
3529
|
-
|
|
4027
|
+
join11(here, "..", ".."),
|
|
3530
4028
|
// dist/cli.js → packages
|
|
3531
|
-
|
|
4029
|
+
join11(here, "..", "..", "..")
|
|
3532
4030
|
// src/create → packages
|
|
3533
4031
|
];
|
|
3534
4032
|
for (const c2 of candidates) {
|
|
3535
|
-
if (isPackagesDir(c2)) return
|
|
4033
|
+
if (isPackagesDir(c2)) return resolve12(c2);
|
|
3536
4034
|
}
|
|
3537
4035
|
return void 0;
|
|
3538
4036
|
}
|
|
3539
4037
|
function defaultNameFromPath(dir) {
|
|
3540
|
-
const base =
|
|
4038
|
+
const base = basename3(resolve12(dir));
|
|
3541
4039
|
const slug = base.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase() || "plugin";
|
|
3542
4040
|
return `@plugin/${slug}`;
|
|
3543
4041
|
}
|
|
@@ -3565,18 +4063,18 @@ function versionSpecFrom(status, stdout2) {
|
|
|
3565
4063
|
const v = (stdout2 ?? "").trim();
|
|
3566
4064
|
return status === 0 && /^\d+\.\d+\.\d+/.test(v) ? `^${v}` : "latest";
|
|
3567
4065
|
}
|
|
3568
|
-
function registryDevDeps(game, sdkVersion,
|
|
4066
|
+
function registryDevDeps(game, sdkVersion, resolve13 = resolvePublishedVersion) {
|
|
3569
4067
|
const deps = {};
|
|
3570
4068
|
for (const n3 of createPackageNames(game)) {
|
|
3571
|
-
deps[`@s2script/${n3}`] = n3 === "sdk" ? `^${sdkVersion}` :
|
|
4069
|
+
deps[`@s2script/${n3}`] = n3 === "sdk" ? `^${sdkVersion}` : resolve13(`@s2script/${n3}`);
|
|
3572
4070
|
}
|
|
3573
4071
|
return deps;
|
|
3574
4072
|
}
|
|
3575
4073
|
function fileDevDeps(packagesDir, game) {
|
|
3576
4074
|
const deps = {};
|
|
3577
4075
|
for (const n3 of createPackageNames(game)) {
|
|
3578
|
-
const abs =
|
|
3579
|
-
if (!
|
|
4076
|
+
const abs = join11(packagesDir, n3);
|
|
4077
|
+
if (!existsSync11(join11(abs, "package.json"))) return void 0;
|
|
3580
4078
|
deps[`@s2script/${n3}`] = `file:${abs}`;
|
|
3581
4079
|
}
|
|
3582
4080
|
return deps;
|
|
@@ -3621,7 +4119,12 @@ function tsconfigJson() {
|
|
|
3621
4119
|
return JSON.stringify(
|
|
3622
4120
|
{
|
|
3623
4121
|
compilerOptions: sharedCompilerOptionsJson,
|
|
3624
|
-
|
|
4122
|
+
// `.s2script/gamedata.d.ts` is generated by `s2s build` from a plugin's own gamedata and
|
|
4123
|
+
// augments EngineCalls. It must be in `include` or the EDITOR sees `keyof EngineCalls` as
|
|
4124
|
+
// `never` — every Engine.call() shows as an error while `s2s build` passes, because the build
|
|
4125
|
+
// adds the file to its own program (typecheck.ts) but tsconfig never did. Harmless when the
|
|
4126
|
+
// plugin ships no gamedata: the path simply matches nothing.
|
|
4127
|
+
include: ["src", ".s2script/gamedata.d.ts", "node_modules/@s2script/sdk/globals.d.ts"]
|
|
3625
4128
|
},
|
|
3626
4129
|
null,
|
|
3627
4130
|
2
|
|
@@ -3656,10 +4159,10 @@ dist/
|
|
|
3656
4159
|
`;
|
|
3657
4160
|
}
|
|
3658
4161
|
function linkLocalPackagesForNoInstall(targetPath, localPackagesDir) {
|
|
3659
|
-
const nm =
|
|
3660
|
-
|
|
3661
|
-
const dest =
|
|
3662
|
-
if (!
|
|
4162
|
+
const nm = join11(targetPath, "node_modules");
|
|
4163
|
+
mkdirSync5(nm, { recursive: true });
|
|
4164
|
+
const dest = join11(nm, "@s2script");
|
|
4165
|
+
if (!existsSync11(dest)) symlinkSync(localPackagesDir, dest);
|
|
3663
4166
|
}
|
|
3664
4167
|
function runInstall(dir, pm) {
|
|
3665
4168
|
if (pm === "none") return;
|
|
@@ -3672,7 +4175,7 @@ function runInstall(dir, pm) {
|
|
|
3672
4175
|
if (r2.status !== 0) throw new Error(`${pm} install failed (exit ${r2.status})`);
|
|
3673
4176
|
}
|
|
3674
4177
|
async function createPlugin(opts = {}) {
|
|
3675
|
-
const targetPath =
|
|
4178
|
+
const targetPath = resolve12(opts.path ?? ".");
|
|
3676
4179
|
const interactive = isInteractive({ yes: opts.yes });
|
|
3677
4180
|
let game = assertGame(opts.game);
|
|
3678
4181
|
let name = opts.name;
|
|
@@ -3681,7 +4184,7 @@ async function createPlugin(opts = {}) {
|
|
|
3681
4184
|
if (template !== "minimal") {
|
|
3682
4185
|
throw new Error(`unknown template ${JSON.stringify(template)} (v1 supports: minimal)`);
|
|
3683
4186
|
}
|
|
3684
|
-
if (
|
|
4187
|
+
if (existsSync11(targetPath)) {
|
|
3685
4188
|
const kids = readdirSync2(targetPath);
|
|
3686
4189
|
const meaningful = kids.filter((k) => k !== ".git");
|
|
3687
4190
|
if (meaningful.length) {
|
|
@@ -3734,15 +4237,15 @@ async function createPlugin(opts = {}) {
|
|
|
3734
4237
|
install = install ?? (opts.noInstall ? "none" : "npm");
|
|
3735
4238
|
const version = readCliVersion();
|
|
3736
4239
|
const localPackagesDir = findLocalPackagesDir();
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
4240
|
+
mkdirSync5(join11(targetPath, "src"), { recursive: true });
|
|
4241
|
+
writeFileSync7(
|
|
4242
|
+
join11(targetPath, "package.json"),
|
|
3740
4243
|
packageJsonContent(name, game, version, localPackagesDir)
|
|
3741
4244
|
);
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
4245
|
+
writeFileSync7(join11(targetPath, "tsconfig.json"), tsconfigJson());
|
|
4246
|
+
writeFileSync7(join11(targetPath, "eslint.config.mjs"), eslintConfig());
|
|
4247
|
+
writeFileSync7(join11(targetPath, "src", "plugin.ts"), pluginSource(game));
|
|
4248
|
+
writeFileSync7(join11(targetPath, ".gitignore"), gitignore());
|
|
3746
4249
|
let installed = false;
|
|
3747
4250
|
if (install !== "none") {
|
|
3748
4251
|
const pm = install;
|
|
@@ -3765,7 +4268,7 @@ async function createPlugin(opts = {}) {
|
|
|
3765
4268
|
}
|
|
3766
4269
|
|
|
3767
4270
|
// src/commands/create.ts
|
|
3768
|
-
async function
|
|
4271
|
+
async function run5(argv2) {
|
|
3769
4272
|
const yes = hasFlag(argv2, "--yes") || hasFlag(argv2, "-y");
|
|
3770
4273
|
const interactive = isInteractive({ yes });
|
|
3771
4274
|
const pathArg = argv2.find((a2) => !a2.startsWith("-"));
|
|
@@ -3955,7 +4458,7 @@ async function loginInteractive(opts) {
|
|
|
3955
4458
|
}
|
|
3956
4459
|
|
|
3957
4460
|
// src/commands/login.ts
|
|
3958
|
-
async function
|
|
4461
|
+
async function run6(argv2) {
|
|
3959
4462
|
const interactive = isInteractive({ ci: hasFlag(argv2, "--ci") });
|
|
3960
4463
|
try {
|
|
3961
4464
|
const creds = await loginInteractive({
|
|
@@ -3974,8 +4477,8 @@ async function run5(argv2) {
|
|
|
3974
4477
|
}
|
|
3975
4478
|
|
|
3976
4479
|
// src/config/gen.ts
|
|
3977
|
-
import { readFileSync as
|
|
3978
|
-
import { join as
|
|
4480
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6 } from "node:fs";
|
|
4481
|
+
import { join as join12 } from "node:path";
|
|
3979
4482
|
|
|
3980
4483
|
// src/config/config-path.ts
|
|
3981
4484
|
function configFileName(id) {
|
|
@@ -4021,13 +4524,13 @@ function genConfigFromManifest(manifest, outDir) {
|
|
|
4021
4524
|
}
|
|
4022
4525
|
const id = typeof manifest.id === "string" ? manifest.id : "";
|
|
4023
4526
|
if (!id) throw new Error("config gen: manifest declares config but has no id");
|
|
4024
|
-
|
|
4025
|
-
const path =
|
|
4026
|
-
|
|
4527
|
+
mkdirSync6(outDir, { recursive: true });
|
|
4528
|
+
const path = join12(outDir, configFileName(id));
|
|
4529
|
+
writeFileSync8(path, generateDefaultJsonc(config));
|
|
4027
4530
|
return path;
|
|
4028
4531
|
}
|
|
4029
4532
|
function genConfigForS2sp(s2spPath, outDir) {
|
|
4030
|
-
const entry = unzipSync(
|
|
4533
|
+
const entry = unzipSync(readFileSync12(s2spPath))["manifest.json"];
|
|
4031
4534
|
if (!entry) throw new Error(`config gen: ${s2spPath} has no manifest.json`);
|
|
4032
4535
|
const manifest = JSON.parse(Buffer.from(entry).toString("utf8"));
|
|
4033
4536
|
return genConfigFromManifest(manifest, outDir);
|
|
@@ -4044,7 +4547,7 @@ function runConfigGen(s2sps, outDir) {
|
|
|
4044
4547
|
}
|
|
4045
4548
|
|
|
4046
4549
|
// src/commands/config.ts
|
|
4047
|
-
async function
|
|
4550
|
+
async function run7(argv2) {
|
|
4048
4551
|
const rest = argv2[0] === "gen" ? argv2.slice(1) : argv2;
|
|
4049
4552
|
const interactive = isInteractive();
|
|
4050
4553
|
const outDir = parseFlag(rest, "--out") ?? process.cwd();
|
|
@@ -4074,12 +4577,12 @@ async function run6(argv2) {
|
|
|
4074
4577
|
}
|
|
4075
4578
|
|
|
4076
4579
|
// src/commands/codegen.ts
|
|
4077
|
-
import { dirname as dirname3, join as
|
|
4580
|
+
import { dirname as dirname3, join as join16 } from "node:path";
|
|
4078
4581
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
4079
4582
|
|
|
4080
4583
|
// src/schemagen/gen.ts
|
|
4081
|
-
import { readFileSync as
|
|
4082
|
-
import { join as
|
|
4584
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "node:fs";
|
|
4585
|
+
import { join as join13 } from "node:path";
|
|
4083
4586
|
|
|
4084
4587
|
// src/schemagen/model.ts
|
|
4085
4588
|
var READ = {
|
|
@@ -4271,21 +4774,21 @@ function emitJs(model) {
|
|
|
4271
4774
|
for (const c2 of model.classes) {
|
|
4272
4775
|
out.push(` A[${S(c2.className)}] = {`);
|
|
4273
4776
|
for (const f of flattenedFields(model, c2.className)) {
|
|
4274
|
-
const
|
|
4777
|
+
const resolve13 = `off(${S(f.declaringClass)},${S(f.rawName)})`;
|
|
4275
4778
|
let entry;
|
|
4276
4779
|
if (f.accessorKind === "str") {
|
|
4277
|
-
entry = `get: function () { return this.ref.readString(${
|
|
4780
|
+
entry = `get: function () { return this.ref.readString(${resolve13}, ${f.strLen}); }`;
|
|
4278
4781
|
} else if (f.accessorKind === "u64" || f.accessorKind === "i64") {
|
|
4279
|
-
entry = `get: function () { var v = this.ref.${READ[f.accessorKind]}(${
|
|
4782
|
+
entry = `get: function () { var v = this.ref.${READ[f.accessorKind]}(${resolve13}); return v === null ? null : v.toString(); }`;
|
|
4280
4783
|
} else if (VEC_INFO[f.accessorKind]) {
|
|
4281
4784
|
const vi = VEC_INFO[f.accessorKind];
|
|
4282
|
-
entry = `get: function () { var a = this.ref.readFloats(${
|
|
4785
|
+
entry = `get: function () { var a = this.ref.readFloats(${resolve13}, ${vi.count}); return a === null ? null : new ${vi.cls}(a[0], a[1], a[2]); }`;
|
|
4283
4786
|
} else {
|
|
4284
|
-
entry = `get: function () { return this.ref.${READ[f.accessorKind]}(${
|
|
4787
|
+
entry = `get: function () { return this.ref.${READ[f.accessorKind]}(${resolve13}); }`;
|
|
4285
4788
|
}
|
|
4286
4789
|
if (f.writable) {
|
|
4287
4790
|
const wm = WRITE[f.accessorKind];
|
|
4288
|
-
entry += `, set: function (v) { var o = ${
|
|
4791
|
+
entry += `, set: function (v) { var o = ${resolve13}; if (this.ref.${wm}(o, v)) this.ref.notifyStateChanged(o); }`;
|
|
4289
4792
|
}
|
|
4290
4793
|
out.push(` ${S(f.propName)}: { ${entry} },`);
|
|
4291
4794
|
}
|
|
@@ -4311,8 +4814,8 @@ var LIST_PATH = "games/cs2/codegen-classes.json";
|
|
|
4311
4814
|
var JS_OUT = "games/cs2/js/schema.generated.js";
|
|
4312
4815
|
var DTS_OUT = "packages/cs2/schema.generated.d.ts";
|
|
4313
4816
|
function runGenSchema(repoRoot2, opts) {
|
|
4314
|
-
const catalog = JSON.parse(
|
|
4315
|
-
const list = JSON.parse(
|
|
4817
|
+
const catalog = JSON.parse(readFileSync13(join13(repoRoot2, CATALOG_PATH), "utf8"));
|
|
4818
|
+
const list = JSON.parse(readFileSync13(join13(repoRoot2, LIST_PATH), "utf8"));
|
|
4316
4819
|
const model = buildModel(catalog, list);
|
|
4317
4820
|
const dts = emitDts(model);
|
|
4318
4821
|
const js = emitJs(model);
|
|
@@ -4321,16 +4824,16 @@ function runGenSchema(repoRoot2, opts) {
|
|
|
4321
4824
|
const drift = [];
|
|
4322
4825
|
const files = [[JS_OUT, js], [DTS_OUT, dts]];
|
|
4323
4826
|
for (const [rel, content] of files) {
|
|
4324
|
-
const abs =
|
|
4827
|
+
const abs = join13(repoRoot2, rel);
|
|
4325
4828
|
if (opts.check) {
|
|
4326
4829
|
let cur = "";
|
|
4327
4830
|
try {
|
|
4328
|
-
cur =
|
|
4831
|
+
cur = readFileSync13(abs, "utf8");
|
|
4329
4832
|
} catch {
|
|
4330
4833
|
}
|
|
4331
4834
|
if (cur !== content) drift.push(rel);
|
|
4332
4835
|
} else {
|
|
4333
|
-
|
|
4836
|
+
writeFileSync9(abs, content);
|
|
4334
4837
|
}
|
|
4335
4838
|
}
|
|
4336
4839
|
if (model.collisions.length) console.error(`gen-schema: ${model.collisions.length} name collision(s) \u2192 raw fallback:
|
|
@@ -4340,8 +4843,8 @@ function runGenSchema(repoRoot2, opts) {
|
|
|
4340
4843
|
}
|
|
4341
4844
|
|
|
4342
4845
|
// src/eventgen/gen.ts
|
|
4343
|
-
import { readFileSync as
|
|
4344
|
-
import { join as
|
|
4846
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "node:fs";
|
|
4847
|
+
import { join as join14 } from "node:path";
|
|
4345
4848
|
|
|
4346
4849
|
// src/eventgen/model.ts
|
|
4347
4850
|
var TYPE_TO_GETTER = {
|
|
@@ -4426,27 +4929,27 @@ function emitEventDts(model) {
|
|
|
4426
4929
|
var CATALOG_PATH2 = "games/cs2/gamedata/event-catalog.json";
|
|
4427
4930
|
var DTS_OUT2 = "packages/cs2/events.generated.d.ts";
|
|
4428
4931
|
function runGenEvents(repoRoot2, opts) {
|
|
4429
|
-
const catalog = JSON.parse(
|
|
4932
|
+
const catalog = JSON.parse(readFileSync14(join14(repoRoot2, CATALOG_PATH2), "utf8"));
|
|
4430
4933
|
const model = buildEventModel(catalog);
|
|
4431
4934
|
const dts = emitEventDts(model);
|
|
4432
4935
|
const drift = [];
|
|
4433
|
-
const abs =
|
|
4936
|
+
const abs = join14(repoRoot2, DTS_OUT2);
|
|
4434
4937
|
if (opts.check) {
|
|
4435
4938
|
let cur = "";
|
|
4436
4939
|
try {
|
|
4437
|
-
cur =
|
|
4940
|
+
cur = readFileSync14(abs, "utf8");
|
|
4438
4941
|
} catch {
|
|
4439
4942
|
}
|
|
4440
4943
|
if (cur !== dts) drift.push(DTS_OUT2);
|
|
4441
4944
|
} else {
|
|
4442
|
-
|
|
4945
|
+
writeFileSync10(abs, dts);
|
|
4443
4946
|
}
|
|
4444
4947
|
return { events: model.length, drift };
|
|
4445
4948
|
}
|
|
4446
4949
|
|
|
4447
4950
|
// src/navgen/gen.ts
|
|
4448
|
-
import { readFileSync as
|
|
4449
|
-
import { join as
|
|
4951
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync11 } from "node:fs";
|
|
4952
|
+
import { join as join15 } from "node:path";
|
|
4450
4953
|
|
|
4451
4954
|
// src/navgen/model.ts
|
|
4452
4955
|
var SUPPORTED_NAV_KINDS = /* @__PURE__ */ new Set([
|
|
@@ -4464,21 +4967,37 @@ var SUPPORTED_NAV_KINDS = /* @__PURE__ */ new Set([
|
|
|
4464
4967
|
"vector",
|
|
4465
4968
|
"qangle"
|
|
4466
4969
|
]);
|
|
4970
|
+
var WRITABLE_NAV_KINDS = /* @__PURE__ */ new Set(["f32", "bool", "i32"]);
|
|
4467
4971
|
function buildNavModel(config, catalog) {
|
|
4468
4972
|
const targetClasses = [...new Set(config.map((e) => e.target))];
|
|
4469
4973
|
const schemaModel = buildModel(catalog, targetClasses);
|
|
4470
4974
|
const sorted = [...config].sort((a2, b) => a2.wrapper < b.wrapper ? -1 : a2.wrapper > b.wrapper ? 1 : 0);
|
|
4471
4975
|
const wrappers = sorted.map((entry) => {
|
|
4472
4976
|
const all = flattenedFields(schemaModel, entry.target);
|
|
4977
|
+
const wantWritable = new Set(entry.writable ?? []);
|
|
4473
4978
|
const fields = [];
|
|
4474
4979
|
const skippedKinds = [];
|
|
4475
4980
|
for (const f of all) {
|
|
4476
4981
|
if (SUPPORTED_NAV_KINDS.has(f.accessorKind)) {
|
|
4477
|
-
fields.push(f);
|
|
4982
|
+
fields.push({ ...f, navWritable: wantWritable.has(f.rawName) });
|
|
4478
4983
|
} else {
|
|
4479
4984
|
skippedKinds.push({ propName: f.propName, accessorKind: f.accessorKind });
|
|
4480
4985
|
}
|
|
4481
4986
|
}
|
|
4987
|
+
for (const raw of wantWritable) {
|
|
4988
|
+
const f = fields.find((x) => x.rawName === raw);
|
|
4989
|
+
if (!f) {
|
|
4990
|
+
const known = all.some((x) => x.rawName === raw);
|
|
4991
|
+
throw new Error(
|
|
4992
|
+
`nav-targets: ${entry.wrapper}.writable lists ${JSON.stringify(raw)}, which ` + (known ? `exists on ${entry.target} but has an unsupported accessor kind, so it has no nav accessor at all.` : `does not exist on ${entry.target} or any of its ancestors (renamed or removed?).`)
|
|
4993
|
+
);
|
|
4994
|
+
}
|
|
4995
|
+
if (!WRITABLE_NAV_KINDS.has(f.accessorKind)) {
|
|
4996
|
+
throw new Error(
|
|
4997
|
+
`nav-targets: ${entry.wrapper}.writable lists ${JSON.stringify(raw)} (kind '${f.accessorKind}'), but there is no EntityRef.write*Via for that kind. Add the native and extend WRITABLE_NAV_KINDS, or drop the field from the allowlist.`
|
|
4998
|
+
);
|
|
4999
|
+
}
|
|
5000
|
+
}
|
|
4482
5001
|
return {
|
|
4483
5002
|
wrapper: entry.wrapper,
|
|
4484
5003
|
prop: entry.prop,
|
|
@@ -4505,6 +5024,11 @@ var READ_VIA = {
|
|
|
4505
5024
|
u16: "readUInt16Via",
|
|
4506
5025
|
u32: "readUInt32Via"
|
|
4507
5026
|
};
|
|
5027
|
+
var WRITE_VIA = {
|
|
5028
|
+
f32: "writeFloat32Via",
|
|
5029
|
+
bool: "writeBoolVia",
|
|
5030
|
+
i32: "writeInt32Via"
|
|
5031
|
+
};
|
|
4508
5032
|
function emitNavJs(model) {
|
|
4509
5033
|
const out = [HEADER4, "(function () {", " var off = __s2_schema_offset;"];
|
|
4510
5034
|
const vecClasses = /* @__PURE__ */ new Set();
|
|
@@ -4521,24 +5045,26 @@ function emitNavJs(model) {
|
|
|
4521
5045
|
out.push(` function ${w.wrapper}(root, path) { this.root = root; this.path = path; }`);
|
|
4522
5046
|
out.push(` Object.defineProperties(${w.wrapper}.prototype, {`);
|
|
4523
5047
|
for (const f of w.fields) {
|
|
4524
|
-
const
|
|
5048
|
+
const resolve13 = `off(${S2(f.declaringClass)},${S2(f.rawName)})`;
|
|
4525
5049
|
let getter;
|
|
4526
5050
|
const kind = f.accessorKind;
|
|
4527
5051
|
if (kind === "u64") {
|
|
4528
|
-
getter = `get: function () { var v = this.root.readUInt64Via(this.path, ${
|
|
5052
|
+
getter = `get: function () { var v = this.root.readUInt64Via(this.path, ${resolve13}); return v === null ? null : v.toString(); }`;
|
|
4529
5053
|
} else if (kind === "i64") {
|
|
4530
|
-
getter = `get: function () { var v = this.root.readInt64Via(this.path, ${
|
|
5054
|
+
getter = `get: function () { var v = this.root.readInt64Via(this.path, ${resolve13}); return v === null ? null : v.toString(); }`;
|
|
4531
5055
|
} else if (VEC_INFO[kind]) {
|
|
4532
5056
|
const vi = VEC_INFO[kind];
|
|
4533
|
-
getter = `get: function () { var a = this.root.readFloatsChain(this.path, ${
|
|
5057
|
+
getter = `get: function () { var a = this.root.readFloatsChain(this.path, ${resolve13}, ${vi.count}); return a === null ? null : new ${vi.cls}(a[0], a[1], a[2]); }`;
|
|
4534
5058
|
} else if (kind === "handle") {
|
|
4535
|
-
getter = `get: function () { return this.root.readHandleVia(this.path, ${
|
|
5059
|
+
getter = `get: function () { return this.root.readHandleVia(this.path, ${resolve13}); }`;
|
|
4536
5060
|
} else {
|
|
4537
5061
|
const method = READ_VIA[kind];
|
|
4538
5062
|
if (!method) continue;
|
|
4539
|
-
getter = `get: function () { return this.root.${method}(this.path, ${
|
|
5063
|
+
getter = `get: function () { return this.root.${method}(this.path, ${resolve13}); }`;
|
|
4540
5064
|
}
|
|
4541
|
-
|
|
5065
|
+
const wmethod = f.navWritable ? WRITE_VIA[kind] : void 0;
|
|
5066
|
+
const setter = wmethod ? `, set: function (v) { this.root.${wmethod}(this.path, ${resolve13}, v); }` : "";
|
|
5067
|
+
out.push(` ${S2(f.propName)}: { ${getter}${setter} },`);
|
|
4542
5068
|
}
|
|
4543
5069
|
out.push(" });");
|
|
4544
5070
|
}
|
|
@@ -4593,9 +5119,20 @@ function emitNavDts(model) {
|
|
|
4593
5119
|
if (needsEntityRef) out.push('import type { EntityRef } from "@s2script/sdk/entity";');
|
|
4594
5120
|
out.push("");
|
|
4595
5121
|
for (const w of model.wrappers) {
|
|
5122
|
+
if (w.fields.some((f) => f.navWritable)) {
|
|
5123
|
+
out.push(`/**`);
|
|
5124
|
+
out.push(` * Fields NOT marked \`readonly\` are writable.`);
|
|
5125
|
+
out.push(` *`);
|
|
5126
|
+
out.push(` * A write takes effect immediately \u2014 the server reads these during movement \u2014 but it is **not**`);
|
|
5127
|
+
out.push(` * flagged for replication, because this object is reached through a pointer chain and the`);
|
|
5128
|
+
out.push(` * change-notifier addresses the root entity. A client predicting the old value may therefore`);
|
|
5129
|
+
out.push(` * see brief mismatch until the next authoritative correction. SourceMod's \`SetEntPropFloat\``);
|
|
5130
|
+
out.push(` * on the same fields behaves the same way.`);
|
|
5131
|
+
out.push(` */`);
|
|
5132
|
+
}
|
|
4596
5133
|
out.push(`export interface ${w.wrapper} {`);
|
|
4597
5134
|
for (const f of w.fields) {
|
|
4598
|
-
out.push(` readonly ${f.propName}: ${TSTYPE[f.accessorKind]};`);
|
|
5135
|
+
out.push(` ${f.navWritable ? "" : "readonly "}${f.propName}: ${TSTYPE[f.accessorKind]};`);
|
|
4599
5136
|
}
|
|
4600
5137
|
out.push("}", "");
|
|
4601
5138
|
}
|
|
@@ -4608,8 +5145,8 @@ var CATALOG_PATH3 = "games/cs2/gamedata/schema-catalog.json";
|
|
|
4608
5145
|
var JS_OUT2 = "games/cs2/js/nav.generated.js";
|
|
4609
5146
|
var DTS_OUT3 = "packages/cs2/nav.generated.d.ts";
|
|
4610
5147
|
function runGenNav(repoRoot2, opts) {
|
|
4611
|
-
const config = JSON.parse(
|
|
4612
|
-
const catalog = JSON.parse(
|
|
5148
|
+
const config = JSON.parse(readFileSync15(join15(repoRoot2, TARGETS_PATH), "utf8"));
|
|
5149
|
+
const catalog = JSON.parse(readFileSync15(join15(repoRoot2, CATALOG_PATH3), "utf8"));
|
|
4613
5150
|
const model = buildNavModel(config, catalog);
|
|
4614
5151
|
const js = emitNavJs(model);
|
|
4615
5152
|
const dts = emitNavDts(model);
|
|
@@ -4617,16 +5154,16 @@ function runGenNav(repoRoot2, opts) {
|
|
|
4617
5154
|
const drift = [];
|
|
4618
5155
|
const files = [[JS_OUT2, js], [DTS_OUT3, dts]];
|
|
4619
5156
|
for (const [rel, content] of files) {
|
|
4620
|
-
const abs =
|
|
5157
|
+
const abs = join15(repoRoot2, rel);
|
|
4621
5158
|
if (opts.check) {
|
|
4622
5159
|
let cur = "";
|
|
4623
5160
|
try {
|
|
4624
|
-
cur =
|
|
5161
|
+
cur = readFileSync15(abs, "utf8");
|
|
4625
5162
|
} catch {
|
|
4626
5163
|
}
|
|
4627
5164
|
if (cur !== content) drift.push(rel);
|
|
4628
5165
|
} else {
|
|
4629
|
-
|
|
5166
|
+
writeFileSync11(abs, content);
|
|
4630
5167
|
}
|
|
4631
5168
|
}
|
|
4632
5169
|
return { wrappers: model.wrappers.length, fields, drift };
|
|
@@ -4634,9 +5171,9 @@ function runGenNav(repoRoot2, opts) {
|
|
|
4634
5171
|
|
|
4635
5172
|
// src/commands/codegen.ts
|
|
4636
5173
|
function repoRoot() {
|
|
4637
|
-
return
|
|
5174
|
+
return join16(dirname3(fileURLToPath3(import.meta.url)), "..", "..", "..");
|
|
4638
5175
|
}
|
|
4639
|
-
async function
|
|
5176
|
+
async function run8(kind, argv2) {
|
|
4640
5177
|
const root = repoRoot();
|
|
4641
5178
|
const check = argv2[0] === "--check";
|
|
4642
5179
|
if (kind === "schema") {
|
|
@@ -4680,15 +5217,16 @@ async function run7(kind, argv2) {
|
|
|
4680
5217
|
|
|
4681
5218
|
// src/commands/index.ts
|
|
4682
5219
|
var COMMANDS = [
|
|
4683
|
-
{ name: "create", summary: "Scaffold a new plugin", run:
|
|
5220
|
+
{ name: "create", summary: "Scaffold a new plugin", run: run5 },
|
|
4684
5221
|
{ name: "build", summary: "Build a plugin to a .s2sp", run },
|
|
4685
5222
|
{ name: "deploy", summary: "Publish a plugin to the registry", run: run2 },
|
|
4686
5223
|
{ name: "add", summary: "Add a registry package's types", run: run3 },
|
|
4687
|
-
{ name: "
|
|
4688
|
-
{ name: "
|
|
4689
|
-
{ name: "
|
|
4690
|
-
{ name: "gen-
|
|
4691
|
-
{ name: "gen-
|
|
5224
|
+
{ name: "install", summary: "Download plugins + their deps into a server", run: run4 },
|
|
5225
|
+
{ name: "login", summary: "Save a registry deploy token", run: run6 },
|
|
5226
|
+
{ name: "config", summary: "Emit a plugin's default config file(s)", run: run7 },
|
|
5227
|
+
{ name: "gen-schema", summary: "Regenerate schema accessors", run: (a2) => run8("schema", a2) },
|
|
5228
|
+
{ name: "gen-events", summary: "Regenerate the event catalog", run: (a2) => run8("events", a2) },
|
|
5229
|
+
{ name: "gen-nav", summary: "Regenerate nav accessors", run: (a2) => run8("nav", a2) }
|
|
4692
5230
|
];
|
|
4693
5231
|
function find(name) {
|
|
4694
5232
|
if (name === "publish") return COMMANDS.find((c2) => c2.name === "deploy");
|
|
@@ -4696,7 +5234,7 @@ function find(name) {
|
|
|
4696
5234
|
}
|
|
4697
5235
|
|
|
4698
5236
|
// src/commands/menu.ts
|
|
4699
|
-
async function
|
|
5237
|
+
async function run9() {
|
|
4700
5238
|
intro2("s2script");
|
|
4701
5239
|
const name = await select2({
|
|
4702
5240
|
message: "What would you like to do?",
|
|
@@ -4711,12 +5249,12 @@ var argv = process.argv.slice(2);
|
|
|
4711
5249
|
var command = argv[0];
|
|
4712
5250
|
function usage() {
|
|
4713
5251
|
console.error(
|
|
4714
|
-
"Usage:\n s2s create [path] [--game cs2|none] [--name <pkg>] [--template minimal]\n [--install npm|pnpm|yarn|bun|none] [--no-install] [-y]\n s2s build <dir> [--packages-dir <path>]\n s2s login [--token s2s_\u2026] [--registry <url>]\n s2s deploy [dir] [--ci] [--registry <url>] [--packages-dir <path>]\n s2s add <pkg>[@range] [--dir <plugin>] [--registry <url>]\n s2s config gen <plugin.s2sp...> --out <dir>\n s2s gen-schema [--check]\n s2s gen-events [--check]\n s2s gen-nav [--check]\n\nEnv: S2SCRIPT_REGISTRY_URL S2SCRIPT_TOKEN (CI deploy)"
|
|
5252
|
+
"Usage:\n s2s create [path] [--game cs2|none] [--name <pkg>] [--template minimal]\n [--install npm|pnpm|yarn|bun|none] [--no-install] [-y]\n s2s build <dir> [--packages-dir <path>]\n s2s login [--token s2s_\u2026] [--registry <url>]\n s2s deploy [dir] [--ci] [--registry <url>] [--packages-dir <path>]\n s2s add <pkg>[@range] [--dir <plugin>] [--registry <url>]\n s2s install [name[@range]...] [--file <path>] [--dir <plugins dir>] [--registry <url>]\n s2s config gen <plugin.s2sp...> --out <dir>\n s2s gen-schema [--check]\n s2s gen-events [--check]\n s2s gen-nav [--check]\n\nEnv: S2SCRIPT_REGISTRY_URL S2SCRIPT_TOKEN (CI deploy)"
|
|
4715
5253
|
);
|
|
4716
5254
|
}
|
|
4717
5255
|
if (!command) {
|
|
4718
5256
|
if (isInteractive()) {
|
|
4719
|
-
await
|
|
5257
|
+
await run9();
|
|
4720
5258
|
} else {
|
|
4721
5259
|
usage();
|
|
4722
5260
|
process.exit(1);
|