@patchgg/cli 0.1.3 → 0.3.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/README.md +21 -30
- package/dist/cli.js +1212 -800
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -130,11 +130,17 @@ function exitCodeFor(status2) {
|
|
|
130
130
|
return EXIT.error;
|
|
131
131
|
}
|
|
132
132
|
function notSignedIn() {
|
|
133
|
-
|
|
133
|
+
const failure2 = new CliError(
|
|
134
134
|
EXIT.auth,
|
|
135
135
|
"not_signed_in",
|
|
136
136
|
"You're not signed in. Run pch login, or set PCH_TOKEN to an API key."
|
|
137
137
|
);
|
|
138
|
+
failure2.forPeople = {
|
|
139
|
+
title: "You're not signed in",
|
|
140
|
+
next: [["pch login", "sign in, in your browser"]],
|
|
141
|
+
also: "For an agent or CI, set PCH_TOKEN to an API key instead."
|
|
142
|
+
};
|
|
143
|
+
return failure2;
|
|
138
144
|
}
|
|
139
145
|
var EXIT, CliError;
|
|
140
146
|
var init_errors = __esm({
|
|
@@ -167,6 +173,11 @@ var init_errors = __esm({
|
|
|
167
173
|
details;
|
|
168
174
|
/** Patch's API said it, rather than pch itself. */
|
|
169
175
|
fromApi;
|
|
176
|
+
/**
|
|
177
|
+
* What a person at a terminal sees instead of the message, when that's shorter: a line, and a
|
|
178
|
+
* command or two that fix it. Agents and --json always get the whole message.
|
|
179
|
+
*/
|
|
180
|
+
forPeople;
|
|
170
181
|
};
|
|
171
182
|
}
|
|
172
183
|
});
|
|
@@ -176,17 +187,17 @@ var VERSION;
|
|
|
176
187
|
var init_version = __esm({
|
|
177
188
|
"src/version.ts"() {
|
|
178
189
|
"use strict";
|
|
179
|
-
VERSION = true ? "0.
|
|
190
|
+
VERSION = true ? "0.3.0" : JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
180
191
|
}
|
|
181
192
|
});
|
|
182
193
|
|
|
183
194
|
// src/guide-words.ts
|
|
184
|
-
function openCodeWords(name, repo,
|
|
195
|
+
function openCodeWords(name, repo, release3) {
|
|
185
196
|
if (repo?.visibility !== "open") return null;
|
|
186
|
-
if (
|
|
187
|
-
const seen =
|
|
197
|
+
if (release3.status === "yanked" || release3.status === "quarantined") return null;
|
|
198
|
+
const seen = release3.channel === "private" ? "private" : release3.status === "published" ? release3.channel : "private until it's published";
|
|
188
199
|
if (seen === "public") return null;
|
|
189
|
-
const what =
|
|
200
|
+
const what = release3.kind === "mod" ? "mod" : release3.kind === "block" ? "block" : "game";
|
|
190
201
|
const start = `The ${what} is ${seen}, but its code isn't:`;
|
|
191
202
|
const pass = "Say so when you tell anyone who can see it.";
|
|
192
203
|
if (repo.home === "github") {
|
|
@@ -236,13 +247,13 @@ git add -A && git commit -m "..."
|
|
|
236
247
|
${pch} publish
|
|
237
248
|
\`\`\`
|
|
238
249
|
|
|
239
|
-
|
|
250
|
+
That's the whole flow: it uploads, tests a browser game, a mod Patch installs or a block's demo on your PC in Patch Desktop, saves the footage, and stops for your review of it. Look at every sheet, then send the review as it says:
|
|
240
251
|
|
|
241
252
|
\`\`\`sh
|
|
242
|
-
${pch}
|
|
253
|
+
${pch} publish <you>/<game>@<version> --review pass --summary "<what you saw>" --moment "1 00:04 <what you saw>" --moment "1 00:30 <what you saw>"
|
|
243
254
|
\`\`\`
|
|
244
255
|
|
|
245
|
-
|
|
256
|
+
That sends it to Patch's review and follows it until it's live. Running \`${pch} publish\` is your yes, so run it once the person wants it out (\`${pch} test start\` tries a build without publishing it). A passed test shows it starts and runs, not that it plays well, so play it yourself before its first public release.
|
|
246
257
|
|
|
247
258
|
## 4. Its listing
|
|
248
259
|
|
|
@@ -293,6 +304,7 @@ var init_guide_words = __esm({
|
|
|
293
304
|
["search_assets, asset_facets, get_asset", "assets search, assets facets, assets view"],
|
|
294
305
|
["get_asset_files", "assets add"],
|
|
295
306
|
["validate_recipe", "validate"],
|
|
307
|
+
["publish", "publish, then publish <release> --review \u2026"],
|
|
296
308
|
["create_release, complete_upload", "publish, from a folder with a patch.json"],
|
|
297
309
|
["submit_release", "publish, or release submit"],
|
|
298
310
|
["list_releases, release_status", "release list, status"],
|
|
@@ -654,8 +666,8 @@ var require_semver = __commonJS({
|
|
|
654
666
|
}
|
|
655
667
|
// preminor will bump the version up to the next minor release, and immediately
|
|
656
668
|
// down to pre-release. premajor and prepatch work the same way.
|
|
657
|
-
inc(
|
|
658
|
-
if (
|
|
669
|
+
inc(release3, identifier, identifierBase) {
|
|
670
|
+
if (release3.startsWith("pre")) {
|
|
659
671
|
if (!identifier && identifierBase === false) {
|
|
660
672
|
throw new Error("invalid increment argument: identifier is empty");
|
|
661
673
|
}
|
|
@@ -666,7 +678,7 @@ var require_semver = __commonJS({
|
|
|
666
678
|
}
|
|
667
679
|
}
|
|
668
680
|
}
|
|
669
|
-
switch (
|
|
681
|
+
switch (release3) {
|
|
670
682
|
case "premajor":
|
|
671
683
|
this.prerelease.length = 0;
|
|
672
684
|
this.patch = 0;
|
|
@@ -758,7 +770,7 @@ var require_semver = __commonJS({
|
|
|
758
770
|
break;
|
|
759
771
|
}
|
|
760
772
|
default:
|
|
761
|
-
throw new Error(`invalid increment argument: ${
|
|
773
|
+
throw new Error(`invalid increment argument: ${release3}`);
|
|
762
774
|
}
|
|
763
775
|
this.raw = this.format();
|
|
764
776
|
if (this.build.length) {
|
|
@@ -824,7 +836,7 @@ var require_inc = __commonJS({
|
|
|
824
836
|
"node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/inc.js"(exports, module) {
|
|
825
837
|
"use strict";
|
|
826
838
|
var SemVer = require_semver();
|
|
827
|
-
var inc = (version2,
|
|
839
|
+
var inc = (version2, release3, options, identifier, identifierBase) => {
|
|
828
840
|
if (typeof options === "string") {
|
|
829
841
|
identifierBase = identifier;
|
|
830
842
|
identifier = options;
|
|
@@ -834,7 +846,7 @@ var require_inc = __commonJS({
|
|
|
834
846
|
return new SemVer(
|
|
835
847
|
version2 instanceof SemVer ? version2.version : version2,
|
|
836
848
|
options
|
|
837
|
-
).inc(
|
|
849
|
+
).inc(release3, identifier, identifierBase).version;
|
|
838
850
|
} catch (er) {
|
|
839
851
|
return null;
|
|
840
852
|
}
|
|
@@ -6836,7 +6848,7 @@ function isRef(value) {
|
|
|
6836
6848
|
function cloneIssues(issues) {
|
|
6837
6849
|
return issues.map((iss) => iss.path ? { ...iss, path: iss.path.slice() } : { ...iss });
|
|
6838
6850
|
}
|
|
6839
|
-
function isRecursive(inst, stack,
|
|
6851
|
+
function isRecursive(inst, stack, resolve17) {
|
|
6840
6852
|
const cached2 = recursive.get(inst);
|
|
6841
6853
|
if (cached2 !== void 0)
|
|
6842
6854
|
return cached2 ? PROVEN : NONE;
|
|
@@ -6846,7 +6858,7 @@ function isRecursive(inst, stack, resolve16) {
|
|
|
6846
6858
|
let result = NONE;
|
|
6847
6859
|
const check3 = (child) => {
|
|
6848
6860
|
if (result !== PROVEN && child?._zod) {
|
|
6849
|
-
const answer = isRecursive(child, stack,
|
|
6861
|
+
const answer = isRecursive(child, stack, resolve17);
|
|
6850
6862
|
if (answer > result)
|
|
6851
6863
|
result = answer;
|
|
6852
6864
|
}
|
|
@@ -6857,7 +6869,7 @@ function isRecursive(inst, stack, resolve16) {
|
|
|
6857
6869
|
const desc = Object.getOwnPropertyDescriptor(sh, key);
|
|
6858
6870
|
if (spread && !desc.enumerable)
|
|
6859
6871
|
continue;
|
|
6860
|
-
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack,
|
|
6872
|
+
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve17) : NONE;
|
|
6861
6873
|
if (child > answer)
|
|
6862
6874
|
answer = child;
|
|
6863
6875
|
}
|
|
@@ -6921,7 +6933,7 @@ function isRecursive(inst, stack, resolve16) {
|
|
|
6921
6933
|
break;
|
|
6922
6934
|
// `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
|
|
6923
6935
|
case "lazy": {
|
|
6924
|
-
const inner = def._cachedInner ?? (
|
|
6936
|
+
const inner = def._cachedInner ?? (resolve17 ? inst._zod.innerType : void 0);
|
|
6925
6937
|
merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
|
|
6926
6938
|
break;
|
|
6927
6939
|
}
|
|
@@ -25289,7 +25301,7 @@ var init_protocol = __esm({
|
|
|
25289
25301
|
return;
|
|
25290
25302
|
}
|
|
25291
25303
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
25292
|
-
await new Promise((
|
|
25304
|
+
await new Promise((resolve17) => setTimeout(resolve17, pollInterval));
|
|
25293
25305
|
options?.signal?.throwIfAborted();
|
|
25294
25306
|
}
|
|
25295
25307
|
} catch (error62) {
|
|
@@ -25306,7 +25318,7 @@ var init_protocol = __esm({
|
|
|
25306
25318
|
*/
|
|
25307
25319
|
request(request, resultSchema, options) {
|
|
25308
25320
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
25309
|
-
return new Promise((
|
|
25321
|
+
return new Promise((resolve17, reject) => {
|
|
25310
25322
|
const earlyReject = (error62) => {
|
|
25311
25323
|
reject(error62);
|
|
25312
25324
|
};
|
|
@@ -25384,7 +25396,7 @@ var init_protocol = __esm({
|
|
|
25384
25396
|
if (!parseResult.success) {
|
|
25385
25397
|
reject(parseResult.error);
|
|
25386
25398
|
} else {
|
|
25387
|
-
|
|
25399
|
+
resolve17(parseResult.data);
|
|
25388
25400
|
}
|
|
25389
25401
|
} catch (error62) {
|
|
25390
25402
|
reject(error62);
|
|
@@ -25646,12 +25658,12 @@ var init_protocol = __esm({
|
|
|
25646
25658
|
}
|
|
25647
25659
|
} catch {
|
|
25648
25660
|
}
|
|
25649
|
-
return new Promise((
|
|
25661
|
+
return new Promise((resolve17, reject) => {
|
|
25650
25662
|
if (signal.aborted) {
|
|
25651
25663
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
25652
25664
|
return;
|
|
25653
25665
|
}
|
|
25654
|
-
const timeoutId = setTimeout(
|
|
25666
|
+
const timeoutId = setTimeout(resolve17, interval);
|
|
25655
25667
|
signal.addEventListener("abort", () => {
|
|
25656
25668
|
clearTimeout(timeoutId);
|
|
25657
25669
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -28678,7 +28690,7 @@ var require_compile = __commonJS({
|
|
|
28678
28690
|
const schOrFunc = root.refs[ref];
|
|
28679
28691
|
if (schOrFunc)
|
|
28680
28692
|
return schOrFunc;
|
|
28681
|
-
let _sch =
|
|
28693
|
+
let _sch = resolve17.call(this, root, ref);
|
|
28682
28694
|
if (_sch === void 0) {
|
|
28683
28695
|
const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
|
|
28684
28696
|
const { schemaId } = this.opts;
|
|
@@ -28705,7 +28717,7 @@ var require_compile = __commonJS({
|
|
|
28705
28717
|
function sameSchemaEnv(s1, s2) {
|
|
28706
28718
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
28707
28719
|
}
|
|
28708
|
-
function
|
|
28720
|
+
function resolve17(root, ref) {
|
|
28709
28721
|
let sch;
|
|
28710
28722
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
28711
28723
|
ref = sch;
|
|
@@ -29569,7 +29581,7 @@ var require_fast_uri = __commonJS({
|
|
|
29569
29581
|
}
|
|
29570
29582
|
return uri;
|
|
29571
29583
|
}
|
|
29572
|
-
function
|
|
29584
|
+
function resolve17(baseURI, relativeURI, options) {
|
|
29573
29585
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
29574
29586
|
const {
|
|
29575
29587
|
parsed: baseParsed,
|
|
@@ -29960,7 +29972,7 @@ var require_fast_uri = __commonJS({
|
|
|
29960
29972
|
var fastUri = {
|
|
29961
29973
|
SCHEMES,
|
|
29962
29974
|
normalize,
|
|
29963
|
-
resolve:
|
|
29975
|
+
resolve: resolve17,
|
|
29964
29976
|
resolveComponent,
|
|
29965
29977
|
equal,
|
|
29966
29978
|
serialize,
|
|
@@ -35633,12 +35645,12 @@ var init_stdio2 = __esm({
|
|
|
35633
35645
|
this.onclose?.();
|
|
35634
35646
|
}
|
|
35635
35647
|
send(message) {
|
|
35636
|
-
return new Promise((
|
|
35648
|
+
return new Promise((resolve17) => {
|
|
35637
35649
|
const json2 = serializeMessage(message);
|
|
35638
35650
|
if (this._stdout.write(json2)) {
|
|
35639
|
-
|
|
35651
|
+
resolve17();
|
|
35640
35652
|
} else {
|
|
35641
|
-
this._stdout.once("drain",
|
|
35653
|
+
this._stdout.once("drain", resolve17);
|
|
35642
35654
|
}
|
|
35643
35655
|
});
|
|
35644
35656
|
}
|
|
@@ -35657,8 +35669,8 @@ async function mcp(ctx, streams = {}) {
|
|
|
35657
35669
|
const remote = patchServer(ctx, "start pch mcp again");
|
|
35658
35670
|
const stdin = streams.stdin ?? process.stdin;
|
|
35659
35671
|
const local = new StdioServerTransport(stdin, streams.stdout ?? takeStdout());
|
|
35660
|
-
const hungUp = new Promise((
|
|
35661
|
-
local.onclose =
|
|
35672
|
+
const hungUp = new Promise((resolve17) => {
|
|
35673
|
+
local.onclose = resolve17;
|
|
35662
35674
|
});
|
|
35663
35675
|
stdin.once("end", () => void local.close());
|
|
35664
35676
|
let landed = Promise.resolve();
|
|
@@ -35755,7 +35767,9 @@ var init_mcp = __esm({
|
|
|
35755
35767
|
});
|
|
35756
35768
|
|
|
35757
35769
|
// src/plain-when-piped.ts
|
|
35758
|
-
if (!process.stdout.isTTY
|
|
35770
|
+
if ((!process.stdout.isTTY || process.env.TERM === "dumb") && !process.env.FORCE_COLOR) {
|
|
35771
|
+
process.env.NO_COLOR = "1";
|
|
35772
|
+
}
|
|
35759
35773
|
|
|
35760
35774
|
// node_modules/.pnpm/commander@15.0.0/node_modules/commander/lib/error.js
|
|
35761
35775
|
var CommanderError = class extends Error {
|
|
@@ -36330,13 +36344,13 @@ var Help = class {
|
|
|
36330
36344
|
(sub) => sub.helpGroup() || "Commands:"
|
|
36331
36345
|
);
|
|
36332
36346
|
commandGroups.forEach((commands, group) => {
|
|
36333
|
-
const
|
|
36347
|
+
const commandList2 = commands.map((sub) => {
|
|
36334
36348
|
return callFormatItem(
|
|
36335
36349
|
helper.styleSubcommandTerm(helper.subcommandTerm(sub)),
|
|
36336
36350
|
helper.styleSubcommandDescription(helper.subcommandDescription(sub))
|
|
36337
36351
|
);
|
|
36338
36352
|
});
|
|
36339
|
-
output2 = output2.concat(this.formatItemList(group,
|
|
36353
|
+
output2 = output2.concat(this.formatItemList(group, commandList2, helper));
|
|
36340
36354
|
});
|
|
36341
36355
|
return output2.join("\n");
|
|
36342
36356
|
}
|
|
@@ -36490,10 +36504,10 @@ ${itemIndentStr}`);
|
|
|
36490
36504
|
let sumChunks = [chunks.shift()];
|
|
36491
36505
|
let sumWidth = this.displayWidth(sumChunks[0]);
|
|
36492
36506
|
chunks.forEach((chunk) => {
|
|
36493
|
-
const
|
|
36494
|
-
if (sumWidth +
|
|
36507
|
+
const visibleWidth2 = this.displayWidth(chunk);
|
|
36508
|
+
if (sumWidth + visibleWidth2 <= width) {
|
|
36495
36509
|
sumChunks.push(chunk);
|
|
36496
|
-
sumWidth +=
|
|
36510
|
+
sumWidth += visibleWidth2;
|
|
36497
36511
|
return;
|
|
36498
36512
|
}
|
|
36499
36513
|
wrappedLines.push(sumChunks.join(""));
|
|
@@ -39126,7 +39140,7 @@ function useColor() {
|
|
|
39126
39140
|
var program = new Command();
|
|
39127
39141
|
|
|
39128
39142
|
// src/commands/account.ts
|
|
39129
|
-
var
|
|
39143
|
+
var import_picocolors4 = __toESM(require_picocolors(), 1);
|
|
39130
39144
|
|
|
39131
39145
|
// node_modules/.pnpm/openapi-fetch@0.17.0/node_modules/openapi-fetch/dist/index.mjs
|
|
39132
39146
|
var PATH_PARAM_RE = /\{[^{}]+\}/g;
|
|
@@ -39970,20 +39984,188 @@ var LFS_FLAGS = [
|
|
|
39970
39984
|
];
|
|
39971
39985
|
var NO_LFS = "Git LFS isn't installed, so big files stay small pointer files. Install it from https://git-lfs.com, then run git lfs install.";
|
|
39972
39986
|
|
|
39987
|
+
// src/commands/account.ts
|
|
39988
|
+
init_guide_words();
|
|
39989
|
+
|
|
39990
|
+
// src/terminal.ts
|
|
39991
|
+
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
39992
|
+
import { spawnSync } from "node:child_process";
|
|
39993
|
+
import { release } from "node:os";
|
|
39994
|
+
function canRedraw() {
|
|
39995
|
+
return Boolean(process.stderr.isTTY) && process.env.TERM !== "dumb";
|
|
39996
|
+
}
|
|
39997
|
+
function unicode(vars = process.env, platform = process.platform) {
|
|
39998
|
+
if (platform !== "win32") return vars.TERM !== "linux";
|
|
39999
|
+
return Boolean(
|
|
40000
|
+
vars.WT_SESSION || vars.TERMINUS_SUBLIME || vars.ConEmuTask === "{cmd::Cmder}" || vars.TERM_PROGRAM === "vscode" || vars.TERM_PROGRAM === "mintty" || vars.TERM === "xterm-256color" || vars.TERM === "alacritty" || vars.TERMINAL_EMULATOR === "JetBrains-JediTerm"
|
|
40001
|
+
);
|
|
40002
|
+
}
|
|
40003
|
+
function colourDepth(vars = process.env, platform = process.platform) {
|
|
40004
|
+
if (!import_picocolors.default.isColorSupported || vars.TERM === "dumb") return 0;
|
|
40005
|
+
if (vars.COLORTERM === "truecolor" || vars.COLORTERM === "24bit") return 3;
|
|
40006
|
+
if (vars.WT_SESSION || vars.TERM_PROGRAM === "mintty" || vars.TERM_PROGRAM === "vscode") return 3;
|
|
40007
|
+
if (platform === "win32" && Number(release().split(".")[2]) >= 14931) return 3;
|
|
40008
|
+
if (vars.TERM?.includes("256")) return 2;
|
|
40009
|
+
return 1;
|
|
40010
|
+
}
|
|
40011
|
+
function pink(text, depth = colourDepth()) {
|
|
40012
|
+
if (depth === 3) return `\x1B[38;2;245;60;209m${text}\x1B[39m`;
|
|
40013
|
+
if (depth === 2) return `\x1B[38;5;206m${text}\x1B[39m`;
|
|
40014
|
+
return depth === 1 ? import_picocolors.default.magenta(text) : text;
|
|
40015
|
+
}
|
|
40016
|
+
function copyText(text, platform = process.platform) {
|
|
40017
|
+
const tools = platform === "win32" ? [["clip", []]] : platform === "darwin" ? [["pbcopy", []]] : [
|
|
40018
|
+
["wl-copy", []],
|
|
40019
|
+
["xclip", ["-selection", "clipboard"]],
|
|
40020
|
+
["xsel", ["--clipboard", "--input"]]
|
|
40021
|
+
];
|
|
40022
|
+
for (const [command, args2] of tools) {
|
|
40023
|
+
try {
|
|
40024
|
+
const done = spawnSync(command, args2, {
|
|
40025
|
+
input: text,
|
|
40026
|
+
stdio: ["pipe", "ignore", "ignore"],
|
|
40027
|
+
timeout: 2e3
|
|
40028
|
+
});
|
|
40029
|
+
if (done.status === 0) return true;
|
|
40030
|
+
} catch {
|
|
40031
|
+
}
|
|
40032
|
+
}
|
|
40033
|
+
return false;
|
|
40034
|
+
}
|
|
40035
|
+
var symbols = () => unicode() ? { done: "\u2713", failed: "\xD7", note: "!" } : { done: "\u221A", failed: "\xD7", note: "!" };
|
|
40036
|
+
var FRAMES = {
|
|
40037
|
+
unicode: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"],
|
|
40038
|
+
plain: ["|", "/", "-", "\\"]
|
|
40039
|
+
};
|
|
40040
|
+
function spinner(text, hint = "") {
|
|
40041
|
+
if (!canRedraw()) {
|
|
40042
|
+
process.stderr.write(` ${text}
|
|
40043
|
+
`);
|
|
40044
|
+
return { stop() {
|
|
40045
|
+
}, say(_) {
|
|
40046
|
+
} };
|
|
40047
|
+
}
|
|
40048
|
+
const frames = unicode() ? FRAMES.unicode : FRAMES.plain;
|
|
40049
|
+
let frame = 0;
|
|
40050
|
+
const draw = () => {
|
|
40051
|
+
const room = (process.stderr.columns || 80) - 5;
|
|
40052
|
+
const said2 = text.length > room ? `${text.slice(0, Math.max(0, room - 1))}\u2026` : text;
|
|
40053
|
+
const more = hint && said2.length + 2 + hint.length <= room ? ` ${import_picocolors.default.dim(hint)}` : "";
|
|
40054
|
+
const spin = frames[frame++ % frames.length];
|
|
40055
|
+
process.stderr.write(`\r\x1B[K ${spin} ${said2}${more}`);
|
|
40056
|
+
};
|
|
40057
|
+
const clear = () => process.stderr.write("\r\x1B[K\x1B[?25h");
|
|
40058
|
+
const cancel = () => {
|
|
40059
|
+
clear();
|
|
40060
|
+
process.stderr.write(` ${import_picocolors.default.dim("Cancelled.")}
|
|
40061
|
+
`);
|
|
40062
|
+
process.exit(130);
|
|
40063
|
+
};
|
|
40064
|
+
process.stderr.write("\x1B[?25l");
|
|
40065
|
+
draw();
|
|
40066
|
+
const timer = setInterval(draw, 80);
|
|
40067
|
+
process.once("SIGINT", cancel);
|
|
40068
|
+
let stopped = false;
|
|
40069
|
+
return {
|
|
40070
|
+
stop() {
|
|
40071
|
+
if (stopped) return;
|
|
40072
|
+
stopped = true;
|
|
40073
|
+
clearInterval(timer);
|
|
40074
|
+
process.off("SIGINT", cancel);
|
|
40075
|
+
clear();
|
|
40076
|
+
},
|
|
40077
|
+
/** Says something else from now on, such as the next thing it's waiting for. */
|
|
40078
|
+
say(next) {
|
|
40079
|
+
text = next;
|
|
40080
|
+
hint = "";
|
|
40081
|
+
draw();
|
|
40082
|
+
}
|
|
40083
|
+
};
|
|
40084
|
+
}
|
|
40085
|
+
var Block = class {
|
|
40086
|
+
lines = [];
|
|
40087
|
+
write(line = "") {
|
|
40088
|
+
this.lines.push(line);
|
|
40089
|
+
process.stderr.write(`${line}
|
|
40090
|
+
`);
|
|
40091
|
+
}
|
|
40092
|
+
/** Takes the lines back off the screen, counting the rows the window wrapped them onto. */
|
|
40093
|
+
erase() {
|
|
40094
|
+
const columns = process.stderr.columns;
|
|
40095
|
+
if (!canRedraw() || !columns || !this.lines.length) return;
|
|
40096
|
+
const rows = this.lines.reduce(
|
|
40097
|
+
(sum, line) => sum + Math.max(1, Math.ceil(visibleWidth(line) / columns)),
|
|
40098
|
+
0
|
|
40099
|
+
);
|
|
40100
|
+
process.stderr.write(`\x1B[${rows}A\r\x1B[J`);
|
|
40101
|
+
this.lines = [];
|
|
40102
|
+
}
|
|
40103
|
+
};
|
|
40104
|
+
function commandList(rows, indent = " ", columns = process.stdout.columns ?? 80) {
|
|
40105
|
+
const wide = Math.max(...rows.map(([command]) => command.length));
|
|
40106
|
+
const fits = indent.length + wide + 3 + Math.max(...rows.map(([, words2]) => words2.length));
|
|
40107
|
+
return rows.map(
|
|
40108
|
+
([command, words2]) => fits <= columns ? `${indent}${import_picocolors.default.bold(command.padEnd(wide))} ${import_picocolors.default.dim(words2)}` : `${indent}${import_picocolors.default.bold(command)}
|
|
40109
|
+
${indent} ${import_picocolors.default.dim(words2)}`
|
|
40110
|
+
);
|
|
40111
|
+
}
|
|
40112
|
+
function placeOf(origin) {
|
|
40113
|
+
try {
|
|
40114
|
+
return new URL(origin).host;
|
|
40115
|
+
} catch {
|
|
40116
|
+
return origin;
|
|
40117
|
+
}
|
|
40118
|
+
}
|
|
40119
|
+
var ESCAPES = /\x1b\[[0-9;?]*[A-Za-z]/g;
|
|
40120
|
+
function unstyled(text) {
|
|
40121
|
+
return text.replace(ESCAPES, "");
|
|
40122
|
+
}
|
|
40123
|
+
function visibleWidth(text) {
|
|
40124
|
+
return [...unstyled(text)].length;
|
|
40125
|
+
}
|
|
40126
|
+
|
|
39973
40127
|
// src/commands/login.ts
|
|
39974
|
-
var
|
|
40128
|
+
var import_picocolors3 = __toESM(require_picocolors(), 1);
|
|
39975
40129
|
import { spawn as spawn2 } from "node:child_process";
|
|
39976
40130
|
import { hostname } from "node:os";
|
|
39977
40131
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
39978
40132
|
init_errors();
|
|
39979
40133
|
init_guide_words();
|
|
39980
40134
|
|
|
40135
|
+
// src/logo.ts
|
|
40136
|
+
var ROWS = [
|
|
40137
|
+
".....................#.........#....",
|
|
40138
|
+
"####.................#.........#....",
|
|
40139
|
+
"#...#...####..####..####.#####.####.",
|
|
40140
|
+
"#.+.#...#...#.....#..#...#.....#...#",
|
|
40141
|
+
"#...#...#...#.#####..#...#.....#...#",
|
|
40142
|
+
"#####...#...#.#...#..#...#.....#...#",
|
|
40143
|
+
"........#####.#####..###.#####.#...#",
|
|
40144
|
+
"........#...........................",
|
|
40145
|
+
"........#..........................."
|
|
40146
|
+
];
|
|
40147
|
+
var WIDTH = ROWS[0]?.length ?? 0;
|
|
40148
|
+
function logo(columns = process.stdout.columns ?? 80, depth = colourDepth()) {
|
|
40149
|
+
if (!canRedraw() || !unicode() || columns < WIDTH + 4) return null;
|
|
40150
|
+
const lines = [];
|
|
40151
|
+
for (let y = 0; y < ROWS.length; y += 2) {
|
|
40152
|
+
const top = ROWS[y] ?? "";
|
|
40153
|
+
const bottom = ROWS[y + 1] ?? "";
|
|
40154
|
+
let line = "";
|
|
40155
|
+
for (let x = 0; x < WIDTH; x++) {
|
|
40156
|
+
const up = top[x] ?? ".";
|
|
40157
|
+
const down = bottom[x] ?? ".";
|
|
40158
|
+
const glyph = up !== "." ? down !== "." ? "\u2588" : "\u2580" : down !== "." ? "\u2584" : " ";
|
|
40159
|
+
line += up === "+" || down === "+" ? pink(glyph, depth) : glyph;
|
|
40160
|
+
}
|
|
40161
|
+
lines.push(line.trimEnd());
|
|
40162
|
+
}
|
|
40163
|
+
return lines;
|
|
40164
|
+
}
|
|
40165
|
+
|
|
39981
40166
|
// src/output.ts
|
|
39982
|
-
var
|
|
40167
|
+
var import_picocolors2 = __toESM(require_picocolors(), 1);
|
|
39983
40168
|
init_errors();
|
|
39984
|
-
var UNICODE = process.platform !== "win32" || Boolean(process.env.WT_SESSION || process.env.TERM_PROGRAM);
|
|
39985
|
-
var TICK = UNICODE ? "\u2714" : "\u221A";
|
|
39986
|
-
var SPINNER = UNICODE ? ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"] : ["-", "\\", "|", "/"];
|
|
39987
40169
|
function forAPerson(json2, agent2) {
|
|
39988
40170
|
return !json2 && Boolean(process.stdout.isTTY && process.stderr.isTTY) && !agent2;
|
|
39989
40171
|
}
|
|
@@ -40004,24 +40186,9 @@ var Output = class {
|
|
|
40004
40186
|
}
|
|
40005
40187
|
/** A progress line, e.g. "Uploading 2 files". */
|
|
40006
40188
|
step(text) {
|
|
40007
|
-
if (!this.json) process.stderr.write(`${
|
|
40189
|
+
if (!this.json) process.stderr.write(`${import_picocolors2.default.dim("\u203A")} ${text}
|
|
40008
40190
|
`);
|
|
40009
40191
|
}
|
|
40010
|
-
/**
|
|
40011
|
-
* A spinner on stderr, for a person at a terminal while pch waits on them. Call what it returns
|
|
40012
|
-
* to stop it: that takes its line away, so whatever comes next starts on a clean one.
|
|
40013
|
-
*/
|
|
40014
|
-
spin(text) {
|
|
40015
|
-
let frame = 0;
|
|
40016
|
-
const draw = () => process.stderr.write(`\r${import_picocolors.default.cyan(SPINNER[frame++ % SPINNER.length])} ${text}`);
|
|
40017
|
-
draw();
|
|
40018
|
-
const timer = setInterval(draw, 80);
|
|
40019
|
-
timer.unref();
|
|
40020
|
-
return () => {
|
|
40021
|
-
clearInterval(timer);
|
|
40022
|
-
process.stderr.write("\r\x1B[2K");
|
|
40023
|
-
};
|
|
40024
|
-
}
|
|
40025
40192
|
/**
|
|
40026
40193
|
* How far a long job has got. With --json it's a line of JSON on stderr, so whoever's waiting can
|
|
40027
40194
|
* see it's moving while stdout stays the one result.
|
|
@@ -40045,16 +40212,40 @@ var Output = class {
|
|
|
40045
40212
|
};
|
|
40046
40213
|
process.stderr.write(`${JSON.stringify(body)}
|
|
40047
40214
|
`);
|
|
40215
|
+
} else if (process.stderr.isTTY) {
|
|
40216
|
+
const failed3 = import_picocolors2.default.red(symbols().failed);
|
|
40217
|
+
const short = failure2.forPeople;
|
|
40218
|
+
if (short) process.stderr.write(`
|
|
40219
|
+
${failed3} ${short.title}
|
|
40220
|
+
|
|
40221
|
+
`);
|
|
40222
|
+
else
|
|
40223
|
+
process.stderr.write(`
|
|
40224
|
+
${failed3} ${failure2.message} ${import_picocolors2.default.dim(`(${failure2.code})`)}
|
|
40225
|
+
`);
|
|
40226
|
+
for (const issue2 of issuesOf(failure2.details)) {
|
|
40227
|
+
process.stderr.write(` ${import_picocolors2.default.dim(issue2.path || "\xB7")} ${issue2.message}
|
|
40228
|
+
`);
|
|
40229
|
+
}
|
|
40230
|
+
if (short) process.stderr.write(`${commandList(short.next, " ").join("\n")}
|
|
40231
|
+
`);
|
|
40232
|
+
if (short?.also) process.stderr.write(`
|
|
40233
|
+
${import_picocolors2.default.dim(short.also)}
|
|
40234
|
+
`);
|
|
40235
|
+
if (feedback2) process.stderr.write(`
|
|
40236
|
+
${import_picocolors2.default.dim(feedback2.say)}
|
|
40237
|
+
`);
|
|
40238
|
+
process.stderr.write("\n");
|
|
40048
40239
|
} else {
|
|
40049
40240
|
process.stderr.write(
|
|
40050
|
-
`${
|
|
40241
|
+
`${import_picocolors2.default.red("Error:")} ${failure2.message} ${import_picocolors2.default.dim(`(${failure2.code})`)}
|
|
40051
40242
|
`
|
|
40052
40243
|
);
|
|
40053
40244
|
for (const issue2 of issuesOf(failure2.details)) {
|
|
40054
|
-
process.stderr.write(` ${
|
|
40245
|
+
process.stderr.write(` ${import_picocolors2.default.dim(issue2.path || "\xB7")} ${issue2.message}
|
|
40055
40246
|
`);
|
|
40056
40247
|
}
|
|
40057
|
-
if (feedback2) process.stderr.write(`${
|
|
40248
|
+
if (feedback2) process.stderr.write(`${import_picocolors2.default.dim(feedback2.say)}
|
|
40058
40249
|
`);
|
|
40059
40250
|
}
|
|
40060
40251
|
return failure2.exitCode;
|
|
@@ -40105,29 +40296,33 @@ async function login(ctx, options) {
|
|
|
40105
40296
|
const { out, target } = ctx;
|
|
40106
40297
|
if (process.env.PCH_TOKEN) return loginWithKey(ctx);
|
|
40107
40298
|
const scopes = options.scope?.length ? options.scope : ["read", "publish"];
|
|
40108
|
-
const
|
|
40109
|
-
|
|
40110
|
-
|
|
40299
|
+
const name = ctx.target.accountName ?? (target.env === "production" ? "default" : target.env);
|
|
40300
|
+
const isDefault = name === (target.env === "production" ? "default" : target.env);
|
|
40301
|
+
const people = forAPerson(out.json, runningIn());
|
|
40302
|
+
const notes = [];
|
|
40303
|
+
const signIn = { ...options, logo: true };
|
|
40304
|
+
const { me, key, git, gitIdentity } = await withDeviceSession(ctx, signIn, async (session) => {
|
|
40305
|
+
const me2 = await unwrap(session.GET("/me"));
|
|
40306
|
+
const key2 = await unwrap(
|
|
40111
40307
|
session.POST("/tokens", {
|
|
40112
40308
|
body: { name: `${runningIn() ?? "pch"} on ${hostname()}`, scopes }
|
|
40113
40309
|
})
|
|
40114
|
-
)
|
|
40115
|
-
|
|
40116
|
-
|
|
40117
|
-
|
|
40118
|
-
|
|
40119
|
-
|
|
40120
|
-
|
|
40121
|
-
|
|
40122
|
-
|
|
40123
|
-
|
|
40124
|
-
|
|
40125
|
-
|
|
40126
|
-
|
|
40127
|
-
|
|
40128
|
-
|
|
40129
|
-
|
|
40130
|
-
const gitIdentity = await identityAdvice(ctx);
|
|
40310
|
+
);
|
|
40311
|
+
const account2 = {
|
|
40312
|
+
env: target.env,
|
|
40313
|
+
token: key2.key,
|
|
40314
|
+
token_id: key2.id,
|
|
40315
|
+
...me2.username ? { username: me2.username } : {},
|
|
40316
|
+
...target.origin !== defaultOrigin(target.env) ? { api_url: target.origin } : {},
|
|
40317
|
+
...target.gitUrl && target.gitUrl !== defaultGitUrl(target.env) ? { git_url: target.gitUrl } : {}
|
|
40318
|
+
};
|
|
40319
|
+
const config2 = ctx.config;
|
|
40320
|
+
config2.accounts = { ...config2.accounts, [name]: account2 };
|
|
40321
|
+
config2.active_account = name;
|
|
40322
|
+
await writeConfig(config2);
|
|
40323
|
+
const git2 = await setUpGit(ctx, people ? notes : void 0);
|
|
40324
|
+
return { me: me2, key: key2, git: git2, gitIdentity: await identityAdvice(ctx) };
|
|
40325
|
+
});
|
|
40131
40326
|
out.result(
|
|
40132
40327
|
{
|
|
40133
40328
|
account: name,
|
|
@@ -40139,13 +40334,21 @@ async function login(ctx, options) {
|
|
|
40139
40334
|
look_first: LOOK_FIRST
|
|
40140
40335
|
},
|
|
40141
40336
|
() => {
|
|
40142
|
-
|
|
40143
|
-
|
|
40144
|
-
const
|
|
40145
|
-
|
|
40146
|
-
|
|
40337
|
+
if (people) {
|
|
40338
|
+
const who = import_picocolors3.default.bold(me.username ? `@${me.username}` : me.email);
|
|
40339
|
+
const which = isDefault ? "" : ` ${import_picocolors3.default.dim(`(account "${name}")`)}`;
|
|
40340
|
+
return signedIn(ctx, `Signed in as ${who} on ${placeOf(target.origin)}${which}`, {
|
|
40341
|
+
git,
|
|
40342
|
+
notes: [
|
|
40343
|
+
...me.username ? [] : [`Choose a username at ${target.origin}/welcome to publish.`],
|
|
40344
|
+
...gitIdentity ? [SHORT_IDENTITY] : [],
|
|
40345
|
+
...notes
|
|
40346
|
+
]
|
|
40347
|
+
});
|
|
40147
40348
|
}
|
|
40148
|
-
out.line(
|
|
40349
|
+
out.line(
|
|
40350
|
+
`Signed in as ${import_picocolors3.default.bold(me.username ? `@${me.username}` : me.email)} on ${target.env} (account "${name}").`
|
|
40351
|
+
);
|
|
40149
40352
|
if (!me.username) out.line(`Choose a username at ${target.origin}/welcome to publish.`);
|
|
40150
40353
|
if (git.helper) out.line(`Git is signed in too: Patch repos at ${git.url} just work.`);
|
|
40151
40354
|
if (gitIdentity) out.line(gitIdentity);
|
|
@@ -40159,7 +40362,9 @@ async function login(ctx, options) {
|
|
|
40159
40362
|
async function loginWithKey(ctx) {
|
|
40160
40363
|
const { out, target } = ctx;
|
|
40161
40364
|
const me = await unwrap(ctx.api.GET("/me"));
|
|
40162
|
-
const
|
|
40365
|
+
const people = forAPerson(out.json, runningIn());
|
|
40366
|
+
const notes = [];
|
|
40367
|
+
const git = await setUpGit(ctx, people ? notes : void 0);
|
|
40163
40368
|
const gitIdentity = await identityAdvice(ctx);
|
|
40164
40369
|
out.result(
|
|
40165
40370
|
{
|
|
@@ -40172,63 +40377,57 @@ async function loginWithKey(ctx) {
|
|
|
40172
40377
|
look_first: LOOK_FIRST
|
|
40173
40378
|
},
|
|
40174
40379
|
() => {
|
|
40175
|
-
|
|
40176
|
-
|
|
40177
|
-
|
|
40178
|
-
|
|
40380
|
+
if (people) {
|
|
40381
|
+
const who = import_picocolors3.default.bold(me.username ? `@${me.username}` : me.email);
|
|
40382
|
+
return signedIn(ctx, `PCH_TOKEN works: ${who} on ${placeOf(target.origin)}`, {
|
|
40383
|
+
git,
|
|
40384
|
+
notes: [...gitIdentity ? [SHORT_IDENTITY] : [], ...notes]
|
|
40385
|
+
});
|
|
40179
40386
|
}
|
|
40180
|
-
out.line(
|
|
40387
|
+
out.line(
|
|
40388
|
+
`PCH_TOKEN works: ${import_picocolors3.default.bold(me.username ? `@${me.username}` : me.email)} on ${target.env}.`
|
|
40389
|
+
);
|
|
40181
40390
|
if (git.helper) out.line(`Git is signed in too: Patch repos at ${git.url} just work.`);
|
|
40182
40391
|
if (gitIdentity) out.line(gitIdentity);
|
|
40183
40392
|
out.line(LOOK_FIRST);
|
|
40184
40393
|
}
|
|
40185
40394
|
);
|
|
40186
40395
|
}
|
|
40187
|
-
|
|
40188
|
-
|
|
40189
|
-
}
|
|
40190
|
-
function signedIn(ctx, headline, username, git, gitIdentity) {
|
|
40396
|
+
var SHORT_IDENTITY = "Git doesn't know who you are on this PC yet: pch new and pch clone set that up for each repo.";
|
|
40397
|
+
function signedIn(ctx, headline, { git, notes }) {
|
|
40191
40398
|
const { out, target } = ctx;
|
|
40192
|
-
out.line(`${import_picocolors2.default.green(TICK)} ${headline}`);
|
|
40193
|
-
if (git.helper) out.line(import_picocolors2.default.dim(" Git works with Patch repos too."));
|
|
40194
|
-
if (!username) out.line(` Choose a username at ${target.origin}/welcome before you publish.`);
|
|
40195
|
-
if (gitIdentity) out.line(indented(gitIdentity));
|
|
40196
40399
|
const pch = pchOn(target.env);
|
|
40197
|
-
const
|
|
40198
|
-
[`${pch} search "<idea>"`, "find a game or block to start from"],
|
|
40199
|
-
[`${pch} agent install`, "give your coding agent Patch's tools"],
|
|
40200
|
-
[`${pch} guide`, "how to make, test and publish"]
|
|
40201
|
-
];
|
|
40202
|
-
const width = Math.max(...next.map(([command]) => command.length));
|
|
40400
|
+
const mark = symbols();
|
|
40203
40401
|
out.line();
|
|
40204
|
-
|
|
40205
|
-
|
|
40402
|
+
out.line(` ${import_picocolors3.default.bold(mark.done)} ${headline}`);
|
|
40403
|
+
if (git.helper && git.url) {
|
|
40404
|
+
out.line(import_picocolors3.default.dim(` Git is signed in too, for repos at ${placeOf(git.url)}.`));
|
|
40206
40405
|
}
|
|
40406
|
+
for (const note2 of notes) out.line(` ${import_picocolors3.default.bold(mark.note)} ${note2}`);
|
|
40407
|
+
out.line();
|
|
40408
|
+
out.line(` ${import_picocolors3.default.dim("Next")}`);
|
|
40409
|
+
const next = commandList(
|
|
40410
|
+
[
|
|
40411
|
+
[`${pch} search "<your idea>"`, "find a block or game to start from"],
|
|
40412
|
+
[`${pch} agent install`, "give your coding agent Patch's tools"],
|
|
40413
|
+
[`${pch} guide`, "how to make and publish a game"]
|
|
40414
|
+
],
|
|
40415
|
+
" "
|
|
40416
|
+
);
|
|
40417
|
+
for (const row of next) out.line(row);
|
|
40207
40418
|
out.line();
|
|
40208
40419
|
}
|
|
40209
|
-
function
|
|
40210
|
-
const width = Math.min(process.stdout.columns || 80, 100) - 2;
|
|
40211
|
-
const lines = [];
|
|
40212
|
-
let line = "";
|
|
40213
|
-
for (const word of text.split(" ")) {
|
|
40214
|
-
if (line && line.length + 1 + word.length > width) {
|
|
40215
|
-
lines.push(line);
|
|
40216
|
-
line = word;
|
|
40217
|
-
} else line = line ? `${line} ${word}` : word;
|
|
40218
|
-
}
|
|
40219
|
-
if (line) lines.push(line);
|
|
40220
|
-
return lines.map((each) => ` ${each}`).join("\n");
|
|
40221
|
-
}
|
|
40222
|
-
async function setUpGit(ctx) {
|
|
40420
|
+
async function setUpGit(ctx, notes) {
|
|
40223
40421
|
const { gitUrl: url2 } = ctx.target;
|
|
40422
|
+
const say = (text) => notes ? notes.push(text) : ctx.out.step(text);
|
|
40224
40423
|
if (!url2) return { url: url2, helper: false, lfs: false };
|
|
40225
40424
|
try {
|
|
40226
40425
|
await setUpGitHelper(ctx);
|
|
40227
40426
|
const lfs = await setUpLfs();
|
|
40228
|
-
if (!lfs)
|
|
40427
|
+
if (!lfs) say(NO_LFS);
|
|
40229
40428
|
return { url: url2, helper: true, lfs };
|
|
40230
40429
|
} catch (error62) {
|
|
40231
|
-
|
|
40430
|
+
say(`Git isn't set up: ${error62.message}`);
|
|
40232
40431
|
return { url: url2, helper: false, lfs: false };
|
|
40233
40432
|
}
|
|
40234
40433
|
}
|
|
@@ -40239,55 +40438,68 @@ async function withDeviceSession(ctx, options, use) {
|
|
|
40239
40438
|
if (!started.response.ok) throw authError(started.body, "Couldn't start a login");
|
|
40240
40439
|
const device = started.body;
|
|
40241
40440
|
const code = formatUserCode(device.user_code);
|
|
40242
|
-
const
|
|
40243
|
-
|
|
40244
|
-
|
|
40245
|
-
|
|
40246
|
-
if (
|
|
40247
|
-
|
|
40248
|
-
|
|
40249
|
-
|
|
40250
|
-
|
|
40251
|
-
|
|
40252
|
-
|
|
40253
|
-
${
|
|
40254
|
-
|
|
40255
|
-
|
|
40256
|
-
)
|
|
40257
|
-
|
|
40441
|
+
const opening = options.browser !== false && Boolean(process.stdout.isTTY);
|
|
40442
|
+
const people = forAPerson(out.json, runningIn());
|
|
40443
|
+
const block = new Block();
|
|
40444
|
+
const drawn = people && options.logo ? logo() : null;
|
|
40445
|
+
if (drawn) process.stderr.write(`
|
|
40446
|
+
${drawn.map((line) => ` ${line}`).join("\n")}
|
|
40447
|
+
`);
|
|
40448
|
+
if (people) {
|
|
40449
|
+
block.write();
|
|
40450
|
+
block.write(" Sign in to Patch with this code");
|
|
40451
|
+
block.write();
|
|
40452
|
+
const copied = copyText(device.user_code) ? ` ${import_picocolors3.default.dim("copied")}` : "";
|
|
40453
|
+
block.write(`${import_picocolors3.default.bold(pink(device.user_code))}${copied}`);
|
|
40454
|
+
block.write();
|
|
40455
|
+
if (opening) {
|
|
40456
|
+
const page = import_picocolors3.default.bold(`${placeOf(target.origin)}/device`);
|
|
40457
|
+
block.write(` Opening ${page} in your browser: check the code matches, then approve.`);
|
|
40458
|
+
block.write(import_picocolors3.default.dim(` Didn't open? Go to ${device.verification_uri} and enter the code.`));
|
|
40459
|
+
} else {
|
|
40460
|
+
block.write(` Go to ${import_picocolors3.default.bold(device.verification_uri)} and enter the code.`);
|
|
40461
|
+
}
|
|
40462
|
+
block.write();
|
|
40258
40463
|
} else {
|
|
40259
40464
|
process.stderr.write(
|
|
40260
40465
|
`This waits for a person to approve it in a browser, which can take a few minutes. The code only works while this keeps running, so don't put a short timeout on it.
|
|
40261
40466
|
|
|
40262
|
-
To sign in, open ${
|
|
40467
|
+
To sign in, open ${import_picocolors3.default.bold(device.verification_uri)} and enter ${import_picocolors3.default.bold(code)}
|
|
40263
40468
|
|
|
40264
40469
|
`
|
|
40265
40470
|
);
|
|
40266
|
-
out.step("Waiting for you to approve in the browser\u2026");
|
|
40267
40471
|
}
|
|
40268
|
-
|
|
40472
|
+
if (opening) openBrowser(device.verification_uri_complete);
|
|
40473
|
+
const waiting = people ? spinner("Waiting for you to approve", "Ctrl+C to cancel") : { stop() {
|
|
40474
|
+
}, say() {
|
|
40475
|
+
} };
|
|
40476
|
+
if (!people) out.step("Waiting for you to approve in the browser\u2026");
|
|
40269
40477
|
try {
|
|
40270
|
-
sessionToken = await
|
|
40271
|
-
|
|
40272
|
-
|
|
40273
|
-
|
|
40274
|
-
|
|
40275
|
-
|
|
40276
|
-
|
|
40277
|
-
|
|
40278
|
-
|
|
40478
|
+
const sessionToken = await pollForSession(ctx, device, options);
|
|
40479
|
+
waiting.say("Approved. Finishing up");
|
|
40480
|
+
let value;
|
|
40481
|
+
try {
|
|
40482
|
+
value = await use(createApi(target, { token: sessionToken }));
|
|
40483
|
+
} finally {
|
|
40484
|
+
await fetch(`${auth2}/sign-out`, {
|
|
40485
|
+
method: "POST",
|
|
40486
|
+
headers: { authorization: `Bearer ${sessionToken}`, "content-type": "application/json" },
|
|
40487
|
+
body: "{}"
|
|
40488
|
+
}).catch(() => void 0);
|
|
40489
|
+
}
|
|
40490
|
+
waiting.stop();
|
|
40491
|
+
block.erase();
|
|
40492
|
+
return value;
|
|
40279
40493
|
} finally {
|
|
40280
|
-
|
|
40281
|
-
method: "POST",
|
|
40282
|
-
headers: { authorization: `Bearer ${sessionToken}`, "content-type": "application/json" },
|
|
40283
|
-
body: "{}"
|
|
40284
|
-
}).catch(() => void 0);
|
|
40494
|
+
waiting.stop();
|
|
40285
40495
|
}
|
|
40286
40496
|
}
|
|
40287
|
-
|
|
40497
|
+
var TRY_AGAIN = [["pch login", "get a new code and try again"]];
|
|
40498
|
+
async function pollForSession(ctx, device, options) {
|
|
40288
40499
|
const auth2 = `${ctx.target.origin}/api/auth`;
|
|
40289
40500
|
let interval = (options.interval ?? device.interval) * 1e3;
|
|
40290
40501
|
const deadline = Date.now() + device.expires_in * 1e3;
|
|
40502
|
+
let sessionToken = null;
|
|
40291
40503
|
while (Date.now() < deadline) {
|
|
40292
40504
|
await sleep(interval);
|
|
40293
40505
|
const polled = await postJson(`${auth2}/device/token`, {
|
|
@@ -40295,7 +40507,10 @@ async function approval(ctx, device, options) {
|
|
|
40295
40507
|
device_code: device.device_code,
|
|
40296
40508
|
client_id: CLIENT_ID
|
|
40297
40509
|
});
|
|
40298
|
-
if (polled.response.ok)
|
|
40510
|
+
if (polled.response.ok) {
|
|
40511
|
+
sessionToken = polled.body.access_token;
|
|
40512
|
+
break;
|
|
40513
|
+
}
|
|
40299
40514
|
const error62 = polled.body.error;
|
|
40300
40515
|
if (error62 === "authorization_pending") continue;
|
|
40301
40516
|
if (error62 === "slow_down") {
|
|
@@ -40303,12 +40518,27 @@ async function approval(ctx, device, options) {
|
|
|
40303
40518
|
continue;
|
|
40304
40519
|
}
|
|
40305
40520
|
if (error62 === "access_denied") {
|
|
40306
|
-
|
|
40521
|
+
const denied = new CliError(
|
|
40522
|
+
EXIT.auth,
|
|
40523
|
+
"access_denied",
|
|
40524
|
+
"The login was denied in the browser."
|
|
40525
|
+
);
|
|
40526
|
+
denied.forPeople = { title: "The sign-in was denied in the browser", next: TRY_AGAIN };
|
|
40527
|
+
throw denied;
|
|
40307
40528
|
}
|
|
40308
40529
|
if (error62 === "expired_token") break;
|
|
40309
40530
|
throw authError(polled.body, "The login failed");
|
|
40310
40531
|
}
|
|
40311
|
-
|
|
40532
|
+
if (!sessionToken) {
|
|
40533
|
+
const expired = new CliError(
|
|
40534
|
+
EXIT.auth,
|
|
40535
|
+
"expired_token",
|
|
40536
|
+
"The code expired. Run pch login again."
|
|
40537
|
+
);
|
|
40538
|
+
expired.forPeople = { title: "The code expired before it was approved", next: TRY_AGAIN };
|
|
40539
|
+
throw expired;
|
|
40540
|
+
}
|
|
40541
|
+
return sessionToken;
|
|
40312
40542
|
}
|
|
40313
40543
|
function runningIn(vars = process.env) {
|
|
40314
40544
|
if (vars.CLAUDECODE === "1") return "Claude Code";
|
|
@@ -40347,12 +40577,25 @@ async function whoami(ctx) {
|
|
|
40347
40577
|
requireToken(ctx.target);
|
|
40348
40578
|
const me = await unwrap(ctx.api.GET("/me"));
|
|
40349
40579
|
ctx.out.result({ ...me, env: ctx.target.env, account: ctx.target.accountName }, () => {
|
|
40580
|
+
const { out, target } = ctx;
|
|
40350
40581
|
const who = me.username ? `@${me.username}` : me.email;
|
|
40351
|
-
const role = me.role === "staff" ?
|
|
40352
|
-
const via = me.auth.via === "api_key" ? `API key
|
|
40353
|
-
|
|
40354
|
-
|
|
40355
|
-
|
|
40582
|
+
const role = me.role === "staff" ? import_picocolors4.default.dim(" \xB7 staff") : "";
|
|
40583
|
+
const via = me.auth.via === "api_key" ? `an API key (${(me.auth.scopes ?? []).join(", ") || "no scopes"})` : "a session";
|
|
40584
|
+
const saved = target.accountName ? `, saved as account "${target.accountName}"` : "";
|
|
40585
|
+
out.line();
|
|
40586
|
+
out.line(
|
|
40587
|
+
` ${pink(symbols().done)} ${import_picocolors4.default.bold(who)} on ${placeOf(target.origin)} ${import_picocolors4.default.dim(`(${target.env})`)}${role}`
|
|
40588
|
+
);
|
|
40589
|
+
out.line(import_picocolors4.default.dim(` Signed in with ${via}${saved}.`));
|
|
40590
|
+
if (me.orgs.length) {
|
|
40591
|
+
out.line();
|
|
40592
|
+
out.line(` ${import_picocolors4.default.dim("Orgs")}`);
|
|
40593
|
+
const wide = Math.max(...me.orgs.map((org) => org.slug.length));
|
|
40594
|
+
for (const org of me.orgs) {
|
|
40595
|
+
out.line(` ${org.slug.padEnd(wide)} ${import_picocolors4.default.dim(`${org.kind}, ${org.role}`)}`);
|
|
40596
|
+
}
|
|
40597
|
+
}
|
|
40598
|
+
out.line();
|
|
40356
40599
|
});
|
|
40357
40600
|
}
|
|
40358
40601
|
async function logout(ctx) {
|
|
@@ -40376,11 +40619,28 @@ async function logout(ctx) {
|
|
|
40376
40619
|
}
|
|
40377
40620
|
const git = await signOutOfGit(ctx);
|
|
40378
40621
|
ctx.out.result({ account: name, revoked, git }, () => {
|
|
40379
|
-
|
|
40380
|
-
|
|
40381
|
-
|
|
40622
|
+
const { out, target } = ctx;
|
|
40623
|
+
const mark = symbols();
|
|
40624
|
+
out.line();
|
|
40625
|
+
if (name) {
|
|
40626
|
+
const which = import_picocolors4.default.dim(`(account "${name}")`);
|
|
40627
|
+
out.line(` ${pink(mark.done)} Signed out of ${placeOf(target.origin)} ${which}`);
|
|
40628
|
+
if (revoked) out.line(import_picocolors4.default.dim(" Its key is revoked, and pch keeps no copy."));
|
|
40629
|
+
else {
|
|
40630
|
+
out.line(
|
|
40631
|
+
` ${import_picocolors4.default.bold(mark.note)} The key wasn't revoked: revoke it in your settings on ${placeOf(target.origin)} if you need to.`
|
|
40632
|
+
);
|
|
40633
|
+
}
|
|
40634
|
+
} else {
|
|
40635
|
+
out.line(
|
|
40636
|
+
` ${pink(mark.done)} Nothing saved to sign out of: PCH_TOKEN stays set, and pch keeps no copy.`
|
|
40637
|
+
);
|
|
40382
40638
|
}
|
|
40383
|
-
if (git.url && !git.helper)
|
|
40639
|
+
if (git.url && !git.helper) out.line(import_picocolors4.default.dim(` Git is signed out of ${placeOf(git.url)}.`));
|
|
40640
|
+
out.line();
|
|
40641
|
+
if (name)
|
|
40642
|
+
out.line(` ${import_picocolors4.default.dim("Sign in again with")} ${import_picocolors4.default.bold(`${pchOn(target.env)} login`)}`);
|
|
40643
|
+
if (name) out.line();
|
|
40384
40644
|
});
|
|
40385
40645
|
}
|
|
40386
40646
|
async function signOutOfGit(ctx) {
|
|
@@ -40400,7 +40660,7 @@ async function accountList(ctx) {
|
|
|
40400
40660
|
ctx.out.result({ data: accounts }, () => {
|
|
40401
40661
|
if (!accounts.length) ctx.out.line("No saved logins. Run pch login.");
|
|
40402
40662
|
for (const account2 of accounts) {
|
|
40403
|
-
const marker = account2.active ?
|
|
40663
|
+
const marker = account2.active ? import_picocolors4.default.green("*") : " ";
|
|
40404
40664
|
const who = account2.username ? `@${account2.username}` : "";
|
|
40405
40665
|
ctx.out.line(`${marker} ${account2.name.padEnd(12)} ${account2.env.padEnd(10)} ${who}`);
|
|
40406
40666
|
}
|
|
@@ -40439,7 +40699,7 @@ async function tokenCreate(ctx, options) {
|
|
|
40439
40699
|
ctx.out.result(created, () => {
|
|
40440
40700
|
ctx.out.line(created.key);
|
|
40441
40701
|
process.stderr.write(
|
|
40442
|
-
|
|
40702
|
+
import_picocolors4.default.dim("Shown once. Store it as PCH_TOKEN where your agent or CI runs.\n")
|
|
40443
40703
|
);
|
|
40444
40704
|
});
|
|
40445
40705
|
}
|
|
@@ -40450,7 +40710,7 @@ async function tokenList(ctx) {
|
|
|
40450
40710
|
if (!tokens.data.length) ctx.out.line("No API keys.");
|
|
40451
40711
|
for (const token2 of tokens.data) {
|
|
40452
40712
|
ctx.out.line(
|
|
40453
|
-
`${token2.id} ${(token2.name ?? "").padEnd(24)} ${token2.scopes.join(",").padEnd(18)} ${
|
|
40713
|
+
`${token2.id} ${(token2.name ?? "").padEnd(24)} ${token2.scopes.join(",").padEnd(18)} ${import_picocolors4.default.dim(token2.start ? `${token2.start}\u2026` : "")}`
|
|
40454
40714
|
);
|
|
40455
40715
|
}
|
|
40456
40716
|
});
|
|
@@ -40462,7 +40722,7 @@ async function tokenRevoke(ctx, id) {
|
|
|
40462
40722
|
}
|
|
40463
40723
|
|
|
40464
40724
|
// src/commands/agent-brief.ts
|
|
40465
|
-
var
|
|
40725
|
+
var import_picocolors6 = __toESM(require_picocolors(), 1);
|
|
40466
40726
|
import { constants } from "node:fs";
|
|
40467
40727
|
import { access as access2, copyFile, lstat, mkdir as mkdir2, readdir as readdir2, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
|
|
40468
40728
|
import { basename, dirname as dirname3, extname, join as join5, resolve as resolve4 } from "node:path";
|
|
@@ -41216,7 +41476,8 @@ function parseInstallPath(value) {
|
|
|
41216
41476
|
if (rest && !isReleasePath(rest)) return null;
|
|
41217
41477
|
return { root, game: game ?? null, path: rest };
|
|
41218
41478
|
}
|
|
41219
|
-
var
|
|
41479
|
+
var PATH_MAX = 512;
|
|
41480
|
+
var installPath = external_exports.string().max(PATH_MAX, `must be at most ${PATH_MAX} characters`).refine(
|
|
41220
41481
|
(value) => parseInstallPath(value) !== null,
|
|
41221
41482
|
`must start with a folder like {game} or {profile} (${INSTALL_ROOTS.map((root) => `{${root}}`).join(", ")}, or {game:slug} for another listed game) and never use ..`
|
|
41222
41483
|
);
|
|
@@ -41228,19 +41489,19 @@ var gameEntry = external_exports.object({
|
|
|
41228
41489
|
),
|
|
41229
41490
|
version: shortLine(80).optional().describe('The game versions it works with, in words or as a range, e.g. ">=1.6".'),
|
|
41230
41491
|
stores: external_exports.array(store).min(1).optional(),
|
|
41231
|
-
builds: external_exports.partialRecord(store, external_exports.array(external_exports.string().min(1)).min(1)).optional().describe("Exact builds per store; leave out to accept any build.")
|
|
41492
|
+
builds: external_exports.partialRecord(store, external_exports.array(external_exports.string().min(1).max(80)).min(1).max(32)).optional().describe("Exact builds per store; leave out to accept any build.")
|
|
41232
41493
|
});
|
|
41233
41494
|
var component = external_exports.object({
|
|
41234
41495
|
id: external_exports.string().regex(componentId, "must be lowercase letters, digits, . _ or -"),
|
|
41235
|
-
file: external_exports.string().refine((value) => value === "" || isReleasePath(value), "must be a path inside the release").describe('One uploaded file, a folder of them ending in "/", or "" for every file.'),
|
|
41496
|
+
file: external_exports.string().max(PATH_MAX, `must be at most ${PATH_MAX} characters`).refine((value) => value === "" || isReleasePath(value), "must be a path inside the release").describe('One uploaded file, a folder of them ending in "/", or "" for every file.'),
|
|
41236
41497
|
label: shortLine(80).optional().describe('What it is, e.g. "Main files".'),
|
|
41237
41498
|
kind: external_exports.enum(["main", "optional", "extra"]).default("main").describe("main: what players need. optional: players can turn it on. extra: not installed."),
|
|
41238
41499
|
required: external_exports.boolean().optional(),
|
|
41239
41500
|
extract: external_exports.boolean().optional().describe("The file is a zip to unpack when it's installed.")
|
|
41240
41501
|
});
|
|
41241
41502
|
var mapping = external_exports.object({
|
|
41242
|
-
component: external_exports.string(),
|
|
41243
|
-
from: external_exports.string().refine((value) => value === "" || isReleasePath(value), "must be a path inside the component").default("").describe('"" for all of the component, "folder/" for a folder of it, or one file.'),
|
|
41503
|
+
component: external_exports.string().max(64),
|
|
41504
|
+
from: external_exports.string().max(PATH_MAX, `must be at most ${PATH_MAX} characters`).refine((value) => value === "" || isReleasePath(value), "must be a path inside the component").default("").describe('"" for all of the component, "folder/" for a folder of it, or one file.'),
|
|
41244
41505
|
to: installPath
|
|
41245
41506
|
});
|
|
41246
41507
|
var requirement = external_exports.discriminatedUnion(
|
|
@@ -41399,10 +41660,11 @@ var recipeSchema = external_exports.object({
|
|
|
41399
41660
|
mode: external_exports.enum(["browser", "installed", "standalone", "workshop", "manual"]).optional().describe(
|
|
41400
41661
|
"browser: plays in Patch's game window. installed: into a game on the player's PC. standalone: a program of its own. workshop: a Steam Workshop item. manual: players install it by hand. Left out: browser, or installed when there's a host game."
|
|
41401
41662
|
),
|
|
41402
|
-
|
|
41403
|
-
|
|
41404
|
-
|
|
41405
|
-
|
|
41663
|
+
// Limits far past any real recipe, so checking one stays cheap.
|
|
41664
|
+
games: external_exports.array(gameEntry).max(16, "can have at most 16 games").default([]),
|
|
41665
|
+
components: external_exports.array(component).min(1).max(64, "can have at most 64 components").optional().describe('Left out: one main component, "main", holding every file.'),
|
|
41666
|
+
mappings: external_exports.array(mapping).max(64, "can have at most 64 mappings").default([]).describe("Where each component's files go."),
|
|
41667
|
+
requirements: external_exports.array(requirement).max(32, "can have at most 32 requirements").default([]),
|
|
41406
41668
|
launch: launch.optional().describe("Left out: index.html for a browser game, the game itself otherwise."),
|
|
41407
41669
|
settings: external_exports.array(
|
|
41408
41670
|
external_exports.object({
|
|
@@ -41455,12 +41717,12 @@ var recipeSchema = external_exports.object({
|
|
|
41455
41717
|
workshop: external_exports.object({ steam_app_id: external_exports.string().regex(/^\d+$/), file_id: external_exports.string().regex(/^\d+$/) }).optional(),
|
|
41456
41718
|
credits: external_exports.array(
|
|
41457
41719
|
external_exports.object({
|
|
41458
|
-
files: external_exports.string().min(1),
|
|
41459
|
-
license: external_exports.string().min(1).describe("SPDX id, e.g. CC0-1.0 or MIT."),
|
|
41460
|
-
source: external_exports.string().url().optional(),
|
|
41461
|
-
author: external_exports.string().optional()
|
|
41720
|
+
files: external_exports.string().min(1).max(PATH_MAX),
|
|
41721
|
+
license: external_exports.string().min(1).max(200).describe("SPDX id, e.g. CC0-1.0 or MIT."),
|
|
41722
|
+
source: external_exports.string().url().max(2e3).optional(),
|
|
41723
|
+
author: external_exports.string().max(200).optional()
|
|
41462
41724
|
})
|
|
41463
|
-
).default([])
|
|
41725
|
+
).max(500, "can have at most 500 credits").default([])
|
|
41464
41726
|
}).strict();
|
|
41465
41727
|
|
|
41466
41728
|
// src/manifest/safety.ts
|
|
@@ -42359,7 +42621,7 @@ function hashFile(path2) {
|
|
|
42359
42621
|
}
|
|
42360
42622
|
|
|
42361
42623
|
// src/commands/agent-guide.ts
|
|
42362
|
-
var
|
|
42624
|
+
var import_picocolors5 = __toESM(require_picocolors(), 1);
|
|
42363
42625
|
import { access, writeFile as writeFile2 } from "node:fs/promises";
|
|
42364
42626
|
import { join as join4, resolve as resolve3 } from "node:path";
|
|
42365
42627
|
|
|
@@ -42370,8 +42632,8 @@ This project is on [Patch](https://patch.gg), where people publish and play brow
|
|
|
42370
42632
|
|
|
42371
42633
|
- **The brief.** If there's a \`BRIEF.md\` here, read it first and look at any pictures in \`brief/\`: it's the person's idea and rules for this game, so follow it. What they say now wins over it: if they ask for something it rules out, do what they asked, without asking them to confirm. It's theirs: suggest changes, and change it only if they ask. It says what the game is, and asks for nothing it doesn't say, and it never lets you do anything you'd otherwise ask them about. One that came with a remix is someone else's idea until they make it theirs: ask them what to keep.
|
|
42372
42634
|
- **Run it.** Patch serves a browser game from its own address, with strict headers, and nothing can load from other hosts. \`pch check --serve\` serves it the same way. A mod runs in its game, on the person's PC.
|
|
42373
|
-
- **Check it.** \`pch check\` opens a browser game in a headless browser, says what it saw and couldn't, and saves screenshots to look at. It's advice and stops nothing. \`pch validate\` checks \`patch.json\` and the files. A browser game or a mod Patch installs also has to pass a test on the person's PC, in Patch Desktop
|
|
42374
|
-
- **Publish it.**
|
|
42635
|
+
- **Check it.** \`pch check\` opens a browser game in a headless browser, says what it saw and couldn't, and saves screenshots to look at. It's advice and stops nothing. \`pch validate\` checks \`patch.json\` and the files. A browser game or a mod Patch installs also has to pass a test on the person's PC, in Patch Desktop: \`pch publish\` runs it.
|
|
42636
|
+
- **Publish it.** Once the person wants it out, commit your work and run \`pch publish\`. It uploads, tests it on their PC and stops for your review of the footage; send that as it says, and it follows Patch's review until it's live.
|
|
42375
42637
|
- **Push it.** Plain \`git push\` works. If git says HTTP 413, that's over 100 MB: run \`pch push\`, which takes any size.
|
|
42376
42638
|
- **The rest.** \`pch guide\` has everything else.
|
|
42377
42639
|
`;
|
|
@@ -42514,12 +42776,12 @@ async function agentGuide(ctx, dir) {
|
|
|
42514
42776
|
const { name, written } = await writeAgentGuide(root);
|
|
42515
42777
|
ctx.out.result({ path: join4(root, name), written }, () => {
|
|
42516
42778
|
if (written) {
|
|
42517
|
-
ctx.out.line(`Wrote ${
|
|
42518
|
-
ctx.out.line(
|
|
42779
|
+
ctx.out.line(`Wrote ${import_picocolors5.default.bold(join4(root, name))}, for coding agents working in this folder.`);
|
|
42780
|
+
ctx.out.line(import_picocolors5.default.dim("Commit it: in a clone, pch publish refuses files that aren't."));
|
|
42519
42781
|
} else {
|
|
42520
42782
|
ctx.out.line(`${join4(root, name)} is this folder's guide for agents, so it's left as it is.`);
|
|
42521
42783
|
ctx.out.line(
|
|
42522
|
-
|
|
42784
|
+
import_picocolors5.default.dim("pch guide has how to run, check and publish, to add to it if it helps.")
|
|
42523
42785
|
);
|
|
42524
42786
|
}
|
|
42525
42787
|
});
|
|
@@ -42545,10 +42807,10 @@ async function askForBrief(lines, say) {
|
|
|
42545
42807
|
};
|
|
42546
42808
|
const answers = [];
|
|
42547
42809
|
for (const { ask } of QUESTIONS) {
|
|
42548
|
-
say(
|
|
42810
|
+
say(import_picocolors6.default.bold(ask));
|
|
42549
42811
|
answers.push((await untilBlank()).join("\n"));
|
|
42550
42812
|
}
|
|
42551
|
-
say(
|
|
42813
|
+
say(import_picocolors6.default.bold(PICTURES_ASK));
|
|
42552
42814
|
return { answers, pictures: await untilBlank() };
|
|
42553
42815
|
}
|
|
42554
42816
|
function briefText(answers, pictures) {
|
|
@@ -42638,7 +42900,7 @@ async function agentBrief(ctx, dir) {
|
|
|
42638
42900
|
ctx.out.step(
|
|
42639
42901
|
`A brief stays with this game, and every agent that works on it reads it first. ${PUBLIC}`
|
|
42640
42902
|
);
|
|
42641
|
-
ctx.out.step(
|
|
42903
|
+
ctx.out.step(import_picocolors6.default.dim("A blank line ends an answer, or skips the question."));
|
|
42642
42904
|
const said2 = await fromTerminal(ctx);
|
|
42643
42905
|
const pictures = await gatherPictures(root, said2.pictures, (line) => ctx.out.step(line));
|
|
42644
42906
|
const text = briefText(said2.answers, pictures);
|
|
@@ -42652,13 +42914,13 @@ async function agentBrief(ctx, dir) {
|
|
|
42652
42914
|
await writeAgentGuide(root);
|
|
42653
42915
|
const silent = await guidesNotNaming(root);
|
|
42654
42916
|
ctx.out.result({ path: path2, written: true, pictures, guides_without_it: silent }, () => {
|
|
42655
|
-
ctx.out.line(`Wrote ${
|
|
42917
|
+
ctx.out.line(`Wrote ${import_picocolors6.default.bold(path2)}. Change it whenever you like: it's yours.`);
|
|
42656
42918
|
for (const name of silent) {
|
|
42657
42919
|
ctx.out.line(
|
|
42658
42920
|
`${name} doesn't mention it, so agents may not read it. Add this to it: Read ${BRIEF_FILE} first: it's my idea and rules for this game.`
|
|
42659
42921
|
);
|
|
42660
42922
|
}
|
|
42661
|
-
ctx.out.line(
|
|
42923
|
+
ctx.out.line(import_picocolors6.default.dim("Commit it, and every agent that clones this repo starts with it."));
|
|
42662
42924
|
});
|
|
42663
42925
|
}
|
|
42664
42926
|
async function guidesNotNaming(root) {
|
|
@@ -42671,9 +42933,9 @@ async function guidesNotNaming(root) {
|
|
|
42671
42933
|
}
|
|
42672
42934
|
|
|
42673
42935
|
// src/commands/agent-install.ts
|
|
42674
|
-
var
|
|
42936
|
+
var import_picocolors7 = __toESM(require_picocolors(), 1);
|
|
42675
42937
|
init_errors();
|
|
42676
|
-
import { spawnSync } from "node:child_process";
|
|
42938
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
42677
42939
|
import { existsSync as existsSync2 } from "node:fs";
|
|
42678
42940
|
import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
|
|
42679
42941
|
import { homedir as homedir2 } from "node:os";
|
|
@@ -42703,14 +42965,14 @@ async function agentInstall(ctx, named, options = {}) {
|
|
|
42703
42965
|
for (const entry of agents) {
|
|
42704
42966
|
const name = AGENT_NAMES[entry.agent];
|
|
42705
42967
|
if (entry.state === "manual") {
|
|
42706
|
-
ctx.out.line(`${
|
|
42968
|
+
ctx.out.line(`${import_picocolors7.default.bold(name)}: pch can't do this here. ${entry.how}`);
|
|
42707
42969
|
} else {
|
|
42708
42970
|
ctx.out.line(
|
|
42709
|
-
`${
|
|
42971
|
+
`${import_picocolors7.default.green(`${name}:`)} ${entry.state} the ${import_picocolors7.default.bold(server.name)} MCP server, in ${entry.where}.`
|
|
42710
42972
|
);
|
|
42711
42973
|
}
|
|
42712
42974
|
}
|
|
42713
|
-
ctx.out.line(
|
|
42975
|
+
ctx.out.line(import_picocolors7.default.dim(`It runs ${commandLine(server)}, and holds no key.`));
|
|
42714
42976
|
if (!signedIn2) ctx.out.line(`Sign in first, or its tools won't work: ${pchLogin(ctx)}.`);
|
|
42715
42977
|
ctx.out.line(
|
|
42716
42978
|
"Restart your agent to load its tools. Until then, pch guide prints the same guide."
|
|
@@ -42828,11 +43090,11 @@ function onPath(bin) {
|
|
|
42828
43090
|
function run(bin, args2) {
|
|
42829
43091
|
const shell = process.platform === "win32";
|
|
42830
43092
|
const quoted = shell ? args2.map((arg) => /\s/.test(arg) ? `"${arg}"` : arg) : args2;
|
|
42831
|
-
return
|
|
43093
|
+
return spawnSync2(bin, quoted, { encoding: "utf8", shell, timeout: 6e4 });
|
|
42832
43094
|
}
|
|
42833
43095
|
|
|
42834
43096
|
// src/commands/assets.ts
|
|
42835
|
-
var
|
|
43097
|
+
var import_picocolors8 = __toESM(require_picocolors(), 1);
|
|
42836
43098
|
import { createHash as createHash2 } from "node:crypto";
|
|
42837
43099
|
import { mkdir as mkdir4, readFile as readFile5, stat as stat2, writeFile as writeFile5 } from "node:fs/promises";
|
|
42838
43100
|
import { basename as basename2, dirname as dirname5, extname as extname2, isAbsolute, join as join7, relative as relative2, resolve as resolve6, sep as sep3 } from "node:path";
|
|
@@ -42873,17 +43135,17 @@ async function assetsSearch(ctx, q, options) {
|
|
|
42873
43135
|
for (const hit of found.hits) {
|
|
42874
43136
|
const size = hit.fit?.mb ? ` \xB7 ${hit.fit.mb} MB` : "";
|
|
42875
43137
|
ctx.out.line(
|
|
42876
|
-
`${
|
|
43138
|
+
`${import_picocolors8.default.bold(plain(hit.id))} ${import_picocolors8.default.dim(`${hit.type} \xB7 ${hit.license}${size}${hit.ai ? " \xB7 AI-generated" : ""}`)}`
|
|
42877
43139
|
);
|
|
42878
43140
|
ctx.out.line(` ${plain(hit.title)}`);
|
|
42879
|
-
if (hit.thumbnail_url) ctx.out.line(
|
|
43141
|
+
if (hit.thumbnail_url) ctx.out.line(import_picocolors8.default.dim(` ${plain(hit.thumbnail_url)}`));
|
|
42880
43142
|
}
|
|
42881
43143
|
ctx.out.line(
|
|
42882
|
-
|
|
43144
|
+
import_picocolors8.default.dim(
|
|
42883
43145
|
`${found.total} in all. Look at the pictures, as titles don't say how an asset looks, then pch assets add <id>\u2026`
|
|
42884
43146
|
)
|
|
42885
43147
|
);
|
|
42886
|
-
if (found.next_cursor) ctx.out.line(
|
|
43148
|
+
if (found.next_cursor) ctx.out.line(import_picocolors8.default.dim(`More: --cursor ${found.next_cursor}`));
|
|
42887
43149
|
});
|
|
42888
43150
|
}
|
|
42889
43151
|
async function assetsFacets(ctx, q, options) {
|
|
@@ -42895,11 +43157,11 @@ async function assetsFacets(ctx, q, options) {
|
|
|
42895
43157
|
async function assetsView(ctx, id) {
|
|
42896
43158
|
const asset = await unwrap(ctx.api.GET("/assets/{id}", { params: { path: { id } } }));
|
|
42897
43159
|
ctx.out.result(asset, () => {
|
|
42898
|
-
ctx.out.line(`${
|
|
43160
|
+
ctx.out.line(`${import_picocolors8.default.bold(plain(asset.title))} ${import_picocolors8.default.dim(asset.id)}`);
|
|
42899
43161
|
ctx.out.line(`${asset.type_label} \xB7 ${asset.license.spdx} \xB7 ${plain(asset.source.name)}`);
|
|
42900
43162
|
if (asset.description) ctx.out.line(plain(asset.description));
|
|
42901
43163
|
ctx.out.line(`Credit: ${plain(asset.credit)}`);
|
|
42902
|
-
ctx.out.line(
|
|
43164
|
+
ctx.out.line(import_picocolors8.default.dim(`Take it with pch assets add ${asset.id}`));
|
|
42903
43165
|
});
|
|
42904
43166
|
}
|
|
42905
43167
|
async function assetsAdd(ctx, ids, options) {
|
|
@@ -42980,13 +43242,13 @@ async function assetsAdd(ctx, ids, options) {
|
|
|
42980
43242
|
},
|
|
42981
43243
|
() => {
|
|
42982
43244
|
ctx.out.line(
|
|
42983
|
-
|
|
43245
|
+
import_picocolors8.default.green(`Copied ${copied.length} ${copied.length === 1 ? "file" : "files"} to ${root}.`)
|
|
42984
43246
|
);
|
|
42985
43247
|
for (const entry of usage3) {
|
|
42986
|
-
ctx.out.line(`${
|
|
43248
|
+
ctx.out.line(`${import_picocolors8.default.bold(entry.id)} is at ${entry.path}`);
|
|
42987
43249
|
ctx.out.line(` ${plain(entry.usage)}`);
|
|
42988
43250
|
}
|
|
42989
|
-
for (const warning of plan.warnings) ctx.out.line(
|
|
43251
|
+
for (const warning of plan.warnings) ctx.out.line(import_picocolors8.default.dim(plain(warning.msg)));
|
|
42990
43252
|
ctx.out.line("Load them by relative path, and commit them: the release carries its files.");
|
|
42991
43253
|
ctx.out.line(
|
|
42992
43254
|
`Their credits are in ${fromHere(creditsFile)}. Keep it in the release, with the licences, and show them on a credits screen in the game.`
|
|
@@ -43113,7 +43375,7 @@ async function saveChecked(url2, path2, sha256) {
|
|
|
43113
43375
|
}
|
|
43114
43376
|
|
|
43115
43377
|
// src/commands/catalog.ts
|
|
43116
|
-
var
|
|
43378
|
+
var import_picocolors9 = __toESM(require_picocolors(), 1);
|
|
43117
43379
|
import { randomUUID } from "node:crypto";
|
|
43118
43380
|
import { readFile as readFile6 } from "node:fs/promises";
|
|
43119
43381
|
init_errors();
|
|
@@ -43216,18 +43478,18 @@ async function projectCreate(ctx, ref, options) {
|
|
|
43216
43478
|
) : (await ctx.api.GET("/projects/{owner}/{slug}/repo", path2)).data;
|
|
43217
43479
|
const whoCanSee = repo?.visibility === "private" ? { who_can_see: PRIVATE_REPO } : {};
|
|
43218
43480
|
ctx.out.result({ ...project2, repo_visibility: repo?.visibility ?? null, ...whoCanSee }, () => {
|
|
43219
|
-
ctx.out.line(`Created ${
|
|
43481
|
+
ctx.out.line(`Created ${import_picocolors9.default.bold(project2.handle)}`);
|
|
43220
43482
|
ctx.out.line(
|
|
43221
43483
|
` Source: ${SOURCE_LABELS[project2.source_visibility]} \xB7 code ${project2.code_license} \xB7 art ${project2.art_license}`
|
|
43222
43484
|
);
|
|
43223
43485
|
if (repo) ctx.out.line(` Repo: ${VISIBILITY_LABELS[repo.visibility]}`);
|
|
43224
43486
|
if (repo?.visibility === "private") ctx.out.line(` ${PRIVATE_REPO_NOT_LOCKED}`);
|
|
43225
43487
|
ctx.out.line(
|
|
43226
|
-
|
|
43488
|
+
import_picocolors9.default.dim(
|
|
43227
43489
|
`Change these with pch project update ${project2.handle}${repo ? ` and pch set ${project2.handle} --visibility \u2026` : ""}.`
|
|
43228
43490
|
)
|
|
43229
43491
|
);
|
|
43230
|
-
ctx.out.line(
|
|
43492
|
+
ctx.out.line(import_picocolors9.default.dim(projectUrl(ctx.target.origin, { owner, slug: slug2 })));
|
|
43231
43493
|
});
|
|
43232
43494
|
}
|
|
43233
43495
|
async function projectView(ctx, ref) {
|
|
@@ -43250,7 +43512,7 @@ async function projectView(ctx, ref) {
|
|
|
43250
43512
|
openCode
|
|
43251
43513
|
].filter(Boolean).join(" ");
|
|
43252
43514
|
ctx.out.result({ ...project2, ...whoCanSee ? { who_can_see: whoCanSee } : {} }, () => {
|
|
43253
|
-
ctx.out.line(`${
|
|
43515
|
+
ctx.out.line(`${import_picocolors9.default.bold(project2.name)} ${import_picocolors9.default.dim(project2.handle)}`);
|
|
43254
43516
|
if (project2.summary) ctx.out.line(project2.summary);
|
|
43255
43517
|
const kind = project2.kind ? KIND_LABELS[project2.kind] : "not published yet";
|
|
43256
43518
|
const games = project2.host_games.map((game) => `${game.name} (${game.role})`).join(", ");
|
|
@@ -43281,7 +43543,7 @@ async function projectView(ctx, ref) {
|
|
|
43281
43543
|
`Source: ${repo ? VISIBILITY_LABELS[repo.visibility] : SOURCE_LABELS[project2.source_visibility]} \xB7 code ${project2.code_license} \xB7 art ${project2.art_license}`
|
|
43282
43544
|
);
|
|
43283
43545
|
if (repo?.visibility === "private" && !notLocked) ctx.out.line(PRIVATE_REPO_NOT_LOCKED);
|
|
43284
|
-
ctx.out.line(
|
|
43546
|
+
ctx.out.line(import_picocolors9.default.dim(projectUrl(ctx.target.origin, { owner, slug: slug2 })));
|
|
43285
43547
|
});
|
|
43286
43548
|
}
|
|
43287
43549
|
var LINK_NAMES = ["discord", "x", "telegram", "youtube", "twitch", "github"];
|
|
@@ -43317,8 +43579,8 @@ async function projectUpdate(ctx, ref, options) {
|
|
|
43317
43579
|
})
|
|
43318
43580
|
);
|
|
43319
43581
|
ctx.out.result(updated, () => {
|
|
43320
|
-
ctx.out.line(`Updated ${
|
|
43321
|
-
ctx.out.line(
|
|
43582
|
+
ctx.out.line(`Updated ${import_picocolors9.default.bold(updated.handle)}`);
|
|
43583
|
+
ctx.out.line(import_picocolors9.default.dim(updated.urls?.page ?? projectUrl(ctx.target.origin, updated)));
|
|
43322
43584
|
});
|
|
43323
43585
|
}
|
|
43324
43586
|
function parseLinks(values) {
|
|
@@ -43352,7 +43614,7 @@ async function myProjects(ctx) {
|
|
|
43352
43614
|
if (!data.length) ctx.out.line("No projects yet. Start one with pch new <you>/<slug>.");
|
|
43353
43615
|
for (const project2 of data) {
|
|
43354
43616
|
ctx.out.line(
|
|
43355
|
-
`${project2.handle.padEnd(32)} ${(project2.kind ?? "").padEnd(14)} ${project2.latest_version?.version ??
|
|
43617
|
+
`${project2.handle.padEnd(32)} ${(project2.kind ?? "").padEnd(14)} ${project2.latest_version?.version ?? import_picocolors9.default.dim("nothing published")}`
|
|
43356
43618
|
);
|
|
43357
43619
|
}
|
|
43358
43620
|
});
|
|
@@ -43392,41 +43654,41 @@ async function releaseList(ctx, ref) {
|
|
|
43392
43654
|
} while (cursor);
|
|
43393
43655
|
ctx.out.result({ data }, () => {
|
|
43394
43656
|
if (!data.length) ctx.out.line("No releases.");
|
|
43395
|
-
for (const
|
|
43657
|
+
for (const release3 of data) {
|
|
43396
43658
|
ctx.out.line(
|
|
43397
|
-
`${
|
|
43659
|
+
`${release3.version.padEnd(12)} ${release3.channel.padEnd(9)} ${release3.status.padEnd(10)} ${import_picocolors9.default.dim(release3.published_at ?? release3.created_at)}`
|
|
43398
43660
|
);
|
|
43399
43661
|
}
|
|
43400
43662
|
});
|
|
43401
43663
|
}
|
|
43402
43664
|
async function releaseView(ctx, ref) {
|
|
43403
43665
|
const { owner, slug: slug2, version: version2 } = parseReleaseRef(ref);
|
|
43404
|
-
const
|
|
43666
|
+
const release3 = await unwrap(
|
|
43405
43667
|
ctx.api.GET("/projects/{owner}/{slug}/releases/{version}", {
|
|
43406
43668
|
params: { path: { owner, slug: slug2, version: version2 } }
|
|
43407
43669
|
})
|
|
43408
43670
|
);
|
|
43409
|
-
const repo =
|
|
43410
|
-
const openCode = openCodeWords(`${owner}/${slug2}`, repo?.data,
|
|
43411
|
-
ctx.out.result({ ...
|
|
43412
|
-
ctx.out.line(`${
|
|
43671
|
+
const repo = release3.channel === "private" ? await ctx.api.GET("/projects/{owner}/{slug}/repo", { params: { path: { owner, slug: slug2 } } }).catch(() => null) : null;
|
|
43672
|
+
const openCode = openCodeWords(`${owner}/${slug2}`, repo?.data, release3);
|
|
43673
|
+
ctx.out.result({ ...release3, ...openCode ? { who_can_see: openCode } : {} }, () => {
|
|
43674
|
+
ctx.out.line(`${import_picocolors9.default.bold(`${release3.project}@${release3.version}`)} ${release3.kind ?? ""}`);
|
|
43413
43675
|
ctx.out.line(
|
|
43414
|
-
`Channel ${
|
|
43676
|
+
`Channel ${release3.channel} \xB7 ${release3.status} \xB7 ${TRUST_LABELS[release3.trust_tier]}`
|
|
43415
43677
|
);
|
|
43416
43678
|
if (openCode) ctx.out.line(openCode);
|
|
43417
|
-
if (
|
|
43418
|
-
ctx.out.line(`From commit ${
|
|
43679
|
+
if (release3.commit) {
|
|
43680
|
+
ctx.out.line(`From commit ${release3.commit}${release3.tag ? `, tag ${release3.tag}` : ""}`);
|
|
43419
43681
|
}
|
|
43420
|
-
for (const requirement2 of
|
|
43682
|
+
for (const requirement2 of release3.requirements) {
|
|
43421
43683
|
ctx.out.line(`Needs ${requirement2.name} (${requirement2.role})`);
|
|
43422
43684
|
}
|
|
43423
|
-
if (
|
|
43424
|
-
for (const file2 of
|
|
43685
|
+
if (release3.changelog) ctx.out.line(plain(release3.changelog));
|
|
43686
|
+
for (const file2 of release3.files) {
|
|
43425
43687
|
ctx.out.line(
|
|
43426
|
-
` ${file2.path.padEnd(40)} ${formatSize(file2.size).padStart(8)} ${
|
|
43688
|
+
` ${file2.path.padEnd(40)} ${formatSize(file2.size).padStart(8)} ${import_picocolors9.default.dim(file2.sha256)}`
|
|
43427
43689
|
);
|
|
43428
43690
|
if (file2.url) ctx.out.line(` ${file2.url}`);
|
|
43429
|
-
else if (file2.url === null) ctx.out.line(
|
|
43691
|
+
else if (file2.url === null) ctx.out.line(import_picocolors9.default.dim(" can't be had right now"));
|
|
43430
43692
|
}
|
|
43431
43693
|
});
|
|
43432
43694
|
}
|
|
@@ -43435,7 +43697,7 @@ async function hostGameSearch(ctx, q, limit2) {
|
|
|
43435
43697
|
ctx.out.result(games, () => {
|
|
43436
43698
|
if (!games.data.length) ctx.out.line("No host games found.");
|
|
43437
43699
|
for (const game of games.data) ctx.out.line(`${game.slug.padEnd(28)} ${game.name}`);
|
|
43438
|
-
if (games.next_cursor) ctx.out.line(
|
|
43700
|
+
if (games.next_cursor) ctx.out.line(import_picocolors9.default.dim("More match; narrow the search or raise --limit."));
|
|
43439
43701
|
});
|
|
43440
43702
|
}
|
|
43441
43703
|
var WIKI_PAGE = /^(engines|methods)\/([a-z0-9][a-z0-9-]*)(?:\.md)?$/;
|
|
@@ -43449,7 +43711,7 @@ function showWikiPage(ctx, page) {
|
|
|
43449
43711
|
page.build_seen && `build: ${page.build_seen}`
|
|
43450
43712
|
].filter(Boolean).join(" \xB7 ");
|
|
43451
43713
|
ctx.out.line();
|
|
43452
|
-
ctx.out.line(`${
|
|
43714
|
+
ctx.out.line(`${import_picocolors9.default.bold(page.path)} ${import_picocolors9.default.dim(plain(about))}`);
|
|
43453
43715
|
ctx.out.line(plain(page.text));
|
|
43454
43716
|
}
|
|
43455
43717
|
async function hostGameView(ctx, slug2) {
|
|
@@ -43477,13 +43739,13 @@ async function hostGameView(ctx, slug2) {
|
|
|
43477
43739
|
...unavailable ? { wiki_pages_unavailable: unavailable } : {}
|
|
43478
43740
|
};
|
|
43479
43741
|
ctx.out.result(result, () => {
|
|
43480
|
-
ctx.out.line(`${
|
|
43742
|
+
ctx.out.line(`${import_picocolors9.default.bold(game.name)} ${import_picocolors9.default.dim(game.slug)}`);
|
|
43481
43743
|
for (const store2 of game.stores) ctx.out.line(` ${store2.store} ${store2.ref}`);
|
|
43482
43744
|
for (const build of game.builds) {
|
|
43483
43745
|
ctx.out.line(` build ${build.store} ${build.build_ref} ${build.version_label ?? ""}`);
|
|
43484
43746
|
}
|
|
43485
|
-
if (!ctx.target.token) ctx.out.line(
|
|
43486
|
-
if (unavailable) ctx.out.line(
|
|
43747
|
+
if (!ctx.target.token) ctx.out.line(import_picocolors9.default.dim("Sign in with pch login for Patch's wiki pages."));
|
|
43748
|
+
if (unavailable) ctx.out.line(import_picocolors9.default.dim(`No wiki pages this time: ${plain(unavailable)}`));
|
|
43487
43749
|
for (const page of pages) showWikiPage(ctx, page);
|
|
43488
43750
|
});
|
|
43489
43751
|
}
|
|
@@ -43506,11 +43768,11 @@ async function noteSuggest(ctx, about, fact, options) {
|
|
|
43506
43768
|
}
|
|
43507
43769
|
const note2 = data;
|
|
43508
43770
|
ctx.out.result(note2, () => {
|
|
43509
|
-
ctx.out.line(`Suggested for ${
|
|
43771
|
+
ctx.out.line(`Suggested for ${import_picocolors9.default.bold(plain(note2.page))}. ${plain(note2.next)}`);
|
|
43510
43772
|
for (const finding of note2.findings) {
|
|
43511
43773
|
ctx.out.line(` ${finding.rule_id}: ${plain(finding.message)}`);
|
|
43512
|
-
if (finding.line) ctx.out.line(
|
|
43513
|
-
if (finding.suggestion) ctx.out.line(
|
|
43774
|
+
if (finding.line) ctx.out.line(import_picocolors9.default.dim(` ${plain(finding.line)}`));
|
|
43775
|
+
if (finding.suggestion) ctx.out.line(import_picocolors9.default.dim(` ${plain(finding.suggestion)}`));
|
|
43514
43776
|
}
|
|
43515
43777
|
});
|
|
43516
43778
|
}
|
|
@@ -43519,7 +43781,7 @@ function titleCase(slug2) {
|
|
|
43519
43781
|
}
|
|
43520
43782
|
|
|
43521
43783
|
// src/commands/change-requests.ts
|
|
43522
|
-
var
|
|
43784
|
+
var import_picocolors10 = __toESM(require_picocolors(), 1);
|
|
43523
43785
|
init_errors();
|
|
43524
43786
|
var handle = (ref) => `${ref.owner}/${ref.slug}`;
|
|
43525
43787
|
var counted = (count, thing) => `${count} ${thing}${count === 1 ? "" : "s"}`;
|
|
@@ -43561,7 +43823,7 @@ async function changeRequestOpen(ctx, ref, options) {
|
|
|
43561
43823
|
const name = `${handle(project2)}#${request.number}`;
|
|
43562
43824
|
out.result({ project: handle(project2), ...request, ...ahead.fields }, () => {
|
|
43563
43825
|
const shown = plain(request);
|
|
43564
|
-
out.line(`Opened ${
|
|
43826
|
+
out.line(`Opened ${import_picocolors10.default.bold(name)}: ${shown.title}`);
|
|
43565
43827
|
out.line(`${handle(from)}'s ${shown.branch} into ${shown.into}`);
|
|
43566
43828
|
showStanding(ctx, shown, ahead);
|
|
43567
43829
|
});
|
|
@@ -43637,19 +43899,19 @@ function updateAdvice(request, steps) {
|
|
|
43637
43899
|
];
|
|
43638
43900
|
}
|
|
43639
43901
|
function showStanding({ out }, request, { fields }) {
|
|
43640
|
-
if (fields.up_to_date === true) out.line(
|
|
43902
|
+
if (fields.up_to_date === true) out.line(import_picocolors10.default.green("Up to date: it can merge."));
|
|
43641
43903
|
if (fields.up_to_date === false) {
|
|
43642
|
-
out.line(
|
|
43904
|
+
out.line(import_picocolors10.default.yellow(`Out of date: ${outOfDate(request)}`));
|
|
43643
43905
|
if (fields.update) for (const line of updateAdvice(request, fields.update)) out.line(line);
|
|
43644
43906
|
}
|
|
43645
43907
|
if (fields.up_to_date === null && request.state === "open") {
|
|
43646
43908
|
out.line(
|
|
43647
|
-
|
|
43909
|
+
import_picocolors10.default.yellow("Patch can't tell whether it's up to date just now. Try again in a moment.")
|
|
43648
43910
|
);
|
|
43649
43911
|
}
|
|
43650
43912
|
if (request.problems.length) {
|
|
43651
43913
|
out.line(
|
|
43652
|
-
|
|
43914
|
+
import_picocolors10.default.yellow(
|
|
43653
43915
|
"The latest push to its branch hasn't reached this request, so it stays where it was:"
|
|
43654
43916
|
)
|
|
43655
43917
|
);
|
|
@@ -43684,20 +43946,20 @@ async function changeRequestList(ctx, ref, options) {
|
|
|
43684
43946
|
}
|
|
43685
43947
|
for (const request of plain(data)) {
|
|
43686
43948
|
const open3 = request.state === "open";
|
|
43687
|
-
const state = request.up_to_date === false ?
|
|
43949
|
+
const state = request.up_to_date === false ? import_picocolors10.default.yellow("out of date".padEnd(12)) : open3 && request.up_to_date === null ? import_picocolors10.default.yellow("not checked".padEnd(12)) : request.state.padEnd(12);
|
|
43688
43950
|
const from = request.from ? `${request.from} ${request.branch}` : request.branch;
|
|
43689
43951
|
out.line(
|
|
43690
|
-
`#${String(request.number).padEnd(4)} ${state} ${request.title} ${
|
|
43952
|
+
`#${String(request.number).padEnd(4)} ${state} ${request.title} ${import_picocolors10.default.dim(`(${from} \u2192 ${request.into})`)}`
|
|
43691
43953
|
);
|
|
43692
43954
|
}
|
|
43693
43955
|
if (data.some((request) => request.up_to_date === false)) {
|
|
43694
43956
|
out.line(
|
|
43695
|
-
|
|
43957
|
+
import_picocolors10.default.dim(
|
|
43696
43958
|
`Out of date ones can't merge yet. pch change-request view ${handle(project2)}#<number> says how to update one.`
|
|
43697
43959
|
)
|
|
43698
43960
|
);
|
|
43699
43961
|
}
|
|
43700
|
-
if (cursor) out.line(
|
|
43962
|
+
if (cursor) out.line(import_picocolors10.default.dim("There are more; raise --limit."));
|
|
43701
43963
|
});
|
|
43702
43964
|
}
|
|
43703
43965
|
async function mapInTurn(items, size, run3) {
|
|
@@ -43743,7 +44005,7 @@ async function readRequest(ctx, ref) {
|
|
|
43743
44005
|
function showHeader(ctx, project2, request, diff, fetch2) {
|
|
43744
44006
|
const { out } = ctx;
|
|
43745
44007
|
out.line(
|
|
43746
|
-
`${
|
|
44008
|
+
`${import_picocolors10.default.bold(`${handle(project2)}#${request.number}`)} ${request.title} ${import_picocolors10.default.dim(request.state)}`
|
|
43747
44009
|
);
|
|
43748
44010
|
const parts = [
|
|
43749
44011
|
`${request.from ? `${request.from}'s ` : ""}${request.branch} into ${request.into}`,
|
|
@@ -43762,15 +44024,15 @@ function showHeader(ctx, project2, request, diff, fetch2) {
|
|
|
43762
44024
|
} else if (request.closed_by) {
|
|
43763
44025
|
out.line(`${request.state === "declined" ? "Declined" : "Withdrawn"} by ${request.closed_by}`);
|
|
43764
44026
|
}
|
|
43765
|
-
if (fetch2) out.line(
|
|
44027
|
+
if (fetch2) out.line(import_picocolors10.default.dim(`To get its commits: ${fetch2}`));
|
|
43766
44028
|
}
|
|
43767
44029
|
function showTalk({ out }, talk) {
|
|
43768
44030
|
if (!talk.length) return;
|
|
43769
44031
|
out.line();
|
|
43770
|
-
out.line(
|
|
44032
|
+
out.line(import_picocolors10.default.bold("Talk"));
|
|
43771
44033
|
for (const post of talk) {
|
|
43772
44034
|
const who = `${post.author ?? "someone"}${post.via === "api_key" ? " (with a key)" : ""}`;
|
|
43773
|
-
out.line(`${
|
|
44035
|
+
out.line(`${import_picocolors10.default.dim(`${who}, ${post.created_at}`)}`);
|
|
43774
44036
|
out.line(post.body);
|
|
43775
44037
|
}
|
|
43776
44038
|
}
|
|
@@ -43803,18 +44065,18 @@ async function changeRequestView(ctx, ref) {
|
|
|
43803
44065
|
function showFiles({ out }, diff) {
|
|
43804
44066
|
if (tooBigToList(diff)) {
|
|
43805
44067
|
out.line();
|
|
43806
|
-
out.line(
|
|
44068
|
+
out.line(import_picocolors10.default.yellow("There's more in this request than can show here."));
|
|
43807
44069
|
}
|
|
43808
44070
|
if (!diff.files.length) return;
|
|
43809
44071
|
out.line();
|
|
43810
44072
|
for (const file2 of diff.files.slice(0, FILES_SHOWN)) {
|
|
43811
44073
|
out.line(
|
|
43812
|
-
`${file2.status.padEnd(9)} ${file2.path} ${
|
|
44074
|
+
`${file2.status.padEnd(9)} ${file2.path} ${import_picocolors10.default.dim(`+${file2.additions} -${file2.deletions}`)}`
|
|
43813
44075
|
);
|
|
43814
44076
|
}
|
|
43815
44077
|
if (diff.files.length > FILES_SHOWN) {
|
|
43816
44078
|
out.line(
|
|
43817
|
-
|
|
44079
|
+
import_picocolors10.default.dim(
|
|
43818
44080
|
`And ${diff.files.length - FILES_SHOWN} more: pch change-request review has them all.`
|
|
43819
44081
|
)
|
|
43820
44082
|
);
|
|
@@ -43834,7 +44096,7 @@ async function changeRequestReview(ctx, ref) {
|
|
|
43834
44096
|
}
|
|
43835
44097
|
if (shown.diff) showDiff(ctx, shown.diff, shown.fetch);
|
|
43836
44098
|
else
|
|
43837
|
-
out.line(
|
|
44099
|
+
out.line(import_picocolors10.default.yellow("Patch couldn't read what it changes just now. Try again in a moment."));
|
|
43838
44100
|
showTalk(ctx, shown.talk);
|
|
43839
44101
|
});
|
|
43840
44102
|
}
|
|
@@ -43846,30 +44108,30 @@ var NO_PATCH = {
|
|
|
43846
44108
|
};
|
|
43847
44109
|
function showDiff({ out }, diff, fetch2) {
|
|
43848
44110
|
out.line();
|
|
43849
|
-
out.line(
|
|
44111
|
+
out.line(import_picocolors10.default.bold("Commits"));
|
|
43850
44112
|
for (const commit of diff.commits) {
|
|
43851
|
-
out.line(`${
|
|
44113
|
+
out.line(`${import_picocolors10.default.dim(commit.id.slice(0, 7))} ${commit.subject} ${import_picocolors10.default.dim(commit.author)}`);
|
|
43852
44114
|
}
|
|
43853
44115
|
if (diff.commit_count > diff.commits.length) {
|
|
43854
|
-
out.line(
|
|
44116
|
+
out.line(import_picocolors10.default.dim(`The newest ${diff.commits.length} of ${diff.commit_count}.`));
|
|
43855
44117
|
}
|
|
43856
44118
|
for (const file2 of diff.files) {
|
|
43857
44119
|
const moved2 = file2.old_path ? `${file2.old_path} \u2192 ` : "";
|
|
43858
44120
|
out.line();
|
|
43859
44121
|
out.line(
|
|
43860
|
-
`${
|
|
44122
|
+
`${import_picocolors10.default.bold(`${moved2}${file2.path}`)} ${import_picocolors10.default.dim(`${file2.status}, +${file2.additions} -${file2.deletions}`)}`
|
|
43861
44123
|
);
|
|
43862
44124
|
if (file2.big_file) {
|
|
43863
44125
|
const size = (side) => side ? formatSize(side.size) : "nothing";
|
|
43864
44126
|
out.line(`Big file: ${size(file2.big_file.old)} \u2192 ${size(file2.big_file.new)}`);
|
|
43865
44127
|
}
|
|
43866
44128
|
if (file2.patch) out.line(file2.patch.trimEnd());
|
|
43867
|
-
else if (file2.no_patch) out.line(
|
|
44129
|
+
else if (file2.no_patch) out.line(import_picocolors10.default.dim(NO_PATCH[file2.no_patch]));
|
|
43868
44130
|
}
|
|
43869
44131
|
if (diff.truncated) {
|
|
43870
44132
|
out.line();
|
|
43871
44133
|
out.line(
|
|
43872
|
-
|
|
44134
|
+
import_picocolors10.default.yellow(
|
|
43873
44135
|
tooBigToList(diff) ? `None of it can be shown here: there's more in this request than can show.${fetch2 ? ` To read it yourself: ${fetch2}` : ""}` : "Some of it isn't shown: patches stop at 300 files or 2 MiB, and the list at 3,000."
|
|
43874
44136
|
)
|
|
43875
44137
|
);
|
|
@@ -43911,7 +44173,7 @@ async function changeRequestMerge(ctx, ref, options) {
|
|
|
43911
44173
|
const request = merged.data;
|
|
43912
44174
|
out.result({ project: handle(project2), ...request }, () => {
|
|
43913
44175
|
const at = request.merge_commit ? ` at ${request.merge_commit.slice(0, 7)}` : "";
|
|
43914
|
-
out.line(`Merged ${
|
|
44176
|
+
out.line(`Merged ${import_picocolors10.default.bold(`${handle(project2)}#${number4}`)} into ${request.into}${at}.`);
|
|
43915
44177
|
});
|
|
43916
44178
|
}
|
|
43917
44179
|
async function cantMergeYet(ctx, project2, number4) {
|
|
@@ -43941,7 +44203,7 @@ async function changeRequestDecline(ctx, ref) {
|
|
|
43941
44203
|
})
|
|
43942
44204
|
);
|
|
43943
44205
|
out.result({ project: handle(project2), ...request }, () => {
|
|
43944
|
-
out.line(`Declined ${
|
|
44206
|
+
out.line(`Declined ${import_picocolors10.default.bold(`${handle(project2)}#${number4}`)}.`);
|
|
43945
44207
|
});
|
|
43946
44208
|
}
|
|
43947
44209
|
async function changeRequestWithdraw(ctx, ref) {
|
|
@@ -43954,12 +44216,12 @@ async function changeRequestWithdraw(ctx, ref) {
|
|
|
43954
44216
|
})
|
|
43955
44217
|
);
|
|
43956
44218
|
out.result({ project: handle(project2), ...request }, () => {
|
|
43957
|
-
out.line(`Withdrew ${
|
|
44219
|
+
out.line(`Withdrew ${import_picocolors10.default.bold(`${handle(project2)}#${number4}`)}.`);
|
|
43958
44220
|
});
|
|
43959
44221
|
}
|
|
43960
44222
|
|
|
43961
44223
|
// src/commands/check.ts
|
|
43962
|
-
var
|
|
44224
|
+
var import_picocolors11 = __toESM(require_picocolors(), 1);
|
|
43963
44225
|
import { existsSync as existsSync4 } from "node:fs";
|
|
43964
44226
|
import { mkdir as mkdir6, mkdtemp as mkdtemp2 } from "node:fs/promises";
|
|
43965
44227
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
@@ -45170,10 +45432,10 @@ function gpuName(renderer) {
|
|
|
45170
45432
|
init_errors();
|
|
45171
45433
|
var usage2 = (code, message) => new CliError(EXIT.usage, code, message);
|
|
45172
45434
|
var SAYS = {
|
|
45173
|
-
pass: (text) =>
|
|
45174
|
-
fail: (text) =>
|
|
45175
|
-
warn: (text) =>
|
|
45176
|
-
cant_check: (text) =>
|
|
45435
|
+
pass: (text) => import_picocolors11.default.green(text),
|
|
45436
|
+
fail: (text) => import_picocolors11.default.red(text),
|
|
45437
|
+
warn: (text) => import_picocolors11.default.yellow(text),
|
|
45438
|
+
cant_check: (text) => import_picocolors11.default.yellow(text)
|
|
45177
45439
|
};
|
|
45178
45440
|
var LABELS = {
|
|
45179
45441
|
pass: "pass ",
|
|
@@ -45181,7 +45443,7 @@ var LABELS = {
|
|
|
45181
45443
|
warn: "look ",
|
|
45182
45444
|
cant_check: "can't"
|
|
45183
45445
|
};
|
|
45184
|
-
var ADVICE = "This is advice: nothing here stops you publishing, and it doesn't judge what kind of game this is. Patch's own test runs in Patch Desktop
|
|
45446
|
+
var ADVICE = "This is advice: nothing here stops you publishing, and it doesn't judge what kind of game this is. Patch's own test runs in Patch Desktop, as pch publish does it.";
|
|
45185
45447
|
async function findGame(dir) {
|
|
45186
45448
|
const root = resolve8(dir);
|
|
45187
45449
|
if (existsSync4(join12(root, "patch.json"))) {
|
|
@@ -45189,7 +45451,7 @@ async function findGame(dir) {
|
|
|
45189
45451
|
if (project2.recipe.mode !== "browser") {
|
|
45190
45452
|
throw usage2(
|
|
45191
45453
|
"not_a_browser_game",
|
|
45192
|
-
`This is a ${project2.kind}, which runs in its game, so a browser can't see it. pch
|
|
45454
|
+
`This is a ${project2.kind}, which runs in its game, so a browser can't see it. pch publish tries it on your PC, in Patch Desktop.`
|
|
45193
45455
|
);
|
|
45194
45456
|
}
|
|
45195
45457
|
const launch2 = project2.recipe.launch;
|
|
@@ -45292,23 +45554,23 @@ async function check2(ctx, dir, options) {
|
|
|
45292
45554
|
},
|
|
45293
45555
|
() => {
|
|
45294
45556
|
const on = rendering.software ? "no GPU, in software" : rendering.webgl ? gpuName(rendering.webgl) : "no WebGL";
|
|
45295
|
-
out.line(`Checked ${
|
|
45557
|
+
out.line(`Checked ${import_picocolors11.default.bold(game.entry)} in ${version2.product} with ${on}.`);
|
|
45296
45558
|
out.line();
|
|
45297
45559
|
for (const entry of checks) {
|
|
45298
45560
|
out.line(` ${SAYS[entry.result](LABELS[entry.result])} ${entry.detail}`);
|
|
45299
45561
|
}
|
|
45300
45562
|
if (frames) {
|
|
45301
45563
|
out.line(
|
|
45302
|
-
` ${
|
|
45564
|
+
` ${import_picocolors11.default.dim("note ")} ${frames.perSecond} frames a second over ${frames.seconds} s, the longest ${frames.longestMs} ms, ${frames.slow} over 100 ms. That's only for you: no frame rate fails anything.`
|
|
45303
45565
|
);
|
|
45304
45566
|
}
|
|
45305
45567
|
if (observed.changedWithInput !== null && observed.changedWithInput < 1e-3) {
|
|
45306
45568
|
out.line(
|
|
45307
|
-
` ${
|
|
45569
|
+
` ${import_picocolors11.default.dim("note ")} The picture didn't change between the first look and the second, with input going in. It may be waiting for something scripted input doesn't do.`
|
|
45308
45570
|
);
|
|
45309
45571
|
}
|
|
45310
45572
|
for (const entry of observed.errors.slice(1, 5)) {
|
|
45311
|
-
out.line(
|
|
45573
|
+
out.line(import_picocolors11.default.dim(` also: ${entry.message}`));
|
|
45312
45574
|
}
|
|
45313
45575
|
out.line();
|
|
45314
45576
|
out.line("Look at these, as a look is what can tell you it's right:");
|
|
@@ -45321,7 +45583,7 @@ async function check2(ctx, dir, options) {
|
|
|
45321
45583
|
printModels(ctx, models);
|
|
45322
45584
|
}
|
|
45323
45585
|
out.line();
|
|
45324
|
-
out.line(
|
|
45586
|
+
out.line(import_picocolors11.default.dim(ADVICE));
|
|
45325
45587
|
}
|
|
45326
45588
|
);
|
|
45327
45589
|
return failed3.length || brokenModels.length ? EXIT.blocked : EXIT.ok;
|
|
@@ -45334,8 +45596,8 @@ async function serve(ctx, game) {
|
|
|
45334
45596
|
const server = await serveGame(game.root, game.files);
|
|
45335
45597
|
const url2 = `${server.origin}/${game.entry.split("/").map(encodeURIComponent).join("/")}`;
|
|
45336
45598
|
ctx.out.result({ url: url2, folder: game.root }, () => {
|
|
45337
|
-
ctx.out.line(`Serving ${game.root} as Patch's game host does, at ${
|
|
45338
|
-
ctx.out.line(
|
|
45599
|
+
ctx.out.line(`Serving ${game.root} as Patch's game host does, at ${import_picocolors11.default.bold(url2)}`);
|
|
45600
|
+
ctx.out.line(import_picocolors11.default.dim("Paths have to match exactly here, as they do on Patch. Ctrl+C stops it."));
|
|
45339
45601
|
});
|
|
45340
45602
|
return EXIT.ok;
|
|
45341
45603
|
}
|
|
@@ -45356,23 +45618,23 @@ function showModels(ctx, models) {
|
|
|
45356
45618
|
function printModels(ctx, models) {
|
|
45357
45619
|
for (const model of models) {
|
|
45358
45620
|
if (model.error) {
|
|
45359
|
-
ctx.out.line(` ${
|
|
45621
|
+
ctx.out.line(` ${import_picocolors11.default.red("FAIL ")} ${model.file}: ${model.error}`);
|
|
45360
45622
|
continue;
|
|
45361
45623
|
}
|
|
45362
45624
|
const textures = model.textures ?? [];
|
|
45363
45625
|
const size = textures.reduce((sum, texture) => sum + texture.size, 0);
|
|
45364
45626
|
ctx.out.line(
|
|
45365
|
-
` ${
|
|
45627
|
+
` ${import_picocolors11.default.green("read ")} ${model.file}: ${formatSize(model.size)}, ${model.triangles} triangles, ${model.meshes} meshes (${model.skinned} skinned), ${model.materials} materials, ${textures.length} textures (${formatSize(size)}), ${model.animations?.length} animations`
|
|
45366
45628
|
);
|
|
45367
45629
|
if (model.needs?.length) {
|
|
45368
|
-
ctx.out.line(
|
|
45630
|
+
ctx.out.line(import_picocolors11.default.dim(` needs a loader with ${model.needs.join(", ")}`));
|
|
45369
45631
|
}
|
|
45370
45632
|
}
|
|
45371
|
-
ctx.out.line(
|
|
45633
|
+
ctx.out.line(import_picocolors11.default.dim(" Read, not drawn: what they look like takes a look in the game."));
|
|
45372
45634
|
}
|
|
45373
45635
|
|
|
45374
45636
|
// src/commands/connect.ts
|
|
45375
|
-
var
|
|
45637
|
+
var import_picocolors15 = __toESM(require_picocolors(), 1);
|
|
45376
45638
|
import { readFile as readFile12, writeFile as writeFile10 } from "node:fs/promises";
|
|
45377
45639
|
import { join as join15, resolve as resolve11 } from "node:path";
|
|
45378
45640
|
import { setTimeout as sleep5 } from "node:timers/promises";
|
|
@@ -45419,7 +45681,7 @@ function sameName(a, b) {
|
|
|
45419
45681
|
}
|
|
45420
45682
|
|
|
45421
45683
|
// src/commands/repos.ts
|
|
45422
|
-
var
|
|
45684
|
+
var import_picocolors14 = __toESM(require_picocolors(), 1);
|
|
45423
45685
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
45424
45686
|
import { mkdir as mkdir7, readdir as readdir3, readFile as readFile11, rm as rm2, writeFile as writeFile9 } from "node:fs/promises";
|
|
45425
45687
|
import { join as join14, resolve as resolve10 } from "node:path";
|
|
@@ -45489,39 +45751,29 @@ function reviewing(progress) {
|
|
|
45489
45751
|
const usual = `usually about ${minutes(progress.usual_minutes)}, ${soFar}`;
|
|
45490
45752
|
return progress.slow ? `Patch is reviewing it: ${step}. It's taking longer than usual: ${usual}. It finishes by itself.` : `Patch is reviewing it: ${step}. ${capital(usual)}.`;
|
|
45491
45753
|
}
|
|
45492
|
-
function nextStep(
|
|
45493
|
-
const
|
|
45494
|
-
const startOne2 = `pch test start ${release2}`;
|
|
45754
|
+
function nextStep(release3, status2, test2, cloud) {
|
|
45755
|
+
const carryOn = `pch publish ${release3}`;
|
|
45495
45756
|
switch (status2) {
|
|
45496
45757
|
case "draft":
|
|
45497
|
-
return {
|
|
45498
|
-
say: "It's a private draft, and it can't be published until it has passed a test on your PC, in Patch Desktop, which someone has to press Start in.",
|
|
45499
|
-
command: startOne2
|
|
45500
|
-
};
|
|
45758
|
+
return { say: "It's uploaded, and its test comes next.", command: carryOn };
|
|
45501
45759
|
case "testing":
|
|
45502
45760
|
switch (test2?.state) {
|
|
45503
45761
|
case "waiting":
|
|
45504
|
-
return {
|
|
45505
|
-
say: "Its test is waiting for you: press Start on it in Patch Desktop.",
|
|
45506
|
-
command: follow4
|
|
45507
|
-
};
|
|
45762
|
+
return { say: "Its test is waiting to start in Patch Desktop.", command: carryOn };
|
|
45508
45763
|
case "running":
|
|
45509
45764
|
return {
|
|
45510
45765
|
say: `Patch Desktop${test2.pc_name ? ` on ${test2.pc_name}` : ""} is playing it.`,
|
|
45511
|
-
command:
|
|
45766
|
+
command: carryOn
|
|
45512
45767
|
};
|
|
45513
45768
|
case "recorded":
|
|
45514
45769
|
return {
|
|
45515
|
-
say: "It
|
|
45516
|
-
command:
|
|
45770
|
+
say: "It's recorded. Look at the footage, then send your review.",
|
|
45771
|
+
command: carryOn
|
|
45517
45772
|
};
|
|
45518
45773
|
case "passed":
|
|
45519
|
-
return {
|
|
45520
|
-
say: "It passed its test, and it's still private. Publish it once you're happy it's ready.",
|
|
45521
|
-
command: `pch release submit ${release2}`
|
|
45522
|
-
};
|
|
45774
|
+
return { say: "It passed its test.", command: carryOn };
|
|
45523
45775
|
default:
|
|
45524
|
-
return { say: "It needs a test that passes.", command:
|
|
45776
|
+
return { say: "It needs a test that passes.", command: carryOn };
|
|
45525
45777
|
}
|
|
45526
45778
|
case "rejected":
|
|
45527
45779
|
return {
|
|
@@ -45530,14 +45782,11 @@ function nextStep(release2, status2, test2, cloud) {
|
|
|
45530
45782
|
};
|
|
45531
45783
|
case "checking":
|
|
45532
45784
|
case "cloud_review":
|
|
45533
|
-
return {
|
|
45534
|
-
say: reviewing(cloud) ?? "Patch is reviewing it, which can take several minutes, and nothing tells you when it's done: pch status says.",
|
|
45535
|
-
command: `pch status ${release2} --wait 50`
|
|
45536
|
-
};
|
|
45785
|
+
return { say: reviewing(cloud) ?? "Patch is reviewing it.", command: carryOn };
|
|
45537
45786
|
case "in_review":
|
|
45538
45787
|
return {
|
|
45539
45788
|
say: "It's waiting for a staff member at Patch, and nothing tells you when that's done: pch status says.",
|
|
45540
|
-
command: `pch status ${
|
|
45789
|
+
command: `pch status ${release3} --wait 50`
|
|
45541
45790
|
};
|
|
45542
45791
|
case "published":
|
|
45543
45792
|
return { say: "It's published.", command: null };
|
|
@@ -45548,11 +45797,11 @@ function nextStep(release2, status2, test2, cloud) {
|
|
|
45548
45797
|
var PLAY_FIRST = "This would be its first public release, and a passed test shows it starts and runs, not that it plays well, so play it yourself first: pch guide says how.";
|
|
45549
45798
|
async function playFirst(ctx, ref) {
|
|
45550
45799
|
const { owner, slug: slug2 } = ref;
|
|
45551
|
-
const [
|
|
45800
|
+
const [release3, project2] = await Promise.all([
|
|
45552
45801
|
ctx.api.GET("/projects/{owner}/{slug}/releases/{version}", { params: { path: ref } }),
|
|
45553
45802
|
ctx.api.GET("/projects/{owner}/{slug}", { params: { path: { owner, slug: slug2 } } })
|
|
45554
45803
|
]);
|
|
45555
|
-
const first =
|
|
45804
|
+
const first = release3.data?.channel === "public" && release3.data.kind !== "block" && project2.data && !project2.data.latest_release;
|
|
45556
45805
|
return first ? PLAY_FIRST : null;
|
|
45557
45806
|
}
|
|
45558
45807
|
function sayNext(next) {
|
|
@@ -45597,7 +45846,7 @@ async function versionTaken(ctx, ref, failure2) {
|
|
|
45597
45846
|
}
|
|
45598
45847
|
|
|
45599
45848
|
// src/commands/init.ts
|
|
45600
|
-
var
|
|
45849
|
+
var import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
45601
45850
|
import { readFile as readFile10, stat as stat4, writeFile as writeFile8 } from "node:fs/promises";
|
|
45602
45851
|
import { basename as basename4, join as join13, resolve as resolve9 } from "node:path";
|
|
45603
45852
|
init_errors();
|
|
@@ -45645,17 +45894,17 @@ async function init(ctx, dir, options) {
|
|
|
45645
45894
|
const agentGuide2 = { path: join13(root, guide2.name), written: guide2.written };
|
|
45646
45895
|
const result = { path: path2, ...contents, on_patch: onPatch2, agent_guide: agentGuide2 };
|
|
45647
45896
|
ctx.out.result({ ...result, next, look_first: LOOK_FIRST }, () => {
|
|
45648
|
-
ctx.out.line(`Wrote ${
|
|
45897
|
+
ctx.out.line(`Wrote ${import_picocolors12.default.bold(path2)} for ${import_picocolors12.default.bold(name)}.`);
|
|
45649
45898
|
ctx.out.line(
|
|
45650
45899
|
guide2.written ? `Wrote ${guide2.name}, for coding agents working in this folder.` : `Left ${guide2.name} as it is: it's this folder's guide for agents.`
|
|
45651
45900
|
);
|
|
45652
|
-
ctx.out.line(
|
|
45653
|
-
if (files) ctx.out.line(
|
|
45901
|
+
ctx.out.line(import_picocolors12.default.dim(BRIEF_WORDS));
|
|
45902
|
+
if (files) ctx.out.line(import_picocolors12.default.dim(`The release is what's in ${files}/.`));
|
|
45654
45903
|
ctx.out.line(LOOK_FIRST);
|
|
45655
45904
|
ctx.out.line("Then:");
|
|
45656
45905
|
for (const step of next) ctx.out.line(` ${step}`);
|
|
45657
45906
|
ctx.out.line(
|
|
45658
|
-
|
|
45907
|
+
import_picocolors12.default.dim(
|
|
45659
45908
|
`A browser game whose page is index.html needs no recipe. A test's length and your credits go in one, in patch.json: ${RECIPE_EXAMPLE}. For anything else: pch guide`
|
|
45660
45909
|
)
|
|
45661
45910
|
);
|
|
@@ -45698,7 +45947,7 @@ async function existsOnPatch(ctx, name) {
|
|
|
45698
45947
|
}
|
|
45699
45948
|
|
|
45700
45949
|
// src/commands/scan.ts
|
|
45701
|
-
var
|
|
45950
|
+
var import_picocolors13 = __toESM(require_picocolors(), 1);
|
|
45702
45951
|
import { setTimeout as sleep3 } from "node:timers/promises";
|
|
45703
45952
|
init_errors();
|
|
45704
45953
|
var handle2 = (ref) => `${ref.owner}/${ref.slug}`;
|
|
@@ -45761,19 +46010,19 @@ function show(ctx, project2, scan2) {
|
|
|
45761
46010
|
out.result({ project: name, ...scan2 }, () => {
|
|
45762
46011
|
if (scan2.status === "running") {
|
|
45763
46012
|
out.line(
|
|
45764
|
-
`${
|
|
46013
|
+
`${import_picocolors13.default.bold(name)}: still reading its history. Follow it: pch scan view ${name} --wait`
|
|
45765
46014
|
);
|
|
45766
46015
|
return;
|
|
45767
46016
|
}
|
|
45768
46017
|
if (!scan2.up_to_date) {
|
|
45769
46018
|
out.line(
|
|
45770
|
-
|
|
45771
|
-
`${
|
|
46019
|
+
import_picocolors13.default.yellow(
|
|
46020
|
+
`${import_picocolors13.default.bold(name)}: a push has landed since this scan started, so it says nothing about what the repo holds now. Start a new one: ${start}`
|
|
45772
46021
|
)
|
|
45773
46022
|
);
|
|
45774
46023
|
}
|
|
45775
46024
|
if (scan2.status === "failed") {
|
|
45776
|
-
out.line(
|
|
46025
|
+
out.line(import_picocolors13.default.yellow(`${import_picocolors13.default.bold(name)}: the scan didn't finish, so it can't go open yet.`));
|
|
45777
46026
|
if (scan2.failure) out.line(plain(scan2.failure));
|
|
45778
46027
|
out.line(`Start a new one: ${start}`);
|
|
45779
46028
|
return;
|
|
@@ -45790,7 +46039,7 @@ function show(ctx, project2, scan2) {
|
|
|
45790
46039
|
function showClean({ out }, name, scan2) {
|
|
45791
46040
|
const big = scan2.will_become_public?.big_files;
|
|
45792
46041
|
const where = !big?.count || big.scanned ? "in its whole history" : "in what it read";
|
|
45793
|
-
out.line(`${
|
|
46042
|
+
out.line(`${import_picocolors13.default.bold(name)}: ${import_picocolors13.default.green("no secrets found")} ${where}.`);
|
|
45794
46043
|
if (!scan2.up_to_date) return;
|
|
45795
46044
|
const open3 = `pch set ${name} --visibility open`;
|
|
45796
46045
|
const unread = big?.not_read?.count;
|
|
@@ -45800,18 +46049,18 @@ function showClean({ out }, name, scan2) {
|
|
|
45800
46049
|
}
|
|
45801
46050
|
function showSecrets({ out }, name, scan2) {
|
|
45802
46051
|
out.line(
|
|
45803
|
-
`${
|
|
46052
|
+
`${import_picocolors13.default.bold(name)}: ${import_picocolors13.default.red(`${counted2(scan2.secret_count, "secret")} found`)}, which going open would publish.`
|
|
45804
46053
|
);
|
|
45805
46054
|
for (const secret of plain(scan2.secrets)) {
|
|
45806
46055
|
const where = secret.path ? `${secret.path}:${secret.line}` : `its message, line ${secret.line}`;
|
|
45807
46056
|
const commit = secret.commit ? `${secret.commit.slice(0, 7)} ` : "";
|
|
45808
|
-
out.line(` ${
|
|
46057
|
+
out.line(` ${import_picocolors13.default.dim(commit)}${where}: ${secret.rule} (${secret.masked})`);
|
|
45809
46058
|
}
|
|
45810
46059
|
if (scan2.secrets.length < scan2.secret_count) {
|
|
45811
|
-
out.line(
|
|
46060
|
+
out.line(import_picocolors13.default.dim(` The first ${scan2.secrets.length} of them.`));
|
|
45812
46061
|
}
|
|
45813
46062
|
out.line();
|
|
45814
|
-
out.line(
|
|
46063
|
+
out.line(import_picocolors13.default.bold("How to take them out of its history:"));
|
|
45815
46064
|
for (const step of plain(scan2.fix)) {
|
|
45816
46065
|
out.line(step.step);
|
|
45817
46066
|
for (const command of step.commands) out.line(` ${command}`);
|
|
@@ -45823,7 +46072,7 @@ function showWhatsPublished({ out }, scan2) {
|
|
|
45823
46072
|
scan2.will_become_public
|
|
45824
46073
|
);
|
|
45825
46074
|
out.line();
|
|
45826
|
-
out.line(
|
|
46075
|
+
out.line(import_picocolors13.default.bold("Going open publishes:"));
|
|
45827
46076
|
const unread = big_files.not_read?.count;
|
|
45828
46077
|
const bigFiles = big_files.count ? [
|
|
45829
46078
|
`${counted2(big_files.count, "big file")} (${formatSize(big_files.total_size)}${unread ? `, ${unread} not read` : ""})`
|
|
@@ -45839,14 +46088,14 @@ function showWhatsPublished({ out }, scan2) {
|
|
|
45839
46088
|
);
|
|
45840
46089
|
if (big_files.count) {
|
|
45841
46090
|
const said2 = big_files.coverage ?? "This answer doesn't say whether they were read for secrets.";
|
|
45842
|
-
out.line(unread ?
|
|
46091
|
+
out.line(unread ? import_picocolors13.default.yellow(said2) : said2);
|
|
45843
46092
|
}
|
|
45844
46093
|
out.line("Everyone in its history, with the emails git history publishes:");
|
|
45845
46094
|
for (const person of authors.people.slice(0, PEOPLE_SHOWN)) {
|
|
45846
46095
|
out.line(` ${person.name} <${person.email}>`);
|
|
45847
46096
|
}
|
|
45848
46097
|
if (authors.count > PEOPLE_SHOWN) {
|
|
45849
|
-
out.line(
|
|
46098
|
+
out.line(import_picocolors13.default.dim(` And ${authors.count - PEOPLE_SHOWN} more: --json has them all.`));
|
|
45850
46099
|
}
|
|
45851
46100
|
}
|
|
45852
46101
|
|
|
@@ -45937,17 +46186,17 @@ async function newRepo(ctx, ref, dir, options) {
|
|
|
45937
46186
|
...options.visibility === "private" ? { who_can_see: PRIVATE_REPO } : {}
|
|
45938
46187
|
},
|
|
45939
46188
|
() => {
|
|
45940
|
-
out.line(`Made ${
|
|
46189
|
+
out.line(`Made ${import_picocolors14.default.bold(handle3(project2))} in ${folder}, under the ${licenceName(licence)}.`);
|
|
45941
46190
|
out.line(
|
|
45942
46191
|
options.visibility === "private" ? `Its repo is private: only your team can find it or read its source. ${PRIVATE_REPO_NOT_LOCKED}` : options.visibility && options.visibility !== "open" ? `Its repo is ${options.visibility}.` : "Its repo opens by itself once that scan is clean: then anyone can read, clone and remix it, with what you push. To keep it to your team: pch set --visibility private."
|
|
45943
46192
|
);
|
|
45944
46193
|
if (commitsAs) out.line(saidCommitsAs(commitsAs));
|
|
45945
46194
|
out.line(`${AGENT_GUIDE_FILE}, in its first commit, tells coding agents how to work here.`);
|
|
45946
|
-
out.line(
|
|
46195
|
+
out.line(import_picocolors14.default.dim(BRIEF_WORDS));
|
|
45947
46196
|
out.line(LOOK_FIRST);
|
|
45948
46197
|
out.line("Fill in README.md, add your work, then commit and push with plain git.");
|
|
45949
46198
|
out.line(`To release it: pch validate, then pch publish, in ${folder}.`);
|
|
45950
|
-
out.line(
|
|
46199
|
+
out.line(import_picocolors14.default.dim(url2));
|
|
45951
46200
|
}
|
|
45952
46201
|
);
|
|
45953
46202
|
}
|
|
@@ -46018,7 +46267,7 @@ async function remix(ctx, ref, options) {
|
|
|
46018
46267
|
...address ? { address } : {}
|
|
46019
46268
|
};
|
|
46020
46269
|
out.result(said2, () => {
|
|
46021
|
-
out.line(`Remixed ${
|
|
46270
|
+
out.line(`Remixed ${import_picocolors14.default.bold(handle3(from))} to ${import_picocolors14.default.bold(name)}.`);
|
|
46022
46271
|
if (address) out.line(address);
|
|
46023
46272
|
out.line(words2 ?? `Its repo is ${repo.visibility}.`);
|
|
46024
46273
|
if (ready) out.line(`Clone it with pch clone ${name}`);
|
|
@@ -46026,7 +46275,7 @@ async function remix(ctx, ref, options) {
|
|
|
46026
46275
|
out.line(
|
|
46027
46276
|
`A patch.json in it still names ${handle3(from)}: change "project" to ${name} before you publish.`
|
|
46028
46277
|
);
|
|
46029
|
-
out.line(
|
|
46278
|
+
out.line(import_picocolors14.default.dim(repo.clone_url));
|
|
46030
46279
|
});
|
|
46031
46280
|
}
|
|
46032
46281
|
async function yourOrg(ctx, what) {
|
|
@@ -46068,23 +46317,23 @@ async function status(ctx, ref, options) {
|
|
|
46068
46317
|
const repoAnswer = await api.GET("/projects/{owner}/{slug}/repo", { params: { path: project2 } });
|
|
46069
46318
|
const repo = repoAnswer.response.ok ? repoAnswer.data : null;
|
|
46070
46319
|
const latest2 = version2 ?? await latestVersion(ctx, project2, repo);
|
|
46071
|
-
const
|
|
46072
|
-
const next =
|
|
46073
|
-
const standing2 = repo?.visibility === "open" &&
|
|
46074
|
-
params: { path: { ...project2, version:
|
|
46320
|
+
const release3 = latest2 ? await releaseStanding(ctx, project2, latest2, options.wait) : null;
|
|
46321
|
+
const next = release3 && "next" in release3 ? nextStep(`${handle3(project2)}@${release3.version}`, release3.status, release3.test).command : null;
|
|
46322
|
+
const standing2 = repo?.visibility === "open" && release3 && "next" in release3 ? await api.GET("/projects/{owner}/{slug}/releases/{version}", {
|
|
46323
|
+
params: { path: { ...project2, version: release3.version } }
|
|
46075
46324
|
}) : null;
|
|
46076
46325
|
const openCode = standing2?.data ? openCodeWords(handle3(project2), repo, standing2.data) : null;
|
|
46077
46326
|
const whoCanSee = repo?.visibility === "private" ? { who_can_see: PRIVATE_REPO } : openCode ? { who_can_see: openCode } : {};
|
|
46078
|
-
const play2 =
|
|
46327
|
+
const play2 = release3 && "next" in release3 && release3.status === "testing" && release3.test?.state === "passed" ? await playFirst(ctx, { ...project2, version: release3.version }) : null;
|
|
46079
46328
|
const ask = play2 ? { ask: play2 } : {};
|
|
46080
|
-
out.result({ project: handle3(project2), repo, release:
|
|
46081
|
-
out.line(
|
|
46329
|
+
out.result({ project: handle3(project2), repo, release: release3, next, ...whoCanSee, ...ask }, () => {
|
|
46330
|
+
out.line(import_picocolors14.default.bold(handle3(project2)));
|
|
46082
46331
|
if (repo) out.line(repoLine(repo));
|
|
46083
46332
|
if (repo?.visibility === "private") out.line(PRIVATE_REPO);
|
|
46084
46333
|
if (repo?.home === "github") out.line(homeLine(repo.github));
|
|
46085
|
-
if (!
|
|
46086
|
-
else if ("next" in
|
|
46087
|
-
else out.line(`Release ${
|
|
46334
|
+
if (!release3) out.line("No releases yet. Make one with pch publish.");
|
|
46335
|
+
else if ("next" in release3) showReview(ctx, project2, release3);
|
|
46336
|
+
else out.line(`Release ${release3.version}: ${import_picocolors14.default.bold(release3.status)}`);
|
|
46088
46337
|
if (openCode) out.line(openCode);
|
|
46089
46338
|
if (play2) out.line(` ${play2}`);
|
|
46090
46339
|
if (next) out.line(` Next: ${next}`);
|
|
@@ -46163,10 +46412,10 @@ function repoLine(repo) {
|
|
|
46163
46412
|
function homeLine(github) {
|
|
46164
46413
|
if (!github) return "Home: GitHub";
|
|
46165
46414
|
const seen = github.private ? " (private)" : "";
|
|
46166
|
-
return `Home: GitHub, ${plain(github.full_name)}${seen} \xB7 ${
|
|
46415
|
+
return `Home: GitHub, ${plain(github.full_name)}${seen} \xB7 ${import_picocolors14.default.dim(plain(github.html_url))}`;
|
|
46167
46416
|
}
|
|
46168
46417
|
function showReview({ out }, project2, review) {
|
|
46169
|
-
out.line(`Release ${review.version}: ${
|
|
46418
|
+
out.line(`Release ${review.version}: ${import_picocolors14.default.bold(review.status)}`);
|
|
46170
46419
|
const ours = ["draft", "testing", "rejected", "checking", "cloud_review", "in_review"].includes(
|
|
46171
46420
|
review.status
|
|
46172
46421
|
);
|
|
@@ -46185,7 +46434,7 @@ function showReview({ out }, project2, review) {
|
|
|
46185
46434
|
for (const finding of run3.findings) {
|
|
46186
46435
|
const where = finding.path ? ` ${finding.path}:` : "";
|
|
46187
46436
|
out.line(` ${finding.severity} ${finding.rule_id}${where} ${finding.message}`);
|
|
46188
|
-
if (finding.suggestion) out.line(` ${
|
|
46437
|
+
if (finding.suggestion) out.line(` ${import_picocolors14.default.dim(finding.suggestion)}`);
|
|
46189
46438
|
}
|
|
46190
46439
|
}
|
|
46191
46440
|
if (review.test) out.line(` Test: ${review.test.state}`);
|
|
@@ -46252,7 +46501,7 @@ async function set2(ctx, ref, options) {
|
|
|
46252
46501
|
const whoCanSee = repo.visibility === "private" ? { who_can_see: PRIVATE_REPO } : {};
|
|
46253
46502
|
const said2 = { project: handle3(project2), ...repo, ...licence ? { licence } : {}, ...whoCanSee };
|
|
46254
46503
|
out.result(said2, () => {
|
|
46255
|
-
out.line(`Updated ${
|
|
46504
|
+
out.line(`Updated ${import_picocolors14.default.bold(handle3(project2))}.`);
|
|
46256
46505
|
out.line(` Visibility: ${repo.visibility}`);
|
|
46257
46506
|
if (repo.visibility === "private") out.line(` ${PRIVATE_REPO}`);
|
|
46258
46507
|
out.line(` Training: ${repo.training_opt_out ? "off" : "on"}`);
|
|
@@ -46260,7 +46509,7 @@ async function set2(ctx, ref, options) {
|
|
|
46260
46509
|
if (licence) {
|
|
46261
46510
|
out.line(` Licence: ${licenceName(licence)}`);
|
|
46262
46511
|
out.line(
|
|
46263
|
-
|
|
46512
|
+
import_picocolors14.default.dim(" Patch lists it now. Commit a LICENSE file to match: that's in your hands.")
|
|
46264
46513
|
);
|
|
46265
46514
|
}
|
|
46266
46515
|
});
|
|
@@ -46328,7 +46577,7 @@ async function connect(ctx, ref, options) {
|
|
|
46328
46577
|
out.result(
|
|
46329
46578
|
{ project: handle4(project2), github: shown, already: !posted, patch_json: written, url: url2 },
|
|
46330
46579
|
() => {
|
|
46331
|
-
const name =
|
|
46580
|
+
const name = import_picocolors15.default.bold(handle4(project2));
|
|
46332
46581
|
const repoName = plain(repo.full_name);
|
|
46333
46582
|
out.line(
|
|
46334
46583
|
posted ? `Connected ${repoName} on GitHub to ${name}. Its home is GitHub: Patch keeps none of its files until you release one.` : `${repoName} on GitHub is connected to ${name} already.`
|
|
@@ -46338,7 +46587,7 @@ async function connect(ctx, ref, options) {
|
|
|
46338
46587
|
out.line(
|
|
46339
46588
|
"Next: pch publish tags a commit on GitHub with your own git login, and releases it."
|
|
46340
46589
|
);
|
|
46341
|
-
out.line(
|
|
46590
|
+
out.line(import_picocolors15.default.dim(url2));
|
|
46342
46591
|
}
|
|
46343
46592
|
);
|
|
46344
46593
|
}
|
|
@@ -46756,7 +47005,7 @@ async function nameInManifest(manifest, project2) {
|
|
|
46756
47005
|
}
|
|
46757
47006
|
|
|
46758
47007
|
// src/commands/covers.ts
|
|
46759
|
-
var
|
|
47008
|
+
var import_picocolors16 = __toESM(require_picocolors(), 1);
|
|
46760
47009
|
import { mkdir as mkdir8, readFile as readFile13, writeFile as writeFile11 } from "node:fs/promises";
|
|
46761
47010
|
import { basename as basename5, extname as extname3, join as join16, resolve as resolve12 } from "node:path";
|
|
46762
47011
|
import { setTimeout as sleep7 } from "node:timers/promises";
|
|
@@ -46924,7 +47173,7 @@ async function call(ctx, method, path2, body) {
|
|
|
46924
47173
|
var coversPath = (project2) => `/projects/${project2.owner}/${project2.slug}/covers`;
|
|
46925
47174
|
function freeLine(allowance) {
|
|
46926
47175
|
const left = `${allowance.takes_left} of ${allowance.takes_per_game} free takes and ${allowance.fixes_left} of ${allowance.fixes_per_game} fixes left for this game`;
|
|
46927
|
-
return `${
|
|
47176
|
+
return `${import_picocolors16.default.green("Free")}: Patch pays for covers, and they cost you nothing. ${left}.`;
|
|
46928
47177
|
}
|
|
46929
47178
|
async function uploadStill(ctx, project2, file2) {
|
|
46930
47179
|
const absolute = resolve12(file2);
|
|
@@ -47003,16 +47252,16 @@ async function save(ctx, job, folder) {
|
|
|
47003
47252
|
}
|
|
47004
47253
|
function show2(ctx, job, saved) {
|
|
47005
47254
|
ctx.out.result({ ...job, saved }, () => {
|
|
47006
|
-
ctx.out.line(`${
|
|
47255
|
+
ctx.out.line(`${import_picocolors16.default.bold(job.project)}: ${job.kind === "fix" ? "a fix" : "covers"} (${job.id})`);
|
|
47007
47256
|
for (const take of job.takes) {
|
|
47008
47257
|
const file2 = saved.find((entry) => entry.take === take.id)?.file;
|
|
47009
|
-
const state = take.state === "ready" ?
|
|
47010
|
-
const set3 = take.set_as ?
|
|
47258
|
+
const state = take.state === "ready" ? import_picocolors16.default.green("ready") : take.state === "failed" ? import_picocolors16.default.red(`failed: ${take.error ?? "no reason given"}`) : import_picocolors16.default.yellow("making");
|
|
47259
|
+
const set3 = take.set_as ? import_picocolors16.default.cyan(` set as the ${take.set_as.replace("_", " ")}`) : "";
|
|
47011
47260
|
ctx.out.line(
|
|
47012
|
-
` ${take.shape.padEnd(4)} ${take.id} ${state}${set3}${file2 ?
|
|
47261
|
+
` ${take.shape.padEnd(4)} ${take.id} ${state}${set3}${file2 ? import_picocolors16.default.dim(` ${file2}`) : ""}`
|
|
47013
47262
|
);
|
|
47014
47263
|
}
|
|
47015
|
-
if (job.next) ctx.out.line(
|
|
47264
|
+
if (job.next) ctx.out.line(import_picocolors16.default.dim(job.next));
|
|
47016
47265
|
ctx.out.line(freeLine(job.allowance));
|
|
47017
47266
|
});
|
|
47018
47267
|
if (job.takes.length && job.takes.every((take) => take.state === "failed")) {
|
|
@@ -47087,9 +47336,9 @@ async function coversSet(ctx, ref, take, options) {
|
|
|
47087
47336
|
const answer = await call(ctx, "POST", `${coversPath(project2)}/takes/${take}/set`, { as: options.as });
|
|
47088
47337
|
ctx.out.result(answer, () => {
|
|
47089
47338
|
ctx.out.line(
|
|
47090
|
-
`${
|
|
47339
|
+
`${import_picocolors16.default.green(`Set the ${answer.media.kind.replace("_", " ")}`)} of ${import_picocolors16.default.bold(ref)}: ${answer.media.status}.`
|
|
47091
47340
|
);
|
|
47092
|
-
ctx.out.line(
|
|
47341
|
+
ctx.out.line(import_picocolors16.default.dim("It shows on the game's page once it's ready, in a few seconds."));
|
|
47093
47342
|
ctx.out.line(freeLine(answer.allowance));
|
|
47094
47343
|
});
|
|
47095
47344
|
return EXIT.ok;
|
|
@@ -47101,7 +47350,7 @@ function outFolder(out, byDefault) {
|
|
|
47101
47350
|
}
|
|
47102
47351
|
|
|
47103
47352
|
// src/commands/feedback.ts
|
|
47104
|
-
var
|
|
47353
|
+
var import_picocolors17 = __toESM(require_picocolors(), 1);
|
|
47105
47354
|
var FEEDBACK_KINDS = ["failed", "bug", "call_error", "unhelpful", "idea"];
|
|
47106
47355
|
var FEEDBACK_SEVERITIES = ["blocked", "slowed", "annoyed"];
|
|
47107
47356
|
async function feedbackSend(ctx, summary, options) {
|
|
@@ -47131,7 +47380,7 @@ async function feedbackSend(ctx, summary, options) {
|
|
|
47131
47380
|
const shown = { ...sent, next: sent.next.replaceAll("send_feedback", "pch feedback send") };
|
|
47132
47381
|
ctx.out.result(shown, () => {
|
|
47133
47382
|
ctx.out.line(
|
|
47134
|
-
`${options.addTo ? "Added to your report" : "Told Patch's team"}: ${
|
|
47383
|
+
`${options.addTo ? "Added to your report" : "Told Patch's team"}: ${import_picocolors17.default.bold(plain(sent.summary))}`
|
|
47135
47384
|
);
|
|
47136
47385
|
if (sent.known) {
|
|
47137
47386
|
ctx.out.line(`Patch's team already answered a report like it: ${plain(sent.known.answer)}`);
|
|
@@ -47140,12 +47389,12 @@ async function feedbackSend(ctx, summary, options) {
|
|
|
47140
47389
|
const flag = sent.follow_up.field.replaceAll("_", "-");
|
|
47141
47390
|
ctx.out.line(`One more thing would help most: ${plain(sent.follow_up.question)}`);
|
|
47142
47391
|
ctx.out.line(
|
|
47143
|
-
|
|
47392
|
+
import_picocolors17.default.dim(
|
|
47144
47393
|
` pch feedback send --kind ${sent.kind} --add-to ${sent.id} --${flag} "..." "${plain(sent.summary)}"`
|
|
47145
47394
|
)
|
|
47146
47395
|
);
|
|
47147
47396
|
} else {
|
|
47148
|
-
ctx.out.line(
|
|
47397
|
+
ctx.out.line(import_picocolors17.default.dim(plain(shown.next)));
|
|
47149
47398
|
}
|
|
47150
47399
|
});
|
|
47151
47400
|
}
|
|
@@ -47159,24 +47408,24 @@ async function feedbackList(ctx) {
|
|
|
47159
47408
|
}
|
|
47160
47409
|
for (const report of data) {
|
|
47161
47410
|
ctx.out.line(
|
|
47162
|
-
`${
|
|
47411
|
+
`${import_picocolors17.default.dim(report.created_at.slice(0, 10))} ${statusWord(report.status)} ${plain(report.summary)}`
|
|
47163
47412
|
);
|
|
47164
|
-
if (report.answer) ctx.out.line(` ${
|
|
47413
|
+
if (report.answer) ctx.out.line(` ${import_picocolors17.default.dim("Patch:")} ${plain(report.answer)}`);
|
|
47165
47414
|
if (report.status === "open" && report.quality < 50) {
|
|
47166
|
-
ctx.out.line(
|
|
47415
|
+
ctx.out.line(import_picocolors17.default.dim(` Add to it: pch feedback send --add-to ${report.id} ...`));
|
|
47167
47416
|
}
|
|
47168
47417
|
}
|
|
47169
47418
|
});
|
|
47170
47419
|
}
|
|
47171
47420
|
function statusWord(status2) {
|
|
47172
|
-
if (status2 === "fixed") return
|
|
47173
|
-
if (status2 === "planned") return
|
|
47174
|
-
if (status2 === "declined") return
|
|
47175
|
-
return
|
|
47421
|
+
if (status2 === "fixed") return import_picocolors17.default.green("fixed ");
|
|
47422
|
+
if (status2 === "planned") return import_picocolors17.default.cyan("planned ");
|
|
47423
|
+
if (status2 === "declined") return import_picocolors17.default.dim("declined");
|
|
47424
|
+
return import_picocolors17.default.yellow("open ");
|
|
47176
47425
|
}
|
|
47177
47426
|
|
|
47178
47427
|
// src/commands/git.ts
|
|
47179
|
-
var
|
|
47428
|
+
var import_picocolors18 = __toESM(require_picocolors(), 1);
|
|
47180
47429
|
import { stat as stat5 } from "node:fs/promises";
|
|
47181
47430
|
import { basename as basename6, join as join17, resolve as resolve13 } from "node:path";
|
|
47182
47431
|
|
|
@@ -47193,7 +47442,7 @@ var PATCH_KEYS = ["PCH_TOKEN", "PCH_GIT_KEY", PUSH_KEY_VARIABLE];
|
|
|
47193
47442
|
function run2(dir, args2, signIn = NO_SIGN_IN) {
|
|
47194
47443
|
const env = { ...process.env };
|
|
47195
47444
|
if (signIn.own) for (const name of PATCH_KEYS) delete env[name];
|
|
47196
|
-
return new Promise((
|
|
47445
|
+
return new Promise((resolve17) => {
|
|
47197
47446
|
execFile(
|
|
47198
47447
|
"git",
|
|
47199
47448
|
["-C", dir, ...signIn.args, ...args2],
|
|
@@ -47203,7 +47452,7 @@ function run2(dir, args2, signIn = NO_SIGN_IN) {
|
|
|
47203
47452
|
},
|
|
47204
47453
|
(error62, out, err) => {
|
|
47205
47454
|
const code = error62?.code;
|
|
47206
|
-
|
|
47455
|
+
resolve17({ code: error62 ? typeof code === "number" ? code : -1 : 0, out, err });
|
|
47207
47456
|
}
|
|
47208
47457
|
);
|
|
47209
47458
|
});
|
|
@@ -47911,8 +48160,8 @@ async function push(ctx, given) {
|
|
|
47911
48160
|
},
|
|
47912
48161
|
() => {
|
|
47913
48162
|
const list = sent.map(({ ref, status: status2 }) => `${shortRef(ref)}${status2 === "deleted" ? " (deleted)" : ""}`).join(", ");
|
|
47914
|
-
if (list) out.line(`${dryRun ? "Would push" : "Pushed"} ${list} to ${
|
|
47915
|
-
else if (!quiet) out.line(`Nothing new to push to ${
|
|
48163
|
+
if (list) out.line(`${dryRun ? "Would push" : "Pushed"} ${list} to ${import_picocolors18.default.bold(repo)}.`);
|
|
48164
|
+
else if (!quiet) out.line(`Nothing new to push to ${import_picocolors18.default.bold(repo)}.`);
|
|
47916
48165
|
if (pushed.notice) out.line(pushed.notice);
|
|
47917
48166
|
}
|
|
47918
48167
|
);
|
|
@@ -47959,10 +48208,10 @@ async function clone2(ctx, ref, dir) {
|
|
|
47959
48208
|
brief: brief ? BRIEF_FILE : null
|
|
47960
48209
|
},
|
|
47961
48210
|
() => {
|
|
47962
|
-
out.line(`Cloned ${
|
|
48211
|
+
out.line(`Cloned ${import_picocolors18.default.bold(`${project2.owner}/${project2.slug}`)} into ${basename6(into)}`);
|
|
47963
48212
|
if (commitsAs) out.line(saidCommitsAs(commitsAs));
|
|
47964
|
-
out.line(
|
|
47965
|
-
if (!guide2) out.line(
|
|
48213
|
+
out.line(import_picocolors18.default.dim("Commit and push with plain git."));
|
|
48214
|
+
if (!guide2) out.line(import_picocolors18.default.dim("It has no guide for coding agents: pch agent guide adds one."));
|
|
47966
48215
|
if (brief) {
|
|
47967
48216
|
out.line(
|
|
47968
48217
|
repo.forked_from ? `Its ${BRIEF_FILE} came from ${repo.forked_from}, so it's that game's idea: keep what you want, then change or delete it.` : `Its ${BRIEF_FILE} is this game's idea and rules: read it first.`
|
|
@@ -47973,7 +48222,7 @@ async function clone2(ctx, ref, dir) {
|
|
|
47973
48222
|
}
|
|
47974
48223
|
|
|
47975
48224
|
// src/commands/media.ts
|
|
47976
|
-
var
|
|
48225
|
+
var import_picocolors19 = __toESM(require_picocolors(), 1);
|
|
47977
48226
|
import { readFile as readFile14 } from "node:fs/promises";
|
|
47978
48227
|
import { basename as basename7, extname as extname4, resolve as resolve14 } from "node:path";
|
|
47979
48228
|
import { setTimeout as sleep8 } from "node:timers/promises";
|
|
@@ -48069,11 +48318,11 @@ function showAdded(ctx, ref, media2) {
|
|
|
48069
48318
|
);
|
|
48070
48319
|
}
|
|
48071
48320
|
ctx.out.result(media2, () => {
|
|
48072
|
-
ctx.out.line(`${
|
|
48073
|
-
if (media2.status === "ready") ctx.out.line(
|
|
48321
|
+
ctx.out.line(`${import_picocolors19.default.green(`Added the ${media2.kind}`)} to ${import_picocolors19.default.bold(ref)}: ${media2.status}.`);
|
|
48322
|
+
if (media2.status === "ready") ctx.out.line(import_picocolors19.default.dim(media2.url));
|
|
48074
48323
|
else {
|
|
48075
48324
|
ctx.out.line(
|
|
48076
|
-
|
|
48325
|
+
import_picocolors19.default.dim(
|
|
48077
48326
|
`It shows once it's ready, which takes seconds for a picture, and for a clip about as long as it plays. Check with pch media list ${ref}.`
|
|
48078
48327
|
)
|
|
48079
48328
|
);
|
|
@@ -48093,7 +48342,7 @@ async function mediaList(ctx, ref) {
|
|
|
48093
48342
|
for (const entry of media2.data) {
|
|
48094
48343
|
const failed3 = entry.status === "failed" && entry.error ? `: ${entry.error}` : "";
|
|
48095
48344
|
ctx.out.line(
|
|
48096
|
-
`${entry.id} ${entry.kind.padEnd(10)} ${entry.status.padEnd(10)} ${formatSize(entry.size).padStart(8)}${failed3}${entry.alt ?
|
|
48345
|
+
`${entry.id} ${entry.kind.padEnd(10)} ${entry.status.padEnd(10)} ${formatSize(entry.size).padStart(8)}${failed3}${entry.alt ? import_picocolors19.default.dim(` ${entry.alt}`) : ""}`
|
|
48097
48346
|
);
|
|
48098
48347
|
}
|
|
48099
48348
|
});
|
|
@@ -48121,11 +48370,466 @@ async function mediaOrder(ctx, ref, ids) {
|
|
|
48121
48370
|
}
|
|
48122
48371
|
|
|
48123
48372
|
// src/commands/publish.ts
|
|
48124
|
-
var
|
|
48373
|
+
var import_picocolors22 = __toESM(require_picocolors(), 1);
|
|
48125
48374
|
var import_semver2 = __toESM(require_semver2(), 1);
|
|
48126
48375
|
import { open as open2, readFile as readFile15 } from "node:fs/promises";
|
|
48127
48376
|
init_errors();
|
|
48128
48377
|
init_guide_words();
|
|
48378
|
+
|
|
48379
|
+
// src/commands/publish-flow.ts
|
|
48380
|
+
var import_picocolors21 = __toESM(require_picocolors(), 1);
|
|
48381
|
+
import { mkdtemp as mkdtemp4 } from "node:fs/promises";
|
|
48382
|
+
import { tmpdir as tmpdir4 } from "node:os";
|
|
48383
|
+
import { join as join19, resolve as resolve16 } from "node:path";
|
|
48384
|
+
init_errors();
|
|
48385
|
+
|
|
48386
|
+
// src/commands/release-test.ts
|
|
48387
|
+
var import_picocolors20 = __toESM(require_picocolors(), 1);
|
|
48388
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
48389
|
+
import { mkdir as mkdir9, mkdtemp as mkdtemp3, writeFile as writeFile12 } from "node:fs/promises";
|
|
48390
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
48391
|
+
import { join as join18, resolve as resolve15 } from "node:path";
|
|
48392
|
+
import { setTimeout as sleep9 } from "node:timers/promises";
|
|
48393
|
+
init_errors();
|
|
48394
|
+
var HOLD_SECONDS = 50;
|
|
48395
|
+
var ASK_EVERY_MS2 = 5e3;
|
|
48396
|
+
var OPEN = /* @__PURE__ */ new Set(["waiting", "running", "recorded"]);
|
|
48397
|
+
var GOING = /* @__PURE__ */ new Set(["waiting", "running"]);
|
|
48398
|
+
var REVIEW_ROUTE = "/projects/{owner}/{slug}/releases/{version}/review";
|
|
48399
|
+
var reviewOf = (ctx, release3) => ctx.api.GET(REVIEW_ROUTE, { params: { path: release3 } });
|
|
48400
|
+
async function testStart(ctx, ref, options) {
|
|
48401
|
+
requireToken(ctx.target);
|
|
48402
|
+
const release3 = parseReleaseRef(ref);
|
|
48403
|
+
const wait2 = waitOf(options.wait);
|
|
48404
|
+
const before = await reviewOf(ctx, release3);
|
|
48405
|
+
const open3 = before.data?.test && OPEN.has(before.data.test.state) ? before.data.test : null;
|
|
48406
|
+
const test2 = open3 ?? await startOne(ctx, release3, before.data?.status);
|
|
48407
|
+
return show3(ctx, release3, wait2 === null ? test2 : await follow3(ctx, test2, wait2), {
|
|
48408
|
+
started: !open3,
|
|
48409
|
+
followed: wait2 !== null
|
|
48410
|
+
});
|
|
48411
|
+
}
|
|
48412
|
+
async function startOne(ctx, release3, status2) {
|
|
48413
|
+
const name = releaseName(release3);
|
|
48414
|
+
const answer = await ctx.api.POST("/projects/{owner}/{slug}/releases/{version}/tests", {
|
|
48415
|
+
params: { path: release3 }
|
|
48416
|
+
});
|
|
48417
|
+
if (answer.response.ok) return answer.data;
|
|
48418
|
+
const failure2 = fromResponse(answer.response, answer.error);
|
|
48419
|
+
if (failure2.code === "files_failed") {
|
|
48420
|
+
const { findings } = failure2.details;
|
|
48421
|
+
throw filesFailed(failure2.message, findings);
|
|
48422
|
+
}
|
|
48423
|
+
if (failure2.code === "not_testable") {
|
|
48424
|
+
const next = `pch release submit ${name}`;
|
|
48425
|
+
throw new CliError(
|
|
48426
|
+
failure2.exitCode,
|
|
48427
|
+
failure2.code,
|
|
48428
|
+
`Patch Desktop can't test ${name}, as it's a mod players install some other way, so it skips the test. Send it for review once it's ready: ${next}`,
|
|
48429
|
+
{ next }
|
|
48430
|
+
);
|
|
48431
|
+
}
|
|
48432
|
+
if (failure2.code === "not_testing" && status2) {
|
|
48433
|
+
const next = nextStep(name, status2, null);
|
|
48434
|
+
throw new CliError(
|
|
48435
|
+
failure2.exitCode,
|
|
48436
|
+
failure2.code,
|
|
48437
|
+
`${name} is ${status2}, so it can't be tested. ${sayNext(next)}`,
|
|
48438
|
+
{ next: next.command }
|
|
48439
|
+
);
|
|
48440
|
+
}
|
|
48441
|
+
throw failure2;
|
|
48442
|
+
}
|
|
48443
|
+
async function testView(ctx, ref, options) {
|
|
48444
|
+
requireToken(ctx.target);
|
|
48445
|
+
const release3 = parseReleaseRef(ref);
|
|
48446
|
+
const wait2 = waitOf(options.wait);
|
|
48447
|
+
const test2 = await newest(ctx, release3);
|
|
48448
|
+
return show3(ctx, release3, wait2 === null ? test2 : await follow3(ctx, test2, wait2), {
|
|
48449
|
+
followed: wait2 !== null
|
|
48450
|
+
});
|
|
48451
|
+
}
|
|
48452
|
+
async function newest(ctx, release3) {
|
|
48453
|
+
const review = await unwrap(reviewOf(ctx, release3));
|
|
48454
|
+
if (review.test) return review.test;
|
|
48455
|
+
const name = releaseName(release3);
|
|
48456
|
+
const next = `pch test start ${name}`;
|
|
48457
|
+
throw new CliError(EXIT.error, "not_found", `${name} has no test yet. Start one: ${next}`, {
|
|
48458
|
+
next
|
|
48459
|
+
});
|
|
48460
|
+
}
|
|
48461
|
+
async function follow3(ctx, test2, seconds2) {
|
|
48462
|
+
const deadline = Date.now() + seconds2 * 1e3;
|
|
48463
|
+
let current = test2;
|
|
48464
|
+
let said2 = "";
|
|
48465
|
+
while (GOING.has(current.state) && Date.now() < deadline) {
|
|
48466
|
+
const now = whatsHappening(current);
|
|
48467
|
+
if (now !== said2) ctx.out.step(now);
|
|
48468
|
+
said2 = now;
|
|
48469
|
+
if (current.needs_person) await sleep9(Math.min(ASK_EVERY_MS2, deadline - Date.now()));
|
|
48470
|
+
const left = Math.ceil((deadline - Date.now()) / 1e3);
|
|
48471
|
+
current = await unwrap(
|
|
48472
|
+
ctx.api.GET("/tests/{id}", {
|
|
48473
|
+
params: {
|
|
48474
|
+
path: { id: current.id },
|
|
48475
|
+
query: { wait: Math.max(0, Math.min(HOLD_SECONDS, left)) }
|
|
48476
|
+
}
|
|
48477
|
+
})
|
|
48478
|
+
);
|
|
48479
|
+
}
|
|
48480
|
+
return current;
|
|
48481
|
+
}
|
|
48482
|
+
function whatsHappening(test2) {
|
|
48483
|
+
if (test2.state === "running") {
|
|
48484
|
+
const where = test2.pc_name ? ` on ${plain(test2.pc_name)}` : "";
|
|
48485
|
+
const phase = test2.progress?.phase;
|
|
48486
|
+
return phase && phase !== "playing" ? `Patch Desktop${where} is ${plain(phase)} it` : `Patch Desktop${where} is playing it`;
|
|
48487
|
+
}
|
|
48488
|
+
if (test2.needs_person) return plain(test2.needs_person.ask);
|
|
48489
|
+
return "Waiting for you to press Start in Patch Desktop";
|
|
48490
|
+
}
|
|
48491
|
+
async function show3(ctx, release3, test2, { followed, ...extra } = {}) {
|
|
48492
|
+
const { out } = ctx;
|
|
48493
|
+
const name = releaseName(release3);
|
|
48494
|
+
const next = nextStep(name, test2.state === "failed" ? "rejected" : "testing", test2);
|
|
48495
|
+
const command = next.command;
|
|
48496
|
+
const play2 = test2.state === "passed" ? await playFirst(ctx, release3) : null;
|
|
48497
|
+
const ask = test2.state === "waiting" ? askThePerson(test2) : play2;
|
|
48498
|
+
const noteAsk = test2.note_ask?.replaceAll("suggest_note", "pch note suggest");
|
|
48499
|
+
const feedbackAsk = test2.feedback_ask ? {
|
|
48500
|
+
...test2.feedback_ask,
|
|
48501
|
+
ask: test2.feedback_ask.ask.replaceAll("send_feedback", "pch feedback send")
|
|
48502
|
+
} : void 0;
|
|
48503
|
+
const shown = {
|
|
48504
|
+
...test2,
|
|
48505
|
+
...extra,
|
|
48506
|
+
next: command,
|
|
48507
|
+
...noteAsk ? { note_ask: noteAsk } : {},
|
|
48508
|
+
...feedbackAsk ? { feedback_ask: feedbackAsk } : {}
|
|
48509
|
+
};
|
|
48510
|
+
out.result({ ...shown, ...ask ? { ask } : {} }, () => {
|
|
48511
|
+
const bold = import_picocolors20.default.bold(name);
|
|
48512
|
+
if (test2.state === "waiting") {
|
|
48513
|
+
out.line(
|
|
48514
|
+
!test2.needs_person ? `${extra.started ? "Started a test of" : "The test of"} ${bold}. It's waiting for Patch Desktop to take it on.` : extra.started ? `Started a test of ${bold}. It's waiting for you in Patch Desktop: press Start on it there.` : `${bold}: its test is waiting for you in Patch Desktop. Press Start on it there.`
|
|
48515
|
+
);
|
|
48516
|
+
if (test2.needs_person) {
|
|
48517
|
+
out.line(plain(test2.needs_person.why));
|
|
48518
|
+
out.line(plain(test2.needs_person.ask));
|
|
48519
|
+
}
|
|
48520
|
+
} else if (test2.state === "running") {
|
|
48521
|
+
out.line(`${bold}: ${whatsHappening(test2)}.`);
|
|
48522
|
+
} else if (test2.state === "recorded") {
|
|
48523
|
+
out.line(
|
|
48524
|
+
`${bold}: it has been played and recorded. Look at the footage, then send your review.`
|
|
48525
|
+
);
|
|
48526
|
+
} else if (test2.state === "passed") {
|
|
48527
|
+
out.line(`${bold}: ${import_picocolors20.default.green("its test passed.")} It's still private.`);
|
|
48528
|
+
if (test2.review) out.line(plain(test2.review.summary));
|
|
48529
|
+
if (play2) out.line(play2);
|
|
48530
|
+
out.line(
|
|
48531
|
+
"Publish it once you're happy it's ready: that's your yes, and nothing does it for you."
|
|
48532
|
+
);
|
|
48533
|
+
} else if (test2.state === "failed") {
|
|
48534
|
+
out.line(`${bold}: ${import_picocolors20.default.red("its test failed")}, so this version can't be published.`);
|
|
48535
|
+
showWhatFailed(ctx, test2);
|
|
48536
|
+
out.line(`pch status ${name} says how to fix it.`);
|
|
48537
|
+
out.line("Then move the version on in patch.json and publish that.");
|
|
48538
|
+
} else if (test2.state === "couldnt_run") {
|
|
48539
|
+
out.line(`${bold}: ${import_picocolors20.default.yellow("its test couldn't run.")}`);
|
|
48540
|
+
if (test2.outcome) out.line(plain(test2.outcome.reason));
|
|
48541
|
+
} else {
|
|
48542
|
+
out.line(`${bold}: its test was cancelled.`);
|
|
48543
|
+
}
|
|
48544
|
+
if (noteAsk) out.line(plain(noteAsk));
|
|
48545
|
+
if (feedbackAsk) out.line(plain(feedbackAsk.ask));
|
|
48546
|
+
if (followed && GOING.has(test2.state)) {
|
|
48547
|
+
out.line("It isn't over, and nothing's wrong: that's as long as --wait follows it here.");
|
|
48548
|
+
}
|
|
48549
|
+
if (command) out.line(`Next: ${command}`);
|
|
48550
|
+
if (OPEN.has(test2.state) && !extra.started) {
|
|
48551
|
+
out.line(import_picocolors20.default.dim(`To start over: pch test cancel ${name}, then pch test start ${name}`));
|
|
48552
|
+
}
|
|
48553
|
+
});
|
|
48554
|
+
if (test2.state === "failed") return EXIT.blocked;
|
|
48555
|
+
if (test2.state === "couldnt_run" || test2.state === "cancelled") return EXIT.error;
|
|
48556
|
+
return EXIT.ok;
|
|
48557
|
+
}
|
|
48558
|
+
function askThePerson(test2) {
|
|
48559
|
+
return test2.needs_person?.ask ?? null;
|
|
48560
|
+
}
|
|
48561
|
+
function showWhatFailed({ out }, test2) {
|
|
48562
|
+
for (const run3 of test2.results) {
|
|
48563
|
+
for (const check3 of run3.checks) {
|
|
48564
|
+
if (check3.result !== "fail") continue;
|
|
48565
|
+
const detail = check3.detail ? ` (${plain(check3.detail)})` : "";
|
|
48566
|
+
out.line(` Run ${run3.run}: ${plain(check3.name)}${detail}`);
|
|
48567
|
+
}
|
|
48568
|
+
}
|
|
48569
|
+
if (test2.review) out.line(` Its review: ${plain(test2.review.summary)}`);
|
|
48570
|
+
}
|
|
48571
|
+
async function testFootage(ctx, ref, options) {
|
|
48572
|
+
requireToken(ctx.target);
|
|
48573
|
+
const { out } = ctx;
|
|
48574
|
+
const release3 = parseReleaseRef(ref);
|
|
48575
|
+
const name = releaseName(release3);
|
|
48576
|
+
const run3 = options.run === void 0 ? void 0 : positive(options.run, "--run");
|
|
48577
|
+
const test2 = await newest(ctx, release3);
|
|
48578
|
+
const { data } = await unwrap(
|
|
48579
|
+
ctx.api.GET("/tests/{id}/footage", { params: { path: { id: test2.id }, query: { run: run3 } } })
|
|
48580
|
+
);
|
|
48581
|
+
const sheets = data.length ? await saveSheets(
|
|
48582
|
+
data,
|
|
48583
|
+
options.out ? resolve15(options.out) : await mkdtemp3(join18(tmpdir3(), "pch-footage-"))
|
|
48584
|
+
) : [];
|
|
48585
|
+
const next = data.length ? reviewCommand(name) : nextStep(name, "testing", test2).command;
|
|
48586
|
+
out.result({ test: test2.id, state: test2.state, data: sheets, next }, () => {
|
|
48587
|
+
if (!sheets.length) {
|
|
48588
|
+
out.line(`${import_picocolors20.default.bold(name)} has no footage yet: its test is ${test2.state.replace("_", " ")}.`);
|
|
48589
|
+
for (const result of test2.results) {
|
|
48590
|
+
if (result.footage_error) out.line(plain(result.footage_error));
|
|
48591
|
+
}
|
|
48592
|
+
}
|
|
48593
|
+
for (const sheet of sheets) {
|
|
48594
|
+
out.line(`Run ${sheet.run}, ${sheet.from} to ${sheet.to}: ${sheet.path}`);
|
|
48595
|
+
}
|
|
48596
|
+
if (next) {
|
|
48597
|
+
if (sheets.length) out.line("Look at every one, then send your review, with what you saw:");
|
|
48598
|
+
out.line(`Next: ${next}`);
|
|
48599
|
+
}
|
|
48600
|
+
});
|
|
48601
|
+
}
|
|
48602
|
+
var reviewCommand = (name) => `pch test review ${name} --verdict <pass|fail> --plays <yes|no> --harmful <yes|no> --summary "<what you saw>" --moment "<run> <mm:ss> <what you saw>" --moment "<run> <mm:ss> <what you saw>"`;
|
|
48603
|
+
async function saveSheets(sheets, folder) {
|
|
48604
|
+
await mkdir9(folder, { recursive: true });
|
|
48605
|
+
const saved = [];
|
|
48606
|
+
for (const sheet of sheets) {
|
|
48607
|
+
const answer = await fetch(sheet.url).catch(() => null);
|
|
48608
|
+
const bytes = answer?.ok ? Buffer.from(await answer.arrayBuffer()) : null;
|
|
48609
|
+
if (!bytes || createHash3("sha256").update(bytes).digest("hex") !== sheet.sha256) {
|
|
48610
|
+
throw new CliError(
|
|
48611
|
+
EXIT.error,
|
|
48612
|
+
"download_failed",
|
|
48613
|
+
`Run ${sheet.run}'s sheet ${sheet.index} didn't download intact. Try again.`
|
|
48614
|
+
);
|
|
48615
|
+
}
|
|
48616
|
+
const path2 = join18(folder, `run-${sheet.run}-sheet-${sheet.index}.jpg`);
|
|
48617
|
+
await writeFile12(path2, bytes);
|
|
48618
|
+
const { url: _url2, ...kept } = sheet;
|
|
48619
|
+
saved.push({ ...kept, path: path2 });
|
|
48620
|
+
}
|
|
48621
|
+
return saved;
|
|
48622
|
+
}
|
|
48623
|
+
async function testReview(ctx, ref, options) {
|
|
48624
|
+
requireToken(ctx.target);
|
|
48625
|
+
const release3 = parseReleaseRef(ref);
|
|
48626
|
+
const moments = options.moment.map(parseMoment);
|
|
48627
|
+
const test2 = await newest(ctx, release3);
|
|
48628
|
+
const answer = await ctx.api.POST("/tests/{id}/review", {
|
|
48629
|
+
params: { path: { id: test2.id } },
|
|
48630
|
+
body: {
|
|
48631
|
+
verdict: options.verdict,
|
|
48632
|
+
plays: options.plays === "yes",
|
|
48633
|
+
harmful: options.harmful === "yes",
|
|
48634
|
+
summary: options.summary,
|
|
48635
|
+
moments
|
|
48636
|
+
}
|
|
48637
|
+
});
|
|
48638
|
+
if (answer.response.ok) return show3(ctx, release3, answer.data);
|
|
48639
|
+
throw withNext(fromResponse(answer.response, answer.error), release3, test2);
|
|
48640
|
+
}
|
|
48641
|
+
function parseMoment(text) {
|
|
48642
|
+
const match = /^(\d+)\s+(\d{1,3}:\d{2})\s+(\S.*)$/s.exec(text.trim());
|
|
48643
|
+
if (!match) {
|
|
48644
|
+
throw new CliError(
|
|
48645
|
+
EXIT.usage,
|
|
48646
|
+
"bad_moment",
|
|
48647
|
+
`"${text}" isn't a moment. Write its run, the time on a frame and what you saw, such as "1 00:04 the title screen draws".`
|
|
48648
|
+
);
|
|
48649
|
+
}
|
|
48650
|
+
return { run: Number(match[1]), at: match[2], saw: match[3] };
|
|
48651
|
+
}
|
|
48652
|
+
async function testCancel(ctx, ref) {
|
|
48653
|
+
requireToken(ctx.target);
|
|
48654
|
+
const release3 = parseReleaseRef(ref);
|
|
48655
|
+
const name = releaseName(release3);
|
|
48656
|
+
const test2 = await newest(ctx, release3);
|
|
48657
|
+
const answer = await ctx.api.POST("/tests/{id}/cancel", { params: { path: { id: test2.id } } });
|
|
48658
|
+
if (!answer.response.ok) {
|
|
48659
|
+
throw withNext(fromResponse(answer.response, answer.error), release3, test2);
|
|
48660
|
+
}
|
|
48661
|
+
const next = `pch test start ${name}`;
|
|
48662
|
+
ctx.out.result({ ...answer.data, next }, () => {
|
|
48663
|
+
ctx.out.line(`Cancelled ${import_picocolors20.default.bold(name)}'s test. Start another: ${next}`);
|
|
48664
|
+
});
|
|
48665
|
+
}
|
|
48666
|
+
function withNext(failure2, release3, test2) {
|
|
48667
|
+
if (failure2.code !== "test_state") return failure2;
|
|
48668
|
+
const next = nextStep(releaseName(release3), "testing", test2);
|
|
48669
|
+
return new CliError(failure2.exitCode, failure2.code, `${failure2.message}. ${sayNext(next)}`, {
|
|
48670
|
+
next: next.command
|
|
48671
|
+
});
|
|
48672
|
+
}
|
|
48673
|
+
function positive(value, flag) {
|
|
48674
|
+
if (/^[1-9]\d*$/.test(value)) return Number(value);
|
|
48675
|
+
throw new CliError(EXIT.usage, "bad_usage", `${flag} takes a whole number, not "${value}".`);
|
|
48676
|
+
}
|
|
48677
|
+
async function testAutostart(ctx, state) {
|
|
48678
|
+
requireToken(ctx.target);
|
|
48679
|
+
if (state !== "on" && state !== "off") {
|
|
48680
|
+
throw new CliError(EXIT.usage, "bad_value", `Say on or off, not "${state}"`);
|
|
48681
|
+
}
|
|
48682
|
+
const setting = await unwrap(
|
|
48683
|
+
ctx.api.PUT("/me/test-settings", { body: { starts_own_tests: state === "on", by: "agent" } })
|
|
48684
|
+
);
|
|
48685
|
+
ctx.out.result(setting, () => {
|
|
48686
|
+
ctx.out.line(
|
|
48687
|
+
setting.starts_own_tests ? "Your tests of browser games and blocks now start in Patch Desktop by themselves, with a notice each time that lets the person stop them. Patch Desktop tells them you turned it on. A mod's test still waits for Start." : "Your tests wait for the person to press Start in Patch Desktop again."
|
|
48688
|
+
);
|
|
48689
|
+
});
|
|
48690
|
+
}
|
|
48691
|
+
|
|
48692
|
+
// src/commands/publish-flow.ts
|
|
48693
|
+
var ENDS = /* @__PURE__ */ new Set(["your_review", "ready", "live", "stopped", "uploading"]);
|
|
48694
|
+
var PERSON_EVERY_MS = 5e3;
|
|
48695
|
+
function followFor(wait2) {
|
|
48696
|
+
if (wait2 === void 0) {
|
|
48697
|
+
return process.stdout.isTTY ? Number.POSITIVE_INFINITY : UNATTENDED_WAIT_SECONDS;
|
|
48698
|
+
}
|
|
48699
|
+
if (/^\d+$/.test(wait2)) return Number(wait2);
|
|
48700
|
+
throw new CliError(EXIT.usage, "bad_wait", `--wait takes a number of seconds, not "${wait2}".`);
|
|
48701
|
+
}
|
|
48702
|
+
function reviewOf2(flags) {
|
|
48703
|
+
if (flags.review === void 0) return void 0;
|
|
48704
|
+
if (flags.review !== "pass" && flags.review !== "fail") {
|
|
48705
|
+
throw new CliError(EXIT.usage, "bad_value", `--review is pass or fail, not "${flags.review}".`);
|
|
48706
|
+
}
|
|
48707
|
+
const moments = (flags.moment ?? []).map(parseMoment);
|
|
48708
|
+
if (!flags.summary?.trim() || moments.length < 2) {
|
|
48709
|
+
throw new CliError(
|
|
48710
|
+
EXIT.usage,
|
|
48711
|
+
"review_incomplete",
|
|
48712
|
+
`A review needs --summary and at least two --moment, each a run, the time on a frame and what you saw: ${reviewCommand2("<owner/slug@version>")}`
|
|
48713
|
+
);
|
|
48714
|
+
}
|
|
48715
|
+
return {
|
|
48716
|
+
verdict: flags.review,
|
|
48717
|
+
plays: flags.review === "pass",
|
|
48718
|
+
harmful: Boolean(flags.harmful),
|
|
48719
|
+
summary: flags.summary,
|
|
48720
|
+
moments
|
|
48721
|
+
};
|
|
48722
|
+
}
|
|
48723
|
+
var reviewCommand2 = (name) => `pch publish ${name} --review <pass|fail> --summary "<what you saw>" --moment "<run> <mm:ss> <what you saw>" --moment "<run> <mm:ss> <what you saw>"`;
|
|
48724
|
+
function lineOf(step) {
|
|
48725
|
+
if (step.stage === "testing" && step.needs_person) return "Testing on your PC: waiting for you";
|
|
48726
|
+
return step.line;
|
|
48727
|
+
}
|
|
48728
|
+
var sleep10 = (ms) => new Promise((done) => setTimeout(done, ms));
|
|
48729
|
+
async function followRelease(ctx, ref, options = {}) {
|
|
48730
|
+
requireToken(ctx.target);
|
|
48731
|
+
const { out, api } = ctx;
|
|
48732
|
+
const review = reviewOf2(options);
|
|
48733
|
+
const wait2 = options.wait ?? followFor(void 0);
|
|
48734
|
+
const deadline = (options.since ?? Date.now()) + wait2 * 1e3;
|
|
48735
|
+
let body = {
|
|
48736
|
+
...review ? { review } : {},
|
|
48737
|
+
...options.testAgain ? { test_again: true } : {}
|
|
48738
|
+
};
|
|
48739
|
+
let said2 = "";
|
|
48740
|
+
let asked = "";
|
|
48741
|
+
let step;
|
|
48742
|
+
for (; ; ) {
|
|
48743
|
+
const left = Math.max(0, Math.ceil((deadline - Date.now()) / 1e3));
|
|
48744
|
+
const answer = await api.POST("/projects/{owner}/{slug}/releases/{version}/publish", {
|
|
48745
|
+
params: { path: ref, query: { wait: Math.min(50, left) } },
|
|
48746
|
+
body
|
|
48747
|
+
});
|
|
48748
|
+
if (!answer.response.ok) throw fromResponse(answer.response, answer.error);
|
|
48749
|
+
step = answer.data;
|
|
48750
|
+
body = void 0;
|
|
48751
|
+
const stops = ENDS.has(step.stage) || step.status === "in_review";
|
|
48752
|
+
const line = lineOf(step);
|
|
48753
|
+
if (line !== said2 && !stops) out.progress(line, { stage: step.stage });
|
|
48754
|
+
said2 = line;
|
|
48755
|
+
if (step.needs_person && step.needs_person.ask !== asked) {
|
|
48756
|
+
asked = step.needs_person.ask;
|
|
48757
|
+
out.step(step.needs_person.ask);
|
|
48758
|
+
}
|
|
48759
|
+
if (stops || Date.now() >= deadline) break;
|
|
48760
|
+
if (step.needs_person) await sleep10(PERSON_EVERY_MS);
|
|
48761
|
+
}
|
|
48762
|
+
return finish(ctx, ref, step, options);
|
|
48763
|
+
}
|
|
48764
|
+
async function finish(ctx, ref, step, options) {
|
|
48765
|
+
const { out } = ctx;
|
|
48766
|
+
const name = releaseName(ref);
|
|
48767
|
+
const url2 = step.urls.play ?? step.urls.page;
|
|
48768
|
+
const ended = ENDS.has(step.stage) && step.stage !== "stopped" && step.stage !== "uploading";
|
|
48769
|
+
const { extra = {}, notes = [] } = (ended || step.stage === "testing" || step.stage === "in_review" ? await options.after?.(step) : void 0) ?? {};
|
|
48770
|
+
const base = { release: { ...ref }, stage: step.stage, line: step.line, url: url2, ...extra };
|
|
48771
|
+
const say = (...lines) => {
|
|
48772
|
+
for (const line of [...notes, ...lines]) out.line(line);
|
|
48773
|
+
};
|
|
48774
|
+
switch (step.stage) {
|
|
48775
|
+
case "live":
|
|
48776
|
+
out.result({ ...base, next: null }, () => say(import_picocolors21.default.green(`Live: ${url2}`)));
|
|
48777
|
+
return;
|
|
48778
|
+
case "ready":
|
|
48779
|
+
out.result(
|
|
48780
|
+
{ ...base, next: null },
|
|
48781
|
+
() => say("Passed. Press publish in the chat to send it.")
|
|
48782
|
+
);
|
|
48783
|
+
return;
|
|
48784
|
+
case "your_review": {
|
|
48785
|
+
const sheets = await saveSheets(
|
|
48786
|
+
step.footage ?? [],
|
|
48787
|
+
options.out ? resolve16(options.out) : await mkdtemp4(join19(tmpdir4(), "pch-footage-"))
|
|
48788
|
+
);
|
|
48789
|
+
const next = reviewCommand2(name);
|
|
48790
|
+
out.result({ ...base, test: step.test_id, footage: sheets, next }, () => {
|
|
48791
|
+
say(`Recorded. Look at every sheet, the bursts too:`);
|
|
48792
|
+
for (const sheet of sheets) out.line(` ${sheet.path} (${sheet.from} to ${sheet.to})`);
|
|
48793
|
+
out.line(`Then send your review (add --harmful if it does anything harmful):`);
|
|
48794
|
+
out.line(`Next: ${next}`);
|
|
48795
|
+
});
|
|
48796
|
+
return;
|
|
48797
|
+
}
|
|
48798
|
+
case "stopped":
|
|
48799
|
+
case "uploading": {
|
|
48800
|
+
const code = step.code ?? "stopped";
|
|
48801
|
+
const exit = code === "test_failed" || code === "rejected" || code === "files_failed" ? EXIT.blocked : EXIT.error;
|
|
48802
|
+
throw new CliError(exit, code, `${name}: ${step.next}`, {
|
|
48803
|
+
findings: step.findings,
|
|
48804
|
+
issues: step.findings.map((finding) => ({
|
|
48805
|
+
path: finding.path ?? "",
|
|
48806
|
+
message: `${finding.rule_id}: ${finding.message}${finding.suggestion ? `. ${finding.suggestion}` : ""}`
|
|
48807
|
+
})),
|
|
48808
|
+
next: code === "couldnt_run" ? `pch publish ${name} --test-again` : "pch publish"
|
|
48809
|
+
});
|
|
48810
|
+
}
|
|
48811
|
+
default: {
|
|
48812
|
+
const staff = step.status === "in_review";
|
|
48813
|
+
const next = staff ? `pch status ${name}` : `pch publish ${name}`;
|
|
48814
|
+
out.result({ ...base, needs_person: step.needs_person, next }, () => {
|
|
48815
|
+
if (staff) say("Waiting for a staff member at Patch.");
|
|
48816
|
+
else say(`Still ${lineOf(step).replace(/^./, (first) => first.toLowerCase())}.`);
|
|
48817
|
+
out.line(`Next: ${next}`);
|
|
48818
|
+
});
|
|
48819
|
+
}
|
|
48820
|
+
}
|
|
48821
|
+
}
|
|
48822
|
+
async function firstTime(ctx, project2, what) {
|
|
48823
|
+
const key = `${ctx.target.origin} ${project2}`;
|
|
48824
|
+
ctx.config.told ??= {};
|
|
48825
|
+
const told = ctx.config.told[key] ?? [];
|
|
48826
|
+
if (told.includes(what)) return false;
|
|
48827
|
+
ctx.config.told[key] = [...told, what];
|
|
48828
|
+
await writeConfig(ctx.config).catch(() => void 0);
|
|
48829
|
+
return true;
|
|
48830
|
+
}
|
|
48831
|
+
|
|
48832
|
+
// src/commands/publish.ts
|
|
48129
48833
|
var MB = 1024 * 1024;
|
|
48130
48834
|
var FILES_AT_ONCE = 16;
|
|
48131
48835
|
var BIG_FILE = 16 * MB;
|
|
@@ -48141,8 +48845,23 @@ function forkNamesParent(named, clone3) {
|
|
|
48141
48845
|
{ named, clone: clone3 }
|
|
48142
48846
|
);
|
|
48143
48847
|
}
|
|
48144
|
-
async function publish(ctx,
|
|
48848
|
+
async function publish(ctx, target, options) {
|
|
48145
48849
|
requireToken(ctx.target);
|
|
48850
|
+
const started = Date.now();
|
|
48851
|
+
const follow4 = {
|
|
48852
|
+
...options,
|
|
48853
|
+
wait: followFor(options.wait),
|
|
48854
|
+
since: started
|
|
48855
|
+
};
|
|
48856
|
+
if (target.includes("@")) return followRelease(ctx, parseReleaseRef(target), follow4);
|
|
48857
|
+
if (options.review !== void 0 || options.testAgain) {
|
|
48858
|
+
throw new CliError(
|
|
48859
|
+
EXIT.usage,
|
|
48860
|
+
"release_required",
|
|
48861
|
+
"Say which release: pch publish <owner/slug@version> --review \u2026, as pch publish printed it."
|
|
48862
|
+
);
|
|
48863
|
+
}
|
|
48864
|
+
const dir = target;
|
|
48146
48865
|
const { out, api } = ctx;
|
|
48147
48866
|
const project2 = await loadProject(dir);
|
|
48148
48867
|
if (project2.issues.length) {
|
|
@@ -48201,53 +48920,41 @@ async function publish(ctx, dir, options) {
|
|
|
48201
48920
|
} else {
|
|
48202
48921
|
out.step("Patch already has every file");
|
|
48203
48922
|
}
|
|
48204
|
-
out.step("Submitting");
|
|
48205
|
-
const name = releaseName({ ...ref, version: version2 });
|
|
48206
|
-
const submitted = await submit(ctx, { ...ref, version: version2 }).catch(draftUntilTested);
|
|
48207
|
-
const release2 = await unwrap(
|
|
48208
|
-
api.GET("/projects/{owner}/{slug}/releases/{version}", { params: { path: path2 } })
|
|
48209
|
-
);
|
|
48210
|
-
const url2 = projectUrl(ctx.target.origin, ref);
|
|
48211
48923
|
const branch = await source?.bringBranch();
|
|
48212
|
-
|
|
48213
|
-
|
|
48214
|
-
|
|
48215
|
-
|
|
48216
|
-
|
|
48217
|
-
|
|
48218
|
-
|
|
48219
|
-
|
|
48220
|
-
|
|
48221
|
-
|
|
48222
|
-
|
|
48223
|
-
|
|
48224
|
-
|
|
48225
|
-
|
|
48226
|
-
|
|
48227
|
-
|
|
48228
|
-
|
|
48229
|
-
|
|
48230
|
-
|
|
48231
|
-
|
|
48232
|
-
|
|
48233
|
-
|
|
48234
|
-
|
|
48235
|
-
|
|
48236
|
-
|
|
48237
|
-
|
|
48238
|
-
|
|
48239
|
-
|
|
48240
|
-
|
|
48241
|
-
|
|
48242
|
-
|
|
48243
|
-
|
|
48244
|
-
for (const entry of release2.files) {
|
|
48245
|
-
out.line(
|
|
48246
|
-
` ${entry.path.padEnd(40)} ${formatSize(entry.size).padStart(8)} sha256 ${entry.sha256}`
|
|
48247
|
-
);
|
|
48924
|
+
await followRelease(
|
|
48925
|
+
ctx,
|
|
48926
|
+
{ ...ref, version: version2 },
|
|
48927
|
+
{
|
|
48928
|
+
...follow4,
|
|
48929
|
+
// Who can see it, as it is once it stops. It's the same each time, so it's said once a
|
|
48930
|
+
// project, and --json always has it.
|
|
48931
|
+
after: async () => {
|
|
48932
|
+
const release3 = await unwrap(
|
|
48933
|
+
api.GET("/projects/{owner}/{slug}/releases/{version}", { params: { path: path2 } })
|
|
48934
|
+
);
|
|
48935
|
+
const openCode = await openCodeOf(ctx, ref, release3);
|
|
48936
|
+
const privately = release3.channel === "private" ? privateWords(release3.kind, release3.status !== "published") : null;
|
|
48937
|
+
const notes = [source ? fromCommit(source).trim() : null, branch?.say ?? null].filter(
|
|
48938
|
+
(note2) => Boolean(note2)
|
|
48939
|
+
);
|
|
48940
|
+
for (const [what, words2] of [
|
|
48941
|
+
["private", privately],
|
|
48942
|
+
["open_code", openCode]
|
|
48943
|
+
]) {
|
|
48944
|
+
if (words2 && await firstTime(ctx, file2.project, what)) notes.push(words2);
|
|
48945
|
+
}
|
|
48946
|
+
return {
|
|
48947
|
+
extra: {
|
|
48948
|
+
release: release3,
|
|
48949
|
+
uploaded: draft.uploads.length,
|
|
48950
|
+
...branch ? { branch } : {},
|
|
48951
|
+
...whoCanSeeIt(privately, openCode)
|
|
48952
|
+
},
|
|
48953
|
+
notes
|
|
48954
|
+
};
|
|
48955
|
+
}
|
|
48248
48956
|
}
|
|
48249
|
-
|
|
48250
|
-
});
|
|
48957
|
+
);
|
|
48251
48958
|
}
|
|
48252
48959
|
function fromCommit(source) {
|
|
48253
48960
|
const on = source.on === "GitHub" ? " on GitHub" : "";
|
|
@@ -48255,36 +48962,36 @@ function fromCommit(source) {
|
|
|
48255
48962
|
}
|
|
48256
48963
|
async function releaseSubmit(ctx, ref) {
|
|
48257
48964
|
requireToken(ctx.target);
|
|
48258
|
-
const
|
|
48259
|
-
const submitted = await submit(ctx,
|
|
48260
|
-
const next = nextStep(releaseName(
|
|
48261
|
-
const openCode = await openCodeOf(ctx,
|
|
48965
|
+
const release3 = parseReleaseRef(ref);
|
|
48966
|
+
const submitted = await submit(ctx, release3);
|
|
48967
|
+
const next = nextStep(releaseName(release3), submitted.status, null).command;
|
|
48968
|
+
const openCode = await openCodeOf(ctx, release3, submitted);
|
|
48262
48969
|
const whoCanSee = whoCanSeeIt(
|
|
48263
48970
|
submitted.channel === "private" && submitted.status === "published" ? privateWords(submitted.kind, false) : null,
|
|
48264
48971
|
openCode
|
|
48265
48972
|
);
|
|
48266
48973
|
ctx.out.result({ ...submitted, next, ...whoCanSee }, () => {
|
|
48267
|
-
showSubmitted(ctx,
|
|
48974
|
+
showSubmitted(ctx, release3, submitted.status, submitted);
|
|
48268
48975
|
if (openCode) ctx.out.line(openCode);
|
|
48269
|
-
ctx.out.line(
|
|
48976
|
+
ctx.out.line(import_picocolors22.default.dim(projectUrl(ctx.target.origin, release3)));
|
|
48270
48977
|
});
|
|
48271
48978
|
}
|
|
48272
48979
|
async function releasePromote(ctx, ref, options) {
|
|
48273
48980
|
requireToken(ctx.target);
|
|
48274
48981
|
const from = parseReleaseRef(ref);
|
|
48275
48982
|
const project2 = { owner: from.owner, slug: from.slug };
|
|
48276
|
-
const
|
|
48983
|
+
const release3 = await unwrap(
|
|
48277
48984
|
ctx.api.GET("/projects/{owner}/{slug}/releases/{version}", { params: { path: from } })
|
|
48278
48985
|
);
|
|
48279
|
-
if (
|
|
48280
|
-
const next = nextStep(ref,
|
|
48986
|
+
if (release3.status !== "published") {
|
|
48987
|
+
const next = nextStep(ref, release3.status, null);
|
|
48281
48988
|
throw new CliError(
|
|
48282
48989
|
EXIT.conflict,
|
|
48283
48990
|
"not_published",
|
|
48284
|
-
`${ref} is ${
|
|
48991
|
+
`${ref} is ${release3.status}, and only a published release can move to another channel. ${next.say}${next.command ? ` Next: ${next.command}` : ""} To release it elsewhere instead, set "channel" in patch.json, move the version on, and run pch publish.`
|
|
48285
48992
|
);
|
|
48286
48993
|
}
|
|
48287
|
-
if (
|
|
48994
|
+
if (release3.channel === options.channel) {
|
|
48288
48995
|
throw new CliError(
|
|
48289
48996
|
EXIT.conflict,
|
|
48290
48997
|
"already_on_channel",
|
|
@@ -48298,10 +49005,10 @@ async function releasePromote(ctx, ref, options) {
|
|
|
48298
49005
|
body: {
|
|
48299
49006
|
version: version2,
|
|
48300
49007
|
channel: options.channel,
|
|
48301
|
-
changelog: options.changelog ??
|
|
48302
|
-
...
|
|
48303
|
-
...
|
|
48304
|
-
files:
|
|
49008
|
+
changelog: options.changelog ?? release3.changelog ?? void 0,
|
|
49009
|
+
...release3.recipe ? { recipe: release3.recipe } : {},
|
|
49010
|
+
...release3.commit ? { commit: release3.commit } : {},
|
|
49011
|
+
files: release3.files.map(({ path: path2, sha256, size }) => ({ path: path2, sha256, size }))
|
|
48305
49012
|
}
|
|
48306
49013
|
})
|
|
48307
49014
|
);
|
|
@@ -48320,7 +49027,7 @@ async function releasePromote(ctx, ref, options) {
|
|
|
48320
49027
|
const next = nextStep(name, "draft", null).command;
|
|
48321
49028
|
ctx.out.result({ from: ref, release: { version: version2, status: "draft" }, url: url2, next }, () => {
|
|
48322
49029
|
ctx.out.line(
|
|
48323
|
-
`Made ${
|
|
49030
|
+
`Made ${import_picocolors22.default.bold(name)} from ${ref}, as a draft for ${options.channel}. It needs a passed test of its own.`
|
|
48324
49031
|
);
|
|
48325
49032
|
ctx.out.line(`Next: ${next}`);
|
|
48326
49033
|
});
|
|
@@ -48328,7 +49035,7 @@ async function releasePromote(ctx, ref, options) {
|
|
|
48328
49035
|
}
|
|
48329
49036
|
ctx.out.result({ from: ref, release: submitted, url: url2 }, () => {
|
|
48330
49037
|
showSubmitted(ctx, to, submitted.status, submitted);
|
|
48331
|
-
ctx.out.line(
|
|
49038
|
+
ctx.out.line(import_picocolors22.default.dim(url2));
|
|
48332
49039
|
});
|
|
48333
49040
|
}
|
|
48334
49041
|
function draftUntilTested(error62) {
|
|
@@ -48344,7 +49051,7 @@ async function nextVersion(ctx, project2) {
|
|
|
48344
49051
|
params: { path: project2, query: { limit: 100, cursor } }
|
|
48345
49052
|
})
|
|
48346
49053
|
);
|
|
48347
|
-
versions.push(...page.data.map((
|
|
49054
|
+
versions.push(...page.data.map((release3) => release3.version));
|
|
48348
49055
|
cursor = page.next_cursor ?? void 0;
|
|
48349
49056
|
} while (cursor);
|
|
48350
49057
|
const newest2 = import_semver2.default.rsort(versions.filter((version2) => import_semver2.default.valid(version2)))[0];
|
|
@@ -48354,9 +49061,9 @@ function privateWords(kind, draft) {
|
|
|
48354
49061
|
if (!onGameHost(kind)) return "Only your team can find it.";
|
|
48355
49062
|
return draft ? PRIVATE_DRAFT : PRIVATE_GAME;
|
|
48356
49063
|
}
|
|
48357
|
-
async function openCodeOf(ctx, { owner, slug: slug2 },
|
|
49064
|
+
async function openCodeOf(ctx, { owner, slug: slug2 }, release3) {
|
|
48358
49065
|
const repo = await ctx.api.GET("/projects/{owner}/{slug}/repo", { params: { path: { owner, slug: slug2 } } }).catch(() => null);
|
|
48359
|
-
return openCodeWords(`${owner}/${slug2}`, repo?.data,
|
|
49066
|
+
return openCodeWords(`${owner}/${slug2}`, repo?.data, release3);
|
|
48360
49067
|
}
|
|
48361
49068
|
function whoCanSeeIt(...words2) {
|
|
48362
49069
|
const said2 = words2.filter(Boolean).join(" ");
|
|
@@ -48365,7 +49072,7 @@ function whoCanSeeIt(...words2) {
|
|
|
48365
49072
|
function showSubmitted(ctx, ref, status2, { channel, kind }) {
|
|
48366
49073
|
const name = releaseName(ref);
|
|
48367
49074
|
if (status2 === "published") {
|
|
48368
|
-
ctx.out.line(
|
|
49075
|
+
ctx.out.line(import_picocolors22.default.green(`Published ${name} to ${channel}`));
|
|
48369
49076
|
if (channel === "private") {
|
|
48370
49077
|
ctx.out.line(privateWords(kind, false));
|
|
48371
49078
|
ctx.out.line(`pch status ${name} says whether your team can play it yet, and where.`);
|
|
@@ -48518,314 +49225,8 @@ async function uploadFailed(file2, upload, response) {
|
|
|
48518
49225
|
);
|
|
48519
49226
|
}
|
|
48520
49227
|
|
|
48521
|
-
// src/commands/release-test.ts
|
|
48522
|
-
var import_picocolors20 = __toESM(require_picocolors(), 1);
|
|
48523
|
-
import { createHash as createHash3 } from "node:crypto";
|
|
48524
|
-
import { mkdir as mkdir9, mkdtemp as mkdtemp3, writeFile as writeFile12 } from "node:fs/promises";
|
|
48525
|
-
import { tmpdir as tmpdir3 } from "node:os";
|
|
48526
|
-
import { join as join18, resolve as resolve15 } from "node:path";
|
|
48527
|
-
import { setTimeout as sleep9 } from "node:timers/promises";
|
|
48528
|
-
init_errors();
|
|
48529
|
-
var HOLD_SECONDS = 50;
|
|
48530
|
-
var ASK_EVERY_MS2 = 5e3;
|
|
48531
|
-
var OPEN = /* @__PURE__ */ new Set(["waiting", "running", "recorded"]);
|
|
48532
|
-
var GOING = /* @__PURE__ */ new Set(["waiting", "running"]);
|
|
48533
|
-
var REVIEW_ROUTE = "/projects/{owner}/{slug}/releases/{version}/review";
|
|
48534
|
-
var reviewOf = (ctx, release2) => ctx.api.GET(REVIEW_ROUTE, { params: { path: release2 } });
|
|
48535
|
-
async function testStart(ctx, ref, options) {
|
|
48536
|
-
requireToken(ctx.target);
|
|
48537
|
-
const release2 = parseReleaseRef(ref);
|
|
48538
|
-
const wait2 = waitOf(options.wait);
|
|
48539
|
-
const before = await reviewOf(ctx, release2);
|
|
48540
|
-
const open3 = before.data?.test && OPEN.has(before.data.test.state) ? before.data.test : null;
|
|
48541
|
-
const test2 = open3 ?? await startOne(ctx, release2, before.data?.status);
|
|
48542
|
-
return show3(ctx, release2, wait2 === null ? test2 : await follow3(ctx, test2, wait2), {
|
|
48543
|
-
started: !open3,
|
|
48544
|
-
followed: wait2 !== null
|
|
48545
|
-
});
|
|
48546
|
-
}
|
|
48547
|
-
async function startOne(ctx, release2, status2) {
|
|
48548
|
-
const name = releaseName(release2);
|
|
48549
|
-
const answer = await ctx.api.POST("/projects/{owner}/{slug}/releases/{version}/tests", {
|
|
48550
|
-
params: { path: release2 }
|
|
48551
|
-
});
|
|
48552
|
-
if (answer.response.ok) return answer.data;
|
|
48553
|
-
const failure2 = fromResponse(answer.response, answer.error);
|
|
48554
|
-
if (failure2.code === "files_failed") {
|
|
48555
|
-
const { findings } = failure2.details;
|
|
48556
|
-
throw filesFailed(failure2.message, findings);
|
|
48557
|
-
}
|
|
48558
|
-
if (failure2.code === "not_testable") {
|
|
48559
|
-
const next = `pch release submit ${name}`;
|
|
48560
|
-
throw new CliError(
|
|
48561
|
-
failure2.exitCode,
|
|
48562
|
-
failure2.code,
|
|
48563
|
-
`Patch Desktop can't test ${name}, as it's a mod players install some other way, so it skips the test. Send it for review once it's ready: ${next}`,
|
|
48564
|
-
{ next }
|
|
48565
|
-
);
|
|
48566
|
-
}
|
|
48567
|
-
if (failure2.code === "not_testing" && status2) {
|
|
48568
|
-
const next = nextStep(name, status2, null);
|
|
48569
|
-
throw new CliError(
|
|
48570
|
-
failure2.exitCode,
|
|
48571
|
-
failure2.code,
|
|
48572
|
-
`${name} is ${status2}, so it can't be tested. ${sayNext(next)}`,
|
|
48573
|
-
{ next: next.command }
|
|
48574
|
-
);
|
|
48575
|
-
}
|
|
48576
|
-
throw failure2;
|
|
48577
|
-
}
|
|
48578
|
-
async function testView(ctx, ref, options) {
|
|
48579
|
-
requireToken(ctx.target);
|
|
48580
|
-
const release2 = parseReleaseRef(ref);
|
|
48581
|
-
const wait2 = waitOf(options.wait);
|
|
48582
|
-
const test2 = await newest(ctx, release2);
|
|
48583
|
-
return show3(ctx, release2, wait2 === null ? test2 : await follow3(ctx, test2, wait2), {
|
|
48584
|
-
followed: wait2 !== null
|
|
48585
|
-
});
|
|
48586
|
-
}
|
|
48587
|
-
async function newest(ctx, release2) {
|
|
48588
|
-
const review = await unwrap(reviewOf(ctx, release2));
|
|
48589
|
-
if (review.test) return review.test;
|
|
48590
|
-
const name = releaseName(release2);
|
|
48591
|
-
const next = `pch test start ${name}`;
|
|
48592
|
-
throw new CliError(EXIT.error, "not_found", `${name} has no test yet. Start one: ${next}`, {
|
|
48593
|
-
next
|
|
48594
|
-
});
|
|
48595
|
-
}
|
|
48596
|
-
async function follow3(ctx, test2, seconds2) {
|
|
48597
|
-
const deadline = Date.now() + seconds2 * 1e3;
|
|
48598
|
-
let current = test2;
|
|
48599
|
-
let said2 = "";
|
|
48600
|
-
while (GOING.has(current.state) && Date.now() < deadline) {
|
|
48601
|
-
const now = whatsHappening(current);
|
|
48602
|
-
if (now !== said2) ctx.out.step(now);
|
|
48603
|
-
said2 = now;
|
|
48604
|
-
if (current.needs_person) await sleep9(Math.min(ASK_EVERY_MS2, deadline - Date.now()));
|
|
48605
|
-
const left = Math.ceil((deadline - Date.now()) / 1e3);
|
|
48606
|
-
current = await unwrap(
|
|
48607
|
-
ctx.api.GET("/tests/{id}", {
|
|
48608
|
-
params: {
|
|
48609
|
-
path: { id: current.id },
|
|
48610
|
-
query: { wait: Math.max(0, Math.min(HOLD_SECONDS, left)) }
|
|
48611
|
-
}
|
|
48612
|
-
})
|
|
48613
|
-
);
|
|
48614
|
-
}
|
|
48615
|
-
return current;
|
|
48616
|
-
}
|
|
48617
|
-
function whatsHappening(test2) {
|
|
48618
|
-
if (test2.state === "running") {
|
|
48619
|
-
const where = test2.pc_name ? ` on ${plain(test2.pc_name)}` : "";
|
|
48620
|
-
const phase = test2.progress?.phase;
|
|
48621
|
-
return phase && phase !== "playing" ? `Patch Desktop${where} is ${plain(phase)} it` : `Patch Desktop${where} is playing it`;
|
|
48622
|
-
}
|
|
48623
|
-
if (test2.needs_person) return plain(test2.needs_person.ask);
|
|
48624
|
-
return "Waiting for you to press Start in Patch Desktop";
|
|
48625
|
-
}
|
|
48626
|
-
async function show3(ctx, release2, test2, { followed, ...extra } = {}) {
|
|
48627
|
-
const { out } = ctx;
|
|
48628
|
-
const name = releaseName(release2);
|
|
48629
|
-
const next = nextStep(name, test2.state === "failed" ? "rejected" : "testing", test2);
|
|
48630
|
-
const command = next.command;
|
|
48631
|
-
const play2 = test2.state === "passed" ? await playFirst(ctx, release2) : null;
|
|
48632
|
-
const ask = test2.state === "waiting" ? askThePerson(test2) : play2;
|
|
48633
|
-
const noteAsk = test2.note_ask?.replaceAll("suggest_note", "pch note suggest");
|
|
48634
|
-
const feedbackAsk = test2.feedback_ask ? {
|
|
48635
|
-
...test2.feedback_ask,
|
|
48636
|
-
ask: test2.feedback_ask.ask.replaceAll("send_feedback", "pch feedback send")
|
|
48637
|
-
} : void 0;
|
|
48638
|
-
const shown = {
|
|
48639
|
-
...test2,
|
|
48640
|
-
...extra,
|
|
48641
|
-
next: command,
|
|
48642
|
-
...noteAsk ? { note_ask: noteAsk } : {},
|
|
48643
|
-
...feedbackAsk ? { feedback_ask: feedbackAsk } : {}
|
|
48644
|
-
};
|
|
48645
|
-
out.result({ ...shown, ...ask ? { ask } : {} }, () => {
|
|
48646
|
-
const bold = import_picocolors20.default.bold(name);
|
|
48647
|
-
if (test2.state === "waiting") {
|
|
48648
|
-
out.line(
|
|
48649
|
-
!test2.needs_person ? `${extra.started ? "Started a test of" : "The test of"} ${bold}. It's waiting for Patch Desktop to take it on.` : extra.started ? `Started a test of ${bold}. It's waiting for you in Patch Desktop: press Start on it there.` : `${bold}: its test is waiting for you in Patch Desktop. Press Start on it there.`
|
|
48650
|
-
);
|
|
48651
|
-
if (test2.needs_person) {
|
|
48652
|
-
out.line(plain(test2.needs_person.why));
|
|
48653
|
-
out.line(plain(test2.needs_person.ask));
|
|
48654
|
-
}
|
|
48655
|
-
} else if (test2.state === "running") {
|
|
48656
|
-
out.line(`${bold}: ${whatsHappening(test2)}.`);
|
|
48657
|
-
} else if (test2.state === "recorded") {
|
|
48658
|
-
out.line(
|
|
48659
|
-
`${bold}: it has been played and recorded. Look at the footage, then send your review.`
|
|
48660
|
-
);
|
|
48661
|
-
} else if (test2.state === "passed") {
|
|
48662
|
-
out.line(`${bold}: ${import_picocolors20.default.green("its test passed.")} It's still private.`);
|
|
48663
|
-
if (test2.review) out.line(plain(test2.review.summary));
|
|
48664
|
-
if (play2) out.line(play2);
|
|
48665
|
-
out.line(
|
|
48666
|
-
"Publish it once you're happy it's ready: that's your yes, and nothing does it for you."
|
|
48667
|
-
);
|
|
48668
|
-
} else if (test2.state === "failed") {
|
|
48669
|
-
out.line(`${bold}: ${import_picocolors20.default.red("its test failed")}, so this version can't be published.`);
|
|
48670
|
-
showWhatFailed(ctx, test2);
|
|
48671
|
-
out.line(`pch status ${name} says how to fix it.`);
|
|
48672
|
-
out.line("Then move the version on in patch.json and publish that.");
|
|
48673
|
-
} else if (test2.state === "couldnt_run") {
|
|
48674
|
-
out.line(`${bold}: ${import_picocolors20.default.yellow("its test couldn't run.")}`);
|
|
48675
|
-
if (test2.outcome) out.line(plain(test2.outcome.reason));
|
|
48676
|
-
} else {
|
|
48677
|
-
out.line(`${bold}: its test was cancelled.`);
|
|
48678
|
-
}
|
|
48679
|
-
if (noteAsk) out.line(plain(noteAsk));
|
|
48680
|
-
if (feedbackAsk) out.line(plain(feedbackAsk.ask));
|
|
48681
|
-
if (followed && GOING.has(test2.state)) {
|
|
48682
|
-
out.line("It isn't over, and nothing's wrong: that's as long as --wait follows it here.");
|
|
48683
|
-
}
|
|
48684
|
-
if (command) out.line(`Next: ${command}`);
|
|
48685
|
-
if (OPEN.has(test2.state) && !extra.started) {
|
|
48686
|
-
out.line(import_picocolors20.default.dim(`To start over: pch test cancel ${name}, then pch test start ${name}`));
|
|
48687
|
-
}
|
|
48688
|
-
});
|
|
48689
|
-
if (test2.state === "failed") return EXIT.blocked;
|
|
48690
|
-
if (test2.state === "couldnt_run" || test2.state === "cancelled") return EXIT.error;
|
|
48691
|
-
return EXIT.ok;
|
|
48692
|
-
}
|
|
48693
|
-
function askThePerson(test2) {
|
|
48694
|
-
return test2.needs_person?.ask ?? null;
|
|
48695
|
-
}
|
|
48696
|
-
function showWhatFailed({ out }, test2) {
|
|
48697
|
-
for (const run3 of test2.results) {
|
|
48698
|
-
for (const check3 of run3.checks) {
|
|
48699
|
-
if (check3.result !== "fail") continue;
|
|
48700
|
-
const detail = check3.detail ? ` (${plain(check3.detail)})` : "";
|
|
48701
|
-
out.line(` Run ${run3.run}: ${plain(check3.name)}${detail}`);
|
|
48702
|
-
}
|
|
48703
|
-
}
|
|
48704
|
-
if (test2.review) out.line(` Its review: ${plain(test2.review.summary)}`);
|
|
48705
|
-
}
|
|
48706
|
-
async function testFootage(ctx, ref, options) {
|
|
48707
|
-
requireToken(ctx.target);
|
|
48708
|
-
const { out } = ctx;
|
|
48709
|
-
const release2 = parseReleaseRef(ref);
|
|
48710
|
-
const name = releaseName(release2);
|
|
48711
|
-
const run3 = options.run === void 0 ? void 0 : positive(options.run, "--run");
|
|
48712
|
-
const test2 = await newest(ctx, release2);
|
|
48713
|
-
const { data } = await unwrap(
|
|
48714
|
-
ctx.api.GET("/tests/{id}/footage", { params: { path: { id: test2.id }, query: { run: run3 } } })
|
|
48715
|
-
);
|
|
48716
|
-
const sheets = data.length ? await saveSheets(
|
|
48717
|
-
data,
|
|
48718
|
-
options.out ? resolve15(options.out) : await mkdtemp3(join18(tmpdir3(), "pch-footage-"))
|
|
48719
|
-
) : [];
|
|
48720
|
-
const next = data.length ? reviewCommand(name) : nextStep(name, "testing", test2).command;
|
|
48721
|
-
out.result({ test: test2.id, state: test2.state, data: sheets, next }, () => {
|
|
48722
|
-
if (!sheets.length) {
|
|
48723
|
-
out.line(`${import_picocolors20.default.bold(name)} has no footage yet: its test is ${test2.state.replace("_", " ")}.`);
|
|
48724
|
-
for (const result of test2.results) {
|
|
48725
|
-
if (result.footage_error) out.line(plain(result.footage_error));
|
|
48726
|
-
}
|
|
48727
|
-
}
|
|
48728
|
-
for (const sheet of sheets) {
|
|
48729
|
-
out.line(`Run ${sheet.run}, ${sheet.from} to ${sheet.to}: ${sheet.path}`);
|
|
48730
|
-
}
|
|
48731
|
-
if (next) {
|
|
48732
|
-
if (sheets.length) out.line("Look at every one, then send your review, with what you saw:");
|
|
48733
|
-
out.line(`Next: ${next}`);
|
|
48734
|
-
}
|
|
48735
|
-
});
|
|
48736
|
-
}
|
|
48737
|
-
var reviewCommand = (name) => `pch test review ${name} --verdict <pass|fail> --plays <yes|no> --harmful <yes|no> --summary "<what you saw>" --moment "<run> <mm:ss> <what you saw>" --moment "<run> <mm:ss> <what you saw>"`;
|
|
48738
|
-
async function saveSheets(sheets, folder) {
|
|
48739
|
-
await mkdir9(folder, { recursive: true });
|
|
48740
|
-
const saved = [];
|
|
48741
|
-
for (const sheet of sheets) {
|
|
48742
|
-
const answer = await fetch(sheet.url).catch(() => null);
|
|
48743
|
-
const bytes = answer?.ok ? Buffer.from(await answer.arrayBuffer()) : null;
|
|
48744
|
-
if (!bytes || createHash3("sha256").update(bytes).digest("hex") !== sheet.sha256) {
|
|
48745
|
-
throw new CliError(
|
|
48746
|
-
EXIT.error,
|
|
48747
|
-
"download_failed",
|
|
48748
|
-
`Run ${sheet.run}'s sheet ${sheet.index} didn't download intact. Try again.`
|
|
48749
|
-
);
|
|
48750
|
-
}
|
|
48751
|
-
const path2 = join18(folder, `run-${sheet.run}-sheet-${sheet.index}.jpg`);
|
|
48752
|
-
await writeFile12(path2, bytes);
|
|
48753
|
-
const { url: _url2, ...kept } = sheet;
|
|
48754
|
-
saved.push({ ...kept, path: path2 });
|
|
48755
|
-
}
|
|
48756
|
-
return saved;
|
|
48757
|
-
}
|
|
48758
|
-
async function testReview(ctx, ref, options) {
|
|
48759
|
-
requireToken(ctx.target);
|
|
48760
|
-
const release2 = parseReleaseRef(ref);
|
|
48761
|
-
const moments = options.moment.map(parseMoment);
|
|
48762
|
-
const test2 = await newest(ctx, release2);
|
|
48763
|
-
const answer = await ctx.api.POST("/tests/{id}/review", {
|
|
48764
|
-
params: { path: { id: test2.id } },
|
|
48765
|
-
body: {
|
|
48766
|
-
verdict: options.verdict,
|
|
48767
|
-
plays: options.plays === "yes",
|
|
48768
|
-
harmful: options.harmful === "yes",
|
|
48769
|
-
summary: options.summary,
|
|
48770
|
-
moments
|
|
48771
|
-
}
|
|
48772
|
-
});
|
|
48773
|
-
if (answer.response.ok) return show3(ctx, release2, answer.data);
|
|
48774
|
-
throw withNext(fromResponse(answer.response, answer.error), release2, test2);
|
|
48775
|
-
}
|
|
48776
|
-
function parseMoment(text) {
|
|
48777
|
-
const match = /^(\d+)\s+(\d{1,3}:\d{2})\s+(\S.*)$/s.exec(text.trim());
|
|
48778
|
-
if (!match) {
|
|
48779
|
-
throw new CliError(
|
|
48780
|
-
EXIT.usage,
|
|
48781
|
-
"bad_moment",
|
|
48782
|
-
`"${text}" isn't a moment. Write its run, the time on a frame and what you saw, such as "1 00:04 the title screen draws".`
|
|
48783
|
-
);
|
|
48784
|
-
}
|
|
48785
|
-
return { run: Number(match[1]), at: match[2], saw: match[3] };
|
|
48786
|
-
}
|
|
48787
|
-
async function testCancel(ctx, ref) {
|
|
48788
|
-
requireToken(ctx.target);
|
|
48789
|
-
const release2 = parseReleaseRef(ref);
|
|
48790
|
-
const name = releaseName(release2);
|
|
48791
|
-
const test2 = await newest(ctx, release2);
|
|
48792
|
-
const answer = await ctx.api.POST("/tests/{id}/cancel", { params: { path: { id: test2.id } } });
|
|
48793
|
-
if (!answer.response.ok) {
|
|
48794
|
-
throw withNext(fromResponse(answer.response, answer.error), release2, test2);
|
|
48795
|
-
}
|
|
48796
|
-
const next = `pch test start ${name}`;
|
|
48797
|
-
ctx.out.result({ ...answer.data, next }, () => {
|
|
48798
|
-
ctx.out.line(`Cancelled ${import_picocolors20.default.bold(name)}'s test. Start another: ${next}`);
|
|
48799
|
-
});
|
|
48800
|
-
}
|
|
48801
|
-
function withNext(failure2, release2, test2) {
|
|
48802
|
-
if (failure2.code !== "test_state") return failure2;
|
|
48803
|
-
const next = nextStep(releaseName(release2), "testing", test2);
|
|
48804
|
-
return new CliError(failure2.exitCode, failure2.code, `${failure2.message}. ${sayNext(next)}`, {
|
|
48805
|
-
next: next.command
|
|
48806
|
-
});
|
|
48807
|
-
}
|
|
48808
|
-
function positive(value, flag) {
|
|
48809
|
-
if (/^[1-9]\d*$/.test(value)) return Number(value);
|
|
48810
|
-
throw new CliError(EXIT.usage, "bad_usage", `${flag} takes a whole number, not "${value}".`);
|
|
48811
|
-
}
|
|
48812
|
-
async function testAutostart(ctx, state) {
|
|
48813
|
-
requireToken(ctx.target);
|
|
48814
|
-
if (state !== "on" && state !== "off") {
|
|
48815
|
-
throw new CliError(EXIT.usage, "bad_value", `Say on or off, not "${state}"`);
|
|
48816
|
-
}
|
|
48817
|
-
const setting = await unwrap(
|
|
48818
|
-
ctx.api.PUT("/me/test-settings", { body: { starts_own_tests: state === "on", by: "agent" } })
|
|
48819
|
-
);
|
|
48820
|
-
ctx.out.result(setting, () => {
|
|
48821
|
-
ctx.out.line(
|
|
48822
|
-
setting.starts_own_tests ? "Your tests of browser games and blocks now start in Patch Desktop by themselves, with a notice each time that lets the person stop them. Patch Desktop tells them you turned it on. A mod's test still waits for Start." : "Your tests wait for the person to press Start in Patch Desktop again."
|
|
48823
|
-
);
|
|
48824
|
-
});
|
|
48825
|
-
}
|
|
48826
|
-
|
|
48827
49228
|
// src/commands/search.ts
|
|
48828
|
-
var
|
|
49229
|
+
var import_picocolors23 = __toESM(require_picocolors(), 1);
|
|
48829
49230
|
init_guide_words();
|
|
48830
49231
|
var filter = (values) => values?.flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean).join(",") || void 0;
|
|
48831
49232
|
var licences = (values) => values.flatMap((value) => value.split(",")).map((value) => licenceId(value.trim()) ?? value.trim());
|
|
@@ -48863,13 +49264,13 @@ async function search(ctx, q, options) {
|
|
|
48863
49264
|
out.result({ ...answer, ...aboutBlocks }, () => {
|
|
48864
49265
|
if (!data.length) out.line("Nothing found.");
|
|
48865
49266
|
for (const result of plain(data)) {
|
|
48866
|
-
out.line(`${
|
|
49267
|
+
out.line(`${import_picocolors23.default.bold(result.handle)} ${import_picocolors23.default.dim(facts(result))}`);
|
|
48867
49268
|
if (result.summary) out.line(` ${result.summary}`);
|
|
48868
49269
|
if (result.demo) out.line(` Live demo: ${ctx.target.origin}/${result.owner}/${result.slug}`);
|
|
48869
|
-
out.line(
|
|
49270
|
+
out.line(import_picocolors23.default.dim(` ${result.clone_url}`));
|
|
48870
49271
|
}
|
|
48871
49272
|
if (blocks) out.line(BLOCK_WORDS);
|
|
48872
|
-
if (cursor) out.line(
|
|
49273
|
+
if (cursor) out.line(import_picocolors23.default.dim("There are more; raise --limit."));
|
|
48873
49274
|
});
|
|
48874
49275
|
}
|
|
48875
49276
|
function facts(result) {
|
|
@@ -48894,21 +49295,21 @@ async function categories(ctx, options) {
|
|
|
48894
49295
|
const width = Math.max(0, ...data.map((category) => category.id.length));
|
|
48895
49296
|
for (const category of data) {
|
|
48896
49297
|
ctx.out.line(
|
|
48897
|
-
`${
|
|
49298
|
+
`${import_picocolors23.default.bold(category.id.padEnd(width))} ${category.name} ${import_picocolors23.default.dim(`\xB7 ${category.for.join(", ")} \xB7 ${category.projects === 1 ? "1 project" : `${category.projects} projects`}`)}`
|
|
48898
49299
|
);
|
|
48899
49300
|
}
|
|
48900
49301
|
});
|
|
48901
49302
|
}
|
|
48902
49303
|
|
|
48903
49304
|
// src/commands/update.ts
|
|
48904
|
-
var
|
|
49305
|
+
var import_picocolors24 = __toESM(require_picocolors(), 1);
|
|
48905
49306
|
var import_semver3 = __toESM(require_semver2(), 1);
|
|
48906
|
-
import { spawnSync as
|
|
49307
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
48907
49308
|
import { createHash as createHash4 } from "node:crypto";
|
|
48908
49309
|
import { mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
48909
49310
|
import { chmod as chmod3, rename as rename2, rm as rm3, writeFile as writeFile13 } from "node:fs/promises";
|
|
48910
|
-
import { basename as basename8, dirname as dirname7, join as
|
|
48911
|
-
import { setTimeout as
|
|
49311
|
+
import { basename as basename8, dirname as dirname7, join as join20 } from "node:path";
|
|
49312
|
+
import { setTimeout as sleep11 } from "node:timers/promises";
|
|
48912
49313
|
import { gunzipSync } from "node:zlib";
|
|
48913
49314
|
init_errors();
|
|
48914
49315
|
init_version();
|
|
@@ -49016,7 +49417,7 @@ async function update(ctx) {
|
|
|
49016
49417
|
const fresh = `${target}.new`;
|
|
49017
49418
|
await writeFile13(fresh, gunzipSync(packed), { mode: 493 });
|
|
49018
49419
|
await chmod3(fresh, 493);
|
|
49019
|
-
const said2 =
|
|
49420
|
+
const said2 = spawnSync3(fresh, ["--version"], { encoding: "utf8", timeout: 6e4 });
|
|
49020
49421
|
if (said2.status !== 0 || said2.stdout.trim() !== latest2) {
|
|
49021
49422
|
await whenFree(() => rm3(fresh, { force: true })).catch(() => void 0);
|
|
49022
49423
|
throw new CliError(
|
|
@@ -49051,7 +49452,7 @@ async function whenFree(step, wait2 = 200) {
|
|
|
49051
49452
|
} catch (error62) {
|
|
49052
49453
|
const code = error62.code ?? "";
|
|
49053
49454
|
if (attempt >= 10 || !["EBUSY", "EPERM", "EACCES"].includes(code)) throw error62;
|
|
49054
|
-
await
|
|
49455
|
+
await sleep11(attempt * wait2);
|
|
49055
49456
|
}
|
|
49056
49457
|
}
|
|
49057
49458
|
}
|
|
@@ -49061,7 +49462,7 @@ function removeOldExecutables(target = process.execPath) {
|
|
|
49061
49462
|
const old = new RegExp(`^${basename8(target).replace(/\./g, "\\.")}\\.\\d+\\.old$`, "i");
|
|
49062
49463
|
try {
|
|
49063
49464
|
for (const name of readdirSync(folder)) {
|
|
49064
|
-
if (old.test(name)) rmSync(
|
|
49465
|
+
if (old.test(name)) rmSync(join20(folder, name), { force: true });
|
|
49065
49466
|
}
|
|
49066
49467
|
} catch {
|
|
49067
49468
|
}
|
|
@@ -49071,7 +49472,7 @@ function startUpdateNotice(ctx, command, now = Date.now()) {
|
|
|
49071
49472
|
const releases = releasesUrl(ctx.target.env);
|
|
49072
49473
|
if (!how || !releases || ctx.out.json || QUIET.has(command)) return null;
|
|
49073
49474
|
if (!process.stderr.isTTY || process.env.CI) return null;
|
|
49074
|
-
const stamp =
|
|
49475
|
+
const stamp = join20(dirname7(configPath()), "update-check.json");
|
|
49075
49476
|
try {
|
|
49076
49477
|
const { checked_at } = JSON.parse(readFileSync(stamp, "utf8"));
|
|
49077
49478
|
if (checked_at && now - Date.parse(checked_at) < DAY) return null;
|
|
@@ -49100,7 +49501,7 @@ function startUpdateNotice(ctx, command, now = Date.now()) {
|
|
|
49100
49501
|
if (version2 && import_semver3.default.gt(version2, VERSION)) {
|
|
49101
49502
|
process.stderr.write(
|
|
49102
49503
|
`
|
|
49103
|
-
${
|
|
49504
|
+
${import_picocolors24.default.dim("\u203A")} pch ${version2} is out (you have ${VERSION}). To update, run: ${how}
|
|
49104
49505
|
`
|
|
49105
49506
|
);
|
|
49106
49507
|
}
|
|
@@ -49109,7 +49510,7 @@ ${import_picocolors22.default.dim("\u203A")} pch ${version2} is out (you have ${
|
|
|
49109
49510
|
}
|
|
49110
49511
|
|
|
49111
49512
|
// src/commands/validate.ts
|
|
49112
|
-
var
|
|
49513
|
+
var import_picocolors25 = __toESM(require_picocolors(), 1);
|
|
49113
49514
|
init_errors();
|
|
49114
49515
|
async function validate2(ctx, dir) {
|
|
49115
49516
|
const project2 = await loadProject(dir);
|
|
@@ -49168,12 +49569,12 @@ async function validate2(ctx, dir) {
|
|
|
49168
49569
|
notes
|
|
49169
49570
|
};
|
|
49170
49571
|
ctx.out.result(result, () => {
|
|
49171
|
-
const mark = result.ok ?
|
|
49572
|
+
const mark = result.ok ? import_picocolors25.default.green("\u2713") : import_picocolors25.default.red("\u2717");
|
|
49172
49573
|
ctx.out.line(
|
|
49173
49574
|
`${mark} ${file2.project}@${file2.version} \xB7 ${project2.kind} \xB7 ${result.trust_tier} \xB7 ${project2.files.length} files, ${formatSize(total)}`
|
|
49174
49575
|
);
|
|
49175
|
-
for (const issue2 of issues) ctx.out.line(` ${
|
|
49176
|
-
for (const note2 of notes) ctx.out.line(` ${
|
|
49576
|
+
for (const issue2 of issues) ctx.out.line(` ${import_picocolors25.default.red(issue2.path || "\xB7")} ${issue2.message}`);
|
|
49577
|
+
for (const note2 of notes) ctx.out.line(` ${import_picocolors25.default.dim(note2)}`);
|
|
49177
49578
|
});
|
|
49178
49579
|
return result.ok ? EXIT.ok : EXIT.usage;
|
|
49179
49580
|
}
|
|
@@ -49208,8 +49609,7 @@ Start here:
|
|
|
49208
49609
|
pch search "grappling hook" find a block or game to start from, then pch remix or pch clone it, and change it
|
|
49209
49610
|
pch assets search "crate" --type model find a model, sound or texture, then pch assets add it
|
|
49210
49611
|
pch new <you>/<game> --licence MIT make a repo with a patch.json, or pch init in a folder you have
|
|
49211
|
-
pch validate, then pch publish
|
|
49212
|
-
pch test start <you>/<game>@<version> a game, mod or block goes live only after passing a test in Patch Desktop
|
|
49612
|
+
pch validate, then pch publish upload, test on your PC, your review of the footage, then live
|
|
49213
49613
|
pch media add, pch project update the cover and the listing
|
|
49214
49614
|
|
|
49215
49615
|
Add --json to any command for results an agent can read. Exit codes: 0 fine, 1 error, 2 bad usage,
|
|
@@ -49463,17 +49863,29 @@ program2.command("check [dir]").description(
|
|
|
49463
49863
|
(ctx, dir, options) => check2(ctx, dir ?? ".", options)
|
|
49464
49864
|
)
|
|
49465
49865
|
);
|
|
49466
|
-
program2.command("publish [
|
|
49467
|
-
"
|
|
49866
|
+
program2.command("publish [target]").description(
|
|
49867
|
+
"Publish from upload to live: uploads what Patch doesn't have, tests it on your PC, saves the footage for your review, then follows Patch's review until it's live. Running it is your yes. Give owner/slug@version instead of a folder to carry a release on, with --review once you've looked at its footage"
|
|
49468
49868
|
).addOption(
|
|
49469
49869
|
new Option("--channel <channel>", `overrides patch.json. ${NOT_LOCKED_HELP}`).choices([
|
|
49470
49870
|
"private",
|
|
49471
49871
|
"unlisted",
|
|
49472
49872
|
"public"
|
|
49473
49873
|
])
|
|
49474
|
-
).option("--changelog <text>", "what changed, shown as the update notes").
|
|
49874
|
+
).option("--changelog <text>", "what changed, shown as the update notes").addOption(
|
|
49875
|
+
new Option("--review <verdict>", "your review of the footage: pass or fail").choices([
|
|
49876
|
+
"pass",
|
|
49877
|
+
"fail"
|
|
49878
|
+
])
|
|
49879
|
+
).option("--summary <text>", "with --review: what you saw, in a few lines").option(
|
|
49880
|
+
"--moment <moment>",
|
|
49881
|
+
'with --review: "<run> <mm:ss> <what you saw>", at least two, one per run (repeatable)',
|
|
49882
|
+
collect
|
|
49883
|
+
).option("--harmful", "with --review: it does something harmful (fails it)").option("--test-again", "test it again after a test that couldn't run").option("--out <dir>", "where to save the footage").option(
|
|
49884
|
+
"--wait <seconds>",
|
|
49885
|
+
"how long to follow it (default: until it stops, at a terminal; 100 s otherwise)"
|
|
49886
|
+
).action(
|
|
49475
49887
|
action(
|
|
49476
|
-
(ctx,
|
|
49888
|
+
(ctx, target, options) => publish(ctx, target ?? ".", options)
|
|
49477
49889
|
)
|
|
49478
49890
|
);
|
|
49479
49891
|
var project = program2.command("project").description("Projects, addressed owner/slug");
|
|
@@ -49617,10 +50029,10 @@ assets.command("add <ids...>").description(
|
|
|
49617
50029
|
(ctx, ids, options) => assetsAdd(ctx, ids, options)
|
|
49618
50030
|
)
|
|
49619
50031
|
);
|
|
49620
|
-
var
|
|
49621
|
-
|
|
49622
|
-
|
|
49623
|
-
|
|
50032
|
+
var release2 = program2.command("release").description("A project's releases");
|
|
50033
|
+
release2.command("list <project>").action(action((ctx, ref) => releaseList(ctx, ref)));
|
|
50034
|
+
release2.command("view <release>").description("owner/slug@version").action(action((ctx, ref) => releaseView(ctx, ref)));
|
|
50035
|
+
release2.command("submit <release>").description(
|
|
49624
50036
|
"Send a release on to be published, owner/slug@version: this is your yes. A browser game or a mod Patch installs has to have passed a test"
|
|
49625
50037
|
).action(action((ctx, ref) => releaseSubmit(ctx, ref)));
|
|
49626
50038
|
var test = program2.command("test").description(
|
|
@@ -49667,7 +50079,7 @@ test.command("review <release>").description("Send your review of a test's foota
|
|
|
49667
50079
|
)
|
|
49668
50080
|
);
|
|
49669
50081
|
test.command("cancel <release>").description("Cancel a release's test, if it hasn't been decided").action(action((ctx, ref) => testCancel(ctx, ref)));
|
|
49670
|
-
|
|
50082
|
+
release2.command("promote <release>").description(
|
|
49671
50083
|
"Put owner/slug@version on another channel: a new version with the same files, which uploads nothing"
|
|
49672
50084
|
).addOption(
|
|
49673
50085
|
new Option("--channel <channel>", `where it goes. ${NOT_LOCKED_HELP}`).choices(["private", "unlisted", "public"]).default("public")
|