@s2script/sdk 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin.d.ts +28 -1
- package/bans.d.ts +8 -1
- package/chat.d.ts +7 -7
- package/clients.d.ts +9 -19
- package/commands.d.ts +14 -4
- package/config.d.ts +12 -2
- package/console.d.ts +9 -0
- package/cookies.d.ts +37 -7
- package/damage.d.ts +7 -6
- package/db.d.ts +41 -3
- package/dist/cli.js +746 -132
- package/entity.d.ts +46 -35
- package/events.d.ts +27 -14
- package/globals.d.ts +6 -1
- package/http.d.ts +35 -4
- package/interfaces.d.ts +9 -25
- package/math.d.ts +15 -1
- package/menu.d.ts +92 -7
- package/net.d.ts +26 -2
- package/package.json +7 -5
- package/plugin.d.ts +195 -0
- package/plugins.d.ts +20 -1
- package/server.d.ts +8 -7
- package/sound.d.ts +9 -4
- package/timers.d.ts +7 -1
- package/topmenu.d.ts +15 -4
- package/trace.d.ts +8 -0
- package/translations.d.ts +10 -0
- package/transmit.d.ts +8 -0
- package/usercmd.d.ts +11 -18
- package/usermessages.d.ts +18 -0
- package/votes.d.ts +46 -3
- package/ws.d.ts +18 -1
- package/frame.d.ts +0 -13
package/dist/cli.js
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
import * as esbuild from "esbuild";
|
|
5
5
|
import AdmZip from "adm-zip";
|
|
6
6
|
import { readFileSync as readFileSync4, mkdirSync } from "node:fs";
|
|
7
|
-
import { resolve as
|
|
7
|
+
import { resolve as resolve5, join as join6 } from "node:path";
|
|
8
8
|
|
|
9
9
|
// src/typecheck/typecheck.ts
|
|
10
10
|
import ts from "typescript";
|
|
11
|
-
import { existsSync as
|
|
12
|
-
import { join as
|
|
11
|
+
import { existsSync as existsSync3, readdirSync, readFileSync, writeFileSync, rmSync, mkdtempSync } from "node:fs";
|
|
12
|
+
import { join as join3, resolve as resolve2 } from "node:path";
|
|
13
13
|
import { tmpdir } from "node:os";
|
|
14
14
|
|
|
15
15
|
// src/packages-resolve.ts
|
|
@@ -55,11 +55,47 @@ function resolvePackagesDir(opts) {
|
|
|
55
55
|
);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
// src/tsconfig-shared.ts
|
|
59
|
+
var sharedCompilerOptionsJson = {
|
|
60
|
+
strict: true,
|
|
61
|
+
noEmit: true,
|
|
62
|
+
moduleResolution: "bundler",
|
|
63
|
+
module: "ESNext",
|
|
64
|
+
target: "ES2020",
|
|
65
|
+
lib: ["ES2020"],
|
|
66
|
+
types: [],
|
|
67
|
+
skipLibCheck: true,
|
|
68
|
+
allowImportingTsExtensions: true
|
|
69
|
+
};
|
|
70
|
+
function sharedProgramOptions(tsMod) {
|
|
71
|
+
return {
|
|
72
|
+
strict: sharedCompilerOptionsJson.strict,
|
|
73
|
+
noEmit: sharedCompilerOptionsJson.noEmit,
|
|
74
|
+
allowImportingTsExtensions: sharedCompilerOptionsJson.allowImportingTsExtensions,
|
|
75
|
+
moduleResolution: tsMod.ModuleResolutionKind.Bundler,
|
|
76
|
+
module: tsMod.ModuleKind.ESNext,
|
|
77
|
+
target: tsMod.ScriptTarget.ES2020,
|
|
78
|
+
lib: sharedCompilerOptionsJson.lib.map((l) => `lib.${l.toLowerCase()}.d.ts`),
|
|
79
|
+
types: [...sharedCompilerOptionsJson.types],
|
|
80
|
+
skipLibCheck: sharedCompilerOptionsJson.skipLibCheck
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/contracts.ts
|
|
85
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
86
|
+
import { join as join2 } from "node:path";
|
|
87
|
+
function localContractPath(pluginDir, dep) {
|
|
88
|
+
const segs = dep.split("/");
|
|
89
|
+
if (segs.some((s) => s === "" || s === "." || s === "..")) return null;
|
|
90
|
+
const p = join2(pluginDir, ".s2script", "types", ...segs, "index.d.ts");
|
|
91
|
+
return existsSync2(p) ? p : null;
|
|
92
|
+
}
|
|
93
|
+
|
|
58
94
|
// src/typecheck/typecheck.ts
|
|
59
95
|
function localDeclarationFiles(pluginDir) {
|
|
60
|
-
const srcDir =
|
|
61
|
-
if (!
|
|
62
|
-
return readdirSync(srcDir).filter((f) => f.endsWith(".d.ts")).map((f) =>
|
|
96
|
+
const srcDir = join3(pluginDir, "src");
|
|
97
|
+
if (!existsSync3(srcDir)) return [];
|
|
98
|
+
return readdirSync(srcDir).filter((f) => f.endsWith(".d.ts")).map((f) => join3(srcDir, f));
|
|
63
99
|
}
|
|
64
100
|
function declaredModules(dtsFiles) {
|
|
65
101
|
const out = /* @__PURE__ */ new Set();
|
|
@@ -72,31 +108,33 @@ function declaredModules(dtsFiles) {
|
|
|
72
108
|
function typecheckPlugin(pluginDir, opts) {
|
|
73
109
|
const absDir = resolve2(pluginDir);
|
|
74
110
|
const packagesDir = opts?.packagesDir ? resolve2(opts.packagesDir) : resolvePackagesDir({ pluginDir: absDir });
|
|
75
|
-
const pkg = JSON.parse(readFileSync(
|
|
111
|
+
const pkg = JSON.parse(readFileSync(join3(absDir, "package.json"), "utf8"));
|
|
76
112
|
const s2 = pkg.s2script ?? {};
|
|
77
113
|
const entryRel = s2.main ?? pkg.main;
|
|
78
|
-
if (!entryRel) throw new Error(`typecheckPlugin: no entry point in ${
|
|
114
|
+
if (!entryRel) throw new Error(`typecheckPlugin: no entry point in ${join3(absDir, "package.json")}`);
|
|
79
115
|
const entry = resolve2(absDir, entryRel);
|
|
80
116
|
const isAlwaysResolved = (d) => d.startsWith("@s2script/sdk/") || d === "@s2script/cs2" || d.startsWith("@s2script/cs2/");
|
|
81
117
|
const localDts = localDeclarationFiles(absDir);
|
|
82
118
|
const locallyDeclared = declaredModules(localDts);
|
|
119
|
+
const allDeclaredDeps = [
|
|
120
|
+
...Object.keys(s2.pluginDependencies ?? {}),
|
|
121
|
+
...Object.keys(s2.optionalPluginDependencies ?? {})
|
|
122
|
+
];
|
|
123
|
+
const contractPaths = {};
|
|
124
|
+
for (const d of allDeclaredDeps) {
|
|
125
|
+
const p = localContractPath(absDir, d);
|
|
126
|
+
if (p !== null) contractPaths[d] = [p];
|
|
127
|
+
}
|
|
83
128
|
const deps = [
|
|
84
129
|
...Object.keys(s2.pluginDependencies ?? {}),
|
|
85
130
|
...Object.keys(s2.optionalPluginDependencies ?? {})
|
|
86
131
|
// Never stub a module the plugin declares itself — a shorthand `declare module "X";` and a
|
|
87
132
|
// full `declare module "X" { … }` for the same X collide.
|
|
88
|
-
].filter((d) => !isAlwaysResolved(d) && !locallyDeclared.has(d));
|
|
133
|
+
].filter((d) => !isAlwaysResolved(d) && !locallyDeclared.has(d) && contractPaths[d] === void 0);
|
|
89
134
|
const options = {
|
|
90
|
-
strict: true,
|
|
91
|
-
noEmit: true,
|
|
92
135
|
// Accept explicit `.ts` import extensions (node type-stripping requires them for source-to-source
|
|
93
136
|
// imports; esbuild strips them at bundle time). Backward-compatible — extensionless imports still resolve.
|
|
94
|
-
|
|
95
|
-
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
96
|
-
module: ts.ModuleKind.ESNext,
|
|
97
|
-
target: ts.ScriptTarget.ES2020,
|
|
98
|
-
lib: ["lib.es2020.d.ts"],
|
|
99
|
-
types: [],
|
|
137
|
+
...sharedProgramOptions(ts),
|
|
100
138
|
baseUrl: packagesDir,
|
|
101
139
|
paths: {
|
|
102
140
|
// Builtins are `@s2script/sdk/<cap>` → packages/sdk/<cap>.d.ts. The `@s2script/*` fallback
|
|
@@ -104,15 +142,15 @@ function typecheckPlugin(pluginDir, opts) {
|
|
|
104
142
|
// dirs are deleted). tsc picks the longest matching prefix, so `@s2script/sdk/*` wins for
|
|
105
143
|
// sdk imports.
|
|
106
144
|
"@s2script/sdk/*": ["sdk/*.d.ts"],
|
|
107
|
-
"@s2script/*": ["*/index.d.ts"]
|
|
108
|
-
|
|
109
|
-
|
|
145
|
+
"@s2script/*": ["*/index.d.ts"],
|
|
146
|
+
...contractPaths
|
|
147
|
+
}
|
|
110
148
|
};
|
|
111
|
-
const rootNames = [entry,
|
|
112
|
-
const tmp = mkdtempSync(
|
|
149
|
+
const rootNames = [entry, join3(packagesDir, "sdk", "globals.d.ts"), ...localDts];
|
|
150
|
+
const tmp = mkdtempSync(join3(tmpdir(), "s2tc-"));
|
|
113
151
|
try {
|
|
114
152
|
if (deps.length) {
|
|
115
|
-
const stub =
|
|
153
|
+
const stub = join3(tmp, "ambient.d.ts");
|
|
116
154
|
writeFileSync(stub, deps.map((d) => `declare module ${JSON.stringify(d)};`).join("\n") + "\n");
|
|
117
155
|
rootNames.push(stub);
|
|
118
156
|
}
|
|
@@ -132,7 +170,7 @@ function typecheckPlugin(pluginDir, opts) {
|
|
|
132
170
|
}
|
|
133
171
|
return { file, line, col, code: d.code, message: ts.flattenDiagnosticMessageText(d.messageText, "\n") };
|
|
134
172
|
});
|
|
135
|
-
return { ok: out.length === 0, diagnostics: out };
|
|
173
|
+
return { ok: out.length === 0, diagnostics: out, program };
|
|
136
174
|
} finally {
|
|
137
175
|
rmSync(tmp, { recursive: true, force: true });
|
|
138
176
|
}
|
|
@@ -239,8 +277,8 @@ function validateDecl(full, decl, typeStr, errs) {
|
|
|
239
277
|
}
|
|
240
278
|
|
|
241
279
|
// src/publish-gate.ts
|
|
242
|
-
import { existsSync as
|
|
243
|
-
import { join as
|
|
280
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2, statSync } from "node:fs";
|
|
281
|
+
import { join as join4, resolve as resolve3 } from "node:path";
|
|
244
282
|
function hasPublishes(publishes) {
|
|
245
283
|
if (publishes == null) return false;
|
|
246
284
|
if (typeof publishes === "string") return publishes.trim().length > 0;
|
|
@@ -266,7 +304,7 @@ function assertPublishesTypes(pkg, pluginDir) {
|
|
|
266
304
|
};
|
|
267
305
|
}
|
|
268
306
|
const typesPath = resolve3(pluginDir, typesRel);
|
|
269
|
-
if (!
|
|
307
|
+
if (!existsSync4(typesPath)) {
|
|
270
308
|
return { ok: false, error: `types file not found: ${typesRel}` };
|
|
271
309
|
}
|
|
272
310
|
const st = statSync(typesPath);
|
|
@@ -341,32 +379,160 @@ function derivePublishes(authored, pkgName, pkgVersion, typesPath) {
|
|
|
341
379
|
return out;
|
|
342
380
|
}
|
|
343
381
|
|
|
382
|
+
// src/api-version.ts
|
|
383
|
+
var HOST_API_VERSION_MAJOR = 2;
|
|
384
|
+
var STAMPED_API_VERSION = `${HOST_API_VERSION_MAJOR}.x`;
|
|
385
|
+
|
|
386
|
+
// src/publish-scan.ts
|
|
387
|
+
import ts2 from "typescript";
|
|
388
|
+
function isPluginContext(type) {
|
|
389
|
+
const sym = type.getSymbol() ?? type.aliasSymbol;
|
|
390
|
+
return sym?.getName() === "PluginContext";
|
|
391
|
+
}
|
|
392
|
+
function scanPluginProgram(program, pluginDir) {
|
|
393
|
+
const checker = program.getTypeChecker();
|
|
394
|
+
const out = { publishNames: [], dynamicPublishSites: [], useNames: [] };
|
|
395
|
+
const dirPrefix = pluginDir.replace(/\\/g, "/").replace(/\/+$/, "") + "/";
|
|
396
|
+
for (const sf of program.getSourceFiles()) {
|
|
397
|
+
if (sf.isDeclarationFile) continue;
|
|
398
|
+
if (!sf.fileName.replace(/\\/g, "/").startsWith(dirPrefix)) continue;
|
|
399
|
+
const visit = (node) => {
|
|
400
|
+
if (ts2.isCallExpression(node) && ts2.isPropertyAccessExpression(node.expression)) {
|
|
401
|
+
const method = node.expression.name.text;
|
|
402
|
+
if (method === "publish" || method === "use" || method === "tryUse") {
|
|
403
|
+
const recv = checker.getTypeAtLocation(node.expression.expression);
|
|
404
|
+
if (isPluginContext(recv)) {
|
|
405
|
+
const arg0 = node.arguments[0];
|
|
406
|
+
if (method === "publish") {
|
|
407
|
+
if (arg0 !== void 0 && ts2.isStringLiteralLike(arg0)) {
|
|
408
|
+
out.publishNames.push(arg0.text);
|
|
409
|
+
} else {
|
|
410
|
+
const { line } = sf.getLineAndCharacterOfPosition(node.getStart());
|
|
411
|
+
out.dynamicPublishSites.push(`${sf.fileName}:${line + 1}`);
|
|
412
|
+
}
|
|
413
|
+
} else if (arg0 !== void 0 && ts2.isStringLiteralLike(arg0)) {
|
|
414
|
+
out.useNames.push(arg0.text);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
ts2.forEachChild(node, visit);
|
|
420
|
+
};
|
|
421
|
+
visit(sf);
|
|
422
|
+
}
|
|
423
|
+
out.publishNames = [...new Set(out.publishNames)];
|
|
424
|
+
out.useNames = [...new Set(out.useNames)];
|
|
425
|
+
return out;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// src/lint/lint.ts
|
|
429
|
+
import { ESLint } from "eslint";
|
|
430
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
431
|
+
import { join as join5, resolve as resolve4 } from "node:path";
|
|
432
|
+
import s2lint from "@s2script/eslint-plugin";
|
|
433
|
+
var CONFIG_FILES = ["eslint.config.js", "eslint.config.mjs", "eslint.config.cjs", "eslint.config.ts"];
|
|
434
|
+
async function lintPlugin(pluginDir, program) {
|
|
435
|
+
const absDir = resolve4(pluginDir);
|
|
436
|
+
const hasOwnConfig = CONFIG_FILES.some((f) => existsSync5(join5(absDir, f)));
|
|
437
|
+
const eslint = hasOwnConfig ? new ESLint({ cwd: absDir, errorOnUnmatchedPattern: false }) : new ESLint({
|
|
438
|
+
cwd: absDir,
|
|
439
|
+
overrideConfigFile: true,
|
|
440
|
+
overrideConfig: s2lint.configs.build([program]),
|
|
441
|
+
errorOnUnmatchedPattern: false
|
|
442
|
+
});
|
|
443
|
+
const dirPrefix = absDir.replace(/\\/g, "/").replace(/\/+$/, "") + "/";
|
|
444
|
+
const targets = hasOwnConfig ? ["**/*.ts"] : program.getSourceFiles().filter((sf) => !sf.isDeclarationFile && sf.fileName.replace(/\\/g, "/").startsWith(dirPrefix)).map((sf) => sf.fileName);
|
|
445
|
+
if (targets.length === 0) return { ok: true, output: "", errorCount: 0 };
|
|
446
|
+
const results = await eslint.lintFiles(targets);
|
|
447
|
+
const errorCount = results.reduce((n, r) => n + r.errorCount, 0);
|
|
448
|
+
const formatter = await eslint.loadFormatter("stylish");
|
|
449
|
+
const output3 = String(await formatter.format(results));
|
|
450
|
+
return { ok: errorCount === 0, output: output3, errorCount };
|
|
451
|
+
}
|
|
452
|
+
|
|
344
453
|
// src/build.ts
|
|
345
454
|
async function buildPlugin(dir, packagesDir) {
|
|
346
|
-
const absDir =
|
|
347
|
-
const pkgPath =
|
|
455
|
+
const absDir = resolve5(dir);
|
|
456
|
+
const pkgPath = join6(absDir, "package.json");
|
|
348
457
|
const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
|
|
349
|
-
const gate = assertPublishesTypes(pkg, absDir);
|
|
350
|
-
if (!gate.ok) {
|
|
351
|
-
throw new Error(`publish gate failed: ${gate.error}`);
|
|
352
|
-
}
|
|
353
458
|
const s2 = pkg.s2script ?? {};
|
|
354
|
-
const
|
|
459
|
+
const config = s2.config ?? void 0;
|
|
460
|
+
if (config !== void 0) {
|
|
461
|
+
const cfgErrs = validateConfigBlock(config);
|
|
462
|
+
if (cfgErrs.length) throw new Error(`invalid s2script.config:
|
|
463
|
+
${cfgErrs.join("\n ")}`);
|
|
464
|
+
}
|
|
355
465
|
const tc = typecheckPlugin(absDir, packagesDir !== void 0 ? { packagesDir } : void 0);
|
|
356
466
|
if (!tc.ok) {
|
|
357
467
|
throw new Error(`typecheck failed (${tc.diagnostics.length} error(s)):
|
|
358
468
|
${formatDiagnostics(tc.diagnostics)}`);
|
|
359
469
|
}
|
|
360
|
-
const
|
|
361
|
-
const
|
|
470
|
+
const scan = scanPluginProgram(tc.program, absDir);
|
|
471
|
+
const lint = await lintPlugin(absDir, tc.program);
|
|
472
|
+
if (!lint.ok) {
|
|
473
|
+
throw new Error(`lint failed (${lint.errorCount} error(s)):
|
|
474
|
+
${lint.output}`);
|
|
475
|
+
}
|
|
476
|
+
if (lint.output.trim().length > 0) console.warn(lint.output);
|
|
477
|
+
if (scan.dynamicPublishSites.length > 0) {
|
|
478
|
+
throw new Error(
|
|
479
|
+
`ctx.publish name must be a string literal (the manifest publishes block is derived from code):
|
|
480
|
+
` + scan.dynamicPublishSites.join("\n ")
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
let effectivePublishes = s2.publishes;
|
|
484
|
+
if (!hasPublishes(effectivePublishes)) {
|
|
485
|
+
if (scan.publishNames.length === 1 && scan.publishNames[0] === pkg.name) {
|
|
486
|
+
effectivePublishes = "self";
|
|
487
|
+
} else if (scan.publishNames.length > 0) {
|
|
488
|
+
throw new Error(
|
|
489
|
+
`code publishes ${JSON.stringify(scan.publishNames)} but s2script.publishes is missing \u2014 a contract named differently from the package needs an authored entry with a concrete version`
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
} else {
|
|
493
|
+
const authoredNames = Object.keys(expandPublishes(effectivePublishes, pkg.name, pkg.version)).sort();
|
|
494
|
+
const codeNames = [...scan.publishNames].sort();
|
|
495
|
+
if (JSON.stringify(authoredNames) !== JSON.stringify(codeNames)) {
|
|
496
|
+
throw new Error(
|
|
497
|
+
`publishes drift: package.json declares ${JSON.stringify(authoredNames)} but the code's ctx.publish calls are ${JSON.stringify(codeNames)} \u2014 fix whichever is wrong (the manifest is generated from code; the loader re-verifies at Active)`
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
const gate = assertPublishesTypes({ ...pkg, s2script: { ...s2, publishes: effectivePublishes } }, absDir);
|
|
502
|
+
if (!gate.ok) {
|
|
503
|
+
throw new Error(`publish gate failed: ${gate.error}`);
|
|
504
|
+
}
|
|
505
|
+
const derivedPublishes = derivePublishes(
|
|
506
|
+
effectivePublishes,
|
|
507
|
+
pkg.name,
|
|
508
|
+
pkg.version,
|
|
509
|
+
gate.typesPath
|
|
510
|
+
);
|
|
362
511
|
const pluginDependencies = s2.pluginDependencies ?? {};
|
|
363
512
|
const optionalPluginDependencies = s2.optionalPluginDependencies ?? {};
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
513
|
+
const declaredDeps = /* @__PURE__ */ new Set([
|
|
514
|
+
...Object.keys(pluginDependencies),
|
|
515
|
+
...Object.keys(optionalPluginDependencies)
|
|
516
|
+
]);
|
|
517
|
+
for (const used of scan.useNames) {
|
|
518
|
+
if (!declaredDeps.has(used)) {
|
|
519
|
+
console.warn(
|
|
520
|
+
`WARN: ctx.use/tryUse(${JSON.stringify(used)}) is not declared under s2script.pluginDependencies/optionalPluginDependencies \u2014 it will throw at runtime`
|
|
521
|
+
);
|
|
522
|
+
}
|
|
369
523
|
}
|
|
524
|
+
for (const dep of declaredDeps) {
|
|
525
|
+
if (!scan.useNames.includes(dep)) {
|
|
526
|
+
console.warn(`WARN: dependency ${JSON.stringify(dep)} is declared but never ctx.use()d`);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
const { name, version } = pkg;
|
|
530
|
+
if (s2.apiVersion !== void 0) {
|
|
531
|
+
console.warn(
|
|
532
|
+
`WARN: ${pkgPath}: s2script.apiVersion is ignored \u2014 s2s build derives apiVersion from the SDK (stamping ${JSON.stringify(STAMPED_API_VERSION)}). Remove the field.`
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
const apiVersion = STAMPED_API_VERSION;
|
|
370
536
|
const external = Array.from(/* @__PURE__ */ new Set([
|
|
371
537
|
"@s2script/*",
|
|
372
538
|
...Object.keys(pluginDependencies),
|
|
@@ -378,7 +544,7 @@ ${formatDiagnostics(tc.diagnostics)}`);
|
|
|
378
544
|
`buildPlugin: no entry point found in ${pkgPath} (set s2script.main or main)`
|
|
379
545
|
);
|
|
380
546
|
}
|
|
381
|
-
const entryPoint =
|
|
547
|
+
const entryPoint = join6(absDir, entryRelative);
|
|
382
548
|
const result = await esbuild.build({
|
|
383
549
|
entryPoints: [entryPoint],
|
|
384
550
|
bundle: true,
|
|
@@ -401,6 +567,15 @@ ${formatDiagnostics(tc.diagnostics)}`);
|
|
|
401
567
|
manifest.publishes = derivedPublishes;
|
|
402
568
|
}
|
|
403
569
|
if (config !== void 0) manifest.config = config;
|
|
570
|
+
const compiledAgainst = {};
|
|
571
|
+
for (const dep of [
|
|
572
|
+
...Object.keys(pluginDependencies),
|
|
573
|
+
...Object.keys(optionalPluginDependencies)
|
|
574
|
+
]) {
|
|
575
|
+
const contractPath = localContractPath(absDir, dep);
|
|
576
|
+
if (contractPath !== null) compiledAgainst[dep] = hashContract(contractPath);
|
|
577
|
+
}
|
|
578
|
+
if (Object.keys(compiledAgainst).length > 0) manifest.compiledAgainst = compiledAgainst;
|
|
404
579
|
const zip = new AdmZip();
|
|
405
580
|
zip.addFile("manifest.json", Buffer.from(JSON.stringify(manifest, null, 2)));
|
|
406
581
|
zip.addFile("plugin.js", Buffer.from(pluginJs));
|
|
@@ -412,16 +587,16 @@ ${formatDiagnostics(tc.diagnostics)}`);
|
|
|
412
587
|
}
|
|
413
588
|
}
|
|
414
589
|
const sanitizedId = name.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
415
|
-
const outDir =
|
|
590
|
+
const outDir = join6(absDir, "dist");
|
|
416
591
|
mkdirSync(outDir, { recursive: true });
|
|
417
|
-
const outPath =
|
|
592
|
+
const outPath = join6(outDir, `${sanitizedId}.s2sp`);
|
|
418
593
|
zip.writeZip(outPath);
|
|
419
594
|
return outPath;
|
|
420
595
|
}
|
|
421
596
|
|
|
422
597
|
// src/schemagen/gen.ts
|
|
423
598
|
import { readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "node:fs";
|
|
424
|
-
import { join as
|
|
599
|
+
import { join as join7 } from "node:path";
|
|
425
600
|
|
|
426
601
|
// src/schemagen/model.ts
|
|
427
602
|
var READ = {
|
|
@@ -613,21 +788,21 @@ function emitJs(model) {
|
|
|
613
788
|
for (const c of model.classes) {
|
|
614
789
|
out.push(` A[${S(c.className)}] = {`);
|
|
615
790
|
for (const f of flattenedFields(model, c.className)) {
|
|
616
|
-
const
|
|
791
|
+
const resolve10 = `off(${S(f.declaringClass)},${S(f.rawName)})`;
|
|
617
792
|
let entry;
|
|
618
793
|
if (f.accessorKind === "str") {
|
|
619
|
-
entry = `get: function () { return this.ref.readString(${
|
|
794
|
+
entry = `get: function () { return this.ref.readString(${resolve10}, ${f.strLen}); }`;
|
|
620
795
|
} else if (f.accessorKind === "u64" || f.accessorKind === "i64") {
|
|
621
|
-
entry = `get: function () { var v = this.ref.${READ[f.accessorKind]}(${
|
|
796
|
+
entry = `get: function () { var v = this.ref.${READ[f.accessorKind]}(${resolve10}); return v === null ? null : v.toString(); }`;
|
|
622
797
|
} else if (VEC_INFO[f.accessorKind]) {
|
|
623
798
|
const vi = VEC_INFO[f.accessorKind];
|
|
624
|
-
entry = `get: function () { var a = this.ref.readFloats(${
|
|
799
|
+
entry = `get: function () { var a = this.ref.readFloats(${resolve10}, ${vi.count}); return a === null ? null : new ${vi.cls}(a[0], a[1], a[2]); }`;
|
|
625
800
|
} else {
|
|
626
|
-
entry = `get: function () { return this.ref.${READ[f.accessorKind]}(${
|
|
801
|
+
entry = `get: function () { return this.ref.${READ[f.accessorKind]}(${resolve10}); }`;
|
|
627
802
|
}
|
|
628
803
|
if (f.writable) {
|
|
629
804
|
const wm = WRITE[f.accessorKind];
|
|
630
|
-
entry += `, set: function (v) { var o = ${
|
|
805
|
+
entry += `, set: function (v) { var o = ${resolve10}; if (this.ref.${wm}(o, v)) this.ref.notifyStateChanged(o); }`;
|
|
631
806
|
}
|
|
632
807
|
out.push(` ${S(f.propName)}: { ${entry} },`);
|
|
633
808
|
}
|
|
@@ -653,8 +828,8 @@ var LIST_PATH = "games/cs2/codegen-classes.json";
|
|
|
653
828
|
var JS_OUT = "games/cs2/js/schema.generated.js";
|
|
654
829
|
var DTS_OUT = "packages/cs2/schema.generated.d.ts";
|
|
655
830
|
function runGenSchema(repoRoot, opts) {
|
|
656
|
-
const catalog = JSON.parse(readFileSync5(
|
|
657
|
-
const list = JSON.parse(readFileSync5(
|
|
831
|
+
const catalog = JSON.parse(readFileSync5(join7(repoRoot, CATALOG_PATH), "utf8"));
|
|
832
|
+
const list = JSON.parse(readFileSync5(join7(repoRoot, LIST_PATH), "utf8"));
|
|
658
833
|
const model = buildModel(catalog, list);
|
|
659
834
|
const dts = emitDts(model);
|
|
660
835
|
const js = emitJs(model);
|
|
@@ -663,7 +838,7 @@ function runGenSchema(repoRoot, opts) {
|
|
|
663
838
|
const drift = [];
|
|
664
839
|
const files = [[JS_OUT, js], [DTS_OUT, dts]];
|
|
665
840
|
for (const [rel, content] of files) {
|
|
666
|
-
const abs =
|
|
841
|
+
const abs = join7(repoRoot, rel);
|
|
667
842
|
if (opts.check) {
|
|
668
843
|
let cur = "";
|
|
669
844
|
try {
|
|
@@ -683,7 +858,7 @@ function runGenSchema(repoRoot, opts) {
|
|
|
683
858
|
|
|
684
859
|
// src/eventgen/gen.ts
|
|
685
860
|
import { readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "node:fs";
|
|
686
|
-
import { join as
|
|
861
|
+
import { join as join8 } from "node:path";
|
|
687
862
|
|
|
688
863
|
// src/eventgen/model.ts
|
|
689
864
|
var TYPE_TO_GETTER = {
|
|
@@ -768,11 +943,11 @@ function emitEventDts(model) {
|
|
|
768
943
|
var CATALOG_PATH2 = "games/cs2/gamedata/event-catalog.json";
|
|
769
944
|
var DTS_OUT2 = "packages/cs2/events.generated.d.ts";
|
|
770
945
|
function runGenEvents(repoRoot, opts) {
|
|
771
|
-
const catalog = JSON.parse(readFileSync6(
|
|
946
|
+
const catalog = JSON.parse(readFileSync6(join8(repoRoot, CATALOG_PATH2), "utf8"));
|
|
772
947
|
const model = buildEventModel(catalog);
|
|
773
948
|
const dts = emitEventDts(model);
|
|
774
949
|
const drift = [];
|
|
775
|
-
const abs =
|
|
950
|
+
const abs = join8(repoRoot, DTS_OUT2);
|
|
776
951
|
if (opts.check) {
|
|
777
952
|
let cur = "";
|
|
778
953
|
try {
|
|
@@ -788,7 +963,7 @@ function runGenEvents(repoRoot, opts) {
|
|
|
788
963
|
|
|
789
964
|
// src/navgen/gen.ts
|
|
790
965
|
import { readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
|
|
791
|
-
import { join as
|
|
966
|
+
import { join as join9 } from "node:path";
|
|
792
967
|
|
|
793
968
|
// src/navgen/model.ts
|
|
794
969
|
var SUPPORTED_NAV_KINDS = /* @__PURE__ */ new Set([
|
|
@@ -863,22 +1038,22 @@ function emitNavJs(model) {
|
|
|
863
1038
|
out.push(` function ${w.wrapper}(root, path) { this.root = root; this.path = path; }`);
|
|
864
1039
|
out.push(` Object.defineProperties(${w.wrapper}.prototype, {`);
|
|
865
1040
|
for (const f of w.fields) {
|
|
866
|
-
const
|
|
1041
|
+
const resolve10 = `off(${S2(f.declaringClass)},${S2(f.rawName)})`;
|
|
867
1042
|
let getter;
|
|
868
1043
|
const kind = f.accessorKind;
|
|
869
1044
|
if (kind === "u64") {
|
|
870
|
-
getter = `get: function () { var v = this.root.readUInt64Via(this.path, ${
|
|
1045
|
+
getter = `get: function () { var v = this.root.readUInt64Via(this.path, ${resolve10}); return v === null ? null : v.toString(); }`;
|
|
871
1046
|
} else if (kind === "i64") {
|
|
872
|
-
getter = `get: function () { var v = this.root.readInt64Via(this.path, ${
|
|
1047
|
+
getter = `get: function () { var v = this.root.readInt64Via(this.path, ${resolve10}); return v === null ? null : v.toString(); }`;
|
|
873
1048
|
} else if (VEC_INFO[kind]) {
|
|
874
1049
|
const vi = VEC_INFO[kind];
|
|
875
|
-
getter = `get: function () { var a = this.root.readFloatsChain(this.path, ${
|
|
1050
|
+
getter = `get: function () { var a = this.root.readFloatsChain(this.path, ${resolve10}, ${vi.count}); return a === null ? null : new ${vi.cls}(a[0], a[1], a[2]); }`;
|
|
876
1051
|
} else if (kind === "handle") {
|
|
877
|
-
getter = `get: function () { return this.root.readHandleVia(this.path, ${
|
|
1052
|
+
getter = `get: function () { return this.root.readHandleVia(this.path, ${resolve10}); }`;
|
|
878
1053
|
} else {
|
|
879
1054
|
const method = READ_VIA[kind];
|
|
880
1055
|
if (!method) continue;
|
|
881
|
-
getter = `get: function () { return this.root.${method}(this.path, ${
|
|
1056
|
+
getter = `get: function () { return this.root.${method}(this.path, ${resolve10}); }`;
|
|
882
1057
|
}
|
|
883
1058
|
out.push(` ${S2(f.propName)}: { ${getter} },`);
|
|
884
1059
|
}
|
|
@@ -950,8 +1125,8 @@ var CATALOG_PATH3 = "games/cs2/gamedata/schema-catalog.json";
|
|
|
950
1125
|
var JS_OUT2 = "games/cs2/js/nav.generated.js";
|
|
951
1126
|
var DTS_OUT3 = "packages/cs2/nav.generated.d.ts";
|
|
952
1127
|
function runGenNav(repoRoot, opts) {
|
|
953
|
-
const config = JSON.parse(readFileSync7(
|
|
954
|
-
const catalog = JSON.parse(readFileSync7(
|
|
1128
|
+
const config = JSON.parse(readFileSync7(join9(repoRoot, TARGETS_PATH), "utf8"));
|
|
1129
|
+
const catalog = JSON.parse(readFileSync7(join9(repoRoot, CATALOG_PATH3), "utf8"));
|
|
955
1130
|
const model = buildNavModel(config, catalog);
|
|
956
1131
|
const js = emitNavJs(model);
|
|
957
1132
|
const dts = emitNavDts(model);
|
|
@@ -959,7 +1134,7 @@ function runGenNav(repoRoot, opts) {
|
|
|
959
1134
|
const drift = [];
|
|
960
1135
|
const files = [[JS_OUT2, js], [DTS_OUT3, dts]];
|
|
961
1136
|
for (const [rel, content] of files) {
|
|
962
|
-
const abs =
|
|
1137
|
+
const abs = join9(repoRoot, rel);
|
|
963
1138
|
if (opts.check) {
|
|
964
1139
|
let cur = "";
|
|
965
1140
|
try {
|
|
@@ -978,11 +1153,12 @@ function runGenNav(repoRoot, opts) {
|
|
|
978
1153
|
import {
|
|
979
1154
|
mkdirSync as mkdirSync2,
|
|
980
1155
|
writeFileSync as writeFileSync5,
|
|
981
|
-
existsSync as
|
|
1156
|
+
existsSync as existsSync6,
|
|
982
1157
|
readdirSync as readdirSync2,
|
|
983
|
-
readFileSync as readFileSync8
|
|
1158
|
+
readFileSync as readFileSync8,
|
|
1159
|
+
symlinkSync
|
|
984
1160
|
} from "node:fs";
|
|
985
|
-
import { dirname as dirname2, join as
|
|
1161
|
+
import { dirname as dirname2, join as join10, resolve as resolve6, basename } from "node:path";
|
|
986
1162
|
import { createInterface } from "node:readline/promises";
|
|
987
1163
|
import { stdin as input, stdout as output } from "node:process";
|
|
988
1164
|
import { spawnSync } from "node:child_process";
|
|
@@ -990,13 +1166,13 @@ import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
|
990
1166
|
function readCliVersion() {
|
|
991
1167
|
const here = dirname2(fileURLToPath2(import.meta.url));
|
|
992
1168
|
const candidates = [
|
|
993
|
-
|
|
1169
|
+
join10(here, "..", "package.json"),
|
|
994
1170
|
// dist/cli.js → packages/sdk/package.json
|
|
995
|
-
|
|
1171
|
+
join10(here, "..", "..", "package.json")
|
|
996
1172
|
// src/create → packages/sdk/package.json
|
|
997
1173
|
];
|
|
998
1174
|
for (const p of candidates) {
|
|
999
|
-
if (!
|
|
1175
|
+
if (!existsSync6(p)) continue;
|
|
1000
1176
|
try {
|
|
1001
1177
|
const pkg = JSON.parse(readFileSync8(p, "utf8"));
|
|
1002
1178
|
if (pkg.name === "@s2script/sdk" && pkg.version) return pkg.version;
|
|
@@ -1008,18 +1184,18 @@ function readCliVersion() {
|
|
|
1008
1184
|
function findLocalPackagesDir() {
|
|
1009
1185
|
const here = dirname2(fileURLToPath2(import.meta.url));
|
|
1010
1186
|
const candidates = [
|
|
1011
|
-
|
|
1187
|
+
join10(here, "..", ".."),
|
|
1012
1188
|
// dist/cli.js → packages
|
|
1013
|
-
|
|
1189
|
+
join10(here, "..", "..", "..")
|
|
1014
1190
|
// src/create → packages
|
|
1015
1191
|
];
|
|
1016
1192
|
for (const c of candidates) {
|
|
1017
|
-
if (isPackagesDir(c)) return
|
|
1193
|
+
if (isPackagesDir(c)) return resolve6(c);
|
|
1018
1194
|
}
|
|
1019
1195
|
return void 0;
|
|
1020
1196
|
}
|
|
1021
1197
|
function defaultNameFromPath(dir) {
|
|
1022
|
-
const base = basename(
|
|
1198
|
+
const base = basename(resolve6(dir));
|
|
1023
1199
|
const slug = base.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase() || "plugin";
|
|
1024
1200
|
return `@plugin/${slug}`;
|
|
1025
1201
|
}
|
|
@@ -1056,9 +1232,9 @@ function assertInstall(v) {
|
|
|
1056
1232
|
}
|
|
1057
1233
|
function createPackageNames(game) {
|
|
1058
1234
|
if (game === "cs2") {
|
|
1059
|
-
return ["sdk", "cs2"];
|
|
1235
|
+
return ["sdk", "cs2", "eslint-plugin"];
|
|
1060
1236
|
}
|
|
1061
|
-
return ["sdk"];
|
|
1237
|
+
return ["sdk", "eslint-plugin"];
|
|
1062
1238
|
}
|
|
1063
1239
|
function resolvePublishedVersion(pkg) {
|
|
1064
1240
|
const r = spawnSync("npm", ["view", pkg, "version"], { encoding: "utf8", timeout: 5e3 });
|
|
@@ -1068,68 +1244,62 @@ function versionSpecFrom(status, stdout) {
|
|
|
1068
1244
|
const v = (stdout ?? "").trim();
|
|
1069
1245
|
return status === 0 && /^\d+\.\d+\.\d+/.test(v) ? `^${v}` : "latest";
|
|
1070
1246
|
}
|
|
1071
|
-
function registryDevDeps(game, sdkVersion,
|
|
1247
|
+
function registryDevDeps(game, sdkVersion, resolve10 = resolvePublishedVersion) {
|
|
1072
1248
|
const deps = {};
|
|
1073
1249
|
for (const n of createPackageNames(game)) {
|
|
1074
|
-
deps[`@s2script/${n}`] = n === "sdk" ? `^${sdkVersion}` :
|
|
1250
|
+
deps[`@s2script/${n}`] = n === "sdk" ? `^${sdkVersion}` : resolve10(`@s2script/${n}`);
|
|
1075
1251
|
}
|
|
1076
1252
|
return deps;
|
|
1077
1253
|
}
|
|
1078
1254
|
function fileDevDeps(packagesDir, game) {
|
|
1079
1255
|
const deps = {};
|
|
1080
1256
|
for (const n of createPackageNames(game)) {
|
|
1081
|
-
const abs =
|
|
1082
|
-
if (!
|
|
1257
|
+
const abs = join10(packagesDir, n);
|
|
1258
|
+
if (!existsSync6(join10(abs, "package.json"))) return void 0;
|
|
1083
1259
|
deps[`@s2script/${n}`] = `file:${abs}`;
|
|
1084
1260
|
}
|
|
1085
1261
|
return deps;
|
|
1086
1262
|
}
|
|
1087
1263
|
function pluginSource(game) {
|
|
1088
1264
|
if (game === "cs2") {
|
|
1089
|
-
return `import {
|
|
1265
|
+
return `import { plugin } from "@s2script/sdk/plugin";
|
|
1090
1266
|
import { Chat } from "@s2script/sdk/chat";
|
|
1091
1267
|
|
|
1092
|
-
export
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
if (
|
|
1096
|
-
Chat.toSlot(
|
|
1268
|
+
export default plugin((ctx) => {
|
|
1269
|
+
ctx.commands.register("hello", (cmd) => {
|
|
1270
|
+
cmd.reply("hello from s2script");
|
|
1271
|
+
if (cmd.callerSlot >= 0) {
|
|
1272
|
+
Chat.toSlot(cmd.callerSlot, "hello from s2script");
|
|
1097
1273
|
}
|
|
1098
1274
|
});
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
export function onUnload(): void {}
|
|
1275
|
+
});
|
|
1102
1276
|
`;
|
|
1103
1277
|
}
|
|
1104
|
-
return `import {
|
|
1278
|
+
return `import { plugin } from "@s2script/sdk/plugin";
|
|
1105
1279
|
import { delay } from "@s2script/sdk/timers";
|
|
1106
1280
|
|
|
1107
|
-
export
|
|
1281
|
+
export default plugin((ctx) => {
|
|
1108
1282
|
let n = 0;
|
|
1109
|
-
|
|
1283
|
+
ctx.server.onGameFrame(() => {
|
|
1110
1284
|
n += 1;
|
|
1111
1285
|
});
|
|
1112
|
-
void delay(1000).then(() =>
|
|
1113
|
-
|
|
1114
|
-
|
|
1286
|
+
void delay(1000).then(() => console.log("s2script plugin alive; frames so far:", n));
|
|
1287
|
+
});
|
|
1288
|
+
`;
|
|
1115
1289
|
}
|
|
1290
|
+
var ESLINT_RANGE = "^10.7.0";
|
|
1291
|
+
function eslintConfig() {
|
|
1292
|
+
return `import s2script from "@s2script/eslint-plugin";
|
|
1116
1293
|
|
|
1117
|
-
|
|
1294
|
+
// The SAME pinned rules \`s2s build\` enforces \u2014 the editor's ESLint extension picks this up,
|
|
1295
|
+
// so a violation is a red squiggle before you ever build (green editor => green build).
|
|
1296
|
+
export default s2script.configs.recommended({ tsconfigRootDir: import.meta.dirname });
|
|
1118
1297
|
`;
|
|
1119
1298
|
}
|
|
1120
1299
|
function tsconfigJson() {
|
|
1121
1300
|
return JSON.stringify(
|
|
1122
1301
|
{
|
|
1123
|
-
compilerOptions:
|
|
1124
|
-
strict: true,
|
|
1125
|
-
noEmit: true,
|
|
1126
|
-
moduleResolution: "bundler",
|
|
1127
|
-
module: "ESNext",
|
|
1128
|
-
target: "ES2020",
|
|
1129
|
-
lib: ["ES2020"],
|
|
1130
|
-
types: [],
|
|
1131
|
-
skipLibCheck: true
|
|
1132
|
-
},
|
|
1302
|
+
compilerOptions: sharedCompilerOptionsJson,
|
|
1133
1303
|
include: ["src", "node_modules/@s2script/sdk/globals.d.ts"]
|
|
1134
1304
|
},
|
|
1135
1305
|
null,
|
|
@@ -1138,7 +1308,10 @@ function tsconfigJson() {
|
|
|
1138
1308
|
}
|
|
1139
1309
|
function packageJsonContent(name, game, version, localPackagesDir) {
|
|
1140
1310
|
const fileDeps = localPackagesDir ? fileDevDeps(localPackagesDir, game) : void 0;
|
|
1141
|
-
const devDependencies =
|
|
1311
|
+
const devDependencies = {
|
|
1312
|
+
...fileDeps ?? registryDevDeps(game, version),
|
|
1313
|
+
eslint: ESLINT_RANGE
|
|
1314
|
+
};
|
|
1142
1315
|
return JSON.stringify(
|
|
1143
1316
|
{
|
|
1144
1317
|
name,
|
|
@@ -1148,10 +1321,7 @@ function packageJsonContent(name, game, version, localPackagesDir) {
|
|
|
1148
1321
|
scripts: {
|
|
1149
1322
|
build: "s2s build ."
|
|
1150
1323
|
},
|
|
1151
|
-
devDependencies
|
|
1152
|
-
s2script: {
|
|
1153
|
-
apiVersion: "1.x"
|
|
1154
|
-
}
|
|
1324
|
+
devDependencies
|
|
1155
1325
|
},
|
|
1156
1326
|
null,
|
|
1157
1327
|
2
|
|
@@ -1164,6 +1334,12 @@ dist/
|
|
|
1164
1334
|
.DS_Store
|
|
1165
1335
|
`;
|
|
1166
1336
|
}
|
|
1337
|
+
function linkLocalPackagesForNoInstall(targetPath, localPackagesDir) {
|
|
1338
|
+
const nm = join10(targetPath, "node_modules");
|
|
1339
|
+
mkdirSync2(nm, { recursive: true });
|
|
1340
|
+
const dest = join10(nm, "@s2script");
|
|
1341
|
+
if (!existsSync6(dest)) symlinkSync(localPackagesDir, dest);
|
|
1342
|
+
}
|
|
1167
1343
|
function runInstall(dir, pm) {
|
|
1168
1344
|
if (pm === "none") return;
|
|
1169
1345
|
const r = spawnSync(pm, ["install"], {
|
|
@@ -1175,7 +1351,7 @@ function runInstall(dir, pm) {
|
|
|
1175
1351
|
if (r.status !== 0) throw new Error(`${pm} install failed (exit ${r.status})`);
|
|
1176
1352
|
}
|
|
1177
1353
|
async function createPlugin(opts = {}) {
|
|
1178
|
-
const targetPath =
|
|
1354
|
+
const targetPath = resolve6(opts.path ?? ".");
|
|
1179
1355
|
const interactive = Boolean(input.isTTY && output.isTTY && !opts.yes);
|
|
1180
1356
|
let game = assertGame(opts.game);
|
|
1181
1357
|
let name = opts.name;
|
|
@@ -1184,7 +1360,7 @@ async function createPlugin(opts = {}) {
|
|
|
1184
1360
|
if (template !== "minimal") {
|
|
1185
1361
|
throw new Error(`unknown template ${JSON.stringify(template)} (v1 supports: minimal)`);
|
|
1186
1362
|
}
|
|
1187
|
-
if (
|
|
1363
|
+
if (existsSync6(targetPath)) {
|
|
1188
1364
|
const kids = readdirSync2(targetPath);
|
|
1189
1365
|
const meaningful = kids.filter((k) => k !== ".git");
|
|
1190
1366
|
if (meaningful.length) {
|
|
@@ -1231,18 +1407,21 @@ async function createPlugin(opts = {}) {
|
|
|
1231
1407
|
install = install ?? (opts.noInstall ? "none" : "npm");
|
|
1232
1408
|
const version = readCliVersion();
|
|
1233
1409
|
const localPackagesDir = findLocalPackagesDir();
|
|
1234
|
-
mkdirSync2(
|
|
1410
|
+
mkdirSync2(join10(targetPath, "src"), { recursive: true });
|
|
1235
1411
|
writeFileSync5(
|
|
1236
|
-
|
|
1412
|
+
join10(targetPath, "package.json"),
|
|
1237
1413
|
packageJsonContent(name, game, version, localPackagesDir)
|
|
1238
1414
|
);
|
|
1239
|
-
writeFileSync5(
|
|
1240
|
-
writeFileSync5(
|
|
1241
|
-
writeFileSync5(
|
|
1415
|
+
writeFileSync5(join10(targetPath, "tsconfig.json"), tsconfigJson());
|
|
1416
|
+
writeFileSync5(join10(targetPath, "eslint.config.mjs"), eslintConfig());
|
|
1417
|
+
writeFileSync5(join10(targetPath, "src", "plugin.ts"), pluginSource(game));
|
|
1418
|
+
writeFileSync5(join10(targetPath, ".gitignore"), gitignore());
|
|
1242
1419
|
let installed = false;
|
|
1243
1420
|
if (install !== "none") {
|
|
1244
1421
|
runInstall(targetPath, install);
|
|
1245
1422
|
installed = true;
|
|
1423
|
+
} else if (localPackagesDir) {
|
|
1424
|
+
linkLocalPackagesForNoInstall(targetPath, localPackagesDir);
|
|
1246
1425
|
}
|
|
1247
1426
|
return {
|
|
1248
1427
|
dir: targetPath,
|
|
@@ -1257,7 +1436,7 @@ async function createPlugin(opts = {}) {
|
|
|
1257
1436
|
// src/config/gen.ts
|
|
1258
1437
|
import AdmZip2 from "adm-zip";
|
|
1259
1438
|
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync3 } from "node:fs";
|
|
1260
|
-
import { join as
|
|
1439
|
+
import { join as join11 } from "node:path";
|
|
1261
1440
|
|
|
1262
1441
|
// src/config/config-path.ts
|
|
1263
1442
|
function configFileName(id) {
|
|
@@ -1272,7 +1451,7 @@ function generateDefaultJsonc(config) {
|
|
|
1272
1451
|
return "{\n" + genEntries(config, 1) + "}\n";
|
|
1273
1452
|
}
|
|
1274
1453
|
function genEntries(entries, indent) {
|
|
1275
|
-
const
|
|
1454
|
+
const pad2 = " ".repeat(indent);
|
|
1276
1455
|
const keys = Object.keys(entries).filter((k) => !k.includes(".")).sort();
|
|
1277
1456
|
let out = "";
|
|
1278
1457
|
keys.forEach((key, i) => {
|
|
@@ -1282,15 +1461,15 @@ function genEntries(entries, indent) {
|
|
|
1282
1461
|
const obj = raw;
|
|
1283
1462
|
if (isDecl(obj)) {
|
|
1284
1463
|
const desc = typeof obj.description === "string" ? obj.description : "";
|
|
1285
|
-
out += `${
|
|
1464
|
+
out += `${pad2}// ${String(obj.type)}${desc ? ` \u2014 ${desc}` : ""}
|
|
1286
1465
|
`;
|
|
1287
|
-
out += `${
|
|
1466
|
+
out += `${pad2}${JSON.stringify(key)}: ${JSON.stringify(obj.default)}${comma}
|
|
1288
1467
|
`;
|
|
1289
1468
|
} else {
|
|
1290
|
-
out += `${
|
|
1469
|
+
out += `${pad2}${JSON.stringify(key)}: {
|
|
1291
1470
|
`;
|
|
1292
1471
|
out += genEntries(obj, indent + 1);
|
|
1293
|
-
out += `${
|
|
1472
|
+
out += `${pad2}}${comma}
|
|
1294
1473
|
`;
|
|
1295
1474
|
}
|
|
1296
1475
|
});
|
|
@@ -1304,7 +1483,7 @@ function genConfigFromManifest(manifest, outDir) {
|
|
|
1304
1483
|
const id = typeof manifest.id === "string" ? manifest.id : "";
|
|
1305
1484
|
if (!id) throw new Error("config gen: manifest declares config but has no id");
|
|
1306
1485
|
mkdirSync3(outDir, { recursive: true });
|
|
1307
|
-
const path =
|
|
1486
|
+
const path = join11(outDir, configFileName(id));
|
|
1308
1487
|
writeFileSync6(path, generateDefaultJsonc(config));
|
|
1309
1488
|
return path;
|
|
1310
1489
|
}
|
|
@@ -1326,13 +1505,386 @@ function runConfigGen(s2sps, outDir) {
|
|
|
1326
1505
|
return { written, skipped };
|
|
1327
1506
|
}
|
|
1328
1507
|
|
|
1508
|
+
// src/registry/login.ts
|
|
1509
|
+
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
1510
|
+
import { stdin as input2, stdout as output2 } from "node:process";
|
|
1511
|
+
|
|
1512
|
+
// src/registry/credentials.ts
|
|
1513
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "node:fs";
|
|
1514
|
+
import { homedir } from "node:os";
|
|
1515
|
+
import { join as join12 } from "node:path";
|
|
1516
|
+
function credentialsPath() {
|
|
1517
|
+
return join12(homedir(), ".s2script", "credentials.json");
|
|
1518
|
+
}
|
|
1519
|
+
function defaultRegistryUrl() {
|
|
1520
|
+
return process.env.S2SCRIPT_REGISTRY_URL?.replace(/\/$/, "") || "https://s2script.com";
|
|
1521
|
+
}
|
|
1522
|
+
function loadCredentials() {
|
|
1523
|
+
const fromEnv = process.env.S2SCRIPT_TOKEN || process.env.S2SCRIPT_DEPLOY_TOKEN;
|
|
1524
|
+
if (fromEnv) {
|
|
1525
|
+
return { registryUrl: defaultRegistryUrl(), token: fromEnv };
|
|
1526
|
+
}
|
|
1527
|
+
const p = credentialsPath();
|
|
1528
|
+
if (!existsSync7(p)) return null;
|
|
1529
|
+
try {
|
|
1530
|
+
const j = JSON.parse(readFileSync9(p, "utf8"));
|
|
1531
|
+
if (!j.token) return null;
|
|
1532
|
+
return {
|
|
1533
|
+
registryUrl: (j.registryUrl || defaultRegistryUrl()).replace(/\/$/, ""),
|
|
1534
|
+
token: j.token
|
|
1535
|
+
};
|
|
1536
|
+
} catch {
|
|
1537
|
+
return null;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
function saveCredentials(creds) {
|
|
1541
|
+
const dir = join12(homedir(), ".s2script");
|
|
1542
|
+
mkdirSync4(dir, { recursive: true });
|
|
1543
|
+
writeFileSync7(
|
|
1544
|
+
credentialsPath(),
|
|
1545
|
+
JSON.stringify(
|
|
1546
|
+
{ registryUrl: creds.registryUrl.replace(/\/$/, ""), token: creds.token },
|
|
1547
|
+
null,
|
|
1548
|
+
2
|
|
1549
|
+
) + "\n",
|
|
1550
|
+
{ mode: 384 }
|
|
1551
|
+
);
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
// src/registry/login.ts
|
|
1555
|
+
async function loginInteractive(opts) {
|
|
1556
|
+
const registryUrl = (opts?.registryUrl || defaultRegistryUrl()).replace(/\/$/, "");
|
|
1557
|
+
let token = opts?.token || process.env.S2SCRIPT_TOKEN || process.env.S2SCRIPT_DEPLOY_TOKEN;
|
|
1558
|
+
if (!token) {
|
|
1559
|
+
if (opts?.ci || !input2.isTTY) {
|
|
1560
|
+
throw new Error(
|
|
1561
|
+
"no deploy token: set S2SCRIPT_TOKEN or run `s2script login` interactively"
|
|
1562
|
+
);
|
|
1563
|
+
}
|
|
1564
|
+
const rl = createInterface2({ input: input2, output: output2 });
|
|
1565
|
+
try {
|
|
1566
|
+
console.log(`Registry: ${registryUrl}`);
|
|
1567
|
+
console.log("Create a deploy token at /account/tokens on the registry site, then paste it:");
|
|
1568
|
+
token = (await rl.question("token: ")).trim();
|
|
1569
|
+
} finally {
|
|
1570
|
+
rl.close();
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
if (!token || !token.startsWith("s2s_")) {
|
|
1574
|
+
throw new Error('token must start with "s2s_"');
|
|
1575
|
+
}
|
|
1576
|
+
const creds = { registryUrl, token };
|
|
1577
|
+
saveCredentials(creds);
|
|
1578
|
+
return creds;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
// src/registry/deploy.ts
|
|
1582
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
1583
|
+
import { resolve as resolve7 } from "node:path";
|
|
1584
|
+
import AdmZip3 from "adm-zip";
|
|
1585
|
+
|
|
1586
|
+
// src/types-pack.ts
|
|
1587
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
1588
|
+
import { gzipSync } from "node:zlib";
|
|
1589
|
+
function pad(buf, size) {
|
|
1590
|
+
if (buf.length > size) throw new Error(`tar field overflow (${buf.length}>${size})`);
|
|
1591
|
+
const out = Buffer.alloc(size, 0);
|
|
1592
|
+
buf.copy(out);
|
|
1593
|
+
return out;
|
|
1594
|
+
}
|
|
1595
|
+
function tarHeader(name, size, type) {
|
|
1596
|
+
const header = Buffer.alloc(512, 0);
|
|
1597
|
+
pad(Buffer.from(name, "utf8"), 100).copy(header, 0);
|
|
1598
|
+
Buffer.from("0000644\0", "utf8").copy(header, 100);
|
|
1599
|
+
Buffer.from("0000000\0", "utf8").copy(header, 108);
|
|
1600
|
+
Buffer.from("0000000\0", "utf8").copy(header, 116);
|
|
1601
|
+
const sizeOct = size.toString(8).padStart(11, "0") + "\0";
|
|
1602
|
+
Buffer.from(sizeOct, "utf8").copy(header, 124);
|
|
1603
|
+
const mtime = Math.floor(Date.now() / 1e3).toString(8).padStart(11, "0") + "\0";
|
|
1604
|
+
Buffer.from(mtime, "utf8").copy(header, 136);
|
|
1605
|
+
Buffer.from(" ", "utf8").copy(header, 148);
|
|
1606
|
+
header[156] = type.charCodeAt(0);
|
|
1607
|
+
Buffer.from("ustar\0", "utf8").copy(header, 257);
|
|
1608
|
+
Buffer.from("00", "utf8").copy(header, 263);
|
|
1609
|
+
let sum = 0;
|
|
1610
|
+
for (let i = 0; i < 512; i++) sum += header[i];
|
|
1611
|
+
const chk = sum.toString(8).padStart(6, "0") + "\0 ";
|
|
1612
|
+
Buffer.from(chk, "utf8").copy(header, 148);
|
|
1613
|
+
return header;
|
|
1614
|
+
}
|
|
1615
|
+
function tarFile(name, content) {
|
|
1616
|
+
const header = tarHeader(name, content.length, "0");
|
|
1617
|
+
const padLen = (512 - content.length % 512) % 512;
|
|
1618
|
+
return Buffer.concat([header, content, Buffer.alloc(padLen, 0)]);
|
|
1619
|
+
}
|
|
1620
|
+
function packTypesTarball(input3) {
|
|
1621
|
+
const dts = readFileSync10(input3.typesPath);
|
|
1622
|
+
const pkgJson = Buffer.from(
|
|
1623
|
+
JSON.stringify(
|
|
1624
|
+
{
|
|
1625
|
+
name: input3.name,
|
|
1626
|
+
version: input3.version,
|
|
1627
|
+
types: "api.d.ts",
|
|
1628
|
+
s2script: {
|
|
1629
|
+
kind: "interface",
|
|
1630
|
+
...input3.publishes ? { publishes: input3.publishes } : {}
|
|
1631
|
+
}
|
|
1632
|
+
},
|
|
1633
|
+
null,
|
|
1634
|
+
2
|
|
1635
|
+
) + "\n",
|
|
1636
|
+
"utf8"
|
|
1637
|
+
);
|
|
1638
|
+
const tar = Buffer.concat([
|
|
1639
|
+
tarFile("package/package.json", pkgJson),
|
|
1640
|
+
tarFile("package/api.d.ts", dts),
|
|
1641
|
+
Buffer.alloc(1024, 0)
|
|
1642
|
+
// two zero blocks = EOF
|
|
1643
|
+
]);
|
|
1644
|
+
return gzipSync(tar);
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
// src/registry/client.ts
|
|
1648
|
+
var RegistryError = class extends Error {
|
|
1649
|
+
constructor(message, status, body) {
|
|
1650
|
+
super(message);
|
|
1651
|
+
this.status = status;
|
|
1652
|
+
this.body = body;
|
|
1653
|
+
this.name = "RegistryError";
|
|
1654
|
+
}
|
|
1655
|
+
};
|
|
1656
|
+
var RegistryClient = class {
|
|
1657
|
+
constructor(opts) {
|
|
1658
|
+
this.baseUrl = opts.baseUrl.replace(/\/$/, "");
|
|
1659
|
+
this.token = opts.token;
|
|
1660
|
+
this.fetchFn = opts.fetch ?? fetch;
|
|
1661
|
+
}
|
|
1662
|
+
headers(extra) {
|
|
1663
|
+
const h = { ...extra ?? {} };
|
|
1664
|
+
if (this.token) h.Authorization = `Bearer ${this.token}`;
|
|
1665
|
+
return h;
|
|
1666
|
+
}
|
|
1667
|
+
async deploy(opts) {
|
|
1668
|
+
const fd = new FormData();
|
|
1669
|
+
fd.set("manifest", JSON.stringify(opts.manifest));
|
|
1670
|
+
fd.set("s2sp", new Blob([new Uint8Array(opts.s2sp)]), "plugin.s2sp");
|
|
1671
|
+
if (opts.types && opts.types.length) {
|
|
1672
|
+
fd.set("types", new Blob([new Uint8Array(opts.types)]), "types.tgz");
|
|
1673
|
+
}
|
|
1674
|
+
const res = await this.fetchFn(`${this.baseUrl}/api/v1/deploy`, {
|
|
1675
|
+
method: "POST",
|
|
1676
|
+
headers: this.headers(),
|
|
1677
|
+
body: fd
|
|
1678
|
+
});
|
|
1679
|
+
const body = await res.json().catch(() => ({}));
|
|
1680
|
+
if (!res.ok) {
|
|
1681
|
+
throw new RegistryError(
|
|
1682
|
+
body.error || `deploy failed (${res.status})`,
|
|
1683
|
+
res.status,
|
|
1684
|
+
body
|
|
1685
|
+
);
|
|
1686
|
+
}
|
|
1687
|
+
return body;
|
|
1688
|
+
}
|
|
1689
|
+
async resolve(name, range = "*") {
|
|
1690
|
+
const u = new URL(`${this.baseUrl}/api/v1/resolve`);
|
|
1691
|
+
u.searchParams.set("name", name);
|
|
1692
|
+
u.searchParams.set("range", range);
|
|
1693
|
+
const res = await this.fetchFn(u, { headers: this.headers() });
|
|
1694
|
+
const body = await res.json().catch(() => ({}));
|
|
1695
|
+
if (!res.ok) {
|
|
1696
|
+
throw new RegistryError(
|
|
1697
|
+
body.message || `resolve failed (${res.status})`,
|
|
1698
|
+
res.status,
|
|
1699
|
+
body
|
|
1700
|
+
);
|
|
1701
|
+
}
|
|
1702
|
+
return body;
|
|
1703
|
+
}
|
|
1704
|
+
async downloadTypes(name, version) {
|
|
1705
|
+
const u = new URL(`${this.baseUrl}/api/v1/download/types`);
|
|
1706
|
+
u.searchParams.set("name", name);
|
|
1707
|
+
u.searchParams.set("version", version);
|
|
1708
|
+
const res = await this.fetchFn(u, { headers: this.headers() });
|
|
1709
|
+
if (!res.ok) {
|
|
1710
|
+
const body = await res.json().catch(() => ({}));
|
|
1711
|
+
throw new RegistryError(
|
|
1712
|
+
body.message || `types download failed (${res.status})`,
|
|
1713
|
+
res.status,
|
|
1714
|
+
body
|
|
1715
|
+
);
|
|
1716
|
+
}
|
|
1717
|
+
return Buffer.from(await res.arrayBuffer());
|
|
1718
|
+
}
|
|
1719
|
+
async meta(name) {
|
|
1720
|
+
const u = new URL(`${this.baseUrl}/api/v1/meta`);
|
|
1721
|
+
u.searchParams.set("name", name);
|
|
1722
|
+
const res = await this.fetchFn(u, { headers: this.headers() });
|
|
1723
|
+
const body = await res.json().catch(() => ({}));
|
|
1724
|
+
if (!res.ok) {
|
|
1725
|
+
throw new RegistryError(`meta failed (${res.status})`, res.status, body);
|
|
1726
|
+
}
|
|
1727
|
+
return body;
|
|
1728
|
+
}
|
|
1729
|
+
};
|
|
1730
|
+
|
|
1731
|
+
// src/registry/deploy.ts
|
|
1732
|
+
async function deployPlugin(opts) {
|
|
1733
|
+
const absDir = resolve7(opts.dir);
|
|
1734
|
+
const pkg = JSON.parse(readFileSync11(resolve7(absDir, "package.json"), "utf8"));
|
|
1735
|
+
const gate = assertPublishesTypes(pkg, absDir);
|
|
1736
|
+
if (!gate.ok) throw new Error(`publish gate failed: ${gate.error}`);
|
|
1737
|
+
const creds = loadCredentials();
|
|
1738
|
+
if (!creds?.token) {
|
|
1739
|
+
throw new Error(
|
|
1740
|
+
opts.ci ? "S2SCRIPT_TOKEN required in CI" : "not logged in \u2014 run `s2s login` or set S2SCRIPT_TOKEN"
|
|
1741
|
+
);
|
|
1742
|
+
}
|
|
1743
|
+
const outPath = await buildPlugin(absDir, opts.packagesDir);
|
|
1744
|
+
const s2sp = readFileSync11(outPath);
|
|
1745
|
+
const zip = new AdmZip3(s2sp);
|
|
1746
|
+
const manifestEntry = zip.getEntry("manifest.json");
|
|
1747
|
+
if (!manifestEntry) {
|
|
1748
|
+
throw new Error(`built archive missing manifest.json: ${outPath}`);
|
|
1749
|
+
}
|
|
1750
|
+
const manifest = JSON.parse(manifestEntry.getData().toString("utf8"));
|
|
1751
|
+
let types = null;
|
|
1752
|
+
if (gate.typesPath) {
|
|
1753
|
+
types = packTypesTarball({
|
|
1754
|
+
name: typeof manifest.id === "string" ? manifest.id : pkg.name,
|
|
1755
|
+
version: typeof manifest.version === "string" ? manifest.version : pkg.version,
|
|
1756
|
+
typesPath: gate.typesPath,
|
|
1757
|
+
publishes: hasPublishes(manifest.publishes) ? manifest.publishes : void 0
|
|
1758
|
+
});
|
|
1759
|
+
}
|
|
1760
|
+
const client = new RegistryClient({
|
|
1761
|
+
baseUrl: opts.registryUrl || creds.registryUrl || defaultRegistryUrl(),
|
|
1762
|
+
token: creds.token
|
|
1763
|
+
});
|
|
1764
|
+
return client.deploy({ manifest, s2sp, types });
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
// src/registry/add.ts
|
|
1768
|
+
import {
|
|
1769
|
+
mkdirSync as mkdirSync5,
|
|
1770
|
+
writeFileSync as writeFileSync9,
|
|
1771
|
+
readFileSync as readFileSync13,
|
|
1772
|
+
existsSync as existsSync9
|
|
1773
|
+
} from "node:fs";
|
|
1774
|
+
import { join as join14, resolve as resolve8 } from "node:path";
|
|
1775
|
+
import { gunzipSync } from "node:zlib";
|
|
1776
|
+
|
|
1777
|
+
// src/registry/npmrc.ts
|
|
1778
|
+
import { existsSync as existsSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "node:fs";
|
|
1779
|
+
import { join as join13 } from "node:path";
|
|
1780
|
+
function npmCompatRegistryUrl(registryUrl) {
|
|
1781
|
+
return `${registryUrl.replace(/\/$/, "")}/npm/`;
|
|
1782
|
+
}
|
|
1783
|
+
function scopeFromPackageName(name) {
|
|
1784
|
+
if (!name.startsWith("@")) return null;
|
|
1785
|
+
const slash = name.indexOf("/");
|
|
1786
|
+
if (slash < 2) return null;
|
|
1787
|
+
return name.slice(1, slash);
|
|
1788
|
+
}
|
|
1789
|
+
function ensureScopeNpmrc(projectDir, packageName, registryUrl) {
|
|
1790
|
+
const scope = scopeFromPackageName(packageName);
|
|
1791
|
+
if (!scope || scope === "s2script") return null;
|
|
1792
|
+
const compat = npmCompatRegistryUrl(registryUrl);
|
|
1793
|
+
const key = `@${scope}:registry`;
|
|
1794
|
+
const line = `${key}=${compat}`;
|
|
1795
|
+
const npmrcPath = join13(projectDir, ".npmrc");
|
|
1796
|
+
let existing = existsSync8(npmrcPath) ? readFileSync12(npmrcPath, "utf8") : "";
|
|
1797
|
+
const lines = existing.split(/\r?\n/);
|
|
1798
|
+
const idx = lines.findIndex((l) => l.trimStart().startsWith(`${key}=`) || l.trimStart().startsWith(`${key} =`));
|
|
1799
|
+
if (idx >= 0) {
|
|
1800
|
+
lines[idx] = line;
|
|
1801
|
+
} else {
|
|
1802
|
+
if (existing && !existing.endsWith("\n")) lines.push("");
|
|
1803
|
+
lines.push(line);
|
|
1804
|
+
}
|
|
1805
|
+
const out = lines.filter((l, i, a) => !(l === "" && a[i - 1] === "")).join("\n");
|
|
1806
|
+
writeFileSync8(npmrcPath, out.endsWith("\n") ? out : out + "\n");
|
|
1807
|
+
return line;
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
// src/registry/add.ts
|
|
1811
|
+
function parseSpec(spec) {
|
|
1812
|
+
if (spec.startsWith("@")) {
|
|
1813
|
+
const at2 = spec.indexOf("@", 1);
|
|
1814
|
+
if (at2 < 0) return { name: spec, range: "*" };
|
|
1815
|
+
return { name: spec.slice(0, at2), range: spec.slice(at2 + 1) || "*" };
|
|
1816
|
+
}
|
|
1817
|
+
const at = spec.lastIndexOf("@");
|
|
1818
|
+
if (at <= 0) return { name: spec, range: "*" };
|
|
1819
|
+
return { name: spec.slice(0, at), range: spec.slice(at + 1) || "*" };
|
|
1820
|
+
}
|
|
1821
|
+
function extractTypesTarball(tgz, outDir) {
|
|
1822
|
+
const tar = gunzipSync(tgz);
|
|
1823
|
+
mkdirSync5(outDir, { recursive: true });
|
|
1824
|
+
let offset = 0;
|
|
1825
|
+
let wroteContract = false;
|
|
1826
|
+
while (offset + 512 <= tar.length) {
|
|
1827
|
+
const header = tar.subarray(offset, offset + 512);
|
|
1828
|
+
offset += 512;
|
|
1829
|
+
if (header.every((b) => b === 0)) break;
|
|
1830
|
+
const name = header.subarray(0, 100).toString("utf8").replace(/\0.*$/, "");
|
|
1831
|
+
const sizeOct = header.subarray(124, 135).toString("utf8").replace(/\0.*$/, "").trim();
|
|
1832
|
+
const size = parseInt(sizeOct, 8) || 0;
|
|
1833
|
+
const content = tar.subarray(offset, offset + size);
|
|
1834
|
+
offset += size + (512 - size % 512) % 512;
|
|
1835
|
+
if (!name || name.endsWith("/")) continue;
|
|
1836
|
+
const base = name.replace(/^package\//, "");
|
|
1837
|
+
if (base === "package.json") {
|
|
1838
|
+
writeFileSync9(join14(outDir, "package.json"), content);
|
|
1839
|
+
} else if (base === "api.d.ts" || base === "index.d.ts") {
|
|
1840
|
+
writeFileSync9(join14(outDir, "index.d.ts"), content);
|
|
1841
|
+
wroteContract = true;
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
if (!wroteContract && !existsSync9(join14(outDir, "index.d.ts"))) {
|
|
1845
|
+
throw new Error("types tarball missing package/api.d.ts (or index.d.ts)");
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
async function addPackage(opts) {
|
|
1849
|
+
const absDir = resolve8(opts.pluginDir);
|
|
1850
|
+
const { name, range } = parseSpec(opts.spec);
|
|
1851
|
+
const creds = loadCredentials();
|
|
1852
|
+
const client = new RegistryClient({
|
|
1853
|
+
baseUrl: opts.registryUrl || creds?.registryUrl || defaultRegistryUrl(),
|
|
1854
|
+
token: creds?.token
|
|
1855
|
+
});
|
|
1856
|
+
const resolved = await client.resolve(name, range);
|
|
1857
|
+
if (!resolved.hasTypes) {
|
|
1858
|
+
throw new Error(`${name}@${resolved.version} has no types artifact (runtime-only package?)`);
|
|
1859
|
+
}
|
|
1860
|
+
const tgz = await client.downloadTypes(name, resolved.version);
|
|
1861
|
+
const typesDir = join14(absDir, ".s2script", "types", ...name.split("/"));
|
|
1862
|
+
extractTypesTarball(tgz, typesDir);
|
|
1863
|
+
const pkgPath = join14(absDir, "package.json");
|
|
1864
|
+
const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
|
|
1865
|
+
pkg.s2script = pkg.s2script ?? {};
|
|
1866
|
+
pkg.s2script.pluginDependencies = pkg.s2script.pluginDependencies ?? {};
|
|
1867
|
+
const major = resolved.version.split(".")[0] ?? "0";
|
|
1868
|
+
pkg.s2script.pluginDependencies[name] = range.startsWith("^") || range === "*" ? `^${major}.0.0` : range.includes(".") ? range : `^${resolved.version}`;
|
|
1869
|
+
writeFileSync9(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
1870
|
+
const registryUrl = opts.registryUrl || creds?.registryUrl || defaultRegistryUrl();
|
|
1871
|
+
const npmrcLine = ensureScopeNpmrc(absDir, name, registryUrl);
|
|
1872
|
+
return {
|
|
1873
|
+
name: resolved.name,
|
|
1874
|
+
version: resolved.version,
|
|
1875
|
+
reviewState: resolved.reviewState,
|
|
1876
|
+
typesDir,
|
|
1877
|
+
npmrcLine
|
|
1878
|
+
};
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1329
1881
|
// src/cli.ts
|
|
1330
1882
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
1331
|
-
import { dirname as dirname3, join as
|
|
1883
|
+
import { dirname as dirname3, join as join15, resolve as resolve9 } from "node:path";
|
|
1332
1884
|
var argv = process.argv.slice(2);
|
|
1333
1885
|
var command = argv[0];
|
|
1334
1886
|
function repoRootFromCli() {
|
|
1335
|
-
return
|
|
1887
|
+
return join15(dirname3(fileURLToPath3(import.meta.url)), "..", "..", "..");
|
|
1336
1888
|
}
|
|
1337
1889
|
function parseFlag(args, name) {
|
|
1338
1890
|
const eq = args.find((a) => a.startsWith(`${name}=`));
|
|
@@ -1421,6 +1973,68 @@ if (command === "gen-schema") {
|
|
|
1421
1973
|
console.error(String(e instanceof Error ? e.message : e));
|
|
1422
1974
|
process.exit(1);
|
|
1423
1975
|
}
|
|
1976
|
+
} else if (command === "login") {
|
|
1977
|
+
const args = argv.slice(1);
|
|
1978
|
+
try {
|
|
1979
|
+
const creds = await loginInteractive({
|
|
1980
|
+
token: parseFlag(args, "--token"),
|
|
1981
|
+
registryUrl: parseFlag(args, "--registry") || defaultRegistryUrl(),
|
|
1982
|
+
ci: hasFlag(args, "--ci")
|
|
1983
|
+
});
|
|
1984
|
+
console.log(`logged in \u2192 ${creds.registryUrl} (credentials saved)`);
|
|
1985
|
+
} catch (e) {
|
|
1986
|
+
console.error(String(e instanceof Error ? e.message : e));
|
|
1987
|
+
process.exit(1);
|
|
1988
|
+
}
|
|
1989
|
+
} else if (command === "deploy" || command === "publish") {
|
|
1990
|
+
const args = argv.slice(1);
|
|
1991
|
+
const pos = positionals(args, ["--packages-dir", "--registry"]);
|
|
1992
|
+
const dir = pos[0] ?? ".";
|
|
1993
|
+
try {
|
|
1994
|
+
const packagesDir = resolvePackagesDir({
|
|
1995
|
+
explicit: parseFlag(args, "--packages-dir"),
|
|
1996
|
+
pluginDir: resolve9(dir),
|
|
1997
|
+
fromCliUrl: import.meta.url
|
|
1998
|
+
});
|
|
1999
|
+
const result = await deployPlugin({
|
|
2000
|
+
dir,
|
|
2001
|
+
packagesDir,
|
|
2002
|
+
registryUrl: parseFlag(args, "--registry"),
|
|
2003
|
+
ci: hasFlag(args, "--ci")
|
|
2004
|
+
});
|
|
2005
|
+
console.log(`deployed ${result.name}@${result.version} (${result.reviewState})`);
|
|
2006
|
+
if (result.reviewState === "unreviewed") {
|
|
2007
|
+
console.log("note: Not reviewed by s2script \u2014 listed with a disclaimer until approved.");
|
|
2008
|
+
}
|
|
2009
|
+
} catch (e) {
|
|
2010
|
+
console.error(String(e instanceof Error ? e.message : e));
|
|
2011
|
+
process.exit(1);
|
|
2012
|
+
}
|
|
2013
|
+
} else if (command === "add") {
|
|
2014
|
+
const args = argv.slice(1);
|
|
2015
|
+
const pos = positionals(args, ["--dir", "--registry"]);
|
|
2016
|
+
const spec = pos[0];
|
|
2017
|
+
if (!spec) {
|
|
2018
|
+
console.error("Usage: s2s add <pkg>[@range]");
|
|
2019
|
+
process.exit(1);
|
|
2020
|
+
}
|
|
2021
|
+
try {
|
|
2022
|
+
const result = await addPackage({
|
|
2023
|
+
pluginDir: parseFlag(args, "--dir") || ".",
|
|
2024
|
+
spec,
|
|
2025
|
+
registryUrl: parseFlag(args, "--registry")
|
|
2026
|
+
});
|
|
2027
|
+
console.log(`added ${result.name}@${result.version} \u2192 ${result.typesDir}`);
|
|
2028
|
+
if (result.npmrcLine) {
|
|
2029
|
+
console.log(`npmrc: ${result.npmrcLine} (types-only; npm install ${result.name} also works)`);
|
|
2030
|
+
}
|
|
2031
|
+
if (result.reviewState === "unreviewed") {
|
|
2032
|
+
console.log("note: Not reviewed by s2script \u2014 types pulled; use at your own risk.");
|
|
2033
|
+
}
|
|
2034
|
+
} catch (e) {
|
|
2035
|
+
console.error(String(e instanceof Error ? e.message : e));
|
|
2036
|
+
process.exit(1);
|
|
2037
|
+
}
|
|
1424
2038
|
} else if (command === "config" && argv[1] === "gen") {
|
|
1425
2039
|
const args = argv.slice(2);
|
|
1426
2040
|
const outDir = parseFlag(args, "--out") ?? process.cwd();
|
|
@@ -1444,7 +2058,7 @@ if (command === "gen-schema") {
|
|
|
1444
2058
|
try {
|
|
1445
2059
|
const packagesDir = resolvePackagesDir({
|
|
1446
2060
|
explicit: packagesDirFlag,
|
|
1447
|
-
pluginDir:
|
|
2061
|
+
pluginDir: resolve9(dir),
|
|
1448
2062
|
fromCliUrl: import.meta.url
|
|
1449
2063
|
});
|
|
1450
2064
|
console.log(await buildPlugin(dir, packagesDir));
|
|
@@ -1454,7 +2068,7 @@ if (command === "gen-schema") {
|
|
|
1454
2068
|
}
|
|
1455
2069
|
} else {
|
|
1456
2070
|
console.error(
|
|
1457
|
-
"Usage:\n s2s create [path] [--game cs2|none] [--name <pkg>] [--template minimal]\n [--install npm|pnpm|yarn|bun|none] [--no-install] [-y]\n s2s build <dir> [--packages-dir <path>]\n s2s config gen <plugin.s2sp...> --out <dir>\n s2s gen-schema [--check]\n s2s gen-events [--check]\n s2s gen-nav [--check]"
|
|
2071
|
+
"Usage:\n s2s create [path] [--game cs2|none] [--name <pkg>] [--template minimal]\n [--install npm|pnpm|yarn|bun|none] [--no-install] [-y]\n s2s build <dir> [--packages-dir <path>]\n s2s login [--token s2s_\u2026] [--registry <url>]\n s2s deploy [dir] [--ci] [--registry <url>] [--packages-dir <path>]\n s2s add <pkg>[@range] [--dir <plugin>] [--registry <url>]\n s2s config gen <plugin.s2sp...> --out <dir>\n s2s gen-schema [--check]\n s2s gen-events [--check]\n s2s gen-nav [--check]\n\nEnv: S2SCRIPT_REGISTRY_URL S2SCRIPT_TOKEN (CI deploy)"
|
|
1458
2072
|
);
|
|
1459
2073
|
process.exit(1);
|
|
1460
2074
|
}
|