@s2script/sdk 0.25.0 → 0.26.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
@@ -2076,7 +2076,7 @@ var require_semver2 = __commonJS({
2076
2076
  });
2077
2077
 
2078
2078
  // src/commands/build.ts
2079
- import { resolve as resolve11 } from "node:path";
2079
+ import { resolve as resolve12 } from "node:path";
2080
2080
 
2081
2081
  // src/build.ts
2082
2082
  import * as esbuild2 from "esbuild";
@@ -3068,34 +3068,675 @@ function unzipSync(data, opts) {
3068
3068
  }
3069
3069
 
3070
3070
  // src/build.ts
3071
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, rmSync as rmSync2 } from "node:fs";
3072
- import { resolve as resolve10, join as join11, sep as sep2, basename } from "node:path";
3071
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, rmSync as rmSync2 } from "node:fs";
3072
+ import { resolve as resolve11, join as join11, sep as sep2, basename } from "node:path";
3073
3073
 
3074
3074
  // src/typecheck/typecheck.ts
3075
+ import ts3 from "typescript";
3076
+
3077
+ // src/interop.ts
3078
+ import ts2 from "typescript";
3079
+ import { createHash } from "node:crypto";
3080
+
3081
+ // src/publish-scan.ts
3075
3082
  import ts from "typescript";
