@scm-js/cli 0.8.9 → 0.8.11
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/THIRD-PARTY-NOTICES.txt +1 -1
- package/dist/scmjs.mjs +132 -34
- package/package.json +1 -1
package/dist/scmjs.mjs
CHANGED
|
@@ -6856,13 +6856,44 @@ document.getElementById("go").onclick = go;
|
|
|
6856
6856
|
}
|
|
6857
6857
|
//#endregion
|
|
6858
6858
|
//#region cli/src/about.ts
|
|
6859
|
-
/** `scmjs about`: this build, who made it, the machine it runs on, the desktop app and Docker, and where everything is. */
|
|
6860
|
-
var VERSION = "0.8.
|
|
6861
|
-
var COMMIT = "
|
|
6859
|
+
/** `scmjs about`: this build and whether npm has a newer one, who made it, the machine it runs on, the desktop app and Docker, and where everything is. */
|
|
6860
|
+
var VERSION = "0.8.11";
|
|
6861
|
+
var COMMIT = "71d8282";
|
|
6862
6862
|
var BUILT = "2026-11-06";
|
|
6863
|
-
|
|
6863
|
+
var PACKAGE = "@scm-js/cli";
|
|
6864
|
+
/** a < b, for x.y.z versions (a prerelease or build suffix is ignored). */
|
|
6865
|
+
function olderThan(a, b) {
|
|
6866
|
+
const n = (v) => v.split(/[-+]/)[0].split(".").map((x) => Number(x) || 0);
|
|
6867
|
+
const [x, y] = [n(a), n(b)];
|
|
6868
|
+
for (let i = 0; i < 3; i++) if ((x[i] ?? 0) !== (y[i] ?? 0)) return (x[i] ?? 0) < (y[i] ?? 0);
|
|
6869
|
+
return false;
|
|
6870
|
+
}
|
|
6871
|
+
/** The newest version on npm, or null when npm could not be asked (offline, slow): two seconds at most. */
|
|
6872
|
+
async function newestVersion() {
|
|
6873
|
+
try {
|
|
6874
|
+
const res = await fetch(`https://registry.npmjs.org/${PACKAGE}/latest`, {
|
|
6875
|
+
signal: AbortSignal.timeout(2e3),
|
|
6876
|
+
headers: { accept: "application/json" }
|
|
6877
|
+
});
|
|
6878
|
+
if (!res.ok) return null;
|
|
6879
|
+
const v = (await res.json()).version;
|
|
6880
|
+
return typeof v === "string" ? v : null;
|
|
6881
|
+
} catch {
|
|
6882
|
+
return null;
|
|
6883
|
+
}
|
|
6884
|
+
}
|
|
6885
|
+
/** About's "Newest" row: the update to install, this one, or what npm has when this is newer (a build from a clone). */
|
|
6886
|
+
function newestLine(version, latest) {
|
|
6887
|
+
if (latest === null) return import_picocolors.default.dim("could not ask npm");
|
|
6888
|
+
if (olderThan(version, latest)) return `${import_picocolors.default.yellow(latest)}${dot}npm install -g ${PACKAGE}@latest`;
|
|
6889
|
+
return import_picocolors.default.dim(latest === version ? "this one" : `${latest} on npm`);
|
|
6890
|
+
}
|
|
6891
|
+
async function aboutReport() {
|
|
6892
|
+
const latest = await newestVersion();
|
|
6864
6893
|
return {
|
|
6865
6894
|
version: VERSION,
|
|
6895
|
+
latest,
|
|
6896
|
+
outdated: latest !== null && olderThan("0.8.11", latest),
|
|
6866
6897
|
commit: COMMIT,
|
|
6867
6898
|
built: BUILT,
|
|
6868
6899
|
node: process.version,
|
|
@@ -6902,6 +6933,7 @@ function printAbout(r) {
|
|
|
6902
6933
|
r.commit && `commit ${r.commit}`,
|
|
6903
6934
|
r.built && `built ${r.built}`
|
|
6904
6935
|
].filter(Boolean).join(dot)],
|
|
6936
|
+
["Newest", newestLine(r.version, r.latest)],
|
|
6905
6937
|
["Node", `${r.node}${dot}${r.platform}`],
|
|
6906
6938
|
["Desktop app", r.desktopApp ?? import_picocolors.default.dim("not found (scmjs … open uses the browser)")],
|
|
6907
6939
|
["Docker", r.docker ? `${r.docker}` : import_picocolors.default.dim("not found (scmjs bot needs it)")],
|
|
@@ -6921,6 +6953,68 @@ function printAbout(r) {
|
|
|
6921
6953
|
];
|
|
6922
6954
|
}
|
|
6923
6955
|
//#endregion
|
|
6956
|
+
//#region cli/src/banner.ts
|
|
6957
|
+
/**
|
|
6958
|
+
* The app mark, the wireframe globe of `public/favicon.svg` and the splash screen, in braille (2 × 4 dots a
|
|
6959
|
+
* character): the splash's sphere (`src/components/splash/starfield.ts`) with 6 rings and 8 segments, turned 0.3 rad
|
|
6960
|
+
* about y and −0.45 about x, its near half and its outline, on 32 × 32 dots.
|
|
6961
|
+
*/
|
|
6962
|
+
var GLOBE = [
|
|
6963
|
+
" ⣠⡴⢶⡿⣿⢿⢿⣲⢦⣄ ",
|
|
6964
|
+
" ⣠⠞⣡⠟⠯⢤⡧⠬⢷⡴⠛⠮⡳⣄ ",
|
|
6965
|
+
"⢰⢳⡼⠁ ⡞ ⢣ ⣘⣾⡆",
|
|
6966
|
+
"⡇⡼⠉⠙⠒⢦⠧⠤⠤⠤⠼⡖⠋⠁⠘⣾",
|
|
6967
|
+
"⡷⣇ ⢸ ⢧ ⣠⣿",
|
|
6968
|
+
"⠸⣏⠙⠒⠦⢼⠤⠤⠤⠤⠤⢼⠒⠋⢡⠇",
|
|
6969
|
+
" ⠙⢦⣀⡀⢸ ⣞⣀⡴⠋ ",
|
|
6970
|
+
" ⠙⠻⠭⣯⣓⣒⣫⠽⠟⠋ "
|
|
6971
|
+
];
|
|
6972
|
+
/** The same globe in ASCII, for a console without braille (the old Windows one, where ui.ts's marks fall back too). */
|
|
6973
|
+
var GLOBE_ASCII = [
|
|
6974
|
+
" .-'''-. ",
|
|
6975
|
+
" .'/ | \\'. ",
|
|
6976
|
+
" /-+---+---+-\\ ",
|
|
6977
|
+
" | | | | | ",
|
|
6978
|
+
" |==+===+===+==| ",
|
|
6979
|
+
" \\-+---+---+-/ ",
|
|
6980
|
+
" '.\\ | /.' ",
|
|
6981
|
+
" '-...-' "
|
|
6982
|
+
];
|
|
6983
|
+
/** The logo's violet (#a06cf0), or the terminal's magenta without 24-bit colour. */
|
|
6984
|
+
var violet = (s) => truecolor ? rgb("#a06cf0", s) : import_picocolors.default.magenta(s);
|
|
6985
|
+
/**
|
|
6986
|
+
* `text` beside the globe, centred on its height; the text alone when the terminal is narrower than both. `width` is
|
|
6987
|
+
* the terminal's (80 when it is not one).
|
|
6988
|
+
*/
|
|
6989
|
+
function banner(text, { width = 80, ascii = !unicode } = {}) {
|
|
6990
|
+
const art = ascii ? GLOBE_ASCII : GLOBE;
|
|
6991
|
+
const artWidth = Math.max(...art.map((l) => l.length));
|
|
6992
|
+
const indent = " ", gap = " ";
|
|
6993
|
+
if (2 + artWidth + 3 + Math.max(...text.map((l) => visible(l).length)) > width) return text;
|
|
6994
|
+
const top = Math.max(0, Math.floor((art.length - text.length) / 2));
|
|
6995
|
+
return art.map((l, i) => text[i - top] ? `${indent}${violet(l.padEnd(artWidth))}${gap}${text[i - top]}` : `${indent}${violet(l.trimEnd())}`);
|
|
6996
|
+
}
|
|
6997
|
+
/** The overview, in place of commander's: the banner, the command groups as commander lays them out, the options. */
|
|
6998
|
+
function rootHelp(cmd, helper, version, opts = {}) {
|
|
6999
|
+
const termWidth = helper.padWidth(cmd, helper);
|
|
7000
|
+
const out = banner([
|
|
7001
|
+
`${import_picocolors.default.bold(import_picocolors.default.cyan(cmd.name()))} ${import_picocolors.default.bold(version)}`,
|
|
7002
|
+
"scmJS on the command line",
|
|
7003
|
+
import_picocolors.default.dim("StarCraft: Brood War maps, replays, plugins and bots"),
|
|
7004
|
+
"",
|
|
7005
|
+
`${helper.styleTitle("Usage:")} ${helper.styleUsage(helper.commandUsage(cmd))}`,
|
|
7006
|
+
`${helper.styleTitle("Docs:")} ${import_picocolors.default.cyan("https://docs.scmjs.dev")}`
|
|
7007
|
+
], {
|
|
7008
|
+
width: helper.helpWidth ?? 80,
|
|
7009
|
+
...opts
|
|
7010
|
+
});
|
|
7011
|
+
out.push("");
|
|
7012
|
+
const groups = helper.groupItems([...cmd.commands], helper.visibleCommands(cmd), (sub) => sub.helpGroup() || "Commands:");
|
|
7013
|
+
for (const [heading, commands] of groups) out.push(...helper.formatItemList(heading, commands.map((sub) => helper.formatItem(helper.styleSubcommandTerm(helper.subcommandTerm(sub)), termWidth, helper.styleSubcommandDescription(helper.subcommandDescription(sub)), helper)), helper));
|
|
7014
|
+
out.push(`${helper.styleTitle("Options:")} ${helper.visibleOptions(cmd).map((o) => helper.styleOptionTerm(helper.optionTerm(o))).join(" ")}`, "");
|
|
7015
|
+
return out.join("\n");
|
|
7016
|
+
}
|
|
7017
|
+
//#endregion
|
|
6924
7018
|
//#region src/formats/chk/binary.ts
|
|
6925
7019
|
/** Little-endian readers/writers over a Uint8Array. StarCraft data is LE throughout. */
|
|
6926
7020
|
var Reader$1 = class {
|
|
@@ -12976,10 +13070,18 @@ async function restoreBuiltMap(chk, extras) {
|
|
|
12976
13070
|
};
|
|
12977
13071
|
}
|
|
12978
13072
|
//#endregion
|
|
12979
|
-
//#region src/
|
|
12980
|
-
/**
|
|
12981
|
-
|
|
12982
|
-
|
|
13073
|
+
//#region src/editor/mapFile.ts
|
|
13074
|
+
/**
|
|
13075
|
+
* A map file as bytes, in and out — the half of opening and saving that needs no browser.
|
|
13076
|
+
*
|
|
13077
|
+
* `openMapBytes` is what File ▸ Open, a drop, the command line and `@scm-js/core` all run:
|
|
13078
|
+
* the archive read, the scenario parsed, the other members kept, a built map restored to the
|
|
13079
|
+
* map it was built from, and the warnings the editor would show. `writeMapBytes` is the
|
|
13080
|
+
* reverse. The pickers, handles and downloads around them are `services/mapIo.ts`.
|
|
13081
|
+
*/
|
|
13082
|
+
/** Read a map file's bytes (.scm, .scx or a bare .chk) into a parsed scenario and the rest of its archive. */
|
|
13083
|
+
async function openMapBytes(bytes, fileName) {
|
|
13084
|
+
const loaded = await loadMap(bytes);
|
|
12983
13085
|
const problems = [];
|
|
12984
13086
|
let members = {
|
|
12985
13087
|
extras: /* @__PURE__ */ new Map(),
|
|
@@ -13025,8 +13127,7 @@ async function openMapFile(file, handle = null) {
|
|
|
13025
13127
|
scenario,
|
|
13026
13128
|
extras,
|
|
13027
13129
|
stored,
|
|
13028
|
-
fileName
|
|
13029
|
-
handle,
|
|
13130
|
+
fileName,
|
|
13030
13131
|
origin: loaded.scenarioInfo,
|
|
13031
13132
|
builtBy
|
|
13032
13133
|
};
|
|
@@ -13677,7 +13778,7 @@ function kindOf(name, bytes) {
|
|
|
13677
13778
|
}
|
|
13678
13779
|
async function loadMapInput(input) {
|
|
13679
13780
|
try {
|
|
13680
|
-
return await
|
|
13781
|
+
return await openMapBytes(input.bytes, input.name);
|
|
13681
13782
|
} catch (e) {
|
|
13682
13783
|
throw new CliError(`${input.name} is not a map this can read: ${e instanceof Error ? e.message : e}`);
|
|
13683
13784
|
}
|
|
@@ -16215,31 +16316,23 @@ for (const stream of [process.stdout, process.stderr]) stream.on("error", (e) =>
|
|
|
16215
16316
|
if (e.code === "EPIPE") process.exit(process.exitCode ?? 0);
|
|
16216
16317
|
else throw e;
|
|
16217
16318
|
});
|
|
16218
|
-
var program = new Command$1("scmjs").description("StarCraft: Brood War maps, replays, plugins and bots from the command line").version(VERSION, "-v, --version", "print the version").helpOption("-h, --help", "show help for a command").addOption(new Option("--no-color", "plain text, no colour (also NO_COLOR=1)")).showSuggestionAfterError().helpCommand("help [command]", "show help for a command").configureOutput({ outputError: (text, write) => write(text.replace(/^error: /, `${mark.error} `)) }).showHelpAfterError(import_picocolors.default.dim("(scmjs --help lists the commands)")).configureHelp({
|
|
16319
|
+
var program = new Command$1("scmjs").description("StarCraft: Brood War maps, replays, plugins and bots from the command line").version(VERSION, "-v, --version", "print the version").helpOption("-h, --help", "show help for a command").addOption(new Option("--no-color", "plain text, no colour (also NO_COLOR=1)")).showSuggestionAfterError().commandsGroup("This tool:").helpCommand("help [command]", "show help for a command").configureOutput({ outputError: (text, write) => write(text.replace(/^error: /, `${mark.error} `)) }).showHelpAfterError(import_picocolors.default.dim("(scmjs --help lists the commands)")).configureHelp({
|
|
16219
16320
|
styleTitle: (s) => import_picocolors.default.bold(s),
|
|
16220
16321
|
styleCommandText: (s) => import_picocolors.default.cyan(s),
|
|
16221
16322
|
styleSubcommandText: (s) => import_picocolors.default.cyan(s),
|
|
16222
16323
|
styleArgumentText: (s) => import_picocolors.default.yellow(s),
|
|
16223
|
-
styleOptionText: (s) => import_picocolors.default.green(s)
|
|
16224
|
-
|
|
16225
|
-
|
|
16226
|
-
|
|
16227
|
-
|
|
16228
|
-
|
|
16229
|
-
${import_picocolors.default.dim("$")} scmjs plugin new "Base Check" a plugin for the editor to start from
|
|
16230
|
-
${import_picocolors.default.dim("$")} scmjs bot new a BWAPI bot to start from, for scmjs.dev/play
|
|
16231
|
-
${import_picocolors.default.dim("$")} scmjs about this version, and where things are
|
|
16232
|
-
|
|
16233
|
-
${import_picocolors.default.dim("Docs: https://docs.scmjs.dev")}`);
|
|
16234
|
-
program.command("about").description("this version of scmjs, and where things are").addOption(json).action((o) => {
|
|
16235
|
-
const r = aboutReport();
|
|
16236
|
-
print(...o.json ? [JSON.stringify(r, null, 2)] : printAbout(r));
|
|
16324
|
+
styleOptionText: (s) => import_picocolors.default.green(s),
|
|
16325
|
+
subcommandTerm: (cmd) => {
|
|
16326
|
+
const subs = cmd.commands.map((c) => c.name()).filter((n) => n !== "help");
|
|
16327
|
+
return subs.length ? `${cmd.name()} ${subs.join("|")}` : new Help$1().subcommandTerm(cmd).replace(" [options]", "");
|
|
16328
|
+
},
|
|
16329
|
+
formatHelp: (cmd, helper) => cmd.parent ? Help$1.prototype.formatHelp.call(helper, cmd, helper) : rootHelp(cmd, helper, VERSION)
|
|
16237
16330
|
});
|
|
16238
|
-
program.command("info").description("what a map, replay or bot file holds").argument("<files...>", "maps (.scm .scx .chk), replays (.rep) or bots (.scmbot)").addOption(json).action(async (files, o) => void await eachFile(files, !!o.json, async (input) => {
|
|
16331
|
+
program.command("info").description("what a map, replay or bot file holds").helpGroup("Maps and replays:").argument("<files...>", "maps (.scm .scx .chk), replays (.rep) or bots (.scmbot)").addOption(json).action(async (files, o) => void await eachFile(files, !!o.json, async (input) => {
|
|
16239
16332
|
const kind = expectKind(input, "map", "replay", "bot");
|
|
16240
16333
|
return kind === "map" ? mapInfo(input) : kind === "replay" ? replayInfo(input) : botInfo(input);
|
|
16241
16334
|
}));
|
|
16242
|
-
var map = program.command("map").description("read, check and open maps");
|
|
16335
|
+
var map = program.command("map").description("read, check and open maps").helpGroup("Maps and replays:");
|
|
16243
16336
|
map.command("info").description("a map's size, players, units, triggers and its check").argument("<maps...>").addOption(json).action(async (files, o) => void await eachFile(files, !!o.json, mapInfo));
|
|
16244
16337
|
map.command("check").description("Check Map, as the editor's: errors, then warnings").argument("<maps...>").addOption(json).option("--all", "list notes too, not only errors and warnings").option("--strict", "fail on warnings as well as errors").addHelpText("after", `\nExits 1 when a map has errors (or warnings, with --strict): fit for a CI step.`).action(async (files, o) => {
|
|
16245
16338
|
const reports = await eachFile(files, !!o.json, async (input) => {
|
|
@@ -16266,14 +16359,14 @@ map.command("open").description("open a map in the scmJS app, or the editor in y
|
|
|
16266
16359
|
expectKind(input, "map");
|
|
16267
16360
|
await openInScmjs(input, o);
|
|
16268
16361
|
});
|
|
16269
|
-
var replay = program.command("replay").description("read and open replays");
|
|
16362
|
+
var replay = program.command("replay").description("read and open replays").helpGroup("Maps and replays:");
|
|
16270
16363
|
replay.command("info").description("who played, on what map, for how long, at what APM").argument("<replays...>").addOption(json).action(async (files, o) => void await eachFile(files, !!o.json, replayInfo));
|
|
16271
16364
|
replay.command("open").description("watch a replay in the scmJS app, or the editor in your browser").argument("<replay>").option("--browser", "the editor in the browser, even with the app installed").addOption(new Option("--editor <url>", "the editor's address (SCMJS_EDITOR_URL)").hideHelp()).action(async (file, o) => {
|
|
16272
16365
|
const input = await readInput(file);
|
|
16273
16366
|
expectKind(input, "replay");
|
|
16274
16367
|
await openInScmjs(input, o);
|
|
16275
16368
|
});
|
|
16276
|
-
var plugin = program.command("plugin").description("
|
|
16369
|
+
var plugin = program.command("plugin").description("start, check and run a plugin for the editor").helpGroup("Plugins and bots:");
|
|
16277
16370
|
plugin.command("new").description("a plugin to start from, in a new folder").argument("[name]", "the plugin's name, such as \"Base Check\"").option("--dir <folder>", "the folder to write it in (default plugin-<id>)").action((name, o) => {
|
|
16278
16371
|
const made = newPlugin({
|
|
16279
16372
|
name,
|
|
@@ -16297,7 +16390,7 @@ plugin.command("dev").description("the editor with your plugin, reloaded on ever
|
|
|
16297
16390
|
editor: o.editor
|
|
16298
16391
|
});
|
|
16299
16392
|
});
|
|
16300
|
-
var bot = program.command("bot").description("build
|
|
16393
|
+
var bot = program.command("bot").description("build a BWAPI bot for scmjs.dev/play (Docker)").helpGroup("Plugins and bots:").addHelpText("after", () => `
|
|
16301
16394
|
Run these in your bot's folder. The toolchain is a Docker image, downloaded the first time.
|
|
16302
16395
|
A built bot is added on the play page: Options → Community bots → Add a bot from a file…
|
|
16303
16396
|
|
|
@@ -16314,8 +16407,13 @@ bot.command("build").description("out/<key>.scmbot, to add on the play page").ac
|
|
|
16314
16407
|
bot.command("update").description("the newest toolchain, the one the play page expects").action(async () => {
|
|
16315
16408
|
await updateBot();
|
|
16316
16409
|
});
|
|
16317
|
-
|
|
16318
|
-
await
|
|
16410
|
+
program.command("about").description("this version, any newer one, and links").helpGroup("This tool:").addOption(json).action(async (o) => {
|
|
16411
|
+
const r = await aboutReport();
|
|
16412
|
+
print(...o.json ? [JSON.stringify(r, null, 2)] : printAbout(r));
|
|
16413
|
+
});
|
|
16414
|
+
if (!process.env.VITEST) try {
|
|
16415
|
+
if (process.argv.length <= 2) program.outputHelp();
|
|
16416
|
+
else await program.parseAsync();
|
|
16319
16417
|
} catch (e) {
|
|
16320
16418
|
if (e instanceof CliError) {
|
|
16321
16419
|
fail(e);
|
|
@@ -16326,4 +16424,4 @@ try {
|
|
|
16326
16424
|
}
|
|
16327
16425
|
}
|
|
16328
16426
|
//#endregion
|
|
16329
|
-
export {};
|
|
16427
|
+
export { program };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scm-js/cli",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.11",
|
|
4
4
|
"description": "StarCraft: Brood War maps, replays and bots from the command line: what a map or replay holds, Check Map, opening them in scmJS, and building a BWAPI bot for the play page",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"starcraft",
|