@s2script/sdk 0.27.0 → 0.28.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/dist/cli.js CHANGED
@@ -600,7 +600,7 @@ var require_inc = __commonJS({
600
600
  "../../node_modules/semver/functions/inc.js"(exports, module) {
601
601
  "use strict";
602
602
  var SemVer = require_semver();
603
- var inc = (version, release, options, identifier, identifierBase) => {
603
+ var inc2 = (version, release, options, identifier, identifierBase) => {
604
604
  if (typeof options === "string") {
605
605
  identifierBase = identifier;
606
606
  identifier = options;
@@ -615,7 +615,7 @@ var require_inc = __commonJS({
615
615
  return null;
616
616
  }
617
617
  };
618
- module.exports = inc;
618
+ module.exports = inc2;
619
619
  }
620
620
  });
621
621
 
@@ -1989,7 +1989,7 @@ var require_semver2 = __commonJS({
1989
1989
  var parse = require_parse();
1990
1990
  var valid2 = require_valid();
1991
1991
  var clean = require_clean();
1992
- var inc = require_inc();
1992
+ var inc2 = require_inc();
1993
1993
  var diff = require_diff();
1994
1994
  var major2 = require_major();
1995
1995
  var minor = require_minor();
@@ -2028,7 +2028,7 @@ var require_semver2 = __commonJS({
2028
2028
  parse,
2029
2029
  valid: valid2,
2030
2030
  clean,
2031
- inc,
2031
+ inc: inc2,
2032
2032
  diff,
2033
2033
  major: major2,
2034
2034
  minor,
@@ -6143,11 +6143,110 @@ function formatRangeViolations(violations) {
6143
6143
  ` + lines.join("\n");
6144
6144
  }
6145
6145
 
6146
+ // src/workspace/bundle.ts
6147
+ var MAX_BUNDLE_MEMBERS = 64;
6148
+ function isBundleWorkspace(ws) {
6149
+ return ws.pkg.s2script?.kind === "bundle";
6150
+ }
6151
+ function scopeOf(name) {
6152
+ if (!name.startsWith("@")) return null;
6153
+ const slash = name.indexOf("/");
6154
+ return slash > 1 ? name.slice(1, slash) : null;
6155
+ }
6156
+ function bundleProblems(ws) {
6157
+ if (!isBundleWorkspace(ws)) return [];
6158
+ const pkg = ws.pkg;
6159
+ const problems = [];
6160
+ const where2 = "package.json (workspace root)";
6161
+ if (typeof pkg.name !== "string" || pkg.name === "") {
6162
+ problems.push(`${where2}: a bundle needs a "name" \u2014 it is the bundle's registry name`);
6163
+ }
6164
+ if (typeof pkg.version !== "string" || pkg.version === "") {
6165
+ problems.push(`${where2}: a bundle needs a "version"`);
6166
+ }
6167
+ if (pkg.private === true) {
6168
+ problems.push(
6169
+ `${where2}: s2script.kind is "bundle" but the root is "private": true \u2014 a private root is never published; remove "private" or drop the bundle kind`
6170
+ );
6171
+ }
6172
+ if (pkg.main !== void 0 || pkg.s2script?.main !== void 0) {
6173
+ problems.push(`${where2}: a bundle has no code of its own \u2014 remove "main"`);
6174
+ }
6175
+ const members = ws.plugins.filter((p) => !p.private);
6176
+ if (members.length === 0) {
6177
+ problems.push(`${where2}: a bundle needs at least one non-private plugin in s2script.workspace.plugins`);
6178
+ }
6179
+ if (members.length > MAX_BUNDLE_MEMBERS) {
6180
+ problems.push(`${where2}: a bundle may have at most ${MAX_BUNDLE_MEMBERS} members (found ${members.length})`);
6181
+ }
6182
+ const bundleScope = typeof pkg.name === "string" ? scopeOf(pkg.name) : null;
6183
+ for (const m of members) {
6184
+ if (scopeOf(m.name) !== bundleScope) {
6185
+ problems.push(
6186
+ `${m.relDir}: ${m.name} cannot be a member of ${String(pkg.name)} \u2014 a bundle and its members must share a scope (the registry only accepts members with the same publisher)`
6187
+ );
6188
+ }
6189
+ }
6190
+ const raw = pkg.s2script?.bundle;
6191
+ let optional = [];
6192
+ if (raw !== void 0) {
6193
+ const block2 = raw;
6194
+ if (!block2 || typeof block2 !== "object" || Array.isArray(block2)) {
6195
+ problems.push(`${where2}: s2script.bundle must be an object`);
6196
+ } else if (block2.optional !== void 0) {
6197
+ if (!Array.isArray(block2.optional) || block2.optional.some((o) => typeof o !== "string")) {
6198
+ problems.push(`${where2}: s2script.bundle.optional must be an array of member names`);
6199
+ } else {
6200
+ optional = block2.optional;
6201
+ }
6202
+ }
6203
+ }
6204
+ const memberNames = new Set(members.map((m) => m.name));
6205
+ for (const name of optional) {
6206
+ if (!memberNames.has(name)) {
6207
+ problems.push(
6208
+ `${where2}: s2script.bundle.optional names ${JSON.stringify(name)}, which is not a non-private plugin of this workspace`
6209
+ );
6210
+ }
6211
+ }
6212
+ if (members.length > 0 && members.every((m) => optional.includes(m.name))) {
6213
+ problems.push(`${where2}: every member is optional \u2014 a bundle needs at least one required member`);
6214
+ }
6215
+ return problems;
6216
+ }
6217
+ function readBundle(ws) {
6218
+ if (!isBundleWorkspace(ws)) return null;
6219
+ const optional = ws.pkg.s2script.bundle?.optional ?? [];
6220
+ return {
6221
+ name: ws.pkg.name,
6222
+ version: ws.pkg.version,
6223
+ members: ws.plugins.filter((p) => !p.private),
6224
+ optional: new Set(optional)
6225
+ };
6226
+ }
6227
+ function bundleMembers(bundle) {
6228
+ const out = {};
6229
+ for (const m of bundle.members) {
6230
+ out[m.name] = bundle.optional.has(m.name) ? { version: m.version, optional: true } : { version: m.version };
6231
+ }
6232
+ return out;
6233
+ }
6234
+ function bundleManifest(bundle) {
6235
+ return { id: bundle.name, version: bundle.version, kind: "bundle", members: bundleMembers(bundle) };
6236
+ }
6237
+ function sameMembers(a2, b) {
6238
+ const ka = Object.keys(a2).sort();
6239
+ const kb = Object.keys(b).sort();
6240
+ if (ka.length !== kb.length || ka.some((k, i2) => k !== kb[i2])) return false;
6241
+ return ka.every((k) => a2[k].version === b[k].version && !!a2[k].optional === !!b[k].optional);
6242
+ }
6243
+
6146
6244
  // src/workspace/preflight.ts
6147
6245
  function preflightProblems(ws) {
6148
6246
  const problems = [...indexInterfaces(ws.plugins).problems];
6149
6247
  const violations = checkSiblingRanges(ws.plugins);
6150
6248
  if (violations.length > 0) problems.push(formatRangeViolations(violations));
6249
+ problems.push(...bundleProblems(ws));
6151
6250
  return problems;
6152
6251
  }
6153
6252
  function preflightWorkspace(ws) {
@@ -7736,7 +7835,7 @@ async function runWorkspace(argv2, root, interactive, filters, stampVersion) {
7736
7835
  import { resolve as resolve15 } from "node:path";
7737
7836
 
7738
7837
  // src/registry/deploy.ts
7739
- import { readFileSync as readFileSync13 } from "node:fs";
7838
+ import { readdirSync as readdirSync4, readFileSync as readFileSync13 } from "node:fs";
7740
7839
  import { resolve as resolve14 } from "node:path";
7741
7840
 
7742
7841
  // src/types-pack.ts
@@ -7969,6 +8068,9 @@ var RegistryClient = class {
7969
8068
  if (opts.types && opts.types.length) {
7970
8069
  entries["types.tgz"] = new Uint8Array(opts.types);
7971
8070
  }
8071
+ if (opts.readme && opts.readme.trim()) {
8072
+ entries["README.md"] = new TextEncoder().encode(opts.readme);
8073
+ }
7972
8074
  const body = zipSync(entries, { level: 0 });
7973
8075
  const res = await this.request("deploy", `${this.baseUrl}/api/v1/deploy`, {
7974
8076
  method: "POST",
@@ -8005,10 +8107,12 @@ var RegistryClient = class {
8005
8107
  const res = await this.request("meta", u4);
8006
8108
  return this.json(res, "meta");
8007
8109
  }
8008
- async plan(name, range = "*") {
8110
+ /** `withMembers`: optional members of a bundle root to install too (ignored for a plugin root). */
8111
+ async plan(name, range = "*", withMembers) {
8009
8112
  const u4 = new URL(`${this.baseUrl}/api/v1/plan`);
8010
8113
  u4.searchParams.set("name", name);
8011
8114
  u4.searchParams.set("range", range);
8115
+ if (withMembers && withMembers.length > 0) u4.searchParams.set("with", withMembers.join(","));
8012
8116
  const res = await this.request("plan", u4);
8013
8117
  return this.json(res, "plan");
8014
8118
  }
@@ -8029,6 +8133,23 @@ function assertDeployable(pkg, label) {
8029
8133
  );
8030
8134
  }
8031
8135
  }
8136
+ var MAX_README_CHARS = 1e5;
8137
+ function readPackageReadme(pluginDir) {
8138
+ const names = readdirSync4(pluginDir).filter((n3) => /^readme(\.(md|markdown))?$/i.test(n3));
8139
+ if (names.length === 0) return null;
8140
+ const name = names.find((n3) => n3 === "README.md") ?? names.sort()[0];
8141
+ const text3 = readFileSync13(resolve14(pluginDir, name), "utf8").trim();
8142
+ if (!text3) return null;
8143
+ if (text3.length > MAX_README_CHARS) {
8144
+ throw new Error(
8145
+ `${name} is ${text3.length.toLocaleString("en-US")} characters; the registry accepts at most ${MAX_README_CHARS.toLocaleString("en-US")}. Shorten it, or link to longer docs from it.`
8146
+ );
8147
+ }
8148
+ return text3;
8149
+ }
8150
+ function withRegistryFields(manifest, pkg) {
8151
+ return pkg.repository === void 0 ? manifest : { ...manifest, repository: pkg.repository };
8152
+ }
8032
8153
  function assembleDeployArchive(pluginDir, pkg, outPath) {
8033
8154
  const absDir = resolve14(pluginDir);
8034
8155
  if (packageKind(pkg) === "library") {
@@ -8038,7 +8159,13 @@ function assembleDeployArchive(pluginDir, pkg, outPath) {
8038
8159
  throw new Error(`built archive missing manifest.json: ${outPath}`);
8039
8160
  }
8040
8161
  const manifest2 = JSON.parse(Buffer.from(manifestEntry2).toString("utf8"));
8041
- return { manifest: manifest2, s2sp: null, lib, types: null };
8162
+ return {
8163
+ manifest: withRegistryFields(manifest2, pkg),
8164
+ s2sp: null,
8165
+ lib,
8166
+ types: null,
8167
+ readme: readPackageReadme(absDir)
8168
+ };
8042
8169
  }
8043
8170
  const gate = assertPublishesTypes(pkg, absDir);
8044
8171
  if (!gate.ok) throw new Error(`publish gate failed: ${gate.error}`);
@@ -8057,7 +8184,13 @@ function assembleDeployArchive(pluginDir, pkg, outPath) {
8057
8184
  publishes: hasPublishes(manifest.publishes) ? manifest.publishes : void 0
8058
8185
  });
8059
8186
  }
8060
- return { manifest, s2sp, lib: null, types };
8187
+ return {
8188
+ manifest: withRegistryFields(manifest, pkg),
8189
+ s2sp,
8190
+ lib: null,
8191
+ types,
8192
+ readme: readPackageReadme(absDir)
8193
+ };
8061
8194
  }
8062
8195
  async function deployPlugin(opts) {
8063
8196
  const absDir = resolve14(opts.dir);
@@ -8070,13 +8203,13 @@ async function deployPlugin(opts) {
8070
8203
  );
8071
8204
  }
8072
8205
  const outPath = packageKind(pkg) === "library" ? await buildLibrary(absDir, opts.packagesDir) : await buildPlugin(absDir, opts.packagesDir);
8073
- const { manifest, s2sp, lib, types } = assembleDeployArchive(absDir, pkg, outPath);
8206
+ const archive = assembleDeployArchive(absDir, pkg, outPath);
8074
8207
  const client = new RegistryClient({
8075
8208
  baseUrl: opts.registryUrl || creds.registryUrl || defaultRegistryUrl(),
8076
8209
  token: creds.token,
8077
8210
  fetch: opts.fetch
8078
8211
  });
8079
- return client.deploy({ manifest, s2sp, lib, types });
8212
+ return client.deploy(archive);
8080
8213
  }
8081
8214
 
8082
8215
  // src/workspace/deploy-all.ts
@@ -8134,7 +8267,7 @@ function formatPlan(plan) {
8134
8267
  function isDuplicateVersionRejection(e) {
8135
8268
  return e instanceof RegistryError && e.status === 409;
8136
8269
  }
8137
- async function uploadPlan(plan, built, client) {
8270
+ async function uploadPlan(plan, built, client, opts = {}) {
8138
8271
  const results = [];
8139
8272
  for (const entry of plan) {
8140
8273
  if (entry.reason !== "PUBLISH") {
@@ -8149,6 +8282,7 @@ async function uploadPlan(plan, built, client) {
8149
8282
  }
8150
8283
  try {
8151
8284
  const archive = assembleDeployArchive(b.plugin.dir, b.plugin.pkg, b.outPath);
8285
+ if (opts.bundle !== void 0) archive.manifest = { ...archive.manifest, bundle: opts.bundle };
8152
8286
  const res = await client.deploy(archive);
8153
8287
  results.push({ plugin: entry.plugin, status: "published", detail: res.reviewState });
8154
8288
  } catch (e) {
@@ -8165,6 +8299,51 @@ async function uploadPlan(plan, built, client) {
8165
8299
  }
8166
8300
  return results;
8167
8301
  }
8302
+ async function publishedMembers(client, name, version) {
8303
+ let meta;
8304
+ try {
8305
+ meta = await client.meta(name);
8306
+ } catch {
8307
+ return null;
8308
+ }
8309
+ const versions = meta.versions ?? [];
8310
+ const hit = versions.find((v) => v.version === version);
8311
+ return hit ? hit.members ?? {} : null;
8312
+ }
8313
+ async function planBundle(bundle, client) {
8314
+ const manifest = bundleManifest(bundle);
8315
+ const published = await publishedMembers(client, bundle.name, bundle.version);
8316
+ if (published === null) return { bundle, reason: "PUBLISH", manifest };
8317
+ if (!sameMembers(published, bundleMembers(bundle))) {
8318
+ throw new Error(
8319
+ `bundle ${bundle.name}@${bundle.version} is already published with different members \u2014 bump the bundle's version (\`s2s version\` does this when a member changes) and deploy again`
8320
+ );
8321
+ }
8322
+ return { bundle, reason: "skip (already published)", manifest };
8323
+ }
8324
+ function formatBundlePlan(entry) {
8325
+ const count = entry.bundle.members.length;
8326
+ return ` ${entry.bundle.name} ${entry.bundle.version} ${entry.reason} (bundle of ${count} plugin${count === 1 ? "" : "s"})`;
8327
+ }
8328
+ async function uploadBundle(entry, memberResults, client) {
8329
+ if (entry.reason !== "PUBLISH") return { bundle: entry.bundle, status: "skipped", detail: entry.reason };
8330
+ if (memberResults.some((r2) => r2.status === "failed")) {
8331
+ return { bundle: entry.bundle, status: "skipped", detail: "a member failed to publish" };
8332
+ }
8333
+ try {
8334
+ const res = await client.deploy({ manifest: entry.manifest });
8335
+ return { bundle: entry.bundle, status: "published", detail: res.reviewState };
8336
+ } catch (e) {
8337
+ if (isDuplicateVersionRejection(e)) {
8338
+ return {
8339
+ bundle: entry.bundle,
8340
+ status: "skipped",
8341
+ detail: "skip (already published; its members were not verified)"
8342
+ };
8343
+ }
8344
+ return { bundle: entry.bundle, status: "failed", detail: e instanceof Error ? e.message : String(e) };
8345
+ }
8346
+ }
8168
8347
 
8169
8348
  // src/commands/deploy.ts
8170
8349
  async function run2(argv2) {
@@ -8259,18 +8438,23 @@ async function runWorkspaceDeploy(opts) {
8259
8438
  }
8260
8439
  const { ordered, built, unpublishableLibraries } = builtPluginsFromOutcomes(ws, build3.outcomes);
8261
8440
  const plan = await computePlan(ordered, (name, version) => isAlreadyPublished(client, name, version));
8441
+ const bundle = readBundle(ws);
8442
+ const bundleEntry = bundle ? await planBundle(bundle, client) : null;
8262
8443
  label("plan:");
8263
8444
  console.log(formatPlan(plan));
8445
+ if (bundleEntry) console.log(formatBundlePlan(bundleEntry));
8264
8446
  if (unpublishableLibraries.length > 0) {
8265
8447
  console.log(formatUnpublishableLibraries(unpublishableLibraries));
8266
8448
  }
8267
- const toPublish = publishCount(plan);
8449
+ const bundlePublishes = bundleEntry?.reason === "PUBLISH" ? 1 : 0;
8450
+ const toPublish = publishCount(plan) + bundlePublishes;
8451
+ const noun = (n3) => `${n3} package${n3 === 1 ? "" : "s"}`;
8268
8452
  if (toPublish === 0) {
8269
8453
  label("nothing to publish");
8270
8454
  return;
8271
8455
  }
8272
8456
  if (dryRun) {
8273
- label(`dry run \u2014 ${toPublish} plugin${toPublish === 1 ? "" : "s"} would publish, uploading nothing`);
8457
+ label(`dry run \u2014 ${noun(toPublish)} would publish, uploading nothing`);
8274
8458
  return;
8275
8459
  }
8276
8460
  if (!creds?.token) {
@@ -8285,7 +8469,7 @@ async function runWorkspaceDeploy(opts) {
8285
8469
  );
8286
8470
  }
8287
8471
  const proceed = await confirm2({
8288
- message: `Publish ${toPublish} plugin${toPublish === 1 ? "" : "s"} to ${client.baseUrl}?`,
8472
+ message: `Publish ${noun(toPublish)} to ${client.baseUrl}?`,
8289
8473
  initialValue: false
8290
8474
  });
8291
8475
  if (!proceed) {
@@ -8293,7 +8477,7 @@ async function runWorkspaceDeploy(opts) {
8293
8477
  process.exit(130);
8294
8478
  }
8295
8479
  }
8296
- const results = await uploadPlan(plan, built, client);
8480
+ const results = await uploadPlan(plan, built, client, { bundle: bundle?.name });
8297
8481
  let failures = 0;
8298
8482
  for (const r2 of results) {
8299
8483
  if (r2.status === "failed") failures++;
@@ -8304,8 +8488,18 @@ async function runWorkspaceDeploy(opts) {
8304
8488
  console.log(line);
8305
8489
  }
8306
8490
  }
8491
+ if (bundleEntry) {
8492
+ const r2 = await uploadBundle(bundleEntry, results, client);
8493
+ if (r2.status === "failed") failures++;
8494
+ const line = `${r2.bundle.name}@${r2.bundle.version} (bundle): ${r2.status}${r2.detail ? ` (${r2.detail})` : ""}`;
8495
+ if (interactive) {
8496
+ (r2.status === "failed" ? log2.error : r2.status === "published" ? log2.success : log2.info)(line);
8497
+ } else {
8498
+ console.log(line);
8499
+ }
8500
+ }
8307
8501
  if (failures > 0) {
8308
- throw new Error(`${failures} plugin${failures === 1 ? "" : "s"} failed to publish`);
8502
+ throw new Error(`${noun(failures)} failed to publish`);
8309
8503
  }
8310
8504
  }
8311
8505
 
@@ -8314,7 +8508,7 @@ import { resolve as resolve16 } from "node:path";
8314
8508
 
8315
8509
  // src/version/version.ts
8316
8510
  import { existsSync as existsSync11 } from "node:fs";
8317
- import { join as join17 } from "node:path";
8511
+ import { join as join18 } from "node:path";
8318
8512
 
8319
8513
  // src/version/changesets.ts
8320
8514
  var import_semver = __toESM(require_semver2(), 1);
@@ -8527,10 +8721,44 @@ function rewriteSiblingRanges(ws, releases, config) {
8527
8721
  return { rewrites, skips, touchedFiles };
8528
8722
  }
8529
8723
 
8724
+ // src/version/bundle-bump.ts
8725
+ var import_semver3 = __toESM(require_semver2(), 1);
8726
+ import { writeFileSync as writeFileSync7 } from "node:fs";
8727
+ import { join as join17 } from "node:path";
8728
+ var LEVEL = { none: 0, patch: 1, minor: 2, major: 3 };
8729
+ function plannedBundleBump(ws, releases) {
8730
+ const bundle = readBundle(ws);
8731
+ if (!bundle) return null;
8732
+ const members = new Set(bundle.members.map((m) => m.name));
8733
+ const memberReleases = releases.filter((r2) => members.has(r2.name) && r2.type !== "none");
8734
+ if (memberReleases.length === 0) return null;
8735
+ const type = memberReleases.reduce((t2, r2) => LEVEL[r2.type] > LEVEL[t2] ? r2.type : t2, "none");
8736
+ const to = (0, import_semver3.inc)(bundle.version, type);
8737
+ if (to === null) {
8738
+ throw new Error(`s2s version: the bundle's version ${JSON.stringify(bundle.version)} is not valid semver`);
8739
+ }
8740
+ return {
8741
+ name: bundle.name,
8742
+ from: bundle.version,
8743
+ to,
8744
+ type,
8745
+ because: memberReleases.map((r2) => `${r2.name}@${r2.newVersion}`)
8746
+ };
8747
+ }
8748
+ function bumpBundle(ws, releases) {
8749
+ const bump = plannedBundleBump(ws, releases);
8750
+ if (!bump) return { bump: null, touchedFiles: [] };
8751
+ const path = join17(ws.root, "package.json");
8752
+ const { json, indent, eol } = readJsonFile(path);
8753
+ json.version = bump.to;
8754
+ writeFileSync7(path, JSON.stringify(json, null, indent) + eol);
8755
+ return { bump, touchedFiles: [path] };
8756
+ }
8757
+
8530
8758
  // src/version/version.ts
8531
8759
  async function versionWorkspace(root, opts = {}) {
8532
8760
  const ws = loadWorkspace(root);
8533
- const configPath = join17(ws.root, ".changeset", "config.json");
8761
+ const configPath = join18(ws.root, ".changeset", "config.json");
8534
8762
  if (!existsSync11(configPath)) {
8535
8763
  throw new Error(
8536
8764
  `s2s version: ${configPath} does not exist \u2014 this workspace is not set up for changesets. Run \`npx changeset init\` at ${ws.root}.`
@@ -8545,13 +8773,15 @@ async function versionWorkspace(root, opts = {}) {
8545
8773
  const plan = cs.assembleReleasePlan(changesets, mirror, config, preState);
8546
8774
  const touchedFiles = await cs.applyReleasePlan(plan, real, config);
8547
8775
  const ranges = rewriteSiblingRanges(ws, plan.releases, config);
8776
+ const bundle = bumpBundle(ws, plan.releases);
8548
8777
  return {
8549
8778
  root: ws.root,
8550
8779
  changesetCount: changesets.length,
8551
8780
  releases: plan.releases,
8552
8781
  rewrites: ranges.rewrites,
8553
8782
  skips: ranges.skips,
8554
- touchedFiles: [...touchedFiles, ...ranges.touchedFiles],
8783
+ bundleBump: bundle.bump,
8784
+ touchedFiles: [...touchedFiles, ...ranges.touchedFiles, ...bundle.touchedFiles],
8555
8785
  changesetsVersions: cs.versions
8556
8786
  };
8557
8787
  }
@@ -8587,6 +8817,10 @@ async function run3(argv2) {
8587
8817
  for (const r2 of result.releases) {
8588
8818
  lines.push(`${r2.name} ${r2.oldVersion} -> ${r2.newVersion} (${r2.type})`);
8589
8819
  }
8820
+ if (result.bundleBump) {
8821
+ const b = result.bundleBump;
8822
+ lines.push(`${b.name} ${b.from} -> ${b.to} (${b.type}, bundle \u2014 follows ${b.because.join(", ")})`);
8823
+ }
8590
8824
  for (const rw of result.rewrites) {
8591
8825
  lines.push(`${rw.consumer} pluginDependencies[${rw.iface}] ${rw.from} -> ${rw.to}`);
8592
8826
  }
@@ -8611,16 +8845,16 @@ import { resolve as resolve18 } from "node:path";
8611
8845
  // src/registry/add.ts
8612
8846
  import {
8613
8847
  mkdirSync as mkdirSync6,
8614
- writeFileSync as writeFileSync9,
8848
+ writeFileSync as writeFileSync10,
8615
8849
  readFileSync as readFileSync17,
8616
8850
  existsSync as existsSync13
8617
8851
  } from "node:fs";
8618
- import { join as join20, resolve as resolve17 } from "node:path";
8852
+ import { join as join21, resolve as resolve17 } from "node:path";
8619
8853
  import { gunzipSync } from "node:zlib";
8620
8854
 
8621
8855
  // src/registry/npmrc.ts
8622
- import { existsSync as existsSync12, readFileSync as readFileSync16, writeFileSync as writeFileSync7 } from "node:fs";
8623
- import { join as join18 } from "node:path";
8856
+ import { existsSync as existsSync12, readFileSync as readFileSync16, writeFileSync as writeFileSync8 } from "node:fs";
8857
+ import { join as join19 } from "node:path";
8624
8858
  function npmCompatRegistryUrl(registryUrl) {
8625
8859
  return `${registryUrl.replace(/\/$/, "")}/npm/`;
8626
8860
  }
@@ -8636,7 +8870,7 @@ function ensureScopeNpmrc(projectDir, packageName, registryUrl) {
8636
8870
  const compat = npmCompatRegistryUrl(registryUrl);
8637
8871
  const key = `@${scope}:registry`;
8638
8872
  const line = `${key}=${compat}`;
8639
- const npmrcPath = join18(projectDir, ".npmrc");
8873
+ const npmrcPath = join19(projectDir, ".npmrc");
8640
8874
  let existing = existsSync12(npmrcPath) ? readFileSync16(npmrcPath, "utf8") : "";
8641
8875
  const lines = existing.split(/\r?\n/);
8642
8876
  const idx = lines.findIndex((l2) => l2.trimStart().startsWith(`${key}=`) || l2.trimStart().startsWith(`${key} =`));
@@ -8647,13 +8881,13 @@ function ensureScopeNpmrc(projectDir, packageName, registryUrl) {
8647
8881
  lines.push(line);
8648
8882
  }
8649
8883
  const out = lines.filter((l2, i2, a2) => !(l2 === "" && a2[i2 - 1] === "")).join("\n");
8650
- writeFileSync7(npmrcPath, out.endsWith("\n") ? out : out + "\n");
8884
+ writeFileSync8(npmrcPath, out.endsWith("\n") ? out : out + "\n");
8651
8885
  return line;
8652
8886
  }
8653
8887
 
8654
8888
  // src/registry/lib-extract.ts
8655
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync8 } from "node:fs";
8656
- import { join as join19 } from "node:path";
8889
+ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync9 } from "node:fs";
8890
+ import { join as join20 } from "node:path";
8657
8891
  var WANTED = /* @__PURE__ */ new Set(["index.js", "index.d.ts"]);
8658
8892
  function extractLibArchive(s2lib, outDir, meta) {
8659
8893
  const entries = unzipSync(new Uint8Array(s2lib));
@@ -8662,7 +8896,7 @@ function extractLibArchive(s2lib, outDir, meta) {
8662
8896
  mkdirSync5(outDir, { recursive: true });
8663
8897
  for (const [name, bytes] of Object.entries(entries)) {
8664
8898
  if (!WANTED.has(name)) continue;
8665
- writeFileSync8(join19(outDir, name), Buffer.from(bytes));
8899
+ writeFileSync9(join20(outDir, name), Buffer.from(bytes));
8666
8900
  }
8667
8901
  let apiVersion = "1.x";
8668
8902
  try {
@@ -8670,8 +8904,8 @@ function extractLibArchive(s2lib, outDir, meta) {
8670
8904
  if (typeof m.apiVersion === "string") apiVersion = m.apiVersion;
8671
8905
  } catch {
8672
8906
  }
8673
- writeFileSync8(
8674
- join19(outDir, "package.json"),
8907
+ writeFileSync9(
8908
+ join20(outDir, "package.json"),
8675
8909
  JSON.stringify(
8676
8910
  { name: meta.name, version: meta.version, main: "index.js", types: "index.d.ts", s2script: { kind: "library", apiVersion } },
8677
8911
  null,
@@ -8708,13 +8942,13 @@ function extractTypesTarball(tgz, outDir) {
8708
8942
  if (!name || name.endsWith("/")) continue;
8709
8943
  const base = name.replace(/^package\//, "");
8710
8944
  if (base === "package.json") {
8711
- writeFileSync9(join20(outDir, "package.json"), content);
8945
+ writeFileSync10(join21(outDir, "package.json"), content);
8712
8946
  } else if (base === "api.d.ts" || base === "index.d.ts") {
8713
- writeFileSync9(join20(outDir, "index.d.ts"), content);
8947
+ writeFileSync10(join21(outDir, "index.d.ts"), content);
8714
8948
  wroteContract = true;
8715
8949
  }
8716
8950
  }
8717
- if (!wroteContract && !existsSync13(join20(outDir, "index.d.ts"))) {
8951
+ if (!wroteContract && !existsSync13(join21(outDir, "index.d.ts"))) {
8718
8952
  throw new Error("types tarball missing package/api.d.ts (or index.d.ts)");
8719
8953
  }
8720
8954
  }
@@ -8732,16 +8966,21 @@ async function addPackage(opts) {
8732
8966
  fetch: opts.fetch
8733
8967
  });
8734
8968
  const resolved = await client.resolve(name, range);
8969
+ if (resolved.kind === "bundle") {
8970
+ throw new Error(
8971
+ `${name} is a bundle \u2014 it groups plugins for install and has nothing to add to a plugin. Install it on a server with \`s2s install ${name}\`, or \`s2s add\` the member plugin whose interface you use.`
8972
+ );
8973
+ }
8735
8974
  if (resolved.kind === "library") {
8736
8975
  const s2lib = await client.downloadLib(name, resolved.version);
8737
- const libDir = join20(absDir, ".s2script", "libs", ...name.split("/"));
8976
+ const libDir = join21(absDir, ".s2script", "libs", ...name.split("/"));
8738
8977
  extractLibArchive(s2lib, libDir, { name, version: resolved.version });
8739
- const pkgPath2 = join20(absDir, "package.json");
8978
+ const pkgPath2 = join21(absDir, "package.json");
8740
8979
  const pkg2 = JSON.parse(readFileSync17(pkgPath2, "utf8"));
8741
8980
  pkg2.s2script = pkg2.s2script ?? {};
8742
8981
  pkg2.s2script.libraries = pkg2.s2script.libraries ?? {};
8743
8982
  pkg2.s2script.libraries[name] = rangeForResolved(range, resolved.version);
8744
- writeFileSync9(pkgPath2, JSON.stringify(pkg2, null, 2) + "\n");
8983
+ writeFileSync10(pkgPath2, JSON.stringify(pkg2, null, 2) + "\n");
8745
8984
  return {
8746
8985
  kind: "library",
8747
8986
  name: resolved.name,
@@ -8758,14 +8997,14 @@ async function addPackage(opts) {
8758
8997
  throw new Error(`${name}@${resolved.version} has no types artifact (runtime-only package?)`);
8759
8998
  }
8760
8999
  const tgz = await client.downloadTypes(name, resolved.version);
8761
- const typesDir = join20(absDir, ".s2script", "types", ...name.split("/"));
9000
+ const typesDir = join21(absDir, ".s2script", "types", ...name.split("/"));
8762
9001
  extractTypesTarball(tgz, typesDir);
8763
- const pkgPath = join20(absDir, "package.json");
9002
+ const pkgPath = join21(absDir, "package.json");
8764
9003
  const pkg = JSON.parse(readFileSync17(pkgPath, "utf8"));
8765
9004
  pkg.s2script = pkg.s2script ?? {};
8766
9005
  pkg.s2script.pluginDependencies = pkg.s2script.pluginDependencies ?? {};
8767
9006
  pkg.s2script.pluginDependencies[name] = rangeForResolved(range, resolved.version);
8768
- writeFileSync9(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
9007
+ writeFileSync10(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
8769
9008
  const registryUrl = opts.registryUrl || creds?.registryUrl || defaultRegistryUrl();
8770
9009
  const npmrcLine = ensureScopeNpmrc(absDir, name, registryUrl);
8771
9010
  return {
@@ -8847,8 +9086,8 @@ import { existsSync as existsSync15 } from "node:fs";
8847
9086
 
8848
9087
  // src/registry/install.ts
8849
9088
  import { createHash as createHash4 } from "node:crypto";
8850
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync18, writeFileSync as writeFileSync10, renameSync, existsSync as existsSync14 } from "node:fs";
8851
- import { basename as basename2, join as join21, resolve as resolve19, sep as sep3 } from "node:path";
9089
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync18, writeFileSync as writeFileSync11, renameSync, existsSync as existsSync14 } from "node:fs";
9090
+ import { basename as basename2, join as join22, resolve as resolve19, sep as sep3 } from "node:path";
8852
9091
  function parseManifest(input) {
8853
9092
  if (!input || typeof input !== "object" || Array.isArray(input)) {
8854
9093
  throw new Error("manifest must be a JSON object");
@@ -8858,11 +9097,26 @@ function parseManifest(input) {
8858
9097
  throw new Error('manifest must have a "plugins" object (name -> version range)');
8859
9098
  }
8860
9099
  const out = {};
8861
- for (const [name, range] of Object.entries(plugins)) {
8862
- if (typeof range !== "string") throw new Error(`range for ${name} must be a string`);
8863
- out[name] = range;
9100
+ const withMembers = {};
9101
+ for (const [name, entry] of Object.entries(plugins)) {
9102
+ if (typeof entry === "string") {
9103
+ out[name] = entry;
9104
+ continue;
9105
+ }
9106
+ const obj2 = entry;
9107
+ if (!obj2 || typeof obj2 !== "object" || Array.isArray(obj2)) {
9108
+ throw new Error(`entry for ${name} must be a range string or { "range", "with" }`);
9109
+ }
9110
+ if (obj2.range !== void 0 && typeof obj2.range !== "string") {
9111
+ throw new Error(`range for ${name} must be a string`);
9112
+ }
9113
+ if (obj2.with !== void 0 && (!Array.isArray(obj2.with) || obj2.with.some((w) => typeof w !== "string"))) {
9114
+ throw new Error(`"with" for ${name} must be an array of optional member names`);
9115
+ }
9116
+ out[name] = obj2.range ?? "*";
9117
+ if (Array.isArray(obj2.with) && obj2.with.length > 0) withMembers[name] = obj2.with;
8864
9118
  }
8865
- return { plugins: out };
9119
+ return Object.keys(withMembers).length > 0 ? { plugins: out, with: withMembers } : { plugins: out };
8866
9120
  }
8867
9121
  function parseSpec2(spec) {
8868
9122
  const at = spec.lastIndexOf("@");
@@ -8889,12 +9143,12 @@ function safeFilename(e) {
8889
9143
  }
8890
9144
  return base;
8891
9145
  }
8892
- async function resolveMerged(client, specs) {
9146
+ async function resolveMerged(client, specs, withMembers = {}) {
8893
9147
  const chosen = /* @__PURE__ */ new Map();
8894
9148
  const warnings = [];
8895
9149
  const errors = [];
8896
9150
  for (const [name, range] of Object.entries(specs)) {
8897
- const plan = await client.plan(name, range);
9151
+ const plan = await client.plan(name, range, withMembers[name]);
8898
9152
  if (plan.errors.length) {
8899
9153
  errors.push(...plan.errors);
8900
9154
  continue;
@@ -8920,7 +9174,7 @@ async function resolveMerged(client, specs) {
8920
9174
  async function installPlan(opts) {
8921
9175
  const log3 = opts.log ?? (() => {
8922
9176
  });
8923
- const { install, warnings, errors } = await resolveMerged(opts.client, opts.specs);
9177
+ const { install, warnings, errors } = await resolveMerged(opts.client, opts.specs, opts.with);
8924
9178
  if (errors.length) throw new Error(`cannot resolve plugins:
8925
9179
  - ${errors.join("\n - ")}`);
8926
9180
  if (opts.reviewedOnly) {
@@ -8937,7 +9191,7 @@ async function installPlan(opts) {
8937
9191
  throw new Error(`refusing ${e.name}@${e.version}: registry did not provide a sha256 to verify against`);
8938
9192
  }
8939
9193
  const filename = safeFilename(e);
8940
- const dest = resolve19(join21(opts.dir, filename));
9194
+ const dest = resolve19(join22(opts.dir, filename));
8941
9195
  if (dest !== dirResolved && !dest.startsWith(dirResolved + sep3)) {
8942
9196
  throw new Error(`refusing ${e.name}@${e.version}: destination "${dest}" escapes plugins dir "${dirResolved}"`);
8943
9197
  }
@@ -8966,7 +9220,7 @@ async function installPlan(opts) {
8966
9220
  throw new Error(`sha256 mismatch for ${e.name}@${e.version}: expected ${e.sha256}, got ${got}`);
8967
9221
  }
8968
9222
  const tmp = `${dest}.${process.pid}.tmp`;
8969
- writeFileSync10(tmp, bytes);
9223
+ writeFileSync11(tmp, bytes);
8970
9224
  renameSync(tmp, dest);
8971
9225
  written.push(filename);
8972
9226
  log3(`installed ${e.name}@${e.version} -> ${filename}`);
@@ -8980,8 +9234,25 @@ function loadManifestFile(path) {
8980
9234
 
8981
9235
  // src/commands/install.ts
8982
9236
  var AUTODETECT = "addons/s2script/plugins";
9237
+ function collectWith(argv2) {
9238
+ const out = [];
9239
+ for (let i2 = 0; i2 < argv2.length; i2++) {
9240
+ const arg = argv2[i2];
9241
+ if (arg.startsWith("--with=")) {
9242
+ if (arg.length > "--with=".length) out.push(arg.slice("--with=".length));
9243
+ continue;
9244
+ }
9245
+ if (arg !== "--with") continue;
9246
+ const value = argv2[i2 + 1];
9247
+ if (value === void 0 || value.startsWith("-")) {
9248
+ throw new Error("--with requires an optional member name (e.g. --with @edge/jailbreak-gangperks)");
9249
+ }
9250
+ out.push(value);
9251
+ }
9252
+ return out;
9253
+ }
8983
9254
  async function run5(argv2) {
8984
- const args = positionals(argv2, ["--dir", "--file", "--registry"]);
9255
+ const args = positionals(argv2, ["--dir", "--file", "--registry", "--with"]);
8985
9256
  const file = parseFlag(argv2, "--file") ?? "s2script-plugins.json";
8986
9257
  const registry = parseFlag(argv2, "--registry") || loadCredentials()?.registryUrl || defaultRegistryUrl();
8987
9258
  const dryRun = hasFlag(argv2, "--dry-run");
@@ -8999,8 +9270,18 @@ async function run5(argv2) {
8999
9270
  }
9000
9271
  const client = new RegistryClient({ baseUrl: registry });
9001
9272
  try {
9273
+ const withArgs = collectWith(argv2);
9002
9274
  const manifest = loadManifestFile(resolve20(file));
9003
9275
  const specs = mergeSpecs(manifest, args);
9276
+ const withMembers = { ...manifest.with ?? {} };
9277
+ if (withArgs.length > 0) {
9278
+ if (args.length !== 1) {
9279
+ throw new Error(
9280
+ `--with applies to exactly one bundle named on the command line (got ${args.length}) \u2014 for several bundles, use { "range", "with" } entries in ${file}`
9281
+ );
9282
+ }
9283
+ withMembers[parseSpec2(args[0]).name] = withArgs;
9284
+ }
9004
9285
  if (Object.keys(specs).length === 0) {
9005
9286
  console.error(
9006
9287
  `Nothing to install. Add plugins to ${file} or pass names: s2s install rtv@^1.0.0`
@@ -9010,6 +9291,7 @@ async function run5(argv2) {
9010
9291
  const res = await installPlan({
9011
9292
  client,
9012
9293
  specs,
9294
+ with: withMembers,
9013
9295
  dir,
9014
9296
  dryRun,
9015
9297
  reviewedOnly,
@@ -9034,21 +9316,21 @@ import { resolve as resolve23 } from "node:path";
9034
9316
  // src/create/create.ts
9035
9317
  import {
9036
9318
  mkdirSync as mkdirSync8,
9037
- writeFileSync as writeFileSync11,
9319
+ writeFileSync as writeFileSync12,
9038
9320
  existsSync as existsSync16,
9039
- readdirSync as readdirSync4,
9321
+ readdirSync as readdirSync5,
9040
9322
  readFileSync as readFileSync19,
9041
9323
  symlinkSync
9042
9324
  } from "node:fs";
9043
- import { dirname as dirname6, isAbsolute, join as join22, relative as relative2, resolve as resolve21, basename as basename3 } from "node:path";
9325
+ import { dirname as dirname6, isAbsolute, join as join23, relative as relative2, resolve as resolve21, basename as basename3 } from "node:path";
9044
9326
  import { spawnSync } from "node:child_process";
9045
9327
  import { fileURLToPath as fileURLToPath2 } from "node:url";
9046
9328
  function readCliVersion() {
9047
9329
  const here = dirname6(fileURLToPath2(import.meta.url));
9048
9330
  const candidates = [
9049
- join22(here, "..", "package.json"),
9331
+ join23(here, "..", "package.json"),
9050
9332
  // dist/cli.js → packages/sdk/package.json
9051
- join22(here, "..", "..", "package.json")
9333
+ join23(here, "..", "..", "package.json")
9052
9334
  // src/create → packages/sdk/package.json
9053
9335
  ];
9054
9336
  for (const p of candidates) {
@@ -9064,9 +9346,9 @@ function readCliVersion() {
9064
9346
  function findLocalPackagesDir() {
9065
9347
  const here = dirname6(fileURLToPath2(import.meta.url));
9066
9348
  const candidates = [
9067
- join22(here, "..", ".."),
9349
+ join23(here, "..", ".."),
9068
9350
  // dist/cli.js → packages
9069
- join22(here, "..", "..", "..")
9351
+ join23(here, "..", "..", "..")
9070
9352
  // src/create → packages
9071
9353
  ];
9072
9354
  for (const c2 of candidates) {
@@ -9083,7 +9365,7 @@ function defaultNameFromPath(dir, kind = "plugin") {
9083
9365
  }
9084
9366
  function assertEmptyTarget(targetPath) {
9085
9367
  if (!existsSync16(targetPath)) return;
9086
- const kids = readdirSync4(targetPath);
9368
+ const kids = readdirSync5(targetPath);
9087
9369
  const meaningful = kids.filter((k) => k !== ".git");
9088
9370
  if (meaningful.length) {
9089
9371
  throw new Error(`target directory is not empty: ${targetPath}`);
@@ -9131,8 +9413,8 @@ function registryDevDeps(game, sdkVersion, resolve25 = resolvePublishedVersion)
9131
9413
  function fileDevDeps(packagesDir, game) {
9132
9414
  const deps = {};
9133
9415
  for (const n3 of createPackageNames(game)) {
9134
- const abs = join22(packagesDir, n3);
9135
- if (!existsSync16(join22(abs, "package.json"))) return void 0;
9416
+ const abs = join23(packagesDir, n3);
9417
+ if (!existsSync16(join23(abs, "package.json"))) return void 0;
9136
9418
  deps[`@s2script/${n3}`] = `file:${abs}`;
9137
9419
  }
9138
9420
  return deps;
@@ -9197,7 +9479,7 @@ function tsconfigJson() {
9197
9479
  ) + "\n";
9198
9480
  }
9199
9481
  function workspaceTsconfigJson(targetPath, workspaceRoot) {
9200
- const rel = relative2(targetPath, join22(workspaceRoot, "tsconfig.base.json")).replace(/\\/g, "/");
9482
+ const rel = relative2(targetPath, join23(workspaceRoot, "tsconfig.base.json")).replace(/\\/g, "/");
9201
9483
  return JSON.stringify(
9202
9484
  {
9203
9485
  extends: rel.startsWith(".") ? rel : `./${rel}`,
@@ -9273,9 +9555,9 @@ dist/
9273
9555
  `;
9274
9556
  }
9275
9557
  function linkLocalPackagesForNoInstall(targetPath, localPackagesDir) {
9276
- const nm = join22(targetPath, "node_modules");
9558
+ const nm = join23(targetPath, "node_modules");
9277
9559
  mkdirSync8(nm, { recursive: true });
9278
- const dest = join22(nm, "@s2script");
9560
+ const dest = join23(nm, "@s2script");
9279
9561
  if (!existsSync16(dest)) symlinkSync(localPackagesDir, dest);
9280
9562
  }
9281
9563
  function runInstall(dir, pm) {
@@ -9305,7 +9587,7 @@ async function createPlugin(opts = {}) {
9305
9587
  );
9306
9588
  }
9307
9589
  let slug = workspaceRoot !== void 0 && opts.path !== void 0 ? assertBareSlug(opts.path) : opts.path;
9308
- let targetPath = workspaceRoot === void 0 ? resolve21(opts.path ?? ".") : slug === void 0 ? void 0 : join22(workspaceRoot, "plugins", slug);
9590
+ let targetPath = workspaceRoot === void 0 ? resolve21(opts.path ?? ".") : slug === void 0 ? void 0 : join23(workspaceRoot, "plugins", slug);
9309
9591
  if (targetPath !== void 0) assertEmptyTarget(targetPath);
9310
9592
  if (interactive) {
9311
9593
  intro2("Create an s2script plugin");
@@ -9315,7 +9597,7 @@ async function createPlugin(opts = {}) {
9315
9597
  slug = assertBareSlug(
9316
9598
  await text2({ message: "Plugin name (directory under plugins/)", placeholder: "myplugin" })
9317
9599
  );
9318
- targetPath = join22(workspaceRoot, "plugins", slug);
9600
+ targetPath = join23(workspaceRoot, "plugins", slug);
9319
9601
  assertEmptyTarget(targetPath);
9320
9602
  }
9321
9603
  }
@@ -9378,37 +9660,37 @@ async function createPlugin(opts = {}) {
9378
9660
  install = workspaceRoot !== void 0 ? "none" : install ?? (opts.noInstall ? "none" : "npm");
9379
9661
  const finalTargetPath = targetPath;
9380
9662
  const localPackagesDir = workspaceRoot === void 0 ? findLocalPackagesDir() : void 0;
9381
- mkdirSync8(join22(finalTargetPath, "src"), { recursive: true });
9663
+ mkdirSync8(join23(finalTargetPath, "src"), { recursive: true });
9382
9664
  if (workspaceRoot !== void 0) {
9383
- writeFileSync11(join22(finalTargetPath, "package.json"), minimalPackageJsonContent(name));
9384
- writeFileSync11(
9385
- join22(finalTargetPath, "tsconfig.json"),
9665
+ writeFileSync12(join23(finalTargetPath, "package.json"), minimalPackageJsonContent(name));
9666
+ writeFileSync12(
9667
+ join23(finalTargetPath, "tsconfig.json"),
9386
9668
  workspaceTsconfigJson(finalTargetPath, workspaceRoot)
9387
9669
  );
9388
9670
  } else if (kind === "library") {
9389
9671
  const version = readCliVersion();
9390
- writeFileSync11(
9391
- join22(finalTargetPath, "package.json"),
9672
+ writeFileSync12(
9673
+ join23(finalTargetPath, "package.json"),
9392
9674
  libraryPackageJsonContent(name, game, version, localPackagesDir)
9393
9675
  );
9394
- writeFileSync11(join22(finalTargetPath, "tsconfig.json"), tsconfigJson());
9395
- writeFileSync11(join22(finalTargetPath, "eslint.config.mjs"), eslintConfig());
9676
+ writeFileSync12(join23(finalTargetPath, "tsconfig.json"), tsconfigJson());
9677
+ writeFileSync12(join23(finalTargetPath, "eslint.config.mjs"), eslintConfig());
9396
9678
  } else {
9397
9679
  const version = readCliVersion();
9398
- writeFileSync11(
9399
- join22(finalTargetPath, "package.json"),
9680
+ writeFileSync12(
9681
+ join23(finalTargetPath, "package.json"),
9400
9682
  packageJsonContent(name, game, version, localPackagesDir)
9401
9683
  );
9402
- writeFileSync11(join22(finalTargetPath, "tsconfig.json"), tsconfigJson());
9403
- writeFileSync11(join22(finalTargetPath, "eslint.config.mjs"), eslintConfig());
9684
+ writeFileSync12(join23(finalTargetPath, "tsconfig.json"), tsconfigJson());
9685
+ writeFileSync12(join23(finalTargetPath, "eslint.config.mjs"), eslintConfig());
9404
9686
  }
9405
9687
  if (kind === "library") {
9406
- writeFileSync11(join22(finalTargetPath, "src", "index.ts"), LIBRARY_ENTRY);
9407
- writeFileSync11(join22(finalTargetPath, "src", "index.d.ts"), LIBRARY_TYPES);
9688
+ writeFileSync12(join23(finalTargetPath, "src", "index.ts"), LIBRARY_ENTRY);
9689
+ writeFileSync12(join23(finalTargetPath, "src", "index.d.ts"), LIBRARY_TYPES);
9408
9690
  } else {
9409
- writeFileSync11(join22(finalTargetPath, "src", "plugin.ts"), pluginSource(game));
9691
+ writeFileSync12(join23(finalTargetPath, "src", "plugin.ts"), pluginSource(game));
9410
9692
  }
9411
- writeFileSync11(join22(finalTargetPath, ".gitignore"), gitignore());
9693
+ writeFileSync12(join23(finalTargetPath, ".gitignore"), gitignore());
9412
9694
  let installed = false;
9413
9695
  if (workspaceRoot === void 0) {
9414
9696
  if (install !== "none") {
@@ -9435,8 +9717,8 @@ async function createPlugin(opts = {}) {
9435
9717
  }
9436
9718
 
9437
9719
  // src/create/workspace.ts
9438
- import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync12 } from "node:fs";
9439
- import { join as join23, resolve as resolve22 } from "node:path";
9720
+ import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync13 } from "node:fs";
9721
+ import { join as join24, resolve as resolve22 } from "node:path";
9440
9722
  var ROOT_DEFAULT_GAME = "cs2";
9441
9723
  var CHANGESETS_CLI_RANGE = "^2.29.5";
9442
9724
  function defaultWorkspaceName(dir) {
@@ -9520,14 +9802,14 @@ async function createWorkspace(opts = {}) {
9520
9802
  name = name ?? defaultWorkspaceName(targetPath);
9521
9803
  const version = readCliVersion();
9522
9804
  const localPackagesDir = findLocalPackagesDir();
9523
- mkdirSync9(join23(targetPath, "plugins"), { recursive: true });
9524
- mkdirSync9(join23(targetPath, "packages"), { recursive: true });
9525
- mkdirSync9(join23(targetPath, ".changeset"), { recursive: true });
9526
- writeFileSync12(join23(targetPath, "package.json"), rootPackageJsonContent(name, version, localPackagesDir));
9527
- writeFileSync12(join23(targetPath, "tsconfig.base.json"), tsconfigBaseJson());
9528
- writeFileSync12(join23(targetPath, "eslint.config.mjs"), eslintConfig());
9529
- writeFileSync12(join23(targetPath, ".changeset", "config.json"), changesetConfigJson());
9530
- writeFileSync12(join23(targetPath, ".gitignore"), gitignore());
9805
+ mkdirSync9(join24(targetPath, "plugins"), { recursive: true });
9806
+ mkdirSync9(join24(targetPath, "packages"), { recursive: true });
9807
+ mkdirSync9(join24(targetPath, ".changeset"), { recursive: true });
9808
+ writeFileSync13(join24(targetPath, "package.json"), rootPackageJsonContent(name, version, localPackagesDir));
9809
+ writeFileSync13(join24(targetPath, "tsconfig.base.json"), tsconfigBaseJson());
9810
+ writeFileSync13(join24(targetPath, "eslint.config.mjs"), eslintConfig());
9811
+ writeFileSync13(join24(targetPath, ".changeset", "config.json"), changesetConfigJson());
9812
+ writeFileSync13(join24(targetPath, ".gitignore"), gitignore());
9531
9813
  return { dir: targetPath, name };
9532
9814
  }
9533
9815
 
@@ -9763,8 +10045,8 @@ async function run7(argv2) {
9763
10045
  }
9764
10046
 
9765
10047
  // src/config/gen.ts
9766
- import { readFileSync as readFileSync20, writeFileSync as writeFileSync13, mkdirSync as mkdirSync10 } from "node:fs";
9767
- import { join as join24 } from "node:path";
10048
+ import { readFileSync as readFileSync20, writeFileSync as writeFileSync14, mkdirSync as mkdirSync10 } from "node:fs";
10049
+ import { join as join25 } from "node:path";
9768
10050
 
9769
10051
  // src/config/config-path.ts
9770
10052
  function configFileName(id) {
@@ -9811,8 +10093,8 @@ function genConfigFromManifest(manifest, outDir) {
9811
10093
  const id = typeof manifest.id === "string" ? manifest.id : "";
9812
10094
  if (!id) throw new Error("config gen: manifest declares config but has no id");
9813
10095
  mkdirSync10(outDir, { recursive: true });
9814
- const path = join24(outDir, configFileName(id));
9815
- writeFileSync13(path, generateDefaultJsonc(config));
10096
+ const path = join25(outDir, configFileName(id));
10097
+ writeFileSync14(path, generateDefaultJsonc(config));
9816
10098
  return path;
9817
10099
  }
9818
10100
  function genConfigForS2sp(s2spPath, outDir) {
@@ -9863,12 +10145,12 @@ async function run8(argv2) {
9863
10145
  }
9864
10146
 
9865
10147
  // src/commands/codegen.ts
9866
- import { dirname as dirname7, join as join29 } from "node:path";
10148
+ import { dirname as dirname7, join as join30 } from "node:path";
9867
10149
  import { fileURLToPath as fileURLToPath3 } from "node:url";
9868
10150
 
9869
10151
  // src/schemagen/gen.ts
9870
- import { readFileSync as readFileSync21, writeFileSync as writeFileSync14 } from "node:fs";
9871
- import { join as join25 } from "node:path";
10152
+ import { readFileSync as readFileSync21, writeFileSync as writeFileSync15 } from "node:fs";
10153
+ import { join as join26 } from "node:path";
9872
10154
 
9873
10155
  // src/schemagen/model.ts
9874
10156
  function enumMemberNames(enumName, members) {
@@ -10351,16 +10633,16 @@ var EMBED_PATH = "games/cs2/codegen-embedded.json";
10351
10633
  var JS_OUT = "games/cs2/js/schema.generated.js";
10352
10634
  var DTS_OUT = "packages/cs2/schema.generated.d.ts";
10353
10635
  function runGenSchema(repoRoot2, opts) {
10354
- const catalog = JSON.parse(readFileSync21(join25(repoRoot2, CATALOG_PATH), "utf8"));
10355
- const list = JSON.parse(readFileSync21(join25(repoRoot2, LIST_PATH), "utf8"));
10636
+ const catalog = JSON.parse(readFileSync21(join26(repoRoot2, CATALOG_PATH), "utf8"));
10637
+ const list = JSON.parse(readFileSync21(join26(repoRoot2, LIST_PATH), "utf8"));
10356
10638
  let embed = [];
10357
10639
  try {
10358
- embed = JSON.parse(readFileSync21(join25(repoRoot2, EMBED_PATH), "utf8"));
10640
+ embed = JSON.parse(readFileSync21(join26(repoRoot2, EMBED_PATH), "utf8"));
10359
10641
  } catch {
10360
10642
  }
10361
10643
  let enumCatalog = {};
10362
10644
  try {
10363
- enumCatalog = JSON.parse(readFileSync21(join25(repoRoot2, ENUMS_PATH), "utf8"));
10645
+ enumCatalog = JSON.parse(readFileSync21(join26(repoRoot2, ENUMS_PATH), "utf8"));
10364
10646
  } catch {
10365
10647
  }
10366
10648
  const model = buildModel(catalog, list, embed, enumCatalog);
@@ -10371,7 +10653,7 @@ function runGenSchema(repoRoot2, opts) {
10371
10653
  const drift = [];
10372
10654
  const files = [[JS_OUT, js], [DTS_OUT, dts]];
10373
10655
  for (const [rel, content] of files) {
10374
- const abs = join25(repoRoot2, rel);
10656
+ const abs = join26(repoRoot2, rel);
10375
10657
  if (opts.check) {
10376
10658
  let cur = "";
10377
10659
  try {
@@ -10380,7 +10662,7 @@ function runGenSchema(repoRoot2, opts) {
10380
10662
  }
10381
10663
  if (cur !== content) drift.push(rel);
10382
10664
  } else {
10383
- writeFileSync14(abs, content);
10665
+ writeFileSync15(abs, content);
10384
10666
  }
10385
10667
  }
10386
10668
  if (model.collisions.length) console.error(`gen-schema: ${model.collisions.length} name collision(s) \u2192 raw fallback:
@@ -10390,8 +10672,8 @@ function runGenSchema(repoRoot2, opts) {
10390
10672
  }
10391
10673
 
10392
10674
  // src/eventgen/gen.ts
10393
- import { readFileSync as readFileSync22, writeFileSync as writeFileSync15 } from "node:fs";
10394
- import { join as join26 } from "node:path";
10675
+ import { readFileSync as readFileSync22, writeFileSync as writeFileSync16 } from "node:fs";
10676
+ import { join as join27 } from "node:path";
10395
10677
 
10396
10678
  // src/eventgen/model.ts
10397
10679
  var TYPE_TO_GETTER = {
@@ -10476,11 +10758,11 @@ function emitEventDts(model) {
10476
10758
  var CATALOG_PATH2 = "games/cs2/gamedata/event-catalog.json";
10477
10759
  var DTS_OUT2 = "packages/cs2/events.generated.d.ts";
10478
10760
  function runGenEvents(repoRoot2, opts) {
10479
- const catalog = JSON.parse(readFileSync22(join26(repoRoot2, CATALOG_PATH2), "utf8"));
10761
+ const catalog = JSON.parse(readFileSync22(join27(repoRoot2, CATALOG_PATH2), "utf8"));
10480
10762
  const model = buildEventModel(catalog);
10481
10763
  const dts = emitEventDts(model);
10482
10764
  const drift = [];
10483
- const abs = join26(repoRoot2, DTS_OUT2);
10765
+ const abs = join27(repoRoot2, DTS_OUT2);
10484
10766
  if (opts.check) {
10485
10767
  let cur = "";
10486
10768
  try {
@@ -10489,14 +10771,14 @@ function runGenEvents(repoRoot2, opts) {
10489
10771
  }
10490
10772
  if (cur !== dts) drift.push(DTS_OUT2);
10491
10773
  } else {
10492
- writeFileSync15(abs, dts);
10774
+ writeFileSync16(abs, dts);
10493
10775
  }
10494
10776
  return { events: model.length, drift };
10495
10777
  }
10496
10778
 
10497
10779
  // src/navgen/gen.ts
10498
- import { readFileSync as readFileSync23, writeFileSync as writeFileSync16 } from "node:fs";
10499
- import { join as join27 } from "node:path";
10780
+ import { readFileSync as readFileSync23, writeFileSync as writeFileSync17 } from "node:fs";
10781
+ import { join as join28 } from "node:path";
10500
10782
 
10501
10783
  // src/navgen/model.ts
10502
10784
  var SUPPORTED_NAV_KINDS = /* @__PURE__ */ new Set([
@@ -10717,8 +10999,8 @@ var CATALOG_PATH3 = "games/cs2/gamedata/schema-catalog.json";
10717
10999
  var JS_OUT2 = "games/cs2/js/nav.generated.js";
10718
11000
  var DTS_OUT3 = "packages/cs2/nav.generated.d.ts";
10719
11001
  function runGenNav(repoRoot2, opts) {
10720
- const config = JSON.parse(readFileSync23(join27(repoRoot2, TARGETS_PATH), "utf8"));
10721
- const catalog = JSON.parse(readFileSync23(join27(repoRoot2, CATALOG_PATH3), "utf8"));
11002
+ const config = JSON.parse(readFileSync23(join28(repoRoot2, TARGETS_PATH), "utf8"));
11003
+ const catalog = JSON.parse(readFileSync23(join28(repoRoot2, CATALOG_PATH3), "utf8"));
10722
11004
  const model = buildNavModel(config, catalog);
10723
11005
  const js = emitNavJs(model);
10724
11006
  const dts = emitNavDts(model);
@@ -10726,7 +11008,7 @@ function runGenNav(repoRoot2, opts) {
10726
11008
  const drift = [];
10727
11009
  const files = [[JS_OUT2, js], [DTS_OUT3, dts]];
10728
11010
  for (const [rel, content] of files) {
10729
- const abs = join27(repoRoot2, rel);
11011
+ const abs = join28(repoRoot2, rel);
10730
11012
  if (opts.check) {
10731
11013
  let cur = "";
10732
11014
  try {
@@ -10735,25 +11017,25 @@ function runGenNav(repoRoot2, opts) {
10735
11017
  }
10736
11018
  if (cur !== content) drift.push(rel);
10737
11019
  } else {
10738
- writeFileSync16(abs, content);
11020
+ writeFileSync17(abs, content);
10739
11021
  }
10740
11022
  }
10741
11023
  return { wrappers: model.wrappers.length, fields: fields2, drift };
10742
11024
  }
10743
11025
 
10744
11026
  // src/hookgen/gen.ts
10745
- import { readFileSync as readFileSync24, writeFileSync as writeFileSync17 } from "node:fs";
10746
- import { join as join28 } from "node:path";
11027
+ import { readFileSync as readFileSync24, writeFileSync as writeFileSync18 } from "node:fs";
11028
+ import { join as join29 } from "node:path";
10747
11029
  var GAMEDATA_PATH = "games/cs2/gamedata/game.cs2.jsonc";
10748
11030
  var DTS_OUT4 = "packages/cs2/hooks.generated.d.ts";
10749
11031
  function runGenHooks(repoRoot2, opts) {
10750
- const raw = readFileSync24(join28(repoRoot2, GAMEDATA_PATH), "utf8");
11032
+ const raw = readFileSync24(join29(repoRoot2, GAMEDATA_PATH), "utf8");
10751
11033
  const gd = JSON.parse(stripJsonComments(raw));
10752
11034
  const hooks = gd.hooks ?? {};
10753
11035
  const model = buildHookModel(hooks);
10754
11036
  const dts = emitHookDts(model);
10755
11037
  const drift = [];
10756
- const abs = join28(repoRoot2, DTS_OUT4);
11038
+ const abs = join29(repoRoot2, DTS_OUT4);
10757
11039
  if (opts.check) {
10758
11040
  let cur = "";
10759
11041
  try {
@@ -10762,7 +11044,7 @@ function runGenHooks(repoRoot2, opts) {
10762
11044
  }
10763
11045
  if (cur !== dts) drift.push(DTS_OUT4);
10764
11046
  } else {
10765
- writeFileSync17(abs, dts);
11047
+ writeFileSync18(abs, dts);
10766
11048
  }
10767
11049
  const hookCount = model.reduce((n3, ns) => n3 + ns.hooks.length, 0);
10768
11050
  return { hooks: hookCount, drift };
@@ -10770,7 +11052,7 @@ function runGenHooks(repoRoot2, opts) {
10770
11052
 
10771
11053
  // src/commands/codegen.ts
10772
11054
  function repoRoot() {
10773
- return join29(dirname7(fileURLToPath3(import.meta.url)), "..", "..", "..");
11055
+ return join30(dirname7(fileURLToPath3(import.meta.url)), "..", "..", "..");
10774
11056
  }
10775
11057
  async function run9(kind, argv2) {
10776
11058
  const root = repoRoot();
@@ -10858,8 +11140,8 @@ async function run10(argv2) {
10858
11140
  }
10859
11141
 
10860
11142
  // src/engine-functions/migrate-v1.ts
10861
- import { closeSync, existsSync as existsSync17, linkSync, lstatSync as lstatSync2, mkdirSync as mkdirSync11, openSync, readFileSync as readFileSync26, readdirSync as readdirSync5, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync18 } from "node:fs";
10862
- import { dirname as dirname8, isAbsolute as isAbsolute2, join as join30, relative as relative3, resolve as resolve24, sep as sep4 } from "node:path";
11143
+ import { closeSync, existsSync as existsSync17, linkSync, lstatSync as lstatSync2, mkdirSync as mkdirSync11, openSync, readFileSync as readFileSync26, readdirSync as readdirSync6, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync19 } from "node:fs";
11144
+ import { dirname as dirname8, isAbsolute as isAbsolute2, join as join31, relative as relative3, resolve as resolve24, sep as sep4 } from "node:path";
10863
11145
  import { createHash as createHash5, randomUUID } from "node:crypto";
10864
11146
  import ts5 from "typescript";
10865
11147
  var PLATFORM3 = "linuxsteamrt64";
@@ -10986,11 +11268,11 @@ function hookOf(name, value, signatures, errors) {
10986
11268
  }
10987
11269
  function sourceFiles(dir, pkg, errors) {
10988
11270
  const result = /* @__PURE__ */ new Set();
10989
- const src = join30(dir, "src");
11271
+ const src = join31(dir, "src");
10990
11272
  function walk(at) {
10991
11273
  if (!existsSync17(at)) return;
10992
- for (const item of readdirSync5(at, { withFileTypes: true })) {
10993
- const p = join30(at, item.name);
11274
+ for (const item of readdirSync6(at, { withFileTypes: true })) {
11275
+ const p = join31(at, item.name);
10994
11276
  if (item.isDirectory()) walk(p);
10995
11277
  else if (item.isFile() && /\.[cm]?[jt]sx?$/.test(item.name)) result.add(p);
10996
11278
  }
@@ -11013,7 +11295,7 @@ function sourceFiles(dir, pkg, errors) {
11013
11295
  return;
11014
11296
  }
11015
11297
  const stem = base.replace(/\.[cm]?[jt]sx?$/, "");
11016
- const candidates = [base, ...[".ts", ".tsx", ".js", ".jsx", ".mts", ".mjs", ".cts", ".cjs"].map((x) => stem + x), ...["index.ts", "index.tsx", "index.js", "index.jsx"].map((x) => join30(base, x))];
11298
+ const candidates = [base, ...[".ts", ".tsx", ".js", ".jsx", ".mts", ".mjs", ".cts", ".cjs"].map((x) => stem + x), ...["index.ts", "index.tsx", "index.js", "index.jsx"].map((x) => join31(base, x))];
11017
11299
  const found = candidates.find((p) => contained(dir, p) && existsSync17(p) && lstatSync2(p).isFile());
11018
11300
  if (!found) {
11019
11301
  errors.push(`${at}: unresolved local module ${JSON.stringify(spec)}`);
@@ -11184,12 +11466,12 @@ function scanSource(files, calls, hooks, report) {
11184
11466
  }
11185
11467
  function analyzeV1Migration(pluginDir, options = {}) {
11186
11468
  const dir = resolve24(pluginDir);
11187
- const destination = join30(dir, "gamedata", "functions.jsonc");
11469
+ const destination = join31(dir, "gamedata", "functions.jsonc");
11188
11470
  const report = { scope: SCOPE, package: dir, destination, entries: [], permissions: [], sourceReferences: [], generatedDeclarations: [], generatedDeclarationHashes: {}, recommendedEdits: [], ambiguities: [], filesChanged: [] };
11189
11471
  if (pathExists(destination) && !options.force) report.ambiguities.push(`${destination}: destination already exists; use --force to replace it`);
11190
11472
  let pkg;
11191
11473
  try {
11192
- pkg = parseFunctionFile(join30(dir, "package.json"), readFileSync26(join30(dir, "package.json"), "utf8"));
11474
+ pkg = parseFunctionFile(join31(dir, "package.json"), readFileSync26(join31(dir, "package.json"), "utf8"));
11193
11475
  } catch (e) {
11194
11476
  report.ambiguities.push(`package.json: ${e.message}`);
11195
11477
  return report;
@@ -11311,7 +11593,7 @@ function analyzeV1Migration(pluginDir, options = {}) {
11311
11593
  }
11312
11594
  }
11313
11595
  report.output = author;
11314
- report.generatedDeclarations = ["gamedata.d.ts", "hooks.d.ts"].map((n3) => join30(dir, ".s2script", n3)).filter(existsSync17);
11596
+ report.generatedDeclarations = ["gamedata.d.ts", "hooks.d.ts"].map((n3) => join31(dir, ".s2script", n3)).filter(existsSync17);
11315
11597
  for (const file of report.generatedDeclarations) {
11316
11598
  try {
11317
11599
  report.generatedDeclarationHashes[file] = createHash5("sha256").update(readFileSync26(file)).digest("hex");
@@ -11333,12 +11615,12 @@ function migrateV1Package(pluginDir, options = {}) {
11333
11615
  const report = analyzeV1Migration(pluginDir, options);
11334
11616
  if (report.ambiguities.length || !report.output) return report;
11335
11617
  const dest = report.destination;
11336
- const temp = join30(dirname8(dest), `.functions.jsonc.${process.pid}.${randomUUID()}.tmp`);
11618
+ const temp = join31(dirname8(dest), `.functions.jsonc.${process.pid}.${randomUUID()}.tmp`);
11337
11619
  try {
11338
11620
  mkdirSync11(dirname8(dest), { recursive: true });
11339
11621
  const fd2 = openSync(temp, "wx", 384);
11340
11622
  try {
11341
- writeFileSync18(fd2, JSON.stringify(report.output, null, 2) + "\n");
11623
+ writeFileSync19(fd2, JSON.stringify(report.output, null, 2) + "\n");
11342
11624
  } finally {
11343
11625
  closeSync(fd2);
11344
11626
  }