3076
- import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync6, writeFileSync, rmSync, mkdtempSync } from "node:fs";
3077
- import { join as join7, resolve as resolve6 } from "node:path";
3083
+ function pluginApiName(checker, call) {
3084
+ const declaration = checker.getResolvedSignature(call)?.getDeclaration();
3085
+ if (!declaration || !declaration.getSourceFile().fileName.replace(/\\/g, "/").endsWith("/sdk/plugin.d.ts"))
3086
+ return void 0;
3087
+ const name = declaration.name;
3088
+ return name && (ts.isIdentifier(name) || ts.isStringLiteral(name)) ? name.text : void 0;
3089
+ }
3090
+ function recordPublishOrUse(out, method, arg0, sf, node) {
3091
+ if (method === "publish") {
3092
+ if (arg0 !== void 0 && ts.isStringLiteralLike(arg0)) {
3093
+ out.publishNames.push(arg0.text);
3094
+ } else {
3095
+ const { line } = sf.getLineAndCharacterOfPosition(node.getStart());
3096
+ out.dynamicPublishSites.push(`${sf.fileName}:${line + 1}`);
3097
+ }
3098
+ } else if (arg0 !== void 0 && ts.isStringLiteralLike(arg0)) {
3099
+ out.useNames.push(arg0.text);
3100
+ }
3101
+ }
3102
+ function recordImportSpecifier(out, spec) {
3103
+ if (spec.startsWith(".") || spec.startsWith("@s2script/")) return;
3104
+ out.importNames.push(spec);
3105
+ }
3106
+ function scanPluginProgram(program, pluginDir) {
3107
+ const checker = program.getTypeChecker();
3108
+ const out = {
3109
+ publishNames: [],
3110
+ dynamicPublishSites: [],
3111
+ useNames: [],
3112
+ importNames: []
3113
+ };
3114
+ const dirPrefix = pluginDir.replace(/\\/g, "/").replace(/\/+$/, "") + "/";
3115
+ for (const sf of program.getSourceFiles()) {
3116
+ if (sf.isDeclarationFile) continue;
3117
+ if (!sf.fileName.replace(/\\/g, "/").startsWith(dirPrefix)) continue;
3118
+ const visit = (node) => {
3119
+ if ((ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && node.moduleSpecifier !== void 0 && ts.isStringLiteralLike(node.moduleSpecifier)) {
3120
+ recordImportSpecifier(out, node.moduleSpecifier.text);
3121
+ }
3122
+ if (ts.isCallExpression(node)) {
3123
+ const name = pluginApiName(checker, node);
3124
+ if (name && [
3125
+ "publish",
3126
+ "use",
3127
+ "tryUse",
3128
+ "watchOptional",
3129
+ "bindForwards"
3130
+ ].includes(name))
3131
+ recordPublishOrUse(out, name, node.arguments[0], sf, node);
3132
+ }
3133
+ ts.forEachChild(node, visit);
3134
+ };
3135
+ visit(sf);
3136
+ }
3137
+ out.publishNames = [...new Set(out.publishNames)];
3138
+ out.useNames = [...new Set(out.useNames)];
3139
+ out.importNames = [...new Set(out.importNames)];
3140
+ return out;
3141
+ }
3142
+
3143
+ // src/interop.ts
3144
+ import { resolve } from "node:path";
3145
+
3146
+ // src/tsconfig-shared.ts
3147
+ var sharedCompilerOptionsJson = {
3148
+ strict: true,
3149
+ noEmit: true,
3150
+ moduleResolution: "bundler",
3151
+ module: "ESNext",
3152
+ target: "ES2024",
3153
+ lib: ["ES2024"],
3154
+ types: [],
3155
+ skipLibCheck: true,
3156
+ allowImportingTsExtensions: true
3157
+ };
3158
+ function sharedProgramOptions(tsMod) {
3159
+ return {
3160
+ strict: sharedCompilerOptionsJson.strict,
3161
+ noEmit: sharedCompilerOptionsJson.noEmit,
3162
+ allowImportingTsExtensions: sharedCompilerOptionsJson.allowImportingTsExtensions,
3163
+ moduleResolution: tsMod.ModuleResolutionKind.Bundler,
3164
+ module: tsMod.ModuleKind.ESNext,
3165
+ target: tsMod.ScriptTarget.ES2024,
3166
+ lib: sharedCompilerOptionsJson.lib.map((l2) => `lib.${l2.toLowerCase()}.d.ts`),
3167
+ types: [...sharedCompilerOptionsJson.types],
3168
+ skipLibCheck: sharedCompilerOptionsJson.skipLibCheck
3169
+ };
3170
+ }
3171
+
3172
+ // src/interop.ts
3173
+ function canonical(value) {
3174
+ if (typeof value === "string") {
3175
+ if (/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/u.test(
3176
+ value
3177
+ ))
3178
+ throw new Error(
3179
+ "canonical metadata requires well-formed Unicode strings"
3180
+ );
3181
+ return JSON.stringify(value);
3182
+ }
3183
+ if (typeof value === "number" && !Number.isFinite(value))
3184
+ throw new Error("canonical metadata requires finite numbers");
3185
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
3186
+ if (value !== null && typeof value === "object")
3187
+ return `{${Object.keys(value).sort().map(
3188
+ (k) => `${canonical(k)}:${canonical(value[k])}`
3189
+ ).join(",")}}`;
3190
+ if (value === null || typeof value === "boolean" || typeof value === "number")
3191
+ return JSON.stringify(value);
3192
+ throw new Error("canonical metadata requires JSON values");
3193
+ }
3194
+ function extractContract(path, packagesDir) {
3195
+ const program = ts2.createProgram([path], {
3196
+ ...sharedProgramOptions(ts2),
3197
+ skipLibCheck: false,
3198
+ baseUrl: packagesDir,
3199
+ paths: { "@s2script/sdk": ["sdk/index.d.ts"], "@s2script/sdk/*": ["sdk/*.d.ts"] }
3200
+ });
3201
+ const checker = program.getTypeChecker();
3202
+ const sf = program.getSourceFile(path);
3203
+ const fail = (node, reason) => {
3204
+ const pos = sf.getLineAndCharacterOfPosition(node.getStart(sf));
3205
+ throw new Error(
3206
+ `${path}:${pos.line + 1}:${pos.character + 1}: InterfaceContractError: ${reason}`
3207
+ );
3208
+ };
3209
+ const visitImports = (node) => {
3210
+ if (ts2.isImportDeclaration(node)) {
3211
+ const module2 = ts2.isStringLiteral(node.moduleSpecifier) ? node.moduleSpecifier.text : "";
3212
+ const bindings = node.importClause?.namedBindings;
3213
+ const allowed = module2 === "@s2script/sdk/interfaces" ? ["Notification", "Hook", "Transform"] : module2 === "@s2script/sdk/events" || module2 === "@s2script/sdk" ? ["HookResultValue"] : module2 === "@s2script/sdk/entity" ? ["EntityRef"] : [];
3214
+ if (!bindings || !ts2.isNamedImports(bindings) || node.importClause?.name || !bindings.elements.every(
3215
+ (e) => allowed.includes((e.propertyName ?? e.name).text)
3216
+ ))
3217
+ fail(
3218
+ node,
3219
+ "contracts must be self-contained; only SDK Notification, Hook, Transform, HookResultValue and EntityRef imports are supported"
3220
+ );
3221
+ }
3222
+ if (ts2.isExportDeclaration(node) && node.moduleSpecifier || ts2.isImportTypeNode(node) || ts2.isImportEqualsDeclaration(node) || ts2.isModuleDeclaration(node))
3223
+ fail(
3224
+ node,
3225
+ "external declarations and module augmentations are not supported in a contract"
3226
+ );
3227
+ ts2.forEachChild(node, visitImports);
3228
+ };
3229
+ visitImports(sf);
3230
+ if (sf.referencedFiles.length || sf.typeReferenceDirectives.length)
3231
+ fail(sf, "external declaration references are not supported");
3232
+ const diags = [
3233
+ ...program.getSyntacticDiagnostics(sf),
3234
+ ...program.getSemanticDiagnostics(sf)
3235
+ ];
3236
+ if (diags.length)
3237
+ fail(sf, ts2.flattenDiagnosticMessageText(diags[0].messageText, "\n"));
3238
+ const module = checker.getSymbolAtLocation(sf);
3239
+ const symbol2 = module && checker.getExportsOfModule(module).find((s) => s.name === "Contract");
3240
+ if (!symbol2) fail(sf, "protocol 2 requires an exported Contract");
3241
+ const contract = checker.getDeclaredTypeOfSymbol(symbol2);
3242
+ const prop = (type, name, node) => {
3243
+ const p = type.getProperty(name);
3244
+ if (!p) fail(node, `missing ${name}`);
3245
+ return checker.getTypeOfSymbolAtLocation(p, p.valueDeclaration ?? node);
3246
+ };
3247
+ for (const name of ["methods", "forwards"]) {
3248
+ const member = prop(contract, name, sf);
3249
+ if (!(member.flags & ts2.TypeFlags.Object) || member.getCallSignatures().length || checker.getIndexInfosOfType(member).length) {
3250
+ fail(sf, `${name} must be a finite object record`);
3251
+ }
3252
+ }
3253
+ const stack = /* @__PURE__ */ new Set();
3254
+ const schema = (type, node, voidAllowed = false) => {
3255
+ const f = type.flags;
3256
+ if (f & ts2.TypeFlags.Any || f & ts2.TypeFlags.Unknown || f & ts2.TypeFlags.Never)
3257
+ fail(node, `unsupported wire type ${checker.typeToString(type)}`);
3258
+ if (f & ts2.TypeFlags.Void && voidAllowed) return { kind: "void" };
3259
+ if (f & ts2.TypeFlags.Null) return { kind: "null" };
3260
+ if (f & ts2.TypeFlags.StringLiteral)
3261
+ return { kind: "literal", value: type.value };
3262
+ if (f & ts2.TypeFlags.NumberLiteral)
3263
+ return { kind: "literal", value: type.value };
3264
+ if (f & ts2.TypeFlags.BooleanLiteral)
3265
+ return { kind: "literal", value: checker.typeToString(type) === "true" };
3266
+ if (f & ts2.TypeFlags.String) return { kind: "string" };
3267
+ if (f & ts2.TypeFlags.Number) return { kind: "number" };
3268
+ if (f & ts2.TypeFlags.Boolean) return { kind: "boolean" };
3269
+ if (stack.has(type)) fail(node, "recursive wire types are unsupported");
3270
+ stack.add(type);
3271
+ try {
3272
+ if (type.isUnion()) {
3273
+ const variants = type.types.map((t2) => schema(t2, node));
3274
+ const objects = variants.filter((v) => v.kind === "object");
3275
+ if (objects.length > 1 && !Object.keys(objects[0].fields).some(
3276
+ (k) => objects.every(
3277
+ (o) => o.fields[k]?.schema.kind === "literal" && !o.fields[k].optional
3278
+ )
3279
+ ))
3280
+ fail(node, "object unions require a literal discriminant");
3281
+ return { kind: "union", variants };
3282
+ }
3283
+ if (checker.isArrayType(type))
3284
+ return {
3285
+ kind: "array",
3286
+ item: schema(
3287
+ checker.getTypeArguments(type)[0],
3288
+ node
3289
+ )
3290
+ };
3291
+ if (!(f & ts2.TypeFlags.Object) || checker.isTupleType(type) || type.getCallSignatures().length || checker.getIndexInfosOfType(type).length)
3292
+ fail(node, `unsupported wire type ${checker.typeToString(type)}`);
3293
+ const sym = type.getSymbol();
3294
+ if (sym?.name === "EntityRef" && sym.declarations?.some(
3295
+ (d) => d.getSourceFile().fileName === resolve(packagesDir, "sdk/entity.d.ts")
3296
+ ))
3297
+ return { kind: "entityRef" };
3298
+ if (sym?.declarations?.some(
3299
+ (d) => ts2.isClassDeclaration(d) || d.getSourceFile() !== sf
3300
+ ))
3301
+ fail(
3302
+ node,
3303
+ "arbitrary class instances and imported domain types are unsupported"
3304
+ );
3305
+ const fields = /* @__PURE__ */ Object.create(null);
3306
+ for (const p of type.getProperties().sort((a2, b) => a2.name.localeCompare(b.name))) {
3307
+ const decl = p.valueDeclaration ?? node;
3308
+ if (p.name === "__s2ref")
3309
+ fail(decl, "__s2ref is reserved for the EntityRef wire encoding");
3310
+ const optional = !!(p.flags & ts2.SymbolFlags.Optional);
3311
+ let t2 = checker.getTypeOfSymbolAtLocation(p, decl);
3312
+ if (optional && t2.isUnion()) {
3313
+ const parts = t2.types.filter(
3314
+ (t3) => !(t3.flags & ts2.TypeFlags.Undefined)
3315
+ );
3316
+ if (parts.length === 1) t2 = parts[0];
3317
+ else {
3318
+ fields[p.name] = {
3319
+ schema: {
3320
+ kind: "union",
3321
+ variants: parts.map((t3) => schema(t3, decl))
3322
+ },
3323
+ optional
3324
+ };
3325
+ continue;
3326
+ }
3327
+ }
3328
+ fields[p.name] = { schema: schema(t2, decl), optional };
3329
+ }
3330
+ return { kind: "object", fields };
3331
+ } finally {
3332
+ stack.delete(type);
3333
+ }
3334
+ };
3335
+ const methods = /* @__PURE__ */ Object.create(null);
3336
+ for (const p of prop(contract, "methods", sf).getProperties()) {
3337
+ const node = p.valueDeclaration ?? sf, type = checker.getTypeOfSymbolAtLocation(p, node), sigs = type.getCallSignatures();
3338
+ const scanner = ts2.createScanner(
3339
+ ts2.ScriptTarget.ESNext,
3340
+ false,
3341
+ ts2.LanguageVariant.Standard,
3342
+ p.name
3343
+ );
3344
+ const identifier = scanner.scan() === ts2.SyntaxKind.Identifier && scanner.scan() === ts2.SyntaxKind.EndOfFileToken;
3345
+ if (!identifier || ["on", "off", "then"].includes(p.name))
3346
+ fail(node, `unsupported or reserved method name ${p.name}`);
3347
+ const exported = checker.getExportsOfModule(module).find((s) => s.name === p.name);
3348
+ if (exported && (!checker.isTypeAssignableTo(
3349
+ checker.getTypeOfSymbolAtLocation(
3350
+ exported,
3351
+ exported.valueDeclaration ?? sf
3352
+ ),
3353
+ type
3354
+ ) || !checker.isTypeAssignableTo(
3355
+ type,
3356
+ checker.getTypeOfSymbolAtLocation(
3357
+ exported,
3358
+ exported.valueDeclaration ?? sf
3359
+ )
3360
+ )))
3361
+ fail(node, `export ${p.name} must agree with Contract.methods`);
3362
+ if (sigs.length !== 1 || sigs[0].typeParameters?.length || p.flags & ts2.SymbolFlags.Optional)
3363
+ fail(node, "methods require one non-generic synchronous signature");
3364
+ const sig = sigs[0];
3365
+ methods[p.name] = {
3366
+ args: sig.parameters.map((p2) => {
3367
+ const d = p2.valueDeclaration;
3368
+ if (d.dotDotDotToken) fail(d, "rest parameters are unsupported");
3369
+ let t2 = checker.getTypeOfSymbolAtLocation(p2, d);
3370
+ if (d.questionToken && t2.isUnion()) {
3371
+ const parts = t2.types.filter(
3372
+ (t3) => !(t3.flags & ts2.TypeFlags.Undefined)
3373
+ );
3374
+ if (parts.length === 1) t2 = parts[0];
3375
+ else fail(d, "optional union arguments are unsupported");
3376
+ }
3377
+ return { schema: schema(t2, d), optional: !!d.questionToken };
3378
+ }),
3379
+ result: schema(checker.getReturnTypeOfSignature(sig), node, true)
3380
+ };
3381
+ }
3382
+ const forwards = /* @__PURE__ */ Object.create(null);
3383
+ for (const p of prop(contract, "forwards", sf).getProperties()) {
3384
+ const node = p.valueDeclaration ?? sf, type = checker.getTypeOfSymbolAtLocation(p, node);
3385
+ const descriptors = [
3386
+ ["notification", "__notificationPayload"],
3387
+ ["hook", "__hookPayload"],
3388
+ ["transform", "__transformPayload"]
3389
+ ];
3390
+ const descriptor = descriptors.filter(([, key2]) => type.getProperty(key2));
3391
+ if (descriptor.length !== 1)
3392
+ fail(
3393
+ node,
3394
+ "only SDK Notification<P>, Hook<P> and Transform<P,W> forwards are supported"
3395
+ );
3396
+ const [kind, key] = descriptor[0];
3397
+ const brand = type.getProperty(key);
3398
+ if (!brand.declarations?.some(
3399
+ (d) => d.getSourceFile().fileName === resolve(packagesDir, "sdk/interfaces.d.ts")
3400
+ ))
3401
+ fail(node, "forward descriptors must come from the SDK");
3402
+ const payload = schema(
3403
+ checker.getTypeOfSymbolAtLocation(brand, node),
3404
+ node
3405
+ );
3406
+ if (kind === "transform") {
3407
+ if (payload.kind !== "object")
3408
+ fail(node, "Transform requires a finite object payload");
3409
+ const writableType = prop(type, "__transformWritable", node);
3410
+ const keys = writableType.flags & ts2.TypeFlags.Never ? [] : writableType.isUnion() ? writableType.types : [writableType];
3411
+ const writable = keys.map((t2) => {
3412
+ if (!(t2.flags & ts2.TypeFlags.StringLiteral))
3413
+ fail(node, "Transform writable keys must be string field names");
3414
+ const key2 = t2.value;
3415
+ if (!Object.hasOwn(payload.fields, key2))
3416
+ fail(node, "Transform writable key is absent from payload");
3417
+ return key2;
3418
+ }).sort();
3419
+ forwards[p.name] = { kind, payload, writable };
3420
+ } else forwards[p.name] = { kind, payload };
3421
+ }
3422
+ for (const exported of checker.getExportsOfModule(module)) {
3423
+ const typeOnly = exported.declarations?.every(
3424
+ (d) => ts2.isExportSpecifier(d) && (d.isTypeOnly || ts2.isExportDeclaration(d.parent.parent) && d.parent.parent.isTypeOnly)
3425
+ );
3426
+ const target = exported.flags & ts2.SymbolFlags.Alias ? checker.getAliasedSymbol(exported) : exported;
3427
+ if (!typeOnly && target.flags & ts2.SymbolFlags.Value && !Object.hasOwn(methods, exported.name))
3428
+ fail(exported.declarations?.[0] ?? sf, `unsupported value export ${exported.name}; only Contract.methods may export runtime values`);
3429
+ }
3430
+ const metadata = { version: 1, methods, forwards };
3431
+ return {
3432
+ metadata,
3433
+ sha256: createHash("sha256").update(canonical(metadata)).digest("hex")
3434
+ };
3435
+ }
3436
+ function implementationExpression(checker, expression, seen = /* @__PURE__ */ new Set()) {
3437
+ if (seen.has(expression)) return expression;
3438
+ seen.add(expression);
3439
+ const propertyValue = (receiver, name) => {
3440
+ const object = implementationExpression(checker, receiver, seen);
3441
+ const declaration = checker.getTypeAtLocation(object).getProperty(name)?.valueDeclaration;
3442
+ return declaration && (declarationValue(declaration) ?? declaration);
3443
+ };
3444
+ const declarationValue = (declaration) => {
3445
+ if (seen.has(declaration)) return void 0;
3446
+ seen.add(declaration);
3447
+ if ((ts2.isVariableDeclaration(declaration) || ts2.isPropertyAssignment(declaration)) && declaration.initializer)
3448
+ return implementationExpression(checker, declaration.initializer, seen);
3449
+ if (ts2.isShorthandPropertyAssignment(declaration)) {
3450
+ const value = checker.getShorthandAssignmentValueSymbol(
3451
+ declaration
3452
+ )?.valueDeclaration;
3453
+ return value && declarationValue(value);
3454
+ }
3455
+ if (ts2.isBindingElement(declaration) && ts2.isObjectBindingPattern(declaration.parent)) {
3456
+ const object = declarationValue(declaration.parent.parent);
3457
+ const name = declaration.propertyName ?? declaration.name;
3458
+ if (object && (ts2.isIdentifier(name) || ts2.isStringLiteral(name)))
3459
+ return propertyValue(object, name.text);
3460
+ }
3461
+ return void 0;
3462
+ };
3463
+ if (ts2.isParenthesizedExpression(expression))
3464
+ return implementationExpression(checker, expression.expression, seen);
3465
+ if (ts2.isPropertyAccessExpression(expression))
3466
+ return propertyValue(expression.expression, expression.name.text) ?? expression;
3467
+ if (ts2.isElementAccessExpression(expression) && ts2.isStringLiteralLike(expression.argumentExpression))
3468
+ return propertyValue(
3469
+ expression.expression,
3470
+ expression.argumentExpression.text
3471
+ ) ?? expression;
3472
+ let symbol2 = checker.getSymbolAtLocation(expression);
3473
+ if (symbol2 && symbol2.flags & ts2.SymbolFlags.Alias)
3474
+ symbol2 = checker.getAliasedSymbol(symbol2);
3475
+ return (symbol2?.valueDeclaration && declarationValue(symbol2.valueDeclaration)) ?? expression;
3476
+ }
3477
+ function checkInteropCalls(program, paths, publishes, generatedPath, pluginDir, dependencies, optionalDependencies) {
3478
+ const checker = program.getTypeChecker(), out = [];
3479
+ const report = (node, message) => out.push({
3480
+ category: ts2.DiagnosticCategory.Error,
3481
+ code: 92002,
3482
+ file: node.getSourceFile(),
3483
+ start: node.getStart(),
3484
+ length: node.getWidth(),
3485
+ messageText: `InterfaceContractError: ${message}`
3486
+ });
3487
+ const contracts = /* @__PURE__ */ new Map();
3488
+ for (const [name, path] of Object.entries(paths)) {
3489
+ const sf = program.getSourceFile(path);
3490
+ const sym = sf && checker.getSymbolAtLocation(sf);
3491
+ const c2 = sym && checker.getExportsOfModule(sym).find((s) => s.name === "Contract");
3492
+ if (c2) contracts.set(name, checker.getDeclaredTypeOfSymbol(c2));
3493
+ }
3494
+ const sdkInterfaces = resolve(
3495
+ program.getCompilerOptions().baseUrl,
3496
+ "sdk/interfaces.d.ts"
3497
+ );
3498
+ for (const sf of program.getSourceFiles()) {
3499
+ const visit = (node) => {
3500
+ if (ts2.isInterfaceDeclaration(node) && node.name.text === "InterfaceContracts" && sf.fileName !== generatedPath && sf.fileName !== sdkInterfaces)
3501
+ report(
3502
+ node,
3503
+ "InterfaceContracts associations are generated; local augmentation is not authority"
3504
+ );
3505
+ if (ts2.isCallExpression(node) && sf.fileName.startsWith(pluginDir + "/")) {
3506
+ const method = pluginApiName(checker, node);
3507
+ if ([
3508
+ "publish",
3509
+ "use",
3510
+ "tryUse",
3511
+ "watchOptional",
3512
+ "bindForwards"
3513
+ ].includes(method ?? "")) {
3514
+ const arg = node.arguments[0];
3515
+ const name = arg && ts2.isStringLiteralLike(arg) ? arg.text : null;
3516
+ if (!name || !contracts.has(name))
3517
+ report(
3518
+ node,
3519
+ `interface ${name ?? "(dynamic name)"} has no verified protocol 2 contract; declare the dependency and run s2s add`
3520
+ );
3521
+ else {
3522
+ if (node.typeArguments?.length)
3523
+ report(
3524
+ node,
3525
+ "protocol 2 infers Contract from the interface name; explicit generic arguments are forbidden"
3526
+ );
3527
+ if (method === "watchOptional" && !optionalDependencies.has(name))
3528
+ report(node, "watchOptional requires an optionalPluginDependencies entry");
3529
+ if (method === "bindForwards" && optionalDependencies.has(name))
3530
+ report(node, "bindForwards requires a pluginDependencies entry");
3531
+ if (method === "watchOptional" && node.arguments[1]) {
3532
+ const callback = node.arguments[1];
3533
+ const type = checker.getTypeAtLocation(implementationExpression(checker, callback));
3534
+ const asynchronous = (type2) => type2.isUnion() ? type2.types.some(asynchronous) : !!(type2.flags & ts2.TypeFlags.Any) || !!type2.getProperty("then");
3535
+ if (type.flags & ts2.TypeFlags.Any || type.getCallSignatures().some((signature) => asynchronous(checker.getReturnTypeOfSignature(signature))))
3536
+ report(callback, "watchOptional requires a synchronous attachment callback; thenables are forbidden");
3537
+ }
3538
+ if (method === "publish") {
3539
+ if (!publishes.has(name))
3540
+ report(node, `provider is not authorized to publish ${name}`);
3541
+ const c2 = contracts.get(name), m = c2.getProperty("methods");
3542
+ const expected = checker.getTypeOfSymbolAtLocation(
3543
+ m,
3544
+ m.valueDeclaration
3545
+ );
3546
+ const impl = node.arguments[1];
3547
+ const actual = impl && checker.getTypeAtLocation(impl);
3548
+ const implementationType = impl && checker.getTypeAtLocation(
3549
+ implementationExpression(checker, impl)
3550
+ );
3551
+ if (!actual || actual.flags & ts2.TypeFlags.Any || !checker.isTypeAssignableTo(actual, expected))
3552
+ report(
3553
+ impl ?? node,
3554
+ `producer implementation must agree with ${name} Contract.methods (${checker.typeToString(
3555
+ expected
3556
+ )})`
3557
+ );
3558
+ if (actual) {
3559
+ for (const property of actual.getProperties()) {
3560
+ if (!expected.getProperty(property.name) && checker.getTypeOfSymbolAtLocation(property, impl).getCallSignatures().length) {
3561
+ report(
3562
+ impl,
3563
+ `undeclared method ${property.name} is not in Contract.methods`
3564
+ );
3565
+ }
3566
+ }
3567
+ }
3568
+ if (actual) {
3569
+ for (const expectedMethod of expected.getProperties()) {
3570
+ const implementation = implementationType?.getProperty(
3571
+ expectedMethod.name
3572
+ );
3573
+ if (!implementation) continue;
3574
+ const required = checker.getTypeOfSymbolAtLocation(expectedMethod, impl).getCallSignatures()[0];
3575
+ const declaration = implementation.valueDeclaration;
3576
+ const value = declaration && ts2.isPropertyAssignment(declaration) ? declaration.initializer : declaration && ts2.isShorthandPropertyAssignment(declaration) ? declaration.name : void 0;
3577
+ const methodType = value ? checker.getTypeAtLocation(
3578
+ implementationExpression(checker, value)
3579
+ ) : checker.getTypeOfSymbolAtLocation(implementation, impl);
3580
+ const signatures = methodType.getCallSignatures();
3581
+ if (!required || signatures.length !== 1 || signatures[0].typeParameters?.length) {
3582
+ report(
3583
+ impl,
3584
+ `producer method ${expectedMethod.name} signature must be a single non-generic function`
3585
+ );
3586
+ continue;
3587
+ }
3588
+ const provided = signatures[0];
3589
+ const result = checker.getReturnTypeOfSignature(provided);
3590
+ const expectedResult = checker.getReturnTypeOfSignature(required);
3591
+ const resultMatches = expectedResult.flags & ts2.TypeFlags.Void ? !!(result.flags & (ts2.TypeFlags.Void | ts2.TypeFlags.Undefined | ts2.TypeFlags.Never)) : checker.isTypeAssignableTo(result, expectedResult);
3592
+ if (result.flags & ts2.TypeFlags.Any || !resultMatches)
3593
+ report(
3594
+ impl,
3595
+ `producer method ${expectedMethod.name} result must agree with its synchronous Contract signature`
3596
+ );
3597
+ for (const [i2, parameter] of provided.parameters.entries()) {
3598
+ const declaration2 = parameter.valueDeclaration;
3599
+ if (declaration2 && ts2.isParameter(declaration2) && declaration2.dotDotDotToken) {
3600
+ report(
3601
+ impl,
3602
+ `producer method ${expectedMethod.name} signature cannot use rest parameters`
3603
+ );
3604
+ continue;
3605
+ }
3606
+ const input = checker.getTypeOfSymbolAtLocation(
3607
+ parameter,
3608
+ impl
3609
+ );
3610
+ const contractParameter = required.parameters[i2];
3611
+ if (!contractParameter) {
3612
+ if (!(parameter.flags & ts2.SymbolFlags.Optional) && !(declaration2 && ts2.isParameter(declaration2) && (declaration2.questionToken || declaration2.initializer)))
3613
+ report(
3614
+ impl,
3615
+ `producer method ${expectedMethod.name} requires an undeclared input`
3616
+ );
3617
+ } else {
3618
+ const requiredInput = checker.getTypeOfSymbolAtLocation(
3619
+ contractParameter,
3620
+ impl
3621
+ );
3622
+ const variants = requiredInput.isUnion() ? requiredInput.types : [requiredInput];
3623
+ const hasDefault = declaration2 && ts2.isParameter(declaration2) && declaration2.initializer;
3624
+ if (!variants.every(
3625
+ (type) => hasDefault && !!(type.flags & ts2.TypeFlags.Undefined) || checker.isTypeAssignableTo(type, input)
3626
+ ))
3627
+ report(
3628
+ impl,
3629
+ `producer method ${expectedMethod.name} input ${parameter.name} is narrower than its Contract signature`
3630
+ );
3631
+ }
3632
+ }
3633
+ }
3634
+ }
3635
+ } else if (!dependencies.has(name))
3636
+ report(node, `undeclared dependency ${name}`);
3637
+ }
3638
+ }
3639
+ }
3640
+ ts2.forEachChild(node, visit);
3641
+ };
3642
+ visit(sf);
3643
+ }
3644
+ return out;
3645
+ }
3646
+
3647
+ // src/publishes.ts
3648
+ import { createHash as createHash2 } from "node:crypto";
3649
+ import { readFileSync } from "node:fs";
3650
+ function expandPublishes(authored, pkgName, pkgVersion) {
3651
+ if (authored === void 0 || authored === null) return {};
3652
+ if (typeof authored === "string") {
3653
+ if (authored.trim() !== "self") {
3654
+ throw new Error(
3655
+ `publishes: the only valid string form is "self" (got ${JSON.stringify(authored)}); use the map form to publish a differently-named contract`
3656
+ );
3657
+ }
3658
+ return { [pkgName]: pkgVersion };
3659
+ }
3660
+ if (typeof authored !== "object") {
3661
+ throw new Error(`publishes must be "self" or an object (got ${typeof authored})`);
3662
+ }
3663
+ const out = /* @__PURE__ */ Object.create(null);
3664
+ for (const [iface, range] of Object.entries(authored)) {
3665
+ if (typeof range !== "string") {
3666
+ throw new Error(`publishes[${JSON.stringify(iface)}] must be a version range string`);
3667
+ }
3668
+ out[iface] = range;
3669
+ }
3670
+ return out;
3671
+ }
3672
+ function hashContract(typesPath) {
3673
+ return createHash2("sha256").update(readFileSync(typesPath)).digest("hex");
3674
+ }
3675
+ var CONCRETE_SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
3676
+ function isConcreteVersion(v) {
3677
+ return CONCRETE_SEMVER.test(v.trim());
3678
+ }
3679
+ function derivePublishes(authored, pkgName, pkgVersion, typesPath) {
3680
+ const expanded = expandPublishes(authored, pkgName, pkgVersion);
3681
+ const names = Object.keys(expanded);
3682
+ if (names.length === 0) return {};
3683
+ if (names.length > 1) {
3684
+ throw new Error(
3685
+ `publishes declares ${names.length} interfaces (${names.join(", ")}) but a plugin ships a single "types" contract, so they would all carry the SAME typesSha256 \u2014 a hash that cannot say which contract is which. Publish one interface per plugin (a per-interface "types" map is not supported).`
3686
+ );
3687
+ }
3688
+ if (typesPath === null) {
3689
+ throw new Error(
3690
+ `publishes is set but no contract .d.ts was resolved \u2014 set "types": "api.d.ts" in package.json`
3691
+ );
3692
+ }
3693
+ for (const [iface, v] of Object.entries(expanded)) {
3694
+ if (!isConcreteVersion(v)) {
3695
+ throw new Error(
3696
+ `publishes[${JSON.stringify(iface)}] = ${JSON.stringify(v)} is a RANGE. Resolving a range against a published contract requires the registry (design spec \xA74.6, \xA710 \u2014 not yet implemented). Use a concrete version for a contract you ship yourself, or "self".`
3697
+ );
3698
+ }
3699
+ }
3700
+ const typesSha256 = hashContract(typesPath);
3701
+ const out = /* @__PURE__ */ Object.create(null);
3702
+ for (const name of names) {
3703
+ out[name] = { version: expanded[name].trim(), typesSha256 };
3704
+ }
3705
+ return out;
3706
+ }
3707
+
3708
+ // src/typecheck/typecheck.ts
3709
+ import {
3710
+ mkdirSync,
3711
+ existsSync as existsSync6,
3712
+ readdirSync as readdirSync2,
3713
+ readFileSync as readFileSync6,
3714
+ writeFileSync,
3715
+ rmSync,
3716
+ mkdtempSync
3717
+ } from "node:fs";
3718
+ import { join as join7, resolve as resolve7, dirname as dirname3 } from "node:path";
3078
3719
  import { tmpdir } from "node:os";
3079
3720
 
3080
3721
  // src/packages-resolve.ts
3081
3722
  import { existsSync } from "node:fs";
3082
- import { dirname, join, resolve } from "node:path";
3723
+ import { dirname, join, resolve as resolve2 } from "node:path";
3083
3724
  import { fileURLToPath } from "node:url";
3084
3725
  function isPackagesDir(dir) {
3085
- const abs = resolve(dir);
3726
+ const abs = resolve2(dir);
3086
3727
  return existsSync(join(abs, "sdk", "globals.d.ts")) || existsSync(join(abs, "sdk", "entity.d.ts"));
3087
3728
  }
3088
3729
  function findPackagesDirNearCli(fromCliUrl = import.meta.url) {
3089
3730
  const start = dirname(fileURLToPath(fromCliUrl));
3090
3731
  for (const rel of ["../..", "../../.."]) {
3091
3732
  const candidate = join(start, rel);
3092
- if (isPackagesDir(candidate)) return resolve(candidate);
3733
+ if (isPackagesDir(candidate)) return resolve2(candidate);
3093
3734
  }
3094
3735
  return void 0;
3095
3736
  }
3096
3737
  function resolvePackagesDir(opts) {
3097
3738
  if (opts?.explicit) {
3098
- const abs = resolve(opts.explicit);
3739
+ const abs = resolve2(opts.explicit);
3099
3740
  if (!isPackagesDir(abs)) {
3100
3741
  throw new Error(`packages dir does not look like @s2script stubs: ${abs}`);
3101
3742
  }
@@ -3103,7 +3744,7 @@ function resolvePackagesDir(opts) {
3103
3744
  }
3104
3745
  const env = process.env.S2SCRIPT_PACKAGES_DIR;
3105
3746
  if (env) {
3106
- const abs = resolve(env);
3747
+ const abs = resolve2(env);
3107
3748
  if (!isPackagesDir(abs)) {
3108
3749
  throw new Error(`S2SCRIPT_PACKAGES_DIR does not look like @s2script stubs: ${abs}`);
3109
3750
  }
@@ -3112,7 +3753,7 @@ function resolvePackagesDir(opts) {
3112
3753
  const nearCli = findPackagesDirNearCli(opts?.fromCliUrl);
3113
3754
  if (nearCli) return nearCli;
3114
3755
  if (opts?.pluginDir) {
3115
- const nm = join(resolve(opts.pluginDir), "node_modules", "@s2script");
3756
+ const nm = join(resolve2(opts.pluginDir), "node_modules", "@s2script");
3116
3757
  if (isPackagesDir(nm)) return nm;
3117
3758
  }
3118
3759
  throw new Error(
@@ -3120,32 +3761,6 @@ function resolvePackagesDir(opts) {
3120
3761
  );
3121
3762
  }
3122
3763
 
3123
- // src/tsconfig-shared.ts
3124
- var sharedCompilerOptionsJson = {
3125
- strict: true,
3126
- noEmit: true,
3127
- moduleResolution: "bundler",
3128
- module: "ESNext",
3129
- target: "ES2024",
3130
- lib: ["ES2024"],
3131
- types: [],
3132
- skipLibCheck: true,
3133
- allowImportingTsExtensions: true
3134
- };
3135
- function sharedProgramOptions(tsMod) {
3136
- return {
3137
- strict: sharedCompilerOptionsJson.strict,
3138
- noEmit: sharedCompilerOptionsJson.noEmit,
3139
- allowImportingTsExtensions: sharedCompilerOptionsJson.allowImportingTsExtensions,
3140
- moduleResolution: tsMod.ModuleResolutionKind.Bundler,
3141
- module: tsMod.ModuleKind.ESNext,
3142
- target: tsMod.ScriptTarget.ES2024,
3143
- lib: sharedCompilerOptionsJson.lib.map((l2) => `lib.${l2.toLowerCase()}.d.ts`),
3144
- types: [...sharedCompilerOptionsJson.types],
3145
- skipLibCheck: sharedCompilerOptionsJson.skipLibCheck
3146
- };
3147
- }
3148
-
3149
3764
  // src/contracts.ts
3150
3765
  import { existsSync as existsSync2 } from "node:fs";
3151
3766
  import { join as join2 } from "node:path";
@@ -3158,7 +3773,7 @@ function localContractPath(pluginDir, dep) {
3158
3773
 
3159
3774
  // src/workspace/siblings.ts
3160
3775
  import { readFileSync as readFileSync4 } from "node:fs";
3161
- import { join as join5, relative, resolve as resolve4 } from "node:path";
3776
+ import { join as join5, relative, resolve as resolve5 } from "node:path";
3162
3777
 
3163
3778
  // src/workspace/glob.ts
3164
3779
  function toPosix(p) {
@@ -3191,11 +3806,11 @@ function matchGlob(pattern, path) {
3191
3806
  }
3192
3807
 
3193
3808
  // src/workspace/workspace.ts
3194
- import { existsSync as existsSync3, readFileSync, readdirSync, statSync } from "node:fs";
3195
- import { dirname as dirname2, join as join3, resolve as resolve2 } from "node:path";
3809
+ import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync, statSync } from "node:fs";
3810
+ import { dirname as dirname2, join as join3, resolve as resolve3 } from "node:path";
3196
3811
  function readPackageJson(path) {
3197
3812
  try {
3198
- return JSON.parse(readFileSync(path, "utf8"));
3813
+ return JSON.parse(readFileSync2(path, "utf8"));
3199
3814
  } catch (e) {
3200
3815
  throw new Error(`cannot read ${path}: ${e.message}`);
3201
3816
  }
@@ -3204,7 +3819,7 @@ var MARKER_TEXT = /"s2script"[\s\S]*"workspace"\s*:/;
3204
3819
  function carriesWorkspaceMarker(pkgPath) {
3205
3820
  let text3;
3206
3821
  try {
3207
- text3 = readFileSync(pkgPath, "utf8");
3822
+ text3 = readFileSync2(pkgPath, "utf8");
3208
3823
  } catch {
3209
3824
  return false;
3210
3825
  }
@@ -3216,7 +3831,7 @@ function carriesWorkspaceMarker(pkgPath) {
3216
3831
  }
3217
3832
  }
3218
3833
  function findWorkspaceRoot(from) {
3219
- let dir = resolve2(from);
3834
+ let dir = resolve3(from);
3220
3835
  for (; ; ) {
3221
3836
  if (carriesWorkspaceMarker(join3(dir, "package.json"))) return dir;
3222
3837
  const parent = dirname2(dir);
@@ -3275,7 +3890,7 @@ function pnpmWorkspaceGlobs(root) {
3275
3890
  if (!existsSync3(path)) return [];
3276
3891
  let body;
3277
3892
  try {
3278
- body = readFileSync(path, "utf8");
3893
+ body = readFileSync2(path, "utf8");
3279
3894
  } catch {
3280
3895
  return [];
3281
3896
  }
@@ -3316,7 +3931,7 @@ function loadWorkspace(root) {
3316
3931
  return workspace;
3317
3932
  }
3318
3933
  function readWorkspace(root) {
3319
- const absRoot = resolve2(root);
3934
+ const absRoot = resolve3(root);
3320
3935
  const pkg = readPackageJson(join3(absRoot, "package.json"));
3321
3936
  const marker = pkg.s2script?.workspace;
3322
3937
  if (marker === void 0) {
@@ -3415,72 +4030,11 @@ function readWorkspace(root) {
3415
4030
  return { workspace: { root: absRoot, pkg, plugins, libs }, problems };
3416
4031
  }
3417
4032
  function memberForDir(ws, dir) {
3418
- const abs = toPosix(resolve2(dir));
4033
+ const abs = toPosix(resolve3(dir));
3419
4034
  for (const m of [...ws.plugins, ...ws.libs]) if (toPosix(m.dir) === abs) return m;
3420
4035
  return null;
3421
4036
  }
3422
4037
 
3423
- // src/publishes.ts
3424
- import { createHash } from "node:crypto";
3425
- import { readFileSync as readFileSync2 } from "node:fs";
3426
- function expandPublishes(authored, pkgName, pkgVersion) {
3427
- if (authored === void 0 || authored === null) return {};
3428
- if (typeof authored === "string") {
3429
- if (authored.trim() !== "self") {
3430
- throw new Error(
3431
- `publishes: the only valid string form is "self" (got ${JSON.stringify(authored)}); use the map form to publish a differently-named contract`
3432
- );
3433
- }
3434
- return { [pkgName]: pkgVersion };
3435
- }
3436
- if (typeof authored !== "object") {
3437
- throw new Error(`publishes must be "self" or an object (got ${typeof authored})`);
3438
- }
3439
- const out = {};
3440
- for (const [iface, range] of Object.entries(authored)) {
3441
- if (typeof range !== "string") {
3442
- throw new Error(`publishes[${JSON.stringify(iface)}] must be a version range string`);
3443
- }
3444
- out[iface] = range;
3445
- }
3446
- return out;
3447
- }
3448
- function hashContract(typesPath) {
3449
- return createHash("sha256").update(readFileSync2(typesPath)).digest("hex");
3450
- }
3451
- var CONCRETE_SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
3452
- function isConcreteVersion(v) {
3453
- return CONCRETE_SEMVER.test(v.trim());
3454
- }
3455
- function derivePublishes(authored, pkgName, pkgVersion, typesPath) {
3456
- const expanded = expandPublishes(authored, pkgName, pkgVersion);
3457
- const names = Object.keys(expanded);
3458
- if (names.length === 0) return {};
3459
- if (names.length > 1) {
3460
- throw new Error(
3461
- `publishes declares ${names.length} interfaces (${names.join(", ")}) but a plugin ships a single "types" contract, so they would all carry the SAME typesSha256 \u2014 a hash that cannot say which contract is which. Publish one interface per plugin (a per-interface "types" map is not supported).`
3462
- );
3463
- }
3464
- if (typesPath === null) {
3465
- throw new Error(
3466
- `publishes is set but no contract .d.ts was resolved \u2014 set "types": "api.d.ts" in package.json`
3467
- );
3468
- }
3469
- for (const [iface, v] of Object.entries(expanded)) {
3470
- if (!isConcreteVersion(v)) {
3471
- throw new Error(
3472
- `publishes[${JSON.stringify(iface)}] = ${JSON.stringify(v)} is a RANGE. Resolving a range against a published contract requires the registry (design spec \xA74.6, \xA710 \u2014 not yet implemented). Use a concrete version for a contract you ship yourself, or "self".`
3473
- );
3474
- }
3475
- }
3476
- const typesSha256 = hashContract(typesPath);
3477
- const out = {};
3478
- for (const name of names) {
3479
- out[name] = { version: expanded[name].trim(), typesSha256 };
3480
- }
3481
- return out;
3482
- }
3483
-
3484
4038
  // src/workspace/interfaces.ts
3485
4039
  function where(p) {
3486
4040
  return `${p.name} (${p.relDir})`;
@@ -3523,7 +4077,7 @@ function ambiguityProblem(iface, publishers) {
3523
4077
 
3524
4078
  // src/publish-gate.ts
3525
4079
  import { existsSync as existsSync4, readFileSync as readFileSync3, statSync as statSync2 } from "node:fs";
3526
- import { join as join4, resolve as resolve3 } from "node:path";
4080
+ import { join as join4, resolve as resolve4 } from "node:path";
3527
4081
  function hasPublishes(publishes) {
3528
4082
  if (publishes == null) return false;
3529
4083
  if (typeof publishes === "string") return publishes.trim().length > 0;
@@ -3548,7 +4102,7 @@ function assertPublishesTypes(pkg, pluginDir) {
3548
4102
  error: `published API must be a .d.ts file (got ${JSON.stringify(typesRel)})`
3549
4103
  };
3550
4104
  }
3551
- const typesPath = resolve3(pluginDir, typesRel);
4105
+ const typesPath = resolve4(pluginDir, typesRel);
3552
4106
  if (!existsSync4(typesPath)) {
3553
4107
  return { ok: false, error: `types file not found: ${typesRel}` };
3554
4108
  }
@@ -3579,7 +4133,7 @@ function resolveSiblingContracts(pluginDir, deps) {
3579
4133
  const siblings = /* @__PURE__ */ new Map();
3580
4134
  const shadowedCopies = [];
3581
4135
  if (deps.length === 0) return { siblings, shadowedCopies };
3582
- const absDir = resolve4(pluginDir);
4136
+ const absDir = resolve5(pluginDir);
3583
4137
  const root = findWorkspaceRoot(absDir);
3584
4138
  if (root === null) return { siblings, shadowedCopies };
3585
4139
  const rootPkg = JSON.parse(readFileSync4(join5(root, "package.json"), "utf8"));
@@ -3587,7 +4141,7 @@ function resolveSiblingContracts(pluginDir, deps) {
3587
4141
  const scan = scanWorkspace(root);
3588
4142
  const ws = scan.workspace;
3589
4143
  const { producers, ambiguous, byPlugin } = indexInterfaces(ws.plugins);
3590
- const self = ws.plugins.find((p) => resolve4(p.dir) === absDir);
4144
+ const self = ws.plugins.find((p) => resolve5(p.dir) === absDir);
3591
4145
  const label = self?.name ?? toPosix(relative(root, absDir));
3592
4146
  const unread = scan.problems ?? [];
3593
4147
  const problems = [];
@@ -3599,7 +4153,7 @@ function resolveSiblingContracts(pluginDir, deps) {
3599
4153
  }
3600
4154
  const producer = producers.get(dep);
3601
4155
  if (producer === void 0) {
3602
- const namesake = ws.plugins.find((p) => p.name === dep && resolve4(p.dir) !== absDir);
4156
+ const namesake = ws.plugins.find((p) => p.name === dep && resolve5(p.dir) !== absDir);
3603
4157
  if (namesake !== void 0 && (byPlugin.get(namesake)?.length ?? 0) === 0) {
3604
4158
  problems.push(
3605
4159
  `${dep} (workspace sibling at ${namesake.relDir}) declares no s2script.publishes \u2014 you cannot depend on an interface nobody publishes. The engine resolves a dependency name through iface_published (loader.rs:175), which is never true for a plugin that publishes nothing, so this consumer would park in "waiting" forever and every ctx.use would throw InterfaceUnavailable. Add s2script.publishes and "types" to ${namesake.relDir}/package.json.`
@@ -3614,7 +4168,7 @@ function resolveSiblingContracts(pluginDir, deps) {
3614
4168
  continue;
3615
4169
  }
3616
4170
  const sib = producer.plugin;
3617
- if (resolve4(sib.dir) === absDir) continue;
4171
+ if (resolve5(sib.dir) === absDir) continue;
3618
4172
  const gate = assertPublishesTypes(sib.pkg, sib.dir);
3619
4173
  if (!gate.ok || gate.typesPath === null) {
3620
4174
  const why = gate.ok ? "it declares no s2script.publishes" : gate.error;
@@ -3637,7 +4191,7 @@ function resolveSiblingContracts(pluginDir, deps) {
3637
4191
 
3638
4192
  // src/libraries.ts
3639
4193
  import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
3640
- import { join as join6, resolve as resolve5 } from "node:path";
4194
+ import { join as join6, resolve as resolve6 } from "node:path";
3641
4195
  function packageKind(pkg) {
3642
4196
  const k = pkg.s2script?.kind ?? "plugin";
3643
4197
  if (k !== "plugin" && k !== "library") {
@@ -3655,7 +4209,7 @@ function localLibraryDir(pluginDir, name) {
3655
4209
  return existsSync5(join6(p, "index.d.ts")) ? p : null;
3656
4210
  }
3657
4211
  function resolveLibrarySibling(pluginDir, name) {
3658
- const absDir = resolve5(pluginDir);
4212
+ const absDir = resolve6(pluginDir);
3659
4213
  const root = findWorkspaceRoot(absDir);
3660
4214
  if (root === null) return null;
3661
4215
  const scan = scanWorkspace(root);
@@ -3690,7 +4244,7 @@ function resolveLibrarySibling(pluginDir, name) {
3690
4244
  `library ${name} (workspace sibling at ${lib.relDir}) has no "types" in its package.json`
3691
4245
  );
3692
4246
  }
3693
- const typesPath = resolve5(lib.dir, typesRel);
4247
+ const typesPath = resolve6(lib.dir, typesRel);
3694
4248
  if (!existsSync5(typesPath)) {
3695
4249
  throw new Error(
3696
4250
  `library ${name} (workspace sibling at ${lib.relDir}) types file not found: ${typesRel}`
@@ -3783,18 +4337,22 @@ function declaredModules(dtsFiles) {
3783
4337
  const out = /* @__PURE__ */ new Set();
3784
4338
  for (const f of dtsFiles) {
3785
4339
  const body = readFileSync6(f, "utf8");
3786
- for (const m of body.matchAll(/declare\s+module\s+["']([^"']+)["']/g)) out.add(m[1]);
4340
+ for (const m of body.matchAll(/declare\s+module\s+["']([^"']+)["']/g))
4341
+ out.add(m[1]);
3787
4342
  }
3788
4343
  return out;
3789
4344
  }
3790
4345
  function typecheckPlugin(pluginDir, opts) {
3791
- const absDir = resolve6(pluginDir);
3792
- const packagesDir = opts?.packagesDir ? resolve6(opts.packagesDir) : resolvePackagesDir({ pluginDir: absDir });
4346
+ const absDir = resolve7(pluginDir);
4347
+ const packagesDir = opts?.packagesDir ? resolve7(opts.packagesDir) : resolvePackagesDir({ pluginDir: absDir });
3793
4348
  const pkg = JSON.parse(readFileSync6(join7(absDir, "package.json"), "utf8"));
3794
4349
  const s2 = pkg.s2script ?? {};
3795
4350
  const entryRel = s2.main ?? pkg.main;
3796
- if (!entryRel) throw new Error(`typecheckPlugin: no entry point in ${join7(absDir, "package.json")}`);
3797
- const entry = resolve6(absDir, entryRel);
4351
+ if (!entryRel)
4352
+ throw new Error(
4353
+ `typecheckPlugin: no entry point in ${join7(absDir, "package.json")}`
4354
+ );
4355
+ const entry = resolve7(absDir, entryRel);
3798
4356
  const isAlwaysResolved = (d) => d === "@s2script/sdk" || d.startsWith("@s2script/sdk/") || d === "@s2script/cs2" || d.startsWith("@s2script/cs2/");
3799
4357
  const localDts = localDeclarationFiles(absDir);
3800
4358
  const locallyDeclared = declaredModules(localDts);
@@ -3803,7 +4361,7 @@ function typecheckPlugin(pluginDir, opts) {
3803
4361
  ...Object.keys(s2.optionalPluginDependencies ?? {})
3804
4362
  ];
3805
4363
  const { siblings } = resolveSiblingContracts(absDir, allDeclaredDeps);
3806
- const contractPaths = {};
4364
+ const contractPaths = /* @__PURE__ */ Object.create(null);
3807
4365
  for (const d of allDeclaredDeps) {
3808
4366
  const sibling = siblings.get(d);
3809
4367
  if (sibling !== void 0) {
@@ -3813,6 +4371,53 @@ function typecheckPlugin(pluginDir, opts) {
3813
4371
  const p = localContractPath(absDir, d);
3814
4372
  if (p !== null) contractPaths[d] = [p];
3815
4373
  }
4374
+ const protocol2 = s2.interfaceProtocol === 2;
4375
+ const authoritativePaths = Object.fromEntries(
4376
+ Object.entries(contractPaths).map(([name, paths]) => [name, paths[0]])
4377
+ );
4378
+ const ownNames = new Set(
4379
+ Object.keys(expandPublishes(s2.publishes, pkg.name, pkg.version))
4380
+ );
4381
+ if (protocol2 && pkg.types && ownNames.size === 0) ownNames.add(pkg.name);
4382
+ if (protocol2 && pkg.types)
4383
+ for (const name of ownNames)
4384
+ authoritativePaths[name] = resolve7(absDir, pkg.types);
4385
+ const interfaceContracts = /* @__PURE__ */ Object.create(null);
4386
+ if (protocol2) {
4387
+ try {
4388
+ for (const dep of allDeclaredDeps)
4389
+ if (!authoritativePaths[dep])
4390
+ throw new Error(
4391
+ `InterfaceContractError: ${dep} has no verified contract copy; run s2s add`
4392
+ );
4393
+ for (const [name, path] of Object.entries(authoritativePaths)) {
4394
+ interfaceContracts[name] = extractContract(path, packagesDir);
4395
+ if (!siblings.has(name) && !ownNames.has(name)) {
4396
+ const receipt = join7(dirname3(path), "package.json");
4397
+ if (existsSync6(receipt)) {
4398
+ const published = JSON.parse(readFileSync6(receipt, "utf8")).s2script?.publishes?.[name];
4399
+ if (!published || published.typesSha256 !== hashContract(path) || published.contract?.sha256 !== interfaceContracts[name].sha256)
4400
+ throw new Error(
4401
+ `InterfaceContractError: ${name} verified copy hash drift; run s2s add`
4402
+ );
4403
+ }
4404
+ }
4405
+ }
4406
+ } catch (e) {
4407
+ return {
4408
+ ok: false,
4409
+ diagnostics: [
4410
+ {
4411
+ file: entry,
4412
+ line: 1,
4413
+ col: 1,
4414
+ code: 92001,
4415
+ message: e.message
4416
+ }
4417
+ ]
4418
+ };
4419
+ }
4420
+ }
3816
4421
  const libraryPaths = resolveLibraries(absDir, s2.libraries ?? {}).paths;
3817
4422
  const libraryNames = new Set(Object.keys(s2.libraries ?? {}));
3818
4423
  const deps = [
@@ -3826,7 +4431,7 @@ function typecheckPlugin(pluginDir, opts) {
3826
4431
  const options = {
3827
4432
  // Accept explicit `.ts` import extensions (node type-stripping requires them for source-to-source
3828
4433
  // imports; esbuild strips them at bundle time). Backward-compatible — extensionless imports still resolve.
3829
- ...sharedProgramOptions(ts),
4434
+ ...sharedProgramOptions(ts3),
3830
4435
  baseUrl: packagesDir,
3831
4436
  paths: {
3832
4437
  // Builtins: the root barrel `@s2script/sdk` → packages/sdk/index.d.ts; subpaths
@@ -3854,16 +4459,60 @@ function typecheckPlugin(pluginDir, opts) {
3854
4459
  ];
3855
4460
  const tmp = mkdtempSync(join7(tmpdir(), "s2tc-"));
3856
4461
  try {
3857
- if (deps.length) {
4462
+ const generatedPath = join7(absDir, ".s2script", "interfaces.d.ts");
4463
+ rmSync(generatedPath, { force: true });
4464
+ if (protocol2) {
4465
+ mkdirSync(join7(absDir, ".s2script"), { recursive: true });
4466
+ let moduleId = 0;
4467
+ for (const name of allDeclaredDeps) {
4468
+ const path = authoritativePaths[name], wrapper = join7(tmp, `contract-${moduleId++}.d.ts`);
4469
+ writeFileSync(
4470
+ wrapper,
4471
+ `import type {Contract} from ${JSON.stringify(
4472
+ path
4473
+ )};
4474
+ export * from ${JSON.stringify(path)};
4475
+ export declare const on: import("@s2script/sdk/interfaces").TypedInterfaceHandle<Contract>["on"];
4476
+ ` + Object.keys(interfaceContracts[name].metadata.methods).map(
4477
+ (method) => `export declare const ${method}: Contract["methods"][${JSON.stringify(
4478
+ method
4479
+ )}];`
4480
+ ).join("\n")
4481
+ );
4482
+ options.paths[name] = [wrapper];
4483
+ }
4484
+ writeFileSync(
4485
+ generatedPath,
4486
+ 'import "@s2script/sdk/interfaces";\n' + Object.entries(authoritativePaths).map(
4487
+ ([name, path], i2) => `import type { Contract as C${i2} } from ${JSON.stringify(
4488
+ path
4489
+ )};`
4490
+ ).join("\n") + '\ndeclare module "@s2script/sdk/interfaces" { interface InterfaceContracts {\n' + Object.keys(authoritativePaths).map((name, i2) => `${JSON.stringify(name)}: C${i2};`).join("\n") + "\n} }\n"
4491
+ );
4492
+ rootNames.push(generatedPath);
4493
+ }
4494
+ if (deps.length && !protocol2) {
3858
4495
  const stub = join7(tmp, "ambient.d.ts");
3859
- writeFileSync(stub, deps.map((d) => `declare module ${JSON.stringify(d)};`).join("\n") + "\n");
4496
+ writeFileSync(
4497
+ stub,
4498
+ deps.map((d) => `declare module ${JSON.stringify(d)};`).join("\n") + "\n"
4499
+ );
3860
4500
  rootNames.push(stub);
3861
4501
  }
3862
- const program = ts.createProgram(rootNames, options);
4502
+ const program = ts3.createProgram(rootNames, options);
3863
4503
  const diags = [
3864
4504
  ...program.getSyntacticDiagnostics(),
3865
4505
  ...program.getSemanticDiagnostics(),
3866
- ...program.getGlobalDiagnostics()
4506
+ ...program.getGlobalDiagnostics(),
4507
+ ...protocol2 ? checkInteropCalls(
4508
+ program,
4509
+ authoritativePaths,
4510
+ ownNames,
4511
+ generatedPath,
4512
+ absDir,
4513
+ new Set(allDeclaredDeps),
4514
+ new Set(Object.keys(s2.optionalPluginDependencies ?? {}))
4515
+ ) : []
3867
4516
  ];
3868
4517
  const out = diags.map((d) => {
3869
4518
  let file = "?", line = 0, col = 0;
@@ -3873,9 +4522,21 @@ function typecheckPlugin(pluginDir, opts) {
3873
4522
  line = lc2.line + 1;
3874
4523
  col = lc2.character + 1;
3875
4524
  }
3876
- return { file, line, col, code: d.code, message: ts.flattenDiagnosticMessageText(d.messageText, "\n") };
4525
+ return {
4526
+ file,
4527
+ line,
4528
+ col,
4529
+ code: d.code,
4530
+ message: ts3.flattenDiagnosticMessageText(d.messageText, "\n")
4531
+ };
3877
4532
  });
3878
- return { ok: out.length === 0, diagnostics: out, program };
4533
+ return {
4534
+ ok: out.length === 0,
4535
+ diagnostics: out,
4536
+ program,
4537
+ interfaceContracts,
4538
+ contractPaths: authoritativePaths
4539
+ };
3879
4540
  } finally {
3880
4541
  rmSync(tmp, { recursive: true, force: true });
3881
4542
  }
@@ -3982,15 +4643,15 @@ function validateDecl(full, decl, typeStr, errs) {
3982
4643
  }
3983
4644
 
3984
4645
  // src/api-version.ts
3985
- var HOST_API_VERSION_MAJOR = 2;
4646
+ var HOST_API_VERSION_MAJOR = 3;
3986
4647
  var STAMPED_API_VERSION = `${HOST_API_VERSION_MAJOR}.x`;
3987
4648
 
3988
4649
  // src/npm-deps-gate.ts
3989
4650
  import { lstatSync, realpathSync } from "node:fs";
3990
- import { dirname as dirname3, join as join8, resolve as resolve7, sep } from "node:path";
4651
+ import { dirname as dirname4, join as join8, resolve as resolve8, sep } from "node:path";
3991
4652
  function findInNodeModules(startDir, dep) {
3992
4653
  const segs = dep.split("/");
3993
- let dir = resolve7(startDir);
4654
+ let dir = resolve8(startDir);
3994
4655
  for (; ; ) {
3995
4656
  const candidate = join8(dir, "node_modules", ...segs);
3996
4657
  try {
@@ -3998,7 +4659,7 @@ function findInNodeModules(startDir, dep) {
3998
4659
  return candidate;
3999
4660
  } catch {
4000
4661
  }
4001
- const parent = dirname3(dir);
4662
+ const parent = dirname4(dir);
4002
4663
  if (parent === dir) return null;
4003
4664
  dir = parent;
4004
4665
  }
@@ -4033,13 +4694,13 @@ function assertNoNpmRuntimeDeps(pkg, pluginDir) {
4033
4694
 
4034
4695
  // src/build-library.ts
4035
4696
  import * as esbuild from "esbuild";
4036
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync8, statSync as statSync3 } from "node:fs";
4037
- import { resolve as resolve9, join as join10 } from "node:path";
4697
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync8, statSync as statSync3 } from "node:fs";
4698
+ import { resolve as resolve10, join as join10 } from "node:path";
4038
4699
 
4039
4700
  // src/lint/lint.ts
4040
4701
  import { ESLint } from "eslint";
4041
4702
  import { existsSync as existsSync7, readdirSync as readdirSync3 } from "node:fs";
4042
- import { dirname as dirname4, join as join9, resolve as resolve8 } from "node:path";
4703
+ import { dirname as dirname5, join as join9, resolve as resolve9 } from "node:path";
4043
4704
  import s2lint from "@s2script/eslint-plugin";
4044
4705
  var CONFIG_FILES = ["eslint.config.js", "eslint.config.mjs", "eslint.config.cjs", "eslint.config.ts"];
4045
4706
  function ownConfigDir(pluginDir) {
@@ -4048,7 +4709,7 @@ function ownConfigDir(pluginDir) {
4048
4709
  for (; ; ) {
4049
4710
  if (CONFIG_FILES.some((f) => existsSync7(join9(dir, f)))) return dir;
4050
4711
  if (stopAt === null || dir === stopAt) return null;
4051
- const parent = dirname4(dir);
4712
+ const parent = dirname5(dir);
4052
4713
  if (parent === dir) return null;
4053
4714
  dir = parent;
4054
4715
  }
@@ -4070,7 +4731,7 @@ function collectTsFiles(dir) {
4070
4731
  return out;
4071
4732
  }
4072
4733
  async function lintPlugin(pluginDir, program) {
4073
- const absDir = resolve8(pluginDir);
4734
+ const absDir = resolve9(pluginDir);
4074
4735
  const configDir = ownConfigDir(absDir);
4075
4736
  const hasOwnConfig = configDir !== null;
4076
4737
  const eslint = hasOwnConfig ? new ESLint({ cwd: configDir, errorOnUnmatchedPattern: false, warnIgnored: false }) : new ESLint({
@@ -4091,7 +4752,7 @@ async function lintPlugin(pluginDir, program) {
4091
4752
 
4092
4753
  // src/build-library.ts
4093
4754
  async function buildLibrary(dir, packagesDir) {
4094
- const absDir = resolve9(dir);
4755
+ const absDir = resolve10(dir);
4095
4756
  const pkgPath = join10(absDir, "package.json");
4096
4757
  const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
4097
4758
  const s2 = pkg.s2script ?? {};
@@ -4107,7 +4768,7 @@ async function buildLibrary(dir, packagesDir) {
4107
4768
  if (typeof typesRel !== "string" || !typesRel.endsWith(".d.ts")) {
4108
4769
  throw new Error('a library must set "types" to a .d.ts \u2014 consumers vendor it to typecheck against');
4109
4770
  }
4110
- const typesPath = resolve9(absDir, typesRel);
4771
+ const typesPath = resolve10(absDir, typesRel);
4111
4772
  if (!existsSync8(typesPath) || !statSync3(typesPath).isFile() || statSync3(typesPath).size === 0) {
4112
4773
  throw new Error(`types file is missing or empty: ${typesRel}`);
4113
4774
  }
@@ -4154,7 +4815,7 @@ ${lint.output}`);
4154
4815
  };
4155
4816
  const sanitized = String(pkg.name).replace(/[^a-zA-Z0-9._-]/g, "_");
4156
4817
  const outDir = join10(absDir, "dist");
4157
- mkdirSync(outDir, { recursive: true });
4818
+ mkdirSync2(outDir, { recursive: true });
4158
4819
  const outPath = join10(outDir, `${sanitized}.s2lib`);
4159
4820
  writeFileSync2(
4160
4821
  outPath,
@@ -4167,78 +4828,6 @@ ${lint.output}`);
4167
4828
  return outPath;
4168
4829
  }
4169
4830
 
4170
- // src/publish-scan.ts
4171
- import ts2 from "typescript";
4172
- function isPluginContext(type) {
4173
- const sym = type.getSymbol() ?? type.aliasSymbol;
4174
- return sym?.getName() === "PluginContext";
4175
- }
4176
- function isFromPluginDts(checker, node) {
4177
- let sym = checker.getSymbolAtLocation(node);
4178
- if (sym === void 0) return false;
4179
- if (sym.flags & ts2.SymbolFlags.Alias) {
4180
- const aliased = checker.getAliasedSymbol(sym);
4181
- if (aliased !== void 0) sym = aliased;
4182
- }
4183
- for (const d of sym.declarations ?? []) {
4184
- const f = d.getSourceFile().fileName.replace(/\\/g, "/");
4185
- if (f.endsWith("/plugin.d.ts")) return true;
4186
- }
4187
- return false;
4188
- }
4189
- function recordPublishOrUse(out, method, arg0, sf, node) {
4190
- if (method === "publish") {
4191
- if (arg0 !== void 0 && ts2.isStringLiteralLike(arg0)) {
4192
- out.publishNames.push(arg0.text);
4193
- } else {
4194
- const { line } = sf.getLineAndCharacterOfPosition(node.getStart());
4195
- out.dynamicPublishSites.push(`${sf.fileName}:${line + 1}`);
4196
- }
4197
- } else if (arg0 !== void 0 && ts2.isStringLiteralLike(arg0)) {
4198
- out.useNames.push(arg0.text);
4199
- }
4200
- }
4201
- function recordImportSpecifier(out, spec) {
4202
- if (spec.startsWith(".") || spec.startsWith("@s2script/")) return;
4203
- out.importNames.push(spec);
4204
- }
4205
- function scanPluginProgram(program, pluginDir) {
4206
- const checker = program.getTypeChecker();
4207
- const out = { publishNames: [], dynamicPublishSites: [], useNames: [], importNames: [] };
4208
- const dirPrefix = pluginDir.replace(/\\/g, "/").replace(/\/+$/, "") + "/";
4209
- for (const sf of program.getSourceFiles()) {
4210
- if (sf.isDeclarationFile) continue;
4211
- if (!sf.fileName.replace(/\\/g, "/").startsWith(dirPrefix)) continue;
4212
- const visit = (node) => {
4213
- if ((ts2.isImportDeclaration(node) || ts2.isExportDeclaration(node)) && node.moduleSpecifier !== void 0 && ts2.isStringLiteralLike(node.moduleSpecifier)) {
4214
- recordImportSpecifier(out, node.moduleSpecifier.text);
4215
- }
4216
- if (ts2.isCallExpression(node)) {
4217
- if (ts2.isPropertyAccessExpression(node.expression)) {
4218
- const method = node.expression.name.text;
4219
- if (method === "publish" || method === "use" || method === "tryUse") {
4220
- const recv = checker.getTypeAtLocation(node.expression.expression);
4221
- if (isPluginContext(recv)) {
4222
- recordPublishOrUse(out, method, node.arguments[0], sf, node);
4223
- }
4224
- }
4225
- } else if (ts2.isIdentifier(node.expression)) {
4226
- const name = node.expression.text;
4227
- if ((name === "publish" || name === "use" || name === "tryUse") && isFromPluginDts(checker, node.expression)) {
4228
- recordPublishOrUse(out, name, node.arguments[0], sf, node);
4229
- }
4230
- }
4231
- }
4232
- ts2.forEachChild(node, visit);
4233
- };
4234
- visit(sf);
4235
- }
4236
- out.publishNames = [...new Set(out.publishNames)];
4237
- out.useNames = [...new Set(out.useNames)];
4238
- out.importNames = [...new Set(out.importNames)];
4239
- return out;
4240
- }
4241
-
4242
4831
  // src/gamedata/types.ts
4243
4832
  var PLATFORM = "linuxsteamrt64";
4244
4833
  var ARG_KINDS = ["bool", "int", "float", "string", "utlstring", "vector", "entity"];
@@ -4744,7 +5333,7 @@ function stripJsonComments(src) {
4744
5333
 
4745
5334
  // src/build.ts
4746
5335
  async function buildPlugin(dir, packagesDir) {
4747
- const absDir = resolve10(dir);
5336
+ const absDir = resolve11(dir);
4748
5337
  const pkgPath = join11(absDir, "package.json");
4749
5338
  const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
4750
5339
  if (packageKind(pkg) === "library") {
@@ -4774,7 +5363,7 @@ async function buildPlugin(dir, packagesDir) {
4774
5363
  const permissions = Array.isArray(s2.permissions) ? s2.permissions : [];
4775
5364
  let gamedata;
4776
5365
  if (typeof s2.gamedata === "string") {
4777
- const gdPath = resolve10(absDir, s2.gamedata);
5366
+ const gdPath = resolve11(absDir, s2.gamedata);
4778
5367
  const dirPrefix = absDir.endsWith(sep2) ? absDir : absDir + sep2;
4779
5368
  if (gdPath !== absDir && !gdPath.startsWith(dirPrefix)) {
4780
5369
  throw new Error(
@@ -4799,7 +5388,7 @@ async function buildPlugin(dir, packagesDir) {
4799
5388
  if (gdErrs.length) throw new Error(`invalid gamedata:
4800
5389
  ${gdErrs.join("\n ")}`);
4801
5390
  const genDir = join11(absDir, ".s2script");
4802
- mkdirSync2(genDir, { recursive: true });
5391
+ mkdirSync3(genDir, { recursive: true });
4803
5392
  writeFileSync3(join11(genDir, "gamedata.d.ts"), generateGamedataTypes(gamedata));
4804
5393
  if (gamedata.hooks && Object.keys(gamedata.hooks).length) {
4805
5394
  writeFileSync3(join11(genDir, "hooks.d.ts"), generateHookTypes(gamedata));
@@ -4935,9 +5524,18 @@ ${lint.output}`);
4935
5524
  if (Object.keys(derivedPublishes).length > 0) {
4936
5525
  manifest.publishes = derivedPublishes;
4937
5526
  }
5527
+ if (s2.interfaceProtocol === 2) {
5528
+ manifest.interfaceProtocol = 2;
5529
+ const imports = /* @__PURE__ */ Object.create(null);
5530
+ for (const [name2, contract] of Object.entries(tc.interfaceContracts ?? {})) {
5531
+ if (derivedPublishes[name2]) Object.assign(derivedPublishes[name2], { contract });
5532
+ if (declaredDeps.has(name2)) imports[name2] = contract;
5533
+ }
5534
+ manifest.interfaceContracts = imports;
5535
+ }
4938
5536
  if (config !== void 0) manifest.config = config;
4939
5537
  if (permissions.length > 0) manifest.permissions = permissions;
4940
- const compiledAgainst = {};
5538
+ const compiledAgainst = /* @__PURE__ */ Object.create(null);
4941
5539
  for (const dep of [
4942
5540
  ...Object.keys(pluginDependencies),
4943
5541
  ...Object.keys(optionalPluginDependencies)
@@ -4965,13 +5563,13 @@ ${lint.output}`);
4965
5563
  }
4966
5564
  const sanitizedId = name.replace(/[^a-zA-Z0-9._-]/g, "_");
4967
5565
  const outDir = join11(absDir, "dist");
4968
- mkdirSync2(outDir, { recursive: true });
5566
+ mkdirSync3(outDir, { recursive: true });
4969
5567
  const outPath = join11(outDir, `${sanitizedId}.s2sp`);
4970
5568
  writeFileSync3(outPath, zipSync(zipFiles));
4971
5569
  return outPath;
4972
5570
  }
4973
5571
  async function buildPackage(dir, packagesDir) {
4974
- const pkg = JSON.parse(readFileSync8(join11(resolve10(dir), "package.json"), "utf8"));
5572
+ const pkg = JSON.parse(readFileSync8(join11(resolve11(dir), "package.json"), "utf8"));
4975
5573
  return packageKind(pkg) === "library" ? buildLibrary(dir, packagesDir) : buildPlugin(dir, packagesDir);
4976
5574
  }
4977
5575
 
@@ -6619,12 +7217,12 @@ async function run(argv2) {
6619
7217
  const filters = collectFilters(argv2);
6620
7218
  const stampVersion = parseFlag(argv2, "--stamp-version");
6621
7219
  let dir = positionals(argv2, VALUE_FLAGS)[0];
6622
- const wsRoot = findWorkspaceRoot(dir === void 0 ? process.cwd() : resolve11(dir));
6623
- const workspaceMode = wsRoot !== null && (dir === void 0 || resolve11(dir) === wsRoot);
7220
+ const wsRoot = findWorkspaceRoot(dir === void 0 ? process.cwd() : resolve12(dir));
7221
+ const workspaceMode = wsRoot !== null && (dir === void 0 || resolve12(dir) === wsRoot);
6624
7222
  if (!workspaceMode && (filters.length > 0 || stampVersion !== void 0)) {
6625
7223
  const flag = filters.length > 0 ? "--filter" : "--stamp-version";
6626
7224
  throw new Error(
6627
- wsRoot === null ? `${flag} is a workspace flag, but no workspace root was found above ${dir === void 0 ? process.cwd() : resolve11(dir)} \u2014 a workspace root is a package.json carrying an "s2script": { "workspace": { "plugins": [...] } } block` : `${flag} narrows a WORKSPACE build, but ${dir} names a single plugin \u2014 drop the argument (or pass ${wsRoot}) to build the workspace`
7225
+ wsRoot === null ? `${flag} is a workspace flag, but no workspace root was found above ${dir === void 0 ? process.cwd() : resolve12(dir)} \u2014 a workspace root is a package.json carrying an "s2script": { "workspace": { "plugins": [...] } } block` : `${flag} narrows a WORKSPACE build, but ${dir} names a single plugin \u2014 drop the argument (or pass ${wsRoot}) to build the workspace`
6628
7226
  );
6629
7227
  }
6630
7228
  if (wsRoot !== null && workspaceMode) {
@@ -6642,7 +7240,7 @@ async function run(argv2) {
6642
7240
  }
6643
7241
  const packagesDir = resolvePackagesDir({
6644
7242
  explicit: parseFlag(argv2, "--packages-dir"),
6645
- pluginDir: resolve11(dir),
7243
+ pluginDir: resolve12(dir),
6646
7244
  fromCliUrl: import.meta.url
6647
7245
  });
6648
7246
  const out = await task(`Building ${dir}`, () => buildPackage(dir, packagesDir), {
@@ -6702,11 +7300,11 @@ async function runWorkspace(argv2, root, interactive, filters, stampVersion) {
6702
7300
  }
6703
7301
 
6704
7302
  // src/commands/deploy.ts
6705
- import { resolve as resolve13 } from "node:path";
7303
+ import { resolve as resolve14 } from "node:path";
6706
7304
 
6707
7305
  // src/registry/deploy.ts
6708
7306
  import { readFileSync as readFileSync11 } from "node:fs";
6709
- import { resolve as resolve12 } from "node:path";
7307
+ import { resolve as resolve13 } from "node:path";
6710
7308
 
6711
7309
  // src/types-pack.ts
6712
7310
  import { readFileSync as readFileSync9 } from "node:fs";
@@ -6770,7 +7368,7 @@ function packTypesTarball(input) {
6770
7368
  }
6771
7369
 
6772
7370
  // src/registry/credentials.ts
6773
- import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "node:fs";
7371
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "node:fs";
6774
7372
  import { homedir } from "node:os";
6775
7373
  import { join as join13 } from "node:path";
6776
7374
  function credentialsPath() {
@@ -6806,7 +7404,7 @@ function loadCredentials() {
6806
7404
  }
6807
7405
  function saveCredentials(creds) {
6808
7406
  const dir = join13(homedir(), ".s2script");
6809
- mkdirSync3(dir, { recursive: true });
7407
+ mkdirSync4(dir, { recursive: true });
6810
7408
  writeFileSync5(
6811
7409
  credentialsPath(),
6812
7410
  JSON.stringify(
@@ -6999,7 +7597,7 @@ function assertDeployable(pkg, label) {
6999
7597
  }
7000
7598
  }
7001
7599
  function assembleDeployArchive(pluginDir, pkg, outPath) {
7002
- const absDir = resolve12(pluginDir);
7600
+ const absDir = resolve13(pluginDir);
7003
7601
  if (packageKind(pkg) === "library") {
7004
7602
  const lib = readFileSync11(outPath);
7005
7603
  const manifestEntry2 = unzipSync(lib)["manifest.json"];
@@ -7029,8 +7627,8 @@ function assembleDeployArchive(pluginDir, pkg, outPath) {
7029
7627
  return { manifest, s2sp, lib: null, types };
7030
7628
  }
7031
7629
  async function deployPlugin(opts) {
7032
- const absDir = resolve12(opts.dir);
7033
- const pkg = JSON.parse(readFileSync11(resolve12(absDir, "package.json"), "utf8"));
7630
+ const absDir = resolve13(opts.dir);
7631
+ const pkg = JSON.parse(readFileSync11(resolve13(absDir, "package.json"), "utf8"));
7034
7632
  assertDeployable(pkg, absDir);
7035
7633
  const creds = loadCredentials();
7036
7634
  if (!creds?.token) {
@@ -7150,9 +7748,9 @@ async function run2(argv2) {
7150
7748
  "--stamp-version is a build-only flag (design spec 2026-07-27 \xA75.4) \u2014 s2s deploy never rewrites versions; run `s2s build --stamp-version <v>` first, then `s2s deploy`"
7151
7749
  );
7152
7750
  }
7153
- const absDir = resolve13(dir);
7751
+ const absDir = resolve14(dir);
7154
7752
  const root = findWorkspaceRoot(absDir);
7155
- if (root !== null && resolve13(root) === absDir) {
7753
+ if (root !== null && resolve14(root) === absDir) {
7156
7754
  await runWorkspaceDeploy({ root: absDir, argv: argv2, interactive, filters, dryRun, yes, ci });
7157
7755
  return;
7158
7756
  }
@@ -7279,7 +7877,7 @@ async function runWorkspaceDeploy(opts) {
7279
7877
  }
7280
7878
 
7281
7879
  // src/commands/version.ts
7282
- import { resolve as resolve14 } from "node:path";
7880
+ import { resolve as resolve15 } from "node:path";
7283
7881
 
7284
7882
  // src/version/version.ts
7285
7883
  import { existsSync as existsSync10 } from "node:fs";
@@ -7538,7 +8136,7 @@ function resolveVersionRoot(from) {
7538
8136
  async function run3(argv2) {
7539
8137
  const interactive = isInteractive();
7540
8138
  const [dirArg] = positionals(argv2, ["--since"]);
7541
- const from = resolve14(dirArg ?? ".");
8139
+ const from = resolve15(dirArg ?? ".");
7542
8140
  try {
7543
8141
  const root = resolveVersionRoot(from);
7544
8142
  const result = await task(
@@ -7575,16 +8173,16 @@ async function run3(argv2) {
7575
8173
  }
7576
8174
 
7577
8175
  // src/commands/add.ts
7578
- import { resolve as resolve16 } from "node:path";
8176
+ import { resolve as resolve17 } from "node:path";
7579
8177
 
7580
8178
  // src/registry/add.ts
7581
8179
  import {
7582
- mkdirSync as mkdirSync5,
8180
+ mkdirSync as mkdirSync6,
7583
8181
  writeFileSync as writeFileSync9,
7584
8182
  readFileSync as readFileSync15,
7585
8183
  existsSync as existsSync12
7586
8184
  } from "node:fs";
7587
- import { join as join19, resolve as resolve15 } from "node:path";
8185
+ import { join as join19, resolve as resolve16 } from "node:path";
7588
8186
  import { gunzipSync } from "node:zlib";
7589
8187
 
7590
8188
  // src/registry/npmrc.ts
@@ -7621,14 +8219,14 @@ function ensureScopeNpmrc(projectDir, packageName, registryUrl) {
7621
8219
  }
7622
8220
 
7623
8221
  // src/registry/lib-extract.ts
7624
- import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync8 } from "node:fs";
8222
+ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync8 } from "node:fs";
7625
8223
  import { join as join18 } from "node:path";
7626
8224
  var WANTED = /* @__PURE__ */ new Set(["index.js", "index.d.ts"]);
7627
8225
  function extractLibArchive(s2lib, outDir, meta) {
7628
8226
  const entries = unzipSync(new Uint8Array(s2lib));
7629
8227
  if (!entries["index.js"]?.length) throw new Error("library archive missing index.js");
7630
8228
  if (!entries["index.d.ts"]?.length) throw new Error("library archive missing index.d.ts");
7631
- mkdirSync4(outDir, { recursive: true });
8229
+ mkdirSync5(outDir, { recursive: true });
7632
8230
  for (const [name, bytes] of Object.entries(entries)) {
7633
8231
  if (!WANTED.has(name)) continue;
7634
8232
  writeFileSync8(join18(outDir, name), Buffer.from(bytes));
@@ -7662,7 +8260,7 @@ function parseSpec(spec) {
7662
8260
  }
7663
8261
  function extractTypesTarball(tgz, outDir) {
7664
8262
  const tar = gunzipSync(tgz);
7665
- mkdirSync5(outDir, { recursive: true });
8263
+ mkdirSync6(outDir, { recursive: true });
7666
8264
  let offset = 0;
7667
8265
  let wroteContract = false;
7668
8266
  while (offset + 512 <= tar.length) {
@@ -7692,7 +8290,7 @@ function rangeForResolved(range, version) {
7692
8290
  return range.startsWith("^") || range === "*" ? `^${major2}.0.0` : range.includes(".") ? range : `^${version}`;
7693
8291
  }
7694
8292
  async function addPackage(opts) {
7695
- const absDir = resolve15(opts.pluginDir);
8293
+ const absDir = resolve16(opts.pluginDir);
7696
8294
  const { name, range } = parseSpec(opts.spec);
7697
8295
  const creds = loadCredentials();
7698
8296
  const client = new RegistryClient({
@@ -7762,9 +8360,9 @@ async function run4(argv2) {
7762
8360
  }
7763
8361
  try {
7764
8362
  const pluginDir = parseFlag(argv2, "--dir") || ".";
7765
- const absDir = resolve16(pluginDir);
8363
+ const absDir = resolve17(pluginDir);
7766
8364
  const root = findWorkspaceRoot(absDir);
7767
- if (root !== null && resolve16(root) === absDir) {
8365
+ if (root !== null && resolve17(root) === absDir) {
7768
8366
  throw new Error(
7769
8367
  `${absDir} is a workspace root (design spec 2026-07-27 \xA74.1), not a plugin \u2014 \`s2s add\` needs a plugin directory: pass --dir <plugin> (e.g. --dir plugins/shop)`
7770
8368
  );
@@ -7811,13 +8409,13 @@ async function run4(argv2) {
7811
8409
  }
7812
8410
 
7813
8411
  // src/commands/install.ts
7814
- import { resolve as resolve18 } from "node:path";
8412
+ import { resolve as resolve19 } from "node:path";
7815
8413
  import { existsSync as existsSync14 } from "node:fs";
7816
8414
 
7817
8415
  // src/registry/install.ts
7818
- import { createHash as createHash2 } from "node:crypto";
7819
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync16, writeFileSync as writeFileSync10, renameSync, existsSync as existsSync13 } from "node:fs";
7820
- import { basename as basename2, join as join20, resolve as resolve17, sep as sep3 } from "node:path";
8416
+ import { createHash as createHash3 } from "node:crypto";
8417
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync16, writeFileSync as writeFileSync10, renameSync, existsSync as existsSync13 } from "node:fs";
8418
+ import { basename as basename2, join as join20, resolve as resolve18, sep as sep3 } from "node:path";
7821
8419
  function parseManifest(input) {
7822
8420
  if (!input || typeof input !== "object" || Array.isArray(input)) {
7823
8421
  throw new Error("manifest must be a JSON object");
@@ -7847,7 +8445,7 @@ function mergeSpecs(manifest, args) {
7847
8445
  return merged;
7848
8446
  }
7849
8447
  function sha256Hex(bytes) {
7850
- return createHash2("sha256").update(bytes).digest("hex");
8448
+ return createHash3("sha256").update(bytes).digest("hex");
7851
8449
  }
7852
8450
  var SAFE_FILENAME_RE = /^[A-Za-z0-9._-]+$/;
7853
8451
  function safeFilename(e) {
@@ -7900,13 +8498,13 @@ async function installPlan(opts) {
7900
8498
  );
7901
8499
  }
7902
8500
  }
7903
- const dirResolved = resolve17(opts.dir);
8501
+ const dirResolved = resolve18(opts.dir);
7904
8502
  const targets = install.map((e) => {
7905
8503
  if (!e.sha256) {
7906
8504
  throw new Error(`refusing ${e.name}@${e.version}: registry did not provide a sha256 to verify against`);
7907
8505
  }
7908
8506
  const filename = safeFilename(e);
7909
- const dest = resolve17(join20(opts.dir, filename));
8507
+ const dest = resolve18(join20(opts.dir, filename));
7910
8508
  if (dest !== dirResolved && !dest.startsWith(dirResolved + sep3)) {
7911
8509
  throw new Error(`refusing ${e.name}@${e.version}: destination "${dest}" escapes plugins dir "${dirResolved}"`);
7912
8510
  }
@@ -7917,7 +8515,7 @@ async function installPlan(opts) {
7917
8515
  for (const e of install) log3(`would install ${e.name}@${e.version} -> ${e.filename}`);
7918
8516
  return { written: [], skipped: [], warnings };
7919
8517
  }
7920
- mkdirSync6(opts.dir, { recursive: true });
8518
+ mkdirSync7(opts.dir, { recursive: true });
7921
8519
  const written = [];
7922
8520
  const skipped = [];
7923
8521
  for (const { entry: e, filename, dest } of targets) {
@@ -7957,7 +8555,7 @@ async function run5(argv2) {
7957
8555
  const reviewedOnly = hasFlag(argv2, "--reviewed-only");
7958
8556
  let dir = parseFlag(argv2, "--dir");
7959
8557
  if (!dir) {
7960
- if (existsSync14(resolve18(AUTODETECT))) dir = resolve18(AUTODETECT);
8558
+ if (existsSync14(resolve19(AUTODETECT))) dir = resolve19(AUTODETECT);
7961
8559
  else {
7962
8560
  console.error(
7963
8561
  `Usage: s2s install [name[@range]...] --dir <plugins dir>
@@ -7968,7 +8566,7 @@ async function run5(argv2) {
7968
8566
  }
7969
8567
  const client = new RegistryClient({ baseUrl: registry });
7970
8568
  try {
7971
- const manifest = loadManifestFile(resolve18(file));
8569
+ const manifest = loadManifestFile(resolve19(file));
7972
8570
  const specs = mergeSpecs(manifest, args);
7973
8571
  if (Object.keys(specs).length === 0) {
7974
8572
  console.error(
@@ -7998,22 +8596,22 @@ async function run5(argv2) {
7998
8596
  }
7999
8597
 
8000
8598
  // src/commands/create.ts
8001
- import { resolve as resolve21 } from "node:path";
8599
+ import { resolve as resolve22 } from "node:path";
8002
8600
 
8003
8601
  // src/create/create.ts
8004
8602
  import {
8005
- mkdirSync as mkdirSync7,
8603
+ mkdirSync as mkdirSync8,
8006
8604
  writeFileSync as writeFileSync11,
8007
8605
  existsSync as existsSync15,
8008
8606
  readdirSync as readdirSync4,
8009
8607
  readFileSync as readFileSync17,
8010
8608
  symlinkSync
8011
8609
  } from "node:fs";
8012
- import { dirname as dirname5, isAbsolute, join as join21, relative as relative2, resolve as resolve19, basename as basename3 } from "node:path";
8610
+ import { dirname as dirname6, isAbsolute, join as join21, relative as relative2, resolve as resolve20, basename as basename3 } from "node:path";
8013
8611
  import { spawnSync } from "node:child_process";
8014
8612
  import { fileURLToPath as fileURLToPath2 } from "node:url";
8015
8613
  function readCliVersion() {
8016
- const here = dirname5(fileURLToPath2(import.meta.url));
8614
+ const here = dirname6(fileURLToPath2(import.meta.url));
8017
8615
  const candidates = [
8018
8616
  join21(here, "..", "package.json"),
8019
8617
  // dist/cli.js → packages/sdk/package.json
@@ -8031,7 +8629,7 @@ function readCliVersion() {
8031
8629
  return "0.1.0";
8032
8630
  }
8033
8631
  function findLocalPackagesDir() {
8034
- const here = dirname5(fileURLToPath2(import.meta.url));
8632
+ const here = dirname6(fileURLToPath2(import.meta.url));
8035
8633
  const candidates = [
8036
8634
  join21(here, "..", ".."),
8037
8635
  // dist/cli.js → packages
@@ -8039,12 +8637,12 @@ function findLocalPackagesDir() {
8039
8637
  // src/create → packages
8040
8638
  ];
8041
8639
  for (const c2 of candidates) {
8042
- if (isPackagesDir(c2)) return resolve19(c2);
8640
+ if (isPackagesDir(c2)) return resolve20(c2);
8043
8641
  }
8044
8642
  return void 0;
8045
8643
  }
8046
8644
  function slugifyBasename(dir, fallback = "plugin") {
8047
- const base = basename3(resolve19(dir));
8645
+ const base = basename3(resolve20(dir));
8048
8646
  return base.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase() || fallback;
8049
8647
  }
8050
8648
  function defaultNameFromPath(dir, kind = "plugin") {
@@ -8090,10 +8688,10 @@ function versionSpecFrom(status, stdout2) {
8090
8688
  const v = (stdout2 ?? "").trim();
8091
8689
  return status === 0 && /^\d+\.\d+\.\d+/.test(v) ? `^${v}` : "latest";
8092
8690
  }
8093
- function registryDevDeps(game, sdkVersion, resolve22 = resolvePublishedVersion) {
8691
+ function registryDevDeps(game, sdkVersion, resolve23 = resolvePublishedVersion) {
8094
8692
  const deps = {};
8095
8693
  for (const n3 of createPackageNames(game)) {
8096
- deps[`@s2script/${n3}`] = n3 === "sdk" ? `^${sdkVersion}` : resolve22(`@s2script/${n3}`);
8694
+ deps[`@s2script/${n3}`] = n3 === "sdk" ? `^${sdkVersion}` : resolve23(`@s2script/${n3}`);
8097
8695
  }
8098
8696
  return deps;
8099
8697
  }
@@ -8162,7 +8760,7 @@ function tsconfigJson() {
8162
8760
  // `never` — every Engine.call() shows as an error while `s2s build` passes, because the build
8163
8761
  // adds the file to its own program (typecheck.ts) but tsconfig never did. Harmless when the
8164
8762
  // plugin ships no gamedata: the path simply matches nothing.
8165
- include: ["src", ".s2script/gamedata.d.ts", "node_modules/@s2script/sdk/globals.d.ts"]
8763
+ include: ["src", ".s2script/gamedata.d.ts", ".s2script/interfaces.d.ts", "node_modules/@s2script/sdk/globals.d.ts"]
8166
8764
  },
8167
8765
  null,
8168
8766
  2
@@ -8173,7 +8771,7 @@ function workspaceTsconfigJson(targetPath, workspaceRoot) {
8173
8771
  return JSON.stringify(
8174
8772
  {
8175
8773
  extends: rel.startsWith(".") ? rel : `./${rel}`,
8176
- include: ["src", ".s2script/gamedata.d.ts", "node_modules/@s2script/sdk/globals.d.ts"]
8774
+ include: ["src", ".s2script/gamedata.d.ts", ".s2script/interfaces.d.ts", "node_modules/@s2script/sdk/globals.d.ts"]
8177
8775
  },
8178
8776
  null,
8179
8777
  2
@@ -8246,7 +8844,7 @@ dist/
8246
8844
  }
8247
8845
  function linkLocalPackagesForNoInstall(targetPath, localPackagesDir) {
8248
8846
  const nm = join21(targetPath, "node_modules");
8249
- mkdirSync7(nm, { recursive: true });
8847
+ mkdirSync8(nm, { recursive: true });
8250
8848
  const dest = join21(nm, "@s2script");
8251
8849
  if (!existsSync15(dest)) symlinkSync(localPackagesDir, dest);
8252
8850
  }
@@ -8277,7 +8875,7 @@ async function createPlugin(opts = {}) {
8277
8875
  );
8278
8876
  }
8279
8877
  let slug = workspaceRoot !== void 0 && opts.path !== void 0 ? assertBareSlug(opts.path) : opts.path;
8280
- let targetPath = workspaceRoot === void 0 ? resolve19(opts.path ?? ".") : slug === void 0 ? void 0 : join21(workspaceRoot, "plugins", slug);
8878
+ let targetPath = workspaceRoot === void 0 ? resolve20(opts.path ?? ".") : slug === void 0 ? void 0 : join21(workspaceRoot, "plugins", slug);
8281
8879
  if (targetPath !== void 0) assertEmptyTarget(targetPath);
8282
8880
  if (interactive) {
8283
8881
  intro2("Create an s2script plugin");
@@ -8350,7 +8948,7 @@ async function createPlugin(opts = {}) {
8350
8948
  install = workspaceRoot !== void 0 ? "none" : install ?? (opts.noInstall ? "none" : "npm");
8351
8949
  const finalTargetPath = targetPath;
8352
8950
  const localPackagesDir = workspaceRoot === void 0 ? findLocalPackagesDir() : void 0;
8353
- mkdirSync7(join21(finalTargetPath, "src"), { recursive: true });
8951
+ mkdirSync8(join21(finalTargetPath, "src"), { recursive: true });
8354
8952
  if (workspaceRoot !== void 0) {
8355
8953
  writeFileSync11(join21(finalTargetPath, "package.json"), minimalPackageJsonContent(name));
8356
8954
  writeFileSync11(
@@ -8407,8 +9005,8 @@ async function createPlugin(opts = {}) {
8407
9005
  }
8408
9006
 
8409
9007
  // src/create/workspace.ts
8410
- import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync12 } from "node:fs";
8411
- import { join as join22, resolve as resolve20 } from "node:path";
9008
+ import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync12 } from "node:fs";
9009
+ import { join as join22, resolve as resolve21 } from "node:path";
8412
9010
  var ROOT_DEFAULT_GAME = "cs2";
8413
9011
  var CHANGESETS_CLI_RANGE = "^2.29.5";
8414
9012
  function defaultWorkspaceName(dir) {
@@ -8461,7 +9059,7 @@ function changesetConfigJson() {
8461
9059
  ) + "\n";
8462
9060
  }
8463
9061
  async function createWorkspace(opts = {}) {
8464
- const targetPath = resolve20(opts.path ?? ".");
9062
+ const targetPath = resolve21(opts.path ?? ".");
8465
9063
  const interactive = isInteractive({ yes: opts.yes });
8466
9064
  let name = opts.name;
8467
9065
  assertEmptyTarget(targetPath);
@@ -8492,9 +9090,9 @@ async function createWorkspace(opts = {}) {
8492
9090
  name = name ?? defaultWorkspaceName(targetPath);
8493
9091
  const version = readCliVersion();
8494
9092
  const localPackagesDir = findLocalPackagesDir();
8495
- mkdirSync8(join22(targetPath, "plugins"), { recursive: true });
8496
- mkdirSync8(join22(targetPath, "packages"), { recursive: true });
8497
- mkdirSync8(join22(targetPath, ".changeset"), { recursive: true });
9093
+ mkdirSync9(join22(targetPath, "plugins"), { recursive: true });
9094
+ mkdirSync9(join22(targetPath, "packages"), { recursive: true });
9095
+ mkdirSync9(join22(targetPath, ".changeset"), { recursive: true });
8498
9096
  writeFileSync12(join22(targetPath, "package.json"), rootPackageJsonContent(name, version, localPackagesDir));
8499
9097
  writeFileSync12(join22(targetPath, "tsconfig.base.json"), tsconfigBaseJson());
8500
9098
  writeFileSync12(join22(targetPath, "eslint.config.mjs"), eslintConfig());
@@ -8527,7 +9125,7 @@ async function run6(argv2) {
8527
9125
  return;
8528
9126
  }
8529
9127
  const pathArg = positionals(argv2, VALUE_FLAGS2)[0];
8530
- const workspaceRoot = findWorkspaceRoot(pathArg === void 0 ? process.cwd() : resolve21(pathArg));
9128
+ const workspaceRoot = findWorkspaceRoot(pathArg === void 0 ? process.cwd() : resolve22(pathArg));
8531
9129
  const result = await createPlugin({
8532
9130
  path: pathArg,
8533
9131
  name: parseFlag(argv2, "--name"),
@@ -8735,7 +9333,7 @@ async function run7(argv2) {
8735
9333
  }
8736
9334
 
8737
9335
  // src/config/gen.ts
8738
- import { readFileSync as readFileSync18, writeFileSync as writeFileSync13, mkdirSync as mkdirSync9 } from "node:fs";
9336
+ import { readFileSync as readFileSync18, writeFileSync as writeFileSync13, mkdirSync as mkdirSync10 } from "node:fs";
8739
9337
  import { join as join23 } from "node:path";
8740
9338
 
8741
9339
  // src/config/config-path.ts
@@ -8782,7 +9380,7 @@ function genConfigFromManifest(manifest, outDir) {
8782
9380
  }
8783
9381
  const id = typeof manifest.id === "string" ? manifest.id : "";
8784
9382
  if (!id) throw new Error("config gen: manifest declares config but has no id");
8785
- mkdirSync9(outDir, { recursive: true });
9383
+ mkdirSync10(outDir, { recursive: true });
8786
9384
  const path = join23(outDir, configFileName(id));
8787
9385
  writeFileSync13(path, generateDefaultJsonc(config));
8788
9386
  return path;
@@ -8835,7 +9433,7 @@ async function run8(argv2) {
8835
9433
  }
8836
9434
 
8837
9435
  // src/commands/codegen.ts
8838
- import { dirname as dirname6, join as join28 } from "node:path";
9436
+ import { dirname as dirname7, join as join28 } from "node:path";
8839
9437
  import { fileURLToPath as fileURLToPath3 } from "node:url";
8840
9438
 
8841
9439
  // src/schemagen/gen.ts
@@ -9219,23 +9817,23 @@ function resolveExpr(f) {
9219
9817
  const base = `off(${S(f.declaringClass)},${S(f.rawName)})`;
9220
9818
  return f.addOffset ? `${base} + ${f.addOffset}` : base;
9221
9819
  }
9222
- function accessorEntry(f, resolve22, guard2) {
9820
+ function accessorEntry(f, resolve23, guard2) {
9223
9821
  const g = guard2 === "" ? "" : `${guard2} `;
9224
9822
  const gs = guard2 === "" ? "" : `${guard2.replace("return null;", "return;")} `;
9225
9823
  let entry;
9226
9824
  if (f.accessorKind === "str") {
9227
- entry = `get: function () { ${g}return this.ref.readString(${resolve22}, ${f.strLen}); }`;
9825
+ entry = `get: function () { ${g}return this.ref.readString(${resolve23}, ${f.strLen}); }`;
9228
9826
  } else if (f.accessorKind === "u64" || f.accessorKind === "i64") {
9229
- entry = `get: function () { ${g}var v = this.ref.${READ[f.accessorKind]}(${resolve22}); return v === null ? null : v.toString(); }`;
9827
+ entry = `get: function () { ${g}var v = this.ref.${READ[f.accessorKind]}(${resolve23}); return v === null ? null : v.toString(); }`;
9230
9828
  } else if (VEC_INFO[f.accessorKind]) {
9231
9829
  const vi = VEC_INFO[f.accessorKind];
9232
- entry = `get: function () { ${g}var a = this.ref.readFloats(${resolve22}, ${vi.count}); return a === null ? null : new ${vi.cls}(a[0], a[1], a[2]); }`;
9830
+ entry = `get: function () { ${g}var a = this.ref.readFloats(${resolve23}, ${vi.count}); return a === null ? null : new ${vi.cls}(a[0], a[1], a[2]); }`;
9233
9831
  } else {
9234
- entry = `get: function () { ${g}return this.ref.${READ[f.accessorKind]}(${resolve22}); }`;
9832
+ entry = `get: function () { ${g}return this.ref.${READ[f.accessorKind]}(${resolve23}); }`;
9235
9833
  }
9236
9834
  if (f.writable) {
9237
9835
  const wm = WRITE[f.accessorKind];
9238
- entry += `, set: function (v) { ${gs}var o = ${resolve22}; if (this.ref.${wm}(o, v)) this.ref.notifyStateChanged(o); }`;
9836
+ entry += `, set: function (v) { ${gs}var o = ${resolve23}; if (this.ref.${wm}(o, v)) this.ref.notifyStateChanged(o); }`;
9239
9837
  }
9240
9838
  return entry;
9241
9839
  }
@@ -9264,9 +9862,9 @@ function emitJs(model) {
9264
9862
  out.push(` E[${S(e.className)}] = {`);
9265
9863
  for (const f of e.fields) {
9266
9864
  const sub = `off(${S(f.declaringClass)},${S(f.rawName)})`;
9267
- const resolve22 = f.addOffset ? `this.base + ${sub} + ${f.addOffset}` : `this.base + ${sub}`;
9865
+ const resolve23 = f.addOffset ? `this.base + ${sub} + ${f.addOffset}` : `this.base + ${sub}`;
9268
9866
  const guard2 = `if (this.base < 0 || ${sub} < 0) return null;`;
9269
- out.push(` ${S(f.propName)}: { ${accessorEntry(f, resolve22, guard2)} },`);
9867
+ out.push(` ${S(f.propName)}: { ${accessorEntry(f, resolve23, guard2)} },`);
9270
9868
  }
9271
9869
  for (const f of e.embeddedFields) {
9272
9870
  const sub = `off(${S(f.declaringClass)},${S(f.rawName)})`;
@@ -9570,25 +10168,25 @@ function emitNavJs(model) {
9570
10168
  for (const f of w.fields) {
9571
10169
  const own = `off(${S2(f.declaringClass)},${S2(f.rawName)})`;
9572
10170
  const withDelta = f.addOffset ? `${own} + ${f.addOffset}` : own;
9573
- const resolve22 = `${withDelta} + this.base`;
10171
+ const resolve23 = `${withDelta} + this.base`;
9574
10172
  let getter;
9575
10173
  const kind = f.accessorKind;
9576
10174
  if (kind === "u64") {
9577
- getter = `get: function () { var v = this.root.readUInt64Via(this.path, ${resolve22}); return v === null ? null : v.toString(); }`;
10175
+ getter = `get: function () { var v = this.root.readUInt64Via(this.path, ${resolve23}); return v === null ? null : v.toString(); }`;
9578
10176
  } else if (kind === "i64") {
9579
- getter = `get: function () { var v = this.root.readInt64Via(this.path, ${resolve22}); return v === null ? null : v.toString(); }`;
10177
+ getter = `get: function () { var v = this.root.readInt64Via(this.path, ${resolve23}); return v === null ? null : v.toString(); }`;
9580
10178
  } else if (VEC_INFO[kind]) {
9581
10179
  const vi = VEC_INFO[kind];
9582
- getter = `get: function () { var a = this.root.readFloatsChain(this.path, ${resolve22}, ${vi.count}); return a === null ? null : new ${vi.cls}(a[0], a[1], a[2]); }`;
10180
+ getter = `get: function () { var a = this.root.readFloatsChain(this.path, ${resolve23}, ${vi.count}); return a === null ? null : new ${vi.cls}(a[0], a[1], a[2]); }`;
9583
10181
  } else if (kind === "handle") {
9584
- getter = `get: function () { return this.root.readHandleVia(this.path, ${resolve22}); }`;
10182
+ getter = `get: function () { return this.root.readHandleVia(this.path, ${resolve23}); }`;
9585
10183
  } else {
9586
10184
  const method = READ_VIA[kind];
9587
10185
  if (!method) continue;
9588
- getter = `get: function () { return this.root.${method}(this.path, ${resolve22}); }`;
10186
+ getter = `get: function () { return this.root.${method}(this.path, ${resolve23}); }`;
9589
10187
  }
9590
10188
  const wmethod = f.navWritable ? WRITE_VIA[kind] : void 0;
9591
- const setter = wmethod ? `, set: function (v) { this.root.${wmethod}(this.path, ${resolve22}, v); }` : "";
10189
+ const setter = wmethod ? `, set: function (v) { this.root.${wmethod}(this.path, ${resolve23}, v); }` : "";
9592
10190
  out.push(` ${S2(f.propName)}: { ${getter}${setter} },`);
9593
10191
  }
9594
10192
  out.push(" });");
@@ -9742,7 +10340,7 @@ function runGenHooks(repoRoot2, opts) {
9742
10340
 
9743
10341
  // src/commands/codegen.ts
9744
10342
  function repoRoot() {
9745
- return join28(dirname6(fileURLToPath3(import.meta.url)), "..", "..", "..");
10343
+ return join28(dirname7(fileURLToPath3(import.meta.url)), "..", "..", "..");
9746
10344
  }
9747
10345
  async function run9(kind, argv2) {
9748
10346
  const root = repoRoot();