@s2script/sdk 0.5.0 → 0.6.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-APACHE +201 -0
- package/LICENSE-MIT +21 -0
- package/README.md +40 -6
- 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 +65 -4
- 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 +6 -3
- 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/db.d.ts
CHANGED
|
@@ -1,26 +1,64 @@
|
|
|
1
1
|
/** @s2script/db — engine-generic async SQLite database. NO runtime code (injected as __s2pkg_db). */
|
|
2
|
+
|
|
3
|
+
/** A value bindable as a SQL parameter or returned in a {@link Row}. */
|
|
2
4
|
export type SqlValue = string | number | boolean | null;
|
|
5
|
+
/** One query result row — a column-name→{@link SqlValue} map. */
|
|
3
6
|
export type Row = Record<string, SqlValue>;
|
|
4
|
-
|
|
7
|
+
/** The outcome of a non-SELECT statement run via {@link Database.execute}. */
|
|
8
|
+
export interface ExecuteResult {
|
|
9
|
+
/** Rows affected by the statement (INSERT/UPDATE/DELETE). */
|
|
10
|
+
changes: number;
|
|
11
|
+
/** Rowid of the last inserted row (0 if the statement inserted nothing). */
|
|
12
|
+
lastInsertId: number;
|
|
13
|
+
}
|
|
5
14
|
|
|
15
|
+
/** A driver-owned connection — what a {@link Driver} hands back from {@link Driver.connect}. */
|
|
6
16
|
export interface DriverConnection {
|
|
17
|
+
/** Run a SELECT and resolve every result {@link Row}; `params` bind `?` placeholders. */
|
|
7
18
|
query(sql: string, params?: SqlValue[]): Promise<Row[]>;
|
|
19
|
+
/** Run a non-SELECT statement and resolve its {@link ExecuteResult}; `params` bind `?` placeholders. */
|
|
8
20
|
execute(sql: string, params?: SqlValue[]): Promise<ExecuteResult>;
|
|
21
|
+
/** Close the underlying connection. */
|
|
9
22
|
close(): Promise<void>;
|
|
10
23
|
}
|
|
11
|
-
|
|
24
|
+
/** Named-connection config resolved by {@link Database.open} and passed to {@link Driver.connect}. */
|
|
25
|
+
export interface ConnectionConfig {
|
|
26
|
+
/** Driver name to connect with (e.g. `"sqlite"`). */
|
|
27
|
+
driver: string;
|
|
28
|
+
/** The connection's configured name. */
|
|
29
|
+
name: string;
|
|
30
|
+
/** Driver-specific options (path, host, credentials, …). */
|
|
31
|
+
[k: string]: unknown;
|
|
32
|
+
}
|
|
33
|
+
/** A registered database backend; register a custom one via {@link Database.registerDriver}. */
|
|
12
34
|
export interface Driver {
|
|
35
|
+
/** The driver's name, matched against {@link ConnectionConfig.driver}. */
|
|
13
36
|
readonly name: string;
|
|
37
|
+
/** Open a connection for `config`, resolving a {@link DriverConnection}. */
|
|
14
38
|
connect(config: ConnectionConfig): Promise<DriverConnection>;
|
|
15
39
|
}
|
|
16
40
|
/** A live database connection (delegates to its driver). */
|
|
17
41
|
export interface Database {
|
|
42
|
+
/** Run a SELECT and resolve every result {@link Row}; `params` bind `?` placeholders. */
|
|
18
43
|
query(sql: string, params?: SqlValue[]): Promise<Row[]>;
|
|
44
|
+
/** Run a non-SELECT statement and resolve its {@link ExecuteResult}; `params` bind `?` placeholders. */
|
|
19
45
|
execute(sql: string, params?: SqlValue[]): Promise<ExecuteResult>;
|
|
46
|
+
/** Close the connection. */
|
|
20
47
|
close(): Promise<void>;
|
|
21
48
|
}
|
|
49
|
+
/** Entry point for opening databases and registering drivers. */
|
|
22
50
|
export declare const Database: {
|
|
23
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* Open a connection by name (default `"default"`). Resolves the driver + config, then connects.
|
|
53
|
+
* @param name - Connection name from config; omit for `"default"`.
|
|
54
|
+
* @returns Resolves the connected {@link Database} handle.
|
|
55
|
+
* @throws Rejects if the name is unknown or the driver fails to connect.
|
|
56
|
+
* @example
|
|
57
|
+
* import { Database } from "@s2script/sdk/db";
|
|
58
|
+
* const db = await Database.open("clientprefs");
|
|
59
|
+
* await db.execute("CREATE TABLE IF NOT EXISTS cookies (steamid TEXT, name TEXT, value TEXT)");
|
|
60
|
+
* const rows = await db.query("SELECT value FROM cookies WHERE steamid = ?", ["76561199999999999"]);
|
|
61
|
+
*/
|
|
24
62
|
open(name?: string): Promise<Database>;
|
|
25
63
|
/** Register a custom driver (per-plugin context). SQLite is built in. */
|
|
26
64
|
registerDriver(driver: Driver): void;
|
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
|
}
|