@s2script/sdk 0.18.0 → 0.20.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.
Files changed (3) hide show
  1. package/dist/cli.js +119 -6
  2. package/package.json +1 -1
  3. package/ws.d.ts +21 -2
package/dist/cli.js CHANGED
@@ -3769,6 +3769,12 @@ function generatedDeclarationFiles(pluginDir) {
3769
3769
  const gd = join7(pluginDir, ".s2script", "gamedata.d.ts");
3770
3770
  return existsSync6(gd) ? [gd] : [];
3771
3771
  }
3772
+ function gamePackageDeclarationFiles(pkg, packagesDir) {
3773
+ const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
3774
+ if (!("@s2script/cs2" in deps)) return [];
3775
+ const hooks = join7(packagesDir, "cs2", "hooks.generated.d.ts");
3776
+ return existsSync6(hooks) ? [hooks] : [];
3777
+ }
3772
3778
  function declaredModules(dtsFiles) {
3773
3779
  const out = /* @__PURE__ */ new Set();
3774
3780
  for (const f of dtsFiles) {
@@ -3837,7 +3843,8 @@ function typecheckPlugin(pluginDir, opts) {
3837
3843
  entry,
3838
3844
  join7(packagesDir, "sdk", "globals.d.ts"),
3839
3845
  ...localDts,
3840
- ...generatedDeclarationFiles(absDir)
3846
+ ...generatedDeclarationFiles(absDir),
3847
+ ...gamePackageDeclarationFiles(pkg, packagesDir)
3841
3848
  ];
3842
3849
  const tmp = mkdtempSync(join7(tmpdir(), "s2tc-"));
3843
3850
  try {
@@ -8528,7 +8535,7 @@ async function run8(argv2) {
8528
8535
  }
8529
8536
 
8530
8537
  // src/commands/codegen.ts
8531
- import { dirname as dirname6, join as join27 } from "node:path";
8538
+ import { dirname as dirname6, join as join28 } from "node:path";
8532
8539
  import { fileURLToPath as fileURLToPath3 } from "node:url";
8533
8540
 
8534
8541
  // src/schemagen/gen.ts
@@ -9406,9 +9413,102 @@ function runGenNav(repoRoot2, opts) {
9406
9413
  return { wrappers: model.wrappers.length, fields, drift };
9407
9414
  }
9408
9415
 
9416
+ // src/hookgen/gen.ts
9417
+ import { readFileSync as readFileSync22, writeFileSync as writeFileSync17 } from "node:fs";
9418
+ import { join as join27 } from "node:path";
9419
+
9420
+ // src/hookgen/model.ts
9421
+ function capitalize(name) {
9422
+ return name.length === 0 ? name : name.charAt(0).toUpperCase() + name.slice(1);
9423
+ }
9424
+ function buildHookModel(hooks) {
9425
+ const byNs = /* @__PURE__ */ new Map();
9426
+ for (const name of Object.keys(hooks).sort()) {
9427
+ const decl = hooks[name] ?? {};
9428
+ const ns = decl.expose?.ctx;
9429
+ if (!ns) continue;
9430
+ const paramNames = decl.params ?? [];
9431
+ const mutableSet = new Set(decl.mutable ?? []);
9432
+ const params = paramNames.map((p) => ({ name: p, mutable: mutableSet.has(p) }));
9433
+ const receiverAs = decl.receiver?.kind === "entity" ? decl.receiver.as ?? null : null;
9434
+ const list = byNs.get(ns) ?? [];
9435
+ list.push({ name, viewIface: `${capitalize(name)}View`, params, receiverAs });
9436
+ byNs.set(ns, list);
9437
+ }
9438
+ return Array.from(byNs.keys()).sort().map((ns) => ({ ns, ctxIface: `Ctx${capitalize(ns)}`, hooks: byNs.get(ns) }));
9439
+ }
9440
+
9441
+ // src/hookgen/emit-dts.ts
9442
+ var HEADER6 = "// GENERATED by `s2script gen-hooks` from gamedata/cs2/game.cs2.jsonc's `hooks` \u2014 DO NOT EDIT.";
9443
+ function emitViewIface(h2) {
9444
+ const out = [`export interface ${h2.viewIface} {`];
9445
+ for (const p of h2.params) {
9446
+ const mod = p.mutable ? "" : "readonly ";
9447
+ out.push(` ${mod}${p.name}: number;`);
9448
+ }
9449
+ if (h2.receiverAs !== null) {
9450
+ out.push(` readonly ${h2.receiverAs}: EntityRef | null;`);
9451
+ }
9452
+ out.push("}", "");
9453
+ return out;
9454
+ }
9455
+ function emitHookDts(model) {
9456
+ const out = [
9457
+ HEADER6,
9458
+ 'import type { HookResultValue } from "@s2script/sdk/events";',
9459
+ 'import type { EntityRef } from "@s2script/sdk/entity";',
9460
+ ""
9461
+ ];
9462
+ for (const { hooks } of model) {
9463
+ for (const h2 of hooks) {
9464
+ out.push(...emitViewIface(h2));
9465
+ }
9466
+ }
9467
+ for (const { ctxIface, hooks } of model) {
9468
+ out.push(`export interface ${ctxIface} {`);
9469
+ for (const h2 of hooks) {
9470
+ out.push(` ${h2.name}(handler: (view: ${h2.viewIface}) => HookResultValue | void): void;`);
9471
+ }
9472
+ out.push("}", "");
9473
+ }
9474
+ out.push('declare module "@s2script/sdk/plugin" {');
9475
+ out.push(" interface PluginContext {");
9476
+ for (const { ns, ctxIface } of model) {
9477
+ out.push(` readonly ${ns}: ${ctxIface};`);
9478
+ }
9479
+ out.push(" }");
9480
+ out.push("}");
9481
+ return out.join("\n") + "\n";
9482
+ }
9483
+
9484
+ // src/hookgen/gen.ts
9485
+ var GAMEDATA_PATH = "gamedata/cs2/game.cs2.jsonc";
9486
+ var DTS_OUT4 = "packages/cs2/hooks.generated.d.ts";
9487
+ function runGenHooks(repoRoot2, opts) {
9488
+ const raw = readFileSync22(join27(repoRoot2, GAMEDATA_PATH), "utf8");
9489
+ const gd = JSON.parse(stripJsonComments(raw));
9490
+ const hooks = gd.hooks ?? {};
9491
+ const model = buildHookModel(hooks);
9492
+ const dts = emitHookDts(model);
9493
+ const drift = [];
9494
+ const abs = join27(repoRoot2, DTS_OUT4);
9495
+ if (opts.check) {
9496
+ let cur = "";
9497
+ try {
9498
+ cur = readFileSync22(abs, "utf8");
9499
+ } catch {
9500
+ }
9501
+ if (cur !== dts) drift.push(DTS_OUT4);
9502
+ } else {
9503
+ writeFileSync17(abs, dts);
9504
+ }
9505
+ const hookCount = model.reduce((n3, ns) => n3 + ns.hooks.length, 0);
9506
+ return { hooks: hookCount, drift };
9507
+ }
9508
+
9409
9509
  // src/commands/codegen.ts
9410
9510
  function repoRoot() {
9411
- return join27(dirname6(fileURLToPath3(import.meta.url)), "..", "..", "..");
9511
+ return join28(dirname6(fileURLToPath3(import.meta.url)), "..", "..", "..");
9412
9512
  }
9413
9513
  async function run9(kind, argv2) {
9414
9514
  const root = repoRoot();
@@ -9437,7 +9537,7 @@ async function run9(kind, argv2) {
9437
9537
  } else {
9438
9538
  console.log(`gen-events: wrote ${r2.events} events`);
9439
9539
  }
9440
- } else {
9540
+ } else if (kind === "nav") {
9441
9541
  const r2 = runGenNav(root, { check });
9442
9542
  if (check) {
9443
9543
  if (r2.drift.length) {
@@ -9449,6 +9549,18 @@ async function run9(kind, argv2) {
9449
9549
  } else {
9450
9550
  console.log(`gen-nav: wrote ${r2.wrappers} wrappers, ${r2.fields} fields`);
9451
9551
  }
9552
+ } else {
9553
+ const r2 = runGenHooks(root, { check });
9554
+ if (check) {
9555
+ if (r2.drift.length) {
9556
+ console.error(`FAIL: generated files out of date \u2014 run \`s2s gen-hooks\`:
9557
+ ${r2.drift.join("\n ")}`);
9558
+ process.exit(1);
9559
+ }
9560
+ console.log(`hook codegen up to date (${r2.hooks} hooks)`);
9561
+ } else {
9562
+ console.log(`gen-hooks: wrote ${r2.hooks} hooks`);
9563
+ }
9452
9564
  }
9453
9565
  }
9454
9566
 
@@ -9464,7 +9576,8 @@ var COMMANDS = [
9464
9576
  { name: "config", summary: "Emit a plugin's default config file(s)", run: run8 },
9465
9577
  { name: "gen-schema", summary: "Regenerate schema accessors", run: (a2) => run9("schema", a2) },
9466
9578
  { name: "gen-events", summary: "Regenerate the event catalog", run: (a2) => run9("events", a2) },
9467
- { name: "gen-nav", summary: "Regenerate nav accessors", run: (a2) => run9("nav", a2) }
9579
+ { name: "gen-nav", summary: "Regenerate nav accessors", run: (a2) => run9("nav", a2) },
9580
+ { name: "gen-hooks", summary: "Regenerate the ctx hook augmentation", run: (a2) => run9("hooks", a2) }
9468
9581
  ];
9469
9582
  function find(name) {
9470
9583
  if (name === "publish") return COMMANDS.find((c2) => c2.name === "deploy");
@@ -9487,7 +9600,7 @@ var argv = process.argv.slice(2);
9487
9600
  var command = argv[0];
9488
9601
  function usage() {
9489
9602
  console.error(
9490
- "Usage:\n s2s create [path] [--game cs2|none] [--name <pkg>] [--template minimal] [--library]\n [--install npm|pnpm|yarn|bun|none] [--no-install] [-y]\n s2s create --workspace <dir>\n s2s build [dir|workspace root] [--filter <pattern>]... [--stamp-version <v>]\n [--packages-dir <path>]\n s2s login [--token s2s_\u2026] [--registry <url>]\n s2s deploy [dir|workspace root] [--filter <pattern>]... [--dry-run] [--yes|--ci]\n [--registry <url>] [--packages-dir <path>]\n s2s version [dir] [--since <ref>]\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)"
9603
+ "Usage:\n s2s create [path] [--game cs2|none] [--name <pkg>] [--template minimal] [--library]\n [--install npm|pnpm|yarn|bun|none] [--no-install] [-y]\n s2s create --workspace <dir>\n s2s build [dir|workspace root] [--filter <pattern>]... [--stamp-version <v>]\n [--packages-dir <path>]\n s2s login [--token s2s_\u2026] [--registry <url>]\n s2s deploy [dir|workspace root] [--filter <pattern>]... [--dry-run] [--yes|--ci]\n [--registry <url>] [--packages-dir <path>]\n s2s version [dir] [--since <ref>]\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 s2s gen-hooks [--check]\n\nEnv: S2SCRIPT_REGISTRY_URL S2SCRIPT_TOKEN (CI deploy)"
9491
9604
  );
9492
9605
  }
9493
9606
  if (!command) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@s2script/sdk",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
4
4
  "license": "MIT OR Apache-2.0",
5
5
  "description": "s2script SDK — the builtin capability types (imported as @s2script/sdk/entity, …) plus the `s2s` CLI for building Source 2 / CS2 plugins.",
6
6
  "type": "module",
package/ws.d.ts CHANGED
@@ -13,17 +13,36 @@ export interface WebSocket {
13
13
  /** Close the connection. */
14
14
  close(): void;
15
15
  }
16
+ /** Options for {@link WebSocket.connect}. */
17
+ export interface WebSocketInit {
18
+ /**
19
+ * Extra headers to send on the opening handshake — the usual reason being a
20
+ * credential the server requires, e.g. `Authorization: Bearer <token>`.
21
+ *
22
+ * Headers the handshake owns are refused, and the connect rejects rather than
23
+ * silently dropping them: `Host`, `Connection`, `Upgrade`, any
24
+ * `Sec-WebSocket-*`, `Content-Length`, `Transfer-Encoding`. Values containing
25
+ * control characters are refused for the same reason.
26
+ */
27
+ headers?: Record<string, string>;
28
+ }
29
+
16
30
  /** Entry point for opening WebSocket connections. */
17
31
  export declare const WebSocket: {
18
32
  /**
19
33
  * Connect to a WebSocket server (`wss://` for TLS) off the game thread.
20
34
  * @returns Resolves on the open handshake with the live {@link WebSocket} handle.
21
- * @throws Rejects on connect failure (bad URL, refused, TLS/handshake error).
35
+ * @throws Rejects on connect failure (bad URL, refused, TLS/handshake error, or a refused header).
22
36
  * @example
23
37
  * import { WebSocket } from "@s2script/sdk/ws";
24
38
  * const ws = await WebSocket.connect("wss://ws.postman-echo.com/raw");
25
39
  * ws.onMessage((data) => { console.log("echo:", data); ws.close(); });
26
40
  * ws.send("hello-from-s2script");
41
+ * @example
42
+ * // A server that authenticates the handshake rather than the first frame.
43
+ * const ws = await WebSocket.connect("wss://maul.example/v2/ws", {
44
+ * headers: { Authorization: `Bearer ${jwt}` },
45
+ * });
27
46
  */
28
- connect(url: string): Promise<WebSocket>;
47
+ connect(url: string, init?: WebSocketInit): Promise<WebSocket>;
29
48
  };