@s2script/sdk 0.5.0 → 0.5.1
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/admin.d.ts +28 -1
- package/bans.d.ts +8 -1
- package/chat.d.ts +7 -0
- package/clients.d.ts +9 -1
- package/commands.d.ts +13 -0
- package/config.d.ts +12 -0
- package/console.d.ts +9 -0
- package/cookies.d.ts +37 -4
- package/damage.d.ts +7 -1
- package/db.d.ts +41 -3
- package/dist/cli.js +460 -25
- package/entity.d.ts +27 -1
- package/events.d.ts +27 -1
- package/globals.d.ts +6 -1
- package/http.d.ts +35 -4
- package/interfaces.d.ts +8 -1
- package/math.d.ts +15 -1
- package/menu.d.ts +92 -7
- package/net.d.ts +26 -2
- package/package.json +1 -1
- package/plugin.d.ts +85 -2
- package/plugins.d.ts +9 -0
- package/server.d.ts +8 -0
- package/sound.d.ts +9 -0
- package/timers.d.ts +7 -1
- package/topmenu.d.ts +15 -0
- package/trace.d.ts +8 -0
- package/translations.d.ts +10 -0
- package/transmit.d.ts +8 -0
- package/usercmd.d.ts +6 -0
- package/usermessages.d.ts +18 -0
- package/votes.d.ts +46 -3
- package/ws.d.ts +18 -1
package/dist/cli.js
CHANGED
|
@@ -446,8 +446,8 @@ async function lintPlugin(pluginDir, program) {
|
|
|
446
446
|
const results = await eslint.lintFiles(targets);
|
|
447
447
|
const errorCount = results.reduce((n, r) => n + r.errorCount, 0);
|
|
448
448
|
const formatter = await eslint.loadFormatter("stylish");
|
|
449
|
-
const
|
|
450
|
-
return { ok: errorCount === 0, output:
|
|
449
|
+
const output3 = String(await formatter.format(results));
|
|
450
|
+
return { ok: errorCount === 0, output: output3, errorCount };
|
|
451
451
|
}
|
|
452
452
|
|
|
453
453
|
// src/build.ts
|
|
@@ -788,21 +788,21 @@ function emitJs(model) {
|
|
|
788
788
|
for (const c of model.classes) {
|
|
789
789
|
out.push(` A[${S(c.className)}] = {`);
|
|
790
790
|
for (const f of flattenedFields(model, c.className)) {
|
|
791
|
-
const
|
|
791
|
+
const resolve10 = `off(${S(f.declaringClass)},${S(f.rawName)})`;
|
|
792
792
|
let entry;
|
|
793
793
|
if (f.accessorKind === "str") {
|
|
794
|
-
entry = `get: function () { return this.ref.readString(${
|
|
794
|
+
entry = `get: function () { return this.ref.readString(${resolve10}, ${f.strLen}); }`;
|
|
795
795
|
} else if (f.accessorKind === "u64" || f.accessorKind === "i64") {
|
|
796
|
-
entry = `get: function () { var v = this.ref.${READ[f.accessorKind]}(${
|
|
796
|
+
entry = `get: function () { var v = this.ref.${READ[f.accessorKind]}(${resolve10}); return v === null ? null : v.toString(); }`;
|
|
797
797
|
} else if (VEC_INFO[f.accessorKind]) {
|
|
798
798
|
const vi = VEC_INFO[f.accessorKind];
|
|
799
|
-
entry = `get: function () { var a = this.ref.readFloats(${
|
|
799
|
+
entry = `get: function () { var a = this.ref.readFloats(${resolve10}, ${vi.count}); return a === null ? null : new ${vi.cls}(a[0], a[1], a[2]); }`;
|
|
800
800
|
} else {
|
|
801
|
-
entry = `get: function () { return this.ref.${READ[f.accessorKind]}(${
|
|
801
|
+
entry = `get: function () { return this.ref.${READ[f.accessorKind]}(${resolve10}); }`;
|
|
802
802
|
}
|
|
803
803
|
if (f.writable) {
|
|
804
804
|
const wm = WRITE[f.accessorKind];
|
|
805
|
-
entry += `, set: function (v) { var o = ${
|
|
805
|
+
entry += `, set: function (v) { var o = ${resolve10}; if (this.ref.${wm}(o, v)) this.ref.notifyStateChanged(o); }`;
|
|
806
806
|
}
|
|
807
807
|
out.push(` ${S(f.propName)}: { ${entry} },`);
|
|
808
808
|
}
|
|
@@ -1038,22 +1038,22 @@ function emitNavJs(model) {
|
|
|
1038
1038
|
out.push(` function ${w.wrapper}(root, path) { this.root = root; this.path = path; }`);
|
|
1039
1039
|
out.push(` Object.defineProperties(${w.wrapper}.prototype, {`);
|
|
1040
1040
|
for (const f of w.fields) {
|
|
1041
|
-
const
|
|
1041
|
+
const resolve10 = `off(${S2(f.declaringClass)},${S2(f.rawName)})`;
|
|
1042
1042
|
let getter;
|
|
1043
1043
|
const kind = f.accessorKind;
|
|
1044
1044
|
if (kind === "u64") {
|
|
1045
|
-
getter = `get: function () { var v = this.root.readUInt64Via(this.path, ${
|
|
1045
|
+
getter = `get: function () { var v = this.root.readUInt64Via(this.path, ${resolve10}); return v === null ? null : v.toString(); }`;
|
|
1046
1046
|
} else if (kind === "i64") {
|
|
1047
|
-
getter = `get: function () { var v = this.root.readInt64Via(this.path, ${
|
|
1047
|
+
getter = `get: function () { var v = this.root.readInt64Via(this.path, ${resolve10}); return v === null ? null : v.toString(); }`;
|
|
1048
1048
|
} else if (VEC_INFO[kind]) {
|
|
1049
1049
|
const vi = VEC_INFO[kind];
|
|
1050
|
-
getter = `get: function () { var a = this.root.readFloatsChain(this.path, ${
|
|
1050
|
+
getter = `get: function () { var a = this.root.readFloatsChain(this.path, ${resolve10}, ${vi.count}); return a === null ? null : new ${vi.cls}(a[0], a[1], a[2]); }`;
|
|
1051
1051
|
} else if (kind === "handle") {
|
|
1052
|
-
getter = `get: function () { return this.root.readHandleVia(this.path, ${
|
|
1052
|
+
getter = `get: function () { return this.root.readHandleVia(this.path, ${resolve10}); }`;
|
|
1053
1053
|
} else {
|
|
1054
1054
|
const method = READ_VIA[kind];
|
|
1055
1055
|
if (!method) continue;
|
|
1056
|
-
getter = `get: function () { return this.root.${method}(this.path, ${
|
|
1056
|
+
getter = `get: function () { return this.root.${method}(this.path, ${resolve10}); }`;
|
|
1057
1057
|
}
|
|
1058
1058
|
out.push(` ${S2(f.propName)}: { ${getter} },`);
|
|
1059
1059
|
}
|
|
@@ -1244,10 +1244,10 @@ function versionSpecFrom(status, stdout) {
|
|
|
1244
1244
|
const v = (stdout ?? "").trim();
|
|
1245
1245
|
return status === 0 && /^\d+\.\d+\.\d+/.test(v) ? `^${v}` : "latest";
|
|
1246
1246
|
}
|
|
1247
|
-
function registryDevDeps(game, sdkVersion,
|
|
1247
|
+
function registryDevDeps(game, sdkVersion, resolve10 = resolvePublishedVersion) {
|
|
1248
1248
|
const deps = {};
|
|
1249
1249
|
for (const n of createPackageNames(game)) {
|
|
1250
|
-
deps[`@s2script/${n}`] = n === "sdk" ? `^${sdkVersion}` :
|
|
1250
|
+
deps[`@s2script/${n}`] = n === "sdk" ? `^${sdkVersion}` : resolve10(`@s2script/${n}`);
|
|
1251
1251
|
}
|
|
1252
1252
|
return deps;
|
|
1253
1253
|
}
|
|
@@ -1451,7 +1451,7 @@ function generateDefaultJsonc(config) {
|
|
|
1451
1451
|
return "{\n" + genEntries(config, 1) + "}\n";
|
|
1452
1452
|
}
|
|
1453
1453
|
function genEntries(entries, indent) {
|
|
1454
|
-
const
|
|
1454
|
+
const pad2 = " ".repeat(indent);
|
|
1455
1455
|
const keys = Object.keys(entries).filter((k) => !k.includes(".")).sort();
|
|
1456
1456
|
let out = "";
|
|
1457
1457
|
keys.forEach((key, i) => {
|
|
@@ -1461,15 +1461,15 @@ function genEntries(entries, indent) {
|
|
|
1461
1461
|
const obj = raw;
|
|
1462
1462
|
if (isDecl(obj)) {
|
|
1463
1463
|
const desc = typeof obj.description === "string" ? obj.description : "";
|
|
1464
|
-
out += `${
|
|
1464
|
+
out += `${pad2}// ${String(obj.type)}${desc ? ` \u2014 ${desc}` : ""}
|
|
1465
1465
|
`;
|
|
1466
|
-
out += `${
|
|
1466
|
+
out += `${pad2}${JSON.stringify(key)}: ${JSON.stringify(obj.default)}${comma}
|
|
1467
1467
|
`;
|
|
1468
1468
|
} else {
|
|
1469
|
-
out += `${
|
|
1469
|
+
out += `${pad2}${JSON.stringify(key)}: {
|
|
1470
1470
|
`;
|
|
1471
1471
|
out += genEntries(obj, indent + 1);
|
|
1472
|
-
out += `${
|
|
1472
|
+
out += `${pad2}}${comma}
|
|
1473
1473
|
`;
|
|
1474
1474
|
}
|
|
1475
1475
|
});
|
|
@@ -1505,13 +1505,386 @@ function runConfigGen(s2sps, outDir) {
|
|
|
1505
1505
|
return { written, skipped };
|
|
1506
1506
|
}
|
|
1507
1507
|
|
|
1508
|
+
// src/registry/login.ts
|
|
1509
|
+
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
1510
|
+
import { stdin as input2, stdout as output2 } from "node:process";
|
|
1511
|
+
|
|
1512
|
+
// src/registry/credentials.ts
|
|
1513
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "node:fs";
|
|
1514
|
+
import { homedir } from "node:os";
|
|
1515
|
+
import { join as join12 } from "node:path";
|
|
1516
|
+
function credentialsPath() {
|
|
1517
|
+
return join12(homedir(), ".s2script", "credentials.json");
|
|
1518
|
+
}
|
|
1519
|
+
function defaultRegistryUrl() {
|
|
1520
|
+
return process.env.S2SCRIPT_REGISTRY_URL?.replace(/\/$/, "") || "https://s2script.com";
|
|
1521
|
+
}
|
|
1522
|
+
function loadCredentials() {
|
|
1523
|
+
const fromEnv = process.env.S2SCRIPT_TOKEN || process.env.S2SCRIPT_DEPLOY_TOKEN;
|
|
1524
|
+
if (fromEnv) {
|
|
1525
|
+
return { registryUrl: defaultRegistryUrl(), token: fromEnv };
|
|
1526
|
+
}
|
|
1527
|
+
const p = credentialsPath();
|
|
1528
|
+
if (!existsSync7(p)) return null;
|
|
1529
|
+
try {
|
|
1530
|
+
const j = JSON.parse(readFileSync9(p, "utf8"));
|
|
1531
|
+
if (!j.token) return null;
|
|
1532
|
+
return {
|
|
1533
|
+
registryUrl: (j.registryUrl || defaultRegistryUrl()).replace(/\/$/, ""),
|
|
1534
|
+
token: j.token
|
|
1535
|
+
};
|
|
1536
|
+
} catch {
|
|
1537
|
+
return null;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
function saveCredentials(creds) {
|
|
1541
|
+
const dir = join12(homedir(), ".s2script");
|
|
1542
|
+
mkdirSync4(dir, { recursive: true });
|
|
1543
|
+
writeFileSync7(
|
|
1544
|
+
credentialsPath(),
|
|
1545
|
+
JSON.stringify(
|
|
1546
|
+
{ registryUrl: creds.registryUrl.replace(/\/$/, ""), token: creds.token },
|
|
1547
|
+
null,
|
|
1548
|
+
2
|
|
1549
|
+
) + "\n",
|
|
1550
|
+
{ mode: 384 }
|
|
1551
|
+
);
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
// src/registry/login.ts
|
|
1555
|
+
async function loginInteractive(opts) {
|
|
1556
|
+
const registryUrl = (opts?.registryUrl || defaultRegistryUrl()).replace(/\/$/, "");
|
|
1557
|
+
let token = opts?.token || process.env.S2SCRIPT_TOKEN || process.env.S2SCRIPT_DEPLOY_TOKEN;
|
|
1558
|
+
if (!token) {
|
|
1559
|
+
if (opts?.ci || !input2.isTTY) {
|
|
1560
|
+
throw new Error(
|
|
1561
|
+
"no deploy token: set S2SCRIPT_TOKEN or run `s2script login` interactively"
|
|
1562
|
+
);
|
|
1563
|
+
}
|
|
1564
|
+
const rl = createInterface2({ input: input2, output: output2 });
|
|
1565
|
+
try {
|
|
1566
|
+
console.log(`Registry: ${registryUrl}`);
|
|
1567
|
+
console.log("Create a deploy token at /account/tokens on the registry site, then paste it:");
|
|
1568
|
+
token = (await rl.question("token: ")).trim();
|
|
1569
|
+
} finally {
|
|
1570
|
+
rl.close();
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
if (!token || !token.startsWith("s2s_")) {
|
|
1574
|
+
throw new Error('token must start with "s2s_"');
|
|
1575
|
+
}
|
|
1576
|
+
const creds = { registryUrl, token };
|
|
1577
|
+
saveCredentials(creds);
|
|
1578
|
+
return creds;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
// src/registry/deploy.ts
|
|
1582
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
1583
|
+
import { resolve as resolve7 } from "node:path";
|
|
1584
|
+
import AdmZip3 from "adm-zip";
|
|
1585
|
+
|
|
1586
|
+
// src/types-pack.ts
|
|
1587
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
1588
|
+
import { gzipSync } from "node:zlib";
|
|
1589
|
+
function pad(buf, size) {
|
|
1590
|
+
if (buf.length > size) throw new Error(`tar field overflow (${buf.length}>${size})`);
|
|
1591
|
+
const out = Buffer.alloc(size, 0);
|
|
1592
|
+
buf.copy(out);
|
|
1593
|
+
return out;
|
|
1594
|
+
}
|
|
1595
|
+
function tarHeader(name, size, type) {
|
|
1596
|
+
const header = Buffer.alloc(512, 0);
|
|
1597
|
+
pad(Buffer.from(name, "utf8"), 100).copy(header, 0);
|
|
1598
|
+
Buffer.from("0000644\0", "utf8").copy(header, 100);
|
|
1599
|
+
Buffer.from("0000000\0", "utf8").copy(header, 108);
|
|
1600
|
+
Buffer.from("0000000\0", "utf8").copy(header, 116);
|
|
1601
|
+
const sizeOct = size.toString(8).padStart(11, "0") + "\0";
|
|
1602
|
+
Buffer.from(sizeOct, "utf8").copy(header, 124);
|
|
1603
|
+
const mtime = Math.floor(Date.now() / 1e3).toString(8).padStart(11, "0") + "\0";
|
|
1604
|
+
Buffer.from(mtime, "utf8").copy(header, 136);
|
|
1605
|
+
Buffer.from(" ", "utf8").copy(header, 148);
|
|
1606
|
+
header[156] = type.charCodeAt(0);
|
|
1607
|
+
Buffer.from("ustar\0", "utf8").copy(header, 257);
|
|
1608
|
+
Buffer.from("00", "utf8").copy(header, 263);
|
|
1609
|
+
let sum = 0;
|
|
1610
|
+
for (let i = 0; i < 512; i++) sum += header[i];
|
|
1611
|
+
const chk = sum.toString(8).padStart(6, "0") + "\0 ";
|
|
1612
|
+
Buffer.from(chk, "utf8").copy(header, 148);
|
|
1613
|
+
return header;
|
|
1614
|
+
}
|
|
1615
|
+
function tarFile(name, content) {
|
|
1616
|
+
const header = tarHeader(name, content.length, "0");
|
|
1617
|
+
const padLen = (512 - content.length % 512) % 512;
|
|
1618
|
+
return Buffer.concat([header, content, Buffer.alloc(padLen, 0)]);
|
|
1619
|
+
}
|
|
1620
|
+
function packTypesTarball(input3) {
|
|
1621
|
+
const dts = readFileSync10(input3.typesPath);
|
|
1622
|
+
const pkgJson = Buffer.from(
|
|
1623
|
+
JSON.stringify(
|
|
1624
|
+
{
|
|
1625
|
+
name: input3.name,
|
|
1626
|
+
version: input3.version,
|
|
1627
|
+
types: "api.d.ts",
|
|
1628
|
+
s2script: {
|
|
1629
|
+
kind: "interface",
|
|
1630
|
+
...input3.publishes ? { publishes: input3.publishes } : {}
|
|
1631
|
+
}
|
|
1632
|
+
},
|
|
1633
|
+
null,
|
|
1634
|
+
2
|
|
1635
|
+
) + "\n",
|
|
1636
|
+
"utf8"
|
|
1637
|
+
);
|
|
1638
|
+
const tar = Buffer.concat([
|
|
1639
|
+
tarFile("package/package.json", pkgJson),
|
|
1640
|
+
tarFile("package/api.d.ts", dts),
|
|
1641
|
+
Buffer.alloc(1024, 0)
|
|
1642
|
+
// two zero blocks = EOF
|
|
1643
|
+
]);
|
|
1644
|
+
return gzipSync(tar);
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
// src/registry/client.ts
|
|
1648
|
+
var RegistryError = class extends Error {
|
|
1649
|
+
constructor(message, status, body) {
|
|
1650
|
+
super(message);
|
|
1651
|
+
this.status = status;
|
|
1652
|
+
this.body = body;
|
|
1653
|
+
this.name = "RegistryError";
|
|
1654
|
+
}
|
|
1655
|
+
};
|
|
1656
|
+
var RegistryClient = class {
|
|
1657
|
+
constructor(opts) {
|
|
1658
|
+
this.baseUrl = opts.baseUrl.replace(/\/$/, "");
|
|
1659
|
+
this.token = opts.token;
|
|
1660
|
+
this.fetchFn = opts.fetch ?? fetch;
|
|
1661
|
+
}
|
|
1662
|
+
headers(extra) {
|
|
1663
|
+
const h = { ...extra ?? {} };
|
|
1664
|
+
if (this.token) h.Authorization = `Bearer ${this.token}`;
|
|
1665
|
+
return h;
|
|
1666
|
+
}
|
|
1667
|
+
async deploy(opts) {
|
|
1668
|
+
const fd = new FormData();
|
|
1669
|
+
fd.set("manifest", JSON.stringify(opts.manifest));
|
|
1670
|
+
fd.set("s2sp", new Blob([new Uint8Array(opts.s2sp)]), "plugin.s2sp");
|
|
1671
|
+
if (opts.types && opts.types.length) {
|
|
1672
|
+
fd.set("types", new Blob([new Uint8Array(opts.types)]), "types.tgz");
|
|
1673
|
+
}
|
|
1674
|
+
const res = await this.fetchFn(`${this.baseUrl}/api/v1/deploy`, {
|
|
1675
|
+
method: "POST",
|
|
1676
|
+
headers: this.headers(),
|
|
1677
|
+
body: fd
|
|
1678
|
+
});
|
|
1679
|
+
const body = await res.json().catch(() => ({}));
|
|
1680
|
+
if (!res.ok) {
|
|
1681
|
+
throw new RegistryError(
|
|
1682
|
+
body.error || `deploy failed (${res.status})`,
|
|
1683
|
+
res.status,
|
|
1684
|
+
body
|
|
1685
|
+
);
|
|
1686
|
+
}
|
|
1687
|
+
return body;
|
|
1688
|
+
}
|
|
1689
|
+
async resolve(name, range = "*") {
|
|
1690
|
+
const u = new URL(`${this.baseUrl}/api/v1/resolve`);
|
|
1691
|
+
u.searchParams.set("name", name);
|
|
1692
|
+
u.searchParams.set("range", range);
|
|
1693
|
+
const res = await this.fetchFn(u, { headers: this.headers() });
|
|
1694
|
+
const body = await res.json().catch(() => ({}));
|
|
1695
|
+
if (!res.ok) {
|
|
1696
|
+
throw new RegistryError(
|
|
1697
|
+
body.message || `resolve failed (${res.status})`,
|
|
1698
|
+
res.status,
|
|
1699
|
+
body
|
|
1700
|
+
);
|
|
1701
|
+
}
|
|
1702
|
+
return body;
|
|
1703
|
+
}
|
|
1704
|
+
async downloadTypes(name, version) {
|
|
1705
|
+
const u = new URL(`${this.baseUrl}/api/v1/download/types`);
|
|
1706
|
+
u.searchParams.set("name", name);
|
|
1707
|
+
u.searchParams.set("version", version);
|
|
1708
|
+
const res = await this.fetchFn(u, { headers: this.headers() });
|
|
1709
|
+
if (!res.ok) {
|
|
1710
|
+
const body = await res.json().catch(() => ({}));
|
|
1711
|
+
throw new RegistryError(
|
|
1712
|
+
body.message || `types download failed (${res.status})`,
|
|
1713
|
+
res.status,
|
|
1714
|
+
body
|
|
1715
|
+
);
|
|
1716
|
+
}
|
|
1717
|
+
return Buffer.from(await res.arrayBuffer());
|
|
1718
|
+
}
|
|
1719
|
+
async meta(name) {
|
|
1720
|
+
const u = new URL(`${this.baseUrl}/api/v1/meta`);
|
|
1721
|
+
u.searchParams.set("name", name);
|
|
1722
|
+
const res = await this.fetchFn(u, { headers: this.headers() });
|
|
1723
|
+
const body = await res.json().catch(() => ({}));
|
|
1724
|
+
if (!res.ok) {
|
|
1725
|
+
throw new RegistryError(`meta failed (${res.status})`, res.status, body);
|
|
1726
|
+
}
|
|
1727
|
+
return body;
|
|
1728
|
+
}
|
|
1729
|
+
};
|
|
1730
|
+
|
|
1731
|
+
// src/registry/deploy.ts
|
|
1732
|
+
async function deployPlugin(opts) {
|
|
1733
|
+
const absDir = resolve7(opts.dir);
|
|
1734
|
+
const pkg = JSON.parse(readFileSync11(resolve7(absDir, "package.json"), "utf8"));
|
|
1735
|
+
const gate = assertPublishesTypes(pkg, absDir);
|
|
1736
|
+
if (!gate.ok) throw new Error(`publish gate failed: ${gate.error}`);
|
|
1737
|
+
const creds = loadCredentials();
|
|
1738
|
+
if (!creds?.token) {
|
|
1739
|
+
throw new Error(
|
|
1740
|
+
opts.ci ? "S2SCRIPT_TOKEN required in CI" : "not logged in \u2014 run `s2s login` or set S2SCRIPT_TOKEN"
|
|
1741
|
+
);
|
|
1742
|
+
}
|
|
1743
|
+
const outPath = await buildPlugin(absDir, opts.packagesDir);
|
|
1744
|
+
const s2sp = readFileSync11(outPath);
|
|
1745
|
+
const zip = new AdmZip3(s2sp);
|
|
1746
|
+
const manifestEntry = zip.getEntry("manifest.json");
|
|
1747
|
+
if (!manifestEntry) {
|
|
1748
|
+
throw new Error(`built archive missing manifest.json: ${outPath}`);
|
|
1749
|
+
}
|
|
1750
|
+
const manifest = JSON.parse(manifestEntry.getData().toString("utf8"));
|
|
1751
|
+
let types = null;
|
|
1752
|
+
if (gate.typesPath) {
|
|
1753
|
+
types = packTypesTarball({
|
|
1754
|
+
name: typeof manifest.id === "string" ? manifest.id : pkg.name,
|
|
1755
|
+
version: typeof manifest.version === "string" ? manifest.version : pkg.version,
|
|
1756
|
+
typesPath: gate.typesPath,
|
|
1757
|
+
publishes: hasPublishes(manifest.publishes) ? manifest.publishes : void 0
|
|
1758
|
+
});
|
|
1759
|
+
}
|
|
1760
|
+
const client = new RegistryClient({
|
|
1761
|
+
baseUrl: opts.registryUrl || creds.registryUrl || defaultRegistryUrl(),
|
|
1762
|
+
token: creds.token
|
|
1763
|
+
});
|
|
1764
|
+
return client.deploy({ manifest, s2sp, types });
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
// src/registry/add.ts
|
|
1768
|
+
import {
|
|
1769
|
+
mkdirSync as mkdirSync5,
|
|
1770
|
+
writeFileSync as writeFileSync9,
|
|
1771
|
+
readFileSync as readFileSync13,
|
|
1772
|
+
existsSync as existsSync9
|
|
1773
|
+
} from "node:fs";
|
|
1774
|
+
import { join as join14, resolve as resolve8 } from "node:path";
|
|
1775
|
+
import { gunzipSync } from "node:zlib";
|
|
1776
|
+
|
|
1777
|
+
// src/registry/npmrc.ts
|
|
1778
|
+
import { existsSync as existsSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "node:fs";
|
|
1779
|
+
import { join as join13 } from "node:path";
|
|
1780
|
+
function npmCompatRegistryUrl(registryUrl) {
|
|
1781
|
+
return `${registryUrl.replace(/\/$/, "")}/npm/`;
|
|
1782
|
+
}
|
|
1783
|
+
function scopeFromPackageName(name) {
|
|
1784
|
+
if (!name.startsWith("@")) return null;
|
|
1785
|
+
const slash = name.indexOf("/");
|
|
1786
|
+
if (slash < 2) return null;
|
|
1787
|
+
return name.slice(1, slash);
|
|
1788
|
+
}
|
|
1789
|
+
function ensureScopeNpmrc(projectDir, packageName, registryUrl) {
|
|
1790
|
+
const scope = scopeFromPackageName(packageName);
|
|
1791
|
+
if (!scope || scope === "s2script") return null;
|
|
1792
|
+
const compat = npmCompatRegistryUrl(registryUrl);
|
|
1793
|
+
const key = `@${scope}:registry`;
|
|
1794
|
+
const line = `${key}=${compat}`;
|
|
1795
|
+
const npmrcPath = join13(projectDir, ".npmrc");
|
|
1796
|
+
let existing = existsSync8(npmrcPath) ? readFileSync12(npmrcPath, "utf8") : "";
|
|
1797
|
+
const lines = existing.split(/\r?\n/);
|
|
1798
|
+
const idx = lines.findIndex((l) => l.trimStart().startsWith(`${key}=`) || l.trimStart().startsWith(`${key} =`));
|
|
1799
|
+
if (idx >= 0) {
|
|
1800
|
+
lines[idx] = line;
|
|
1801
|
+
} else {
|
|
1802
|
+
if (existing && !existing.endsWith("\n")) lines.push("");
|
|
1803
|
+
lines.push(line);
|
|
1804
|
+
}
|
|
1805
|
+
const out = lines.filter((l, i, a) => !(l === "" && a[i - 1] === "")).join("\n");
|
|
1806
|
+
writeFileSync8(npmrcPath, out.endsWith("\n") ? out : out + "\n");
|
|
1807
|
+
return line;
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
// src/registry/add.ts
|
|
1811
|
+
function parseSpec(spec) {
|
|
1812
|
+
if (spec.startsWith("@")) {
|
|
1813
|
+
const at2 = spec.indexOf("@", 1);
|
|
1814
|
+
if (at2 < 0) return { name: spec, range: "*" };
|
|
1815
|
+
return { name: spec.slice(0, at2), range: spec.slice(at2 + 1) || "*" };
|
|
1816
|
+
}
|
|
1817
|
+
const at = spec.lastIndexOf("@");
|
|
1818
|
+
if (at <= 0) return { name: spec, range: "*" };
|
|
1819
|
+
return { name: spec.slice(0, at), range: spec.slice(at + 1) || "*" };
|
|
1820
|
+
}
|
|
1821
|
+
function extractTypesTarball(tgz, outDir) {
|
|
1822
|
+
const tar = gunzipSync(tgz);
|
|
1823
|
+
mkdirSync5(outDir, { recursive: true });
|
|
1824
|
+
let offset = 0;
|
|
1825
|
+
let wroteContract = false;
|
|
1826
|
+
while (offset + 512 <= tar.length) {
|
|
1827
|
+
const header = tar.subarray(offset, offset + 512);
|
|
1828
|
+
offset += 512;
|
|
1829
|
+
if (header.every((b) => b === 0)) break;
|
|
1830
|
+
const name = header.subarray(0, 100).toString("utf8").replace(/\0.*$/, "");
|
|
1831
|
+
const sizeOct = header.subarray(124, 135).toString("utf8").replace(/\0.*$/, "").trim();
|
|
1832
|
+
const size = parseInt(sizeOct, 8) || 0;
|
|
1833
|
+
const content = tar.subarray(offset, offset + size);
|
|
1834
|
+
offset += size + (512 - size % 512) % 512;
|
|
1835
|
+
if (!name || name.endsWith("/")) continue;
|
|
1836
|
+
const base = name.replace(/^package\//, "");
|
|
1837
|
+
if (base === "package.json") {
|
|
1838
|
+
writeFileSync9(join14(outDir, "package.json"), content);
|
|
1839
|
+
} else if (base === "api.d.ts" || base === "index.d.ts") {
|
|
1840
|
+
writeFileSync9(join14(outDir, "index.d.ts"), content);
|
|
1841
|
+
wroteContract = true;
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
if (!wroteContract && !existsSync9(join14(outDir, "index.d.ts"))) {
|
|
1845
|
+
throw new Error("types tarball missing package/api.d.ts (or index.d.ts)");
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
async function addPackage(opts) {
|
|
1849
|
+
const absDir = resolve8(opts.pluginDir);
|
|
1850
|
+
const { name, range } = parseSpec(opts.spec);
|
|
1851
|
+
const creds = loadCredentials();
|
|
1852
|
+
const client = new RegistryClient({
|
|
1853
|
+
baseUrl: opts.registryUrl || creds?.registryUrl || defaultRegistryUrl(),
|
|
1854
|
+
token: creds?.token
|
|
1855
|
+
});
|
|
1856
|
+
const resolved = await client.resolve(name, range);
|
|
1857
|
+
if (!resolved.hasTypes) {
|
|
1858
|
+
throw new Error(`${name}@${resolved.version} has no types artifact (runtime-only package?)`);
|
|
1859
|
+
}
|
|
1860
|
+
const tgz = await client.downloadTypes(name, resolved.version);
|
|
1861
|
+
const typesDir = join14(absDir, ".s2script", "types", ...name.split("/"));
|
|
1862
|
+
extractTypesTarball(tgz, typesDir);
|
|
1863
|
+
const pkgPath = join14(absDir, "package.json");
|
|
1864
|
+
const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
|
|
1865
|
+
pkg.s2script = pkg.s2script ?? {};
|
|
1866
|
+
pkg.s2script.pluginDependencies = pkg.s2script.pluginDependencies ?? {};
|
|
1867
|
+
const major = resolved.version.split(".")[0] ?? "0";
|
|
1868
|
+
pkg.s2script.pluginDependencies[name] = range.startsWith("^") || range === "*" ? `^${major}.0.0` : range.includes(".") ? range : `^${resolved.version}`;
|
|
1869
|
+
writeFileSync9(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
1870
|
+
const registryUrl = opts.registryUrl || creds?.registryUrl || defaultRegistryUrl();
|
|
1871
|
+
const npmrcLine = ensureScopeNpmrc(absDir, name, registryUrl);
|
|
1872
|
+
return {
|
|
1873
|
+
name: resolved.name,
|
|
1874
|
+
version: resolved.version,
|
|
1875
|
+
reviewState: resolved.reviewState,
|
|
1876
|
+
typesDir,
|
|
1877
|
+
npmrcLine
|
|
1878
|
+
};
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1508
1881
|
// src/cli.ts
|
|
1509
1882
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
1510
|
-
import { dirname as dirname3, join as
|
|
1883
|
+
import { dirname as dirname3, join as join15, resolve as resolve9 } from "node:path";
|
|
1511
1884
|
var argv = process.argv.slice(2);
|
|
1512
1885
|
var command = argv[0];
|
|
1513
1886
|
function repoRootFromCli() {
|
|
1514
|
-
return
|
|
1887
|
+
return join15(dirname3(fileURLToPath3(import.meta.url)), "..", "..", "..");
|
|
1515
1888
|
}
|
|
1516
1889
|
function parseFlag(args, name) {
|
|
1517
1890
|
const eq = args.find((a) => a.startsWith(`${name}=`));
|
|
@@ -1600,6 +1973,68 @@ if (command === "gen-schema") {
|
|
|
1600
1973
|
console.error(String(e instanceof Error ? e.message : e));
|
|
1601
1974
|
process.exit(1);
|
|
1602
1975
|
}
|
|
1976
|
+
} else if (command === "login") {
|
|
1977
|
+
const args = argv.slice(1);
|
|
1978
|
+
try {
|
|
1979
|
+
const creds = await loginInteractive({
|
|
1980
|
+
token: parseFlag(args, "--token"),
|
|
1981
|
+
registryUrl: parseFlag(args, "--registry") || defaultRegistryUrl(),
|
|
1982
|
+
ci: hasFlag(args, "--ci")
|
|
1983
|
+
});
|
|
1984
|
+
console.log(`logged in \u2192 ${creds.registryUrl} (credentials saved)`);
|
|
1985
|
+
} catch (e) {
|
|
1986
|
+
console.error(String(e instanceof Error ? e.message : e));
|
|
1987
|
+
process.exit(1);
|
|
1988
|
+
}
|
|
1989
|
+
} else if (command === "deploy" || command === "publish") {
|
|
1990
|
+
const args = argv.slice(1);
|
|
1991
|
+
const pos = positionals(args, ["--packages-dir", "--registry"]);
|
|
1992
|
+
const dir = pos[0] ?? ".";
|
|
1993
|
+
try {
|
|
1994
|
+
const packagesDir = resolvePackagesDir({
|
|
1995
|
+
explicit: parseFlag(args, "--packages-dir"),
|
|
1996
|
+
pluginDir: resolve9(dir),
|
|
1997
|
+
fromCliUrl: import.meta.url
|
|
1998
|
+
});
|
|
1999
|
+
const result = await deployPlugin({
|
|
2000
|
+
dir,
|
|
2001
|
+
packagesDir,
|
|
2002
|
+
registryUrl: parseFlag(args, "--registry"),
|
|
2003
|
+
ci: hasFlag(args, "--ci")
|
|
2004
|
+
});
|
|
2005
|
+
console.log(`deployed ${result.name}@${result.version} (${result.reviewState})`);
|
|
2006
|
+
if (result.reviewState === "unreviewed") {
|
|
2007
|
+
console.log("note: Not reviewed by s2script \u2014 listed with a disclaimer until approved.");
|
|
2008
|
+
}
|
|
2009
|
+
} catch (e) {
|
|
2010
|
+
console.error(String(e instanceof Error ? e.message : e));
|
|
2011
|
+
process.exit(1);
|
|
2012
|
+
}
|
|
2013
|
+
} else if (command === "add") {
|
|
2014
|
+
const args = argv.slice(1);
|
|
2015
|
+
const pos = positionals(args, ["--dir", "--registry"]);
|
|
2016
|
+
const spec = pos[0];
|
|
2017
|
+
if (!spec) {
|
|
2018
|
+
console.error("Usage: s2s add <pkg>[@range]");
|
|
2019
|
+
process.exit(1);
|
|
2020
|
+
}
|
|
2021
|
+
try {
|
|
2022
|
+
const result = await addPackage({
|
|
2023
|
+
pluginDir: parseFlag(args, "--dir") || ".",
|
|
2024
|
+
spec,
|
|
2025
|
+
registryUrl: parseFlag(args, "--registry")
|
|
2026
|
+
});
|
|
2027
|
+
console.log(`added ${result.name}@${result.version} \u2192 ${result.typesDir}`);
|
|
2028
|
+
if (result.npmrcLine) {
|
|
2029
|
+
console.log(`npmrc: ${result.npmrcLine} (types-only; npm install ${result.name} also works)`);
|
|
2030
|
+
}
|
|
2031
|
+
if (result.reviewState === "unreviewed") {
|
|
2032
|
+
console.log("note: Not reviewed by s2script \u2014 types pulled; use at your own risk.");
|
|
2033
|
+
}
|
|
2034
|
+
} catch (e) {
|
|
2035
|
+
console.error(String(e instanceof Error ? e.message : e));
|
|
2036
|
+
process.exit(1);
|
|
2037
|
+
}
|
|
1603
2038
|
} else if (command === "config" && argv[1] === "gen") {
|
|
1604
2039
|
const args = argv.slice(2);
|
|
1605
2040
|
const outDir = parseFlag(args, "--out") ?? process.cwd();
|
|
@@ -1623,7 +2058,7 @@ if (command === "gen-schema") {
|
|
|
1623
2058
|
try {
|
|
1624
2059
|
const packagesDir = resolvePackagesDir({
|
|
1625
2060
|
explicit: packagesDirFlag,
|
|
1626
|
-
pluginDir:
|
|
2061
|
+
pluginDir: resolve9(dir),
|
|
1627
2062
|
fromCliUrl: import.meta.url
|
|
1628
2063
|
});
|
|
1629
2064
|
console.log(await buildPlugin(dir, packagesDir));
|
|
@@ -1633,7 +2068,7 @@ if (command === "gen-schema") {
|
|
|
1633
2068
|
}
|
|
1634
2069
|
} else {
|
|
1635
2070
|
console.error(
|
|
1636
|
-
"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 config gen <plugin.s2sp...> --out <dir>\n s2s gen-schema [--check]\n s2s gen-events [--check]\n s2s gen-nav [--check]"
|
|
2071
|
+
"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)"
|
|
1637
2072
|
);
|
|
1638
2073
|
process.exit(1);
|
|
1639
2074
|
}
|
package/entity.d.ts
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
* time" footgun). Obtain refs from the engine (events, `findByClass`, `readHandle`, …).
|
|
16
16
|
*/
|
|
17
17
|
export declare class EntityRef {
|
|
18
|
+
/** This entity's slot index in the game entity system. Paired with the host-minted
|
|
19
|
+
* {@link EntityRef.id} for slot validation (books first, then identity-slot match). */
|
|
18
20
|
readonly index: number;
|
|
19
21
|
/** The host-minted liveness id for this ref (books key). Not the raw engine serial. */
|
|
20
22
|
readonly id: number;
|
|
@@ -80,21 +82,33 @@ export declare class EntityRef {
|
|
|
80
82
|
/** Write an int32 at the end of a pointer chain (each hop deref'd, liveness-gated at the root). Returns
|
|
81
83
|
* false on a stale ref or a null hop. Used to clear a flag on a pointer-referenced sub-object. */
|
|
82
84
|
writeInt32Via(pathOffs: number[], finalOff: number, value: number): boolean;
|
|
85
|
+
/** Read an i8 (sign-extended) at the end of a pointer chain (each hop deref'd, liveness-gated at the root); null if the root is stale or any hop is null. */
|
|
83
86
|
readInt8Via(pathOffs: number[], finalOff: number): number | null;
|
|
87
|
+
/** Read an i16 (sign-extended) through a pointer chain; null if the root is stale or any hop is null. */
|
|
84
88
|
readInt16Via(pathOffs: number[], finalOff: number): number | null;
|
|
89
|
+
/** Read a u8 through a pointer chain; null if the root is stale or any hop is null. */
|
|
85
90
|
readUInt8Via(pathOffs: number[], finalOff: number): number | null;
|
|
91
|
+
/** Read a u16 through a pointer chain; null if the root is stale or any hop is null. */
|
|
86
92
|
readUInt16Via(pathOffs: number[], finalOff: number): number | null;
|
|
93
|
+
/** Read a u32 through a pointer chain; null if the root is stale or any hop is null. */
|
|
87
94
|
readUInt32Via(pathOffs: number[], finalOff: number): number | null;
|
|
95
|
+
/** Read an f32 through a pointer chain; null if the root is stale or any hop is null. */
|
|
88
96
|
readFloat32Via(pathOffs: number[], finalOff: number): number | null;
|
|
97
|
+
/** Read a bool through a pointer chain; null if the root is stale or any hop is null. */
|
|
89
98
|
readBoolVia(pathOffs: number[], finalOff: number): boolean | null;
|
|
99
|
+
/** Read a u64 as a BigInt through a pointer chain; null if the root is stale or any hop is null. */
|
|
90
100
|
readUInt64Via(pathOffs: number[], finalOff: number): bigint | null;
|
|
101
|
+
/** Read an i64 as a BigInt through a pointer chain; null if the root is stale or any hop is null. */
|
|
91
102
|
readInt64Via(pathOffs: number[], finalOff: number): bigint | null;
|
|
103
|
+
/** Decode a `CEntityHandle` at the end of a pointer chain into a liveness-gated {@link EntityRef}; null if the root is stale, any hop is null, or the handle is dead. */
|
|
92
104
|
readHandleVia(pathOffs: number[], finalOff: number): EntityRef | null;
|
|
93
105
|
/** Write a scalar through a pointer chain (write mirror of `read*Via`). Liveness-gated at the root;
|
|
94
106
|
* returns false on a stale ref, an unresolved hop, or a bad offset/kind. Does NOT notifyStateChanged —
|
|
95
107
|
* the caller decides (many sub-object fields, e.g. the fire gate, are server-authoritative).
|
|
96
108
|
* (`writeInt32Via` is declared above alongside `readInt32Via`.) */
|
|
97
109
|
writeFloat32Via(pathOffs: number[], finalOff: number, value: number): boolean;
|
|
110
|
+
/** Write a bool through a pointer chain (write mirror of {@link EntityRef.readBoolVia}; shares the
|
|
111
|
+
* no-notify semantics on {@link EntityRef.writeFloat32Via}). False on a stale ref, an unresolved hop, or a bad offset/kind. */
|
|
98
112
|
writeBoolVia(pathOffs: number[], finalOff: number, value: boolean): boolean;
|
|
99
113
|
/** Read a `CEntityHandle` at `offset`, decode it, and return a live `EntityRef` — or null if stale/invalid. */
|
|
100
114
|
readHandle(offset: number): EntityRef | null;
|
|
@@ -138,7 +152,12 @@ export type EntityKeyValueMap = { [key: string]: string | number | boolean };
|
|
|
138
152
|
/** Create a new entity by class name. WITHOUT keyvalues: create only — set fields, then call
|
|
139
153
|
* `.spawn()`. WITH keyvalues: create + DispatchSpawn(keyvalues) in one call — a non-null result is
|
|
140
154
|
* a LIVE, SPAWNED entity (on spawn failure the entity is removed and null returned). The created
|
|
141
|
-
* entity is game-world-owned (NOT auto-removed on plugin unload) — the plugin owns cleanup.
|
|
155
|
+
* entity is game-world-owned (NOT auto-removed on plugin unload) — the plugin owns cleanup.
|
|
156
|
+
* @example
|
|
157
|
+
* import { createEntity } from "@s2script/sdk/entity";
|
|
158
|
+
* const wt = createEntity("point_worldtext", { message: "s2-ekv-proof", fullbright: true });
|
|
159
|
+
* if (wt) wt.remove();
|
|
160
|
+
*/
|
|
142
161
|
export declare function createEntity(className: string, keyvalues?: EntityKeyValueMap): EntityRef | null;
|
|
143
162
|
|
|
144
163
|
/** The payload delivered to an `Entity.onOutput` handler. */
|
|
@@ -155,6 +174,13 @@ export interface OutputEvent {
|
|
|
155
174
|
delay: number;
|
|
156
175
|
}
|
|
157
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Entry point for entity lookup by class name.
|
|
179
|
+
* @example
|
|
180
|
+
* import { Entity } from "@s2script/sdk/entity";
|
|
181
|
+
* const triggers = Entity.findByClass("trigger_multiple");
|
|
182
|
+
* for (const t of triggers) console.log(t.index, t.name);
|
|
183
|
+
*/
|
|
158
184
|
export declare const Entity: {
|
|
159
185
|
/** Find every entity whose designer-name (class) exactly matches `className`. Returns liveness-gated refs. */
|
|
160
186
|
findByClass(className: string): EntityRef[];
|