@patchgg/cli 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -6
- package/dist/cli.js +367 -102
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,10 +5,14 @@ The command-line tool for [Patch](https://patch.gg): make, validate and publish
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
|
-
npm i -g @patchgg/cli
|
|
8
|
+
npm i -g @patchgg/cli # with Node 20 or newer
|
|
9
|
+
curl -fsSL https://patch.gg/install.sh | sh # macOS and Linux, no Node needed
|
|
10
|
+
irm https://patch.gg/install.ps1 | iex # Windows PowerShell, no Node needed
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
Every way of installing pch, and what each one takes, is on [docs.patch.gg/cli](https://docs.patch.gg/cli#install). They all get the same build of a version.
|
|
13
|
+
Every way of installing pch, and what each one takes, is on [docs.patch.gg/cli](https://docs.patch.gg/cli#install). They all get the same build of a version. The one-line installers put pch's executable in `~/.pch/bin` (`%USERPROFILE%\.pch\bin` on Windows) and add that to your PATH.
|
|
14
|
+
|
|
15
|
+
`pch update` brings the installers' pch up to the newest version. Installed with npm or pnpm, it says the command that updates it instead, and Patch Desktop's own pch moves with Patch Desktop. Once a day at most, pch says when a newer version is out, after a command in a terminal; never with `--json`, in CI, or when what it prints goes to another program.
|
|
12
16
|
|
|
13
17
|
## Use
|
|
14
18
|
|
|
@@ -468,6 +472,7 @@ This is on the `repos` branch, and Patch has it behind a flag, until it launches
|
|
|
468
472
|
| | `PCH_API_URL` | Point at another server, e.g. `http://localhost:3000` |
|
|
469
473
|
| | `PCH_GIT_URL` | Where that server's repos live, e.g. `http://localhost:8787` |
|
|
470
474
|
| | `PCH_CONFIG` | Config file (default `~/.pch/config.json`, mode 600) |
|
|
475
|
+
| | `PCH_CDN_URL` | Where pch looks for its own releases, for `pch update` |
|
|
471
476
|
|
|
472
477
|
## Your agent
|
|
473
478
|
|
|
@@ -508,12 +513,35 @@ PCH_BIN=dist/cli.js pnpm test # the tests that run pch as a command, against t
|
|
|
508
513
|
|
|
509
514
|
CI runs both, the first on Windows, macOS and Linux.
|
|
510
515
|
|
|
516
|
+
### What the installers put in place
|
|
517
|
+
|
|
518
|
+
The one-line installers download an executable: pch's `dist/` and Node 26 in one file, through Node's single executable support, so it needs nothing installed. `scripts/build-binary.mjs` makes it from a `dist/` folder, on Node 26, for the machine it runs on, with the licence texts and the notices (Node's own licence added) inside it. On macOS it signs it ad hoc, which Apple Silicon needs.
|
|
519
|
+
|
|
520
|
+
```sh
|
|
521
|
+
node scripts/build-binary.mjs dist out # on Node 26: out/pch-<os>-<arch>[.exe]
|
|
522
|
+
node scripts/smoke.mjs --executable out/pch-... # the smoke test, and pch update, against it
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
CI runs both on Windows, macOS and Linux. pch knows it's the executable through `node:sea` (`src/install.ts`): it reads its licence texts from inside itself, and git's sign-in helper and the MCP server line `pch agent install` writes run it by its own path.
|
|
526
|
+
|
|
511
527
|
### Releasing
|
|
512
528
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
529
|
+
**Release with the button:** [Actions, Release pch](https://github.com/patch-gg/pch/actions/workflows/release.yml), **Run workflow** on `main`, then pick `patch` (fixes), `minor` (new things) or `major` (breaking). It tags `main` with the next version and releases it to npm and the one-line installers together. Patch-gg/patch's `product/design/cli.md`, Releasing pch, says what each step does and what to do when one fails.
|
|
530
|
+
|
|
531
|
+
`package.json` stays `"private": true`, so npm and pnpm refuse to publish pch from a checkout, and a test keeps it that way. [`publish.yml`](.github/workflows/publish.yml) is the only way out. It runs on a `v<version>` tag on `main`, which is the version, and stops unless production has repos. It publishes with npm's trusted publishing, so no npm key is kept anywhere: npm takes `@patchgg/cli` only from this repo's `publish.yml`, as the package's settings on npmjs.com say. One build per version: its `pack` job runs every check, builds once, takes `private` out of its own copy, packs, tries the tarball installed, and keeps it as the run's artifact `npm-package`. Every other way of installing pch is made from that tarball's `dist/`, never rebuilt from source. Then, in order:
|
|
532
|
+
- `executable` makes the six executables, each on its own runner (Linux, macOS and Windows, x64 and arm64), and runs the smoke test against each, and on Linux the tests too.
|
|
533
|
+
- `sign-windows` signs both Windows executables with Patch's Artifact Signing certificate profile and checks each signature, in the `production` environment. It signs in to Azure with GitHub's OIDC token, which the app registration `patch-pch-signing-production` trusts for that environment alone, so there's no secret. A Windows executable is never shipped unsigned: missing settings stop the release.
|
|
534
|
+
- `staging` gzips them, writes `SHA256SUMS`, and puts the version on cdn-staging.patch.gg under `cli/<version>/`, then moves `cli/latest.json` to it ([`scripts/put-release.sh`](scripts/put-release.sh)).
|
|
535
|
+
- `try-installers` installs it from staging.patch.gg on a clean Debian container, Linux on Arm, both kinds of Mac and both kinds of Windows, and checks it runs, says its version, and on Windows, its signature.
|
|
536
|
+
- `publish` sends the tarball to npm, with provenance once this repo is public.
|
|
537
|
+
- `production` puts the same files on cdn.patch.gg and moves production's `latest.json`.
|
|
538
|
+
|
|
539
|
+
Nothing reaches npm or production unless every platform got through. A version's files never change: a version that's on the CDN already with other files stops the release. `latest.json` only moves forward, and it's the one version check: the installers read it, and so do `pch update` and the notice. The `staging` and `production` environments take `v*` tags only, and hold R2 keys for their own public bucket alone (`infra/scripts/create-pch-release-keys.mjs` in patch-gg/patch makes them).
|
|
540
|
+
To release what's on `main`:
|
|
541
|
+
1. `pnpm sync <a patch checkout at main>`, then `pnpm generate`, so what's released checks what the API checks, and merge that to `main` if it changed anything.
|
|
542
|
+
2. Press the button: Actions, **Release pch**, Run workflow on `main`, and pick how big a change it is: `patch` for fixes, `minor` for new things, `major` for a breaking change. Or `gh workflow run release.yml -f bump=minor`.
|
|
543
|
+
|
|
544
|
+
The button ([`release.yml`](.github/workflows/release.yml)) takes the newest `v<version>` tag, moves it on by that much, tags `main`'s newest commit with it and starts `publish.yml` on the tag. It refuses a commit that's released already. A person can still push a tag by hand, which runs the same release. Nobody edits a version: the tag is the version, `publish.yml` writes it into its own copy of `package.json`, and the build writes that into the bundle. `version` in `package.json` on `main` is only what a copy run from source says.
|
|
517
545
|
|
|
518
546
|
### End to end
|
|
519
547
|
|
package/dist/cli.js
CHANGED
|
@@ -49,14 +49,14 @@ var require_picocolors = __commonJS({
|
|
|
49
49
|
var argv = p.argv || [];
|
|
50
50
|
var env = p.env || {};
|
|
51
51
|
var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
|
|
52
|
-
var formatter = (open3, close,
|
|
52
|
+
var formatter = (open3, close, replace2 = open3) => (input2) => {
|
|
53
53
|
let string4 = "" + input2, index = string4.indexOf(close, open3.length);
|
|
54
|
-
return ~index ? open3 + replaceClose(string4, close,
|
|
54
|
+
return ~index ? open3 + replaceClose(string4, close, replace2, index) + close : open3 + string4 + close;
|
|
55
55
|
};
|
|
56
|
-
var replaceClose = (string4, close,
|
|
56
|
+
var replaceClose = (string4, close, replace2, index) => {
|
|
57
57
|
let result = "", cursor = 0;
|
|
58
58
|
do {
|
|
59
|
-
result += string4.substring(cursor, index) +
|
|
59
|
+
result += string4.substring(cursor, index) + replace2;
|
|
60
60
|
cursor = index + close.length;
|
|
61
61
|
index = string4.indexOf(close, cursor);
|
|
62
62
|
} while (~index);
|
|
@@ -172,7 +172,7 @@ var VERSION;
|
|
|
172
172
|
var init_version = __esm({
|
|
173
173
|
"src/version.ts"() {
|
|
174
174
|
"use strict";
|
|
175
|
-
VERSION = true ? "0.1.
|
|
175
|
+
VERSION = true ? "0.1.1" : JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
176
176
|
}
|
|
177
177
|
});
|
|
178
178
|
|
|
@@ -39734,6 +39734,62 @@ init_errors();
|
|
|
39734
39734
|
import { spawn } from "node:child_process";
|
|
39735
39735
|
import { resolve } from "node:path";
|
|
39736
39736
|
init_errors();
|
|
39737
|
+
|
|
39738
|
+
// src/install.ts
|
|
39739
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
39740
|
+
import { createRequire } from "node:module";
|
|
39741
|
+
import { dirname as dirname2, join as join2, sep } from "node:path";
|
|
39742
|
+
var require2 = createRequire(import.meta.url);
|
|
39743
|
+
function seaApi() {
|
|
39744
|
+
try {
|
|
39745
|
+
return require2("node:sea");
|
|
39746
|
+
} catch {
|
|
39747
|
+
return null;
|
|
39748
|
+
}
|
|
39749
|
+
}
|
|
39750
|
+
var sea = seaApi();
|
|
39751
|
+
var isExecutable = sea?.isSea() ?? false;
|
|
39752
|
+
function embeddedText(key) {
|
|
39753
|
+
if (!sea || !isExecutable) throw new Error(`pch isn't an executable, so ${key} isn't in it.`);
|
|
39754
|
+
return sea.getAsset(key, "utf8");
|
|
39755
|
+
}
|
|
39756
|
+
function installKind(script = process.argv[1], versions = process.versions, executable = isExecutable) {
|
|
39757
|
+
if (executable) return "executable";
|
|
39758
|
+
if (versions.electron) return "desktop";
|
|
39759
|
+
let path2 = script ?? "";
|
|
39760
|
+
try {
|
|
39761
|
+
path2 = realpathSync(path2);
|
|
39762
|
+
} catch {
|
|
39763
|
+
}
|
|
39764
|
+
const parts = path2.split(/[\\/]/);
|
|
39765
|
+
const at = parts.lastIndexOf("node_modules");
|
|
39766
|
+
if (at < 0 || parts[at + 1] !== "@patchgg" || parts[at + 2] !== "cli") return "other";
|
|
39767
|
+
if (/[\\/]pnpm[\\/]global[\\/]/.test(path2)) return "pnpm";
|
|
39768
|
+
const owner = parts.slice(0, at).join(sep);
|
|
39769
|
+
return existsSync(join2(owner || sep, "package.json")) ? "other" : "npm";
|
|
39770
|
+
}
|
|
39771
|
+
function updateCommand(kind) {
|
|
39772
|
+
if (kind === "executable") return "pch update";
|
|
39773
|
+
if (kind === "npm") return "npm install -g @patchgg/cli";
|
|
39774
|
+
if (kind === "pnpm") return "pnpm add -g @patchgg/cli";
|
|
39775
|
+
return null;
|
|
39776
|
+
}
|
|
39777
|
+
function executableName(platform = process.platform, arch = process.arch) {
|
|
39778
|
+
const os = { linux: "linux", darwin: "macos", win32: "windows" }[platform];
|
|
39779
|
+
if (!os || arch !== "x64" && arch !== "arm64") return null;
|
|
39780
|
+
return `pch-${os}-${arch}${os === "windows" ? ".exe" : ""}`;
|
|
39781
|
+
}
|
|
39782
|
+
var CDNS = {
|
|
39783
|
+
production: "https://cdn.patch.gg",
|
|
39784
|
+
staging: "https://cdn-staging.patch.gg",
|
|
39785
|
+
local: null
|
|
39786
|
+
};
|
|
39787
|
+
function releasesUrl(env, vars = process.env) {
|
|
39788
|
+
const base = vars.PCH_CDN_URL ?? CDNS[env];
|
|
39789
|
+
return base ? `${base.replace(/\/+$/, "")}/cli` : null;
|
|
39790
|
+
}
|
|
39791
|
+
|
|
39792
|
+
// src/git.ts
|
|
39737
39793
|
function runGit(args2, options = {}) {
|
|
39738
39794
|
return new Promise((done, fail) => {
|
|
39739
39795
|
const child = spawn("git", args2, {
|
|
@@ -39813,7 +39869,7 @@ function gitFailed(args2, result, advice = "") {
|
|
|
39813
39869
|
);
|
|
39814
39870
|
}
|
|
39815
39871
|
var helperKey = (url2) => `credential.${url2}.helper`;
|
|
39816
|
-
var PCH_GONE =
|
|
39872
|
+
var PCH_GONE = `pch isn't where it was, so git can't sign in to Patch. Install it again with ${isExecutable ? "the installer at https://docs.patch.gg/cli" : "npm i -g @patchgg/cli"}, then run pch login.`;
|
|
39817
39873
|
function helperCommand({ config: config2, target }) {
|
|
39818
39874
|
const usesToken = Boolean(process.env.PCH_TOKEN);
|
|
39819
39875
|
const vars = {
|
|
@@ -39827,10 +39883,11 @@ function helperCommand({ config: config2, target }) {
|
|
|
39827
39883
|
ELECTRON_RUN_AS_NODE: process.env.ELECTRON_RUN_AS_NODE
|
|
39828
39884
|
};
|
|
39829
39885
|
const settings = Object.entries(vars).filter(([, value]) => value).map(([name, value]) => `export ${name}=${shellQuote(value)}; `).join("");
|
|
39830
|
-
const
|
|
39831
|
-
const script = shellQuote(process.argv[1]);
|
|
39832
|
-
const here = [
|
|
39833
|
-
|
|
39886
|
+
const program3 = shellQuote(process.execPath);
|
|
39887
|
+
const script = isExecutable ? null : shellQuote(process.argv[1]);
|
|
39888
|
+
const here = script ? [program3, ...nodeFlags().map(shellQuote), script].join(" ") : program3;
|
|
39889
|
+
const there = script ? `[ -x ${program3} ] && [ -e ${script} ]` : `[ -x ${program3} ]`;
|
|
39890
|
+
return `!f() { ${settings}if ${there}; then ${here} git-credential "$1"; elif command -v pch >/dev/null; then pch git-credential "$1"; else echo "${PCH_GONE}" >&2; fi; }; f`;
|
|
39834
39891
|
}
|
|
39835
39892
|
function nodeFlags() {
|
|
39836
39893
|
return process.execArgv.map(
|
|
@@ -40308,7 +40365,7 @@ async function tokenRevoke(ctx, id) {
|
|
|
40308
40365
|
var import_picocolors5 = __toESM(require_picocolors(), 1);
|
|
40309
40366
|
import { constants } from "node:fs";
|
|
40310
40367
|
import { access as access2, copyFile, lstat, mkdir as mkdir2, readdir as readdir2, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
|
|
40311
|
-
import { basename, dirname as
|
|
40368
|
+
import { basename, dirname as dirname3, extname, join as join5, resolve as resolve4 } from "node:path";
|
|
40312
40369
|
import { createInterface } from "node:readline";
|
|
40313
40370
|
|
|
40314
40371
|
// src/project-files.ts
|
|
@@ -40316,7 +40373,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
40316
40373
|
import { createHash } from "node:crypto";
|
|
40317
40374
|
import { createReadStream } from "node:fs";
|
|
40318
40375
|
import { readdir, readFile as readFile2, stat } from "node:fs/promises";
|
|
40319
|
-
import { join as
|
|
40376
|
+
import { join as join3, relative, resolve as resolve2, sep as sep2 } from "node:path";
|
|
40320
40377
|
|
|
40321
40378
|
// node_modules/.pnpm/zod@4.6.7/node_modules/zod/index.js
|
|
40322
40379
|
init_external();
|
|
@@ -42085,7 +42142,7 @@ var projectFileSchema = external_exports.object({
|
|
|
42085
42142
|
var usage = (code, message, details) => new CliError(EXIT.usage, code, message, details);
|
|
42086
42143
|
async function loadProject(dir) {
|
|
42087
42144
|
const root = resolve2(dir);
|
|
42088
|
-
const path2 =
|
|
42145
|
+
const path2 = join3(root, "patch.json");
|
|
42089
42146
|
let text;
|
|
42090
42147
|
try {
|
|
42091
42148
|
text = await readFile2(path2, "utf8");
|
|
@@ -42129,11 +42186,11 @@ async function loadProject(dir) {
|
|
|
42129
42186
|
);
|
|
42130
42187
|
}
|
|
42131
42188
|
const paths = (await listFiles(filesRoot)).filter(
|
|
42132
|
-
(name) =>
|
|
42189
|
+
(name) => join3(filesRoot, name) !== path2 && !(filesRoot === root && (name === BRIEF_FILE || name.startsWith(`${BRIEF_FOLDER}/`)))
|
|
42133
42190
|
);
|
|
42134
42191
|
const files = [];
|
|
42135
42192
|
for (const name of paths) {
|
|
42136
|
-
const absolute =
|
|
42193
|
+
const absolute = join3(filesRoot, ...name.split("/"));
|
|
42137
42194
|
files.push({ path: name, absolute, ...await hashFile(absolute) });
|
|
42138
42195
|
}
|
|
42139
42196
|
return {
|
|
@@ -42179,11 +42236,11 @@ async function listFiles(root) {
|
|
|
42179
42236
|
const found = [];
|
|
42180
42237
|
async function walk(dir) {
|
|
42181
42238
|
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
42182
|
-
const absolute =
|
|
42239
|
+
const absolute = join3(dir, entry.name);
|
|
42183
42240
|
if (entry.isDirectory()) {
|
|
42184
42241
|
if (!SKIPPED_DIRS.has(entry.name)) await walk(absolute);
|
|
42185
42242
|
} else if (entry.isFile() && !SKIPPED_FILES.has(entry.name)) {
|
|
42186
|
-
found.push(relative(root, absolute).split(
|
|
42243
|
+
found.push(relative(root, absolute).split(sep2).join("/"));
|
|
42187
42244
|
}
|
|
42188
42245
|
}
|
|
42189
42246
|
}
|
|
@@ -42204,7 +42261,7 @@ function hashFile(path2) {
|
|
|
42204
42261
|
// src/commands/agent-guide.ts
|
|
42205
42262
|
var import_picocolors4 = __toESM(require_picocolors(), 1);
|
|
42206
42263
|
import { access, writeFile as writeFile2 } from "node:fs/promises";
|
|
42207
|
-
import { join as
|
|
42264
|
+
import { join as join4, resolve as resolve3 } from "node:path";
|
|
42208
42265
|
|
|
42209
42266
|
// src/starter.ts
|
|
42210
42267
|
var AGENT_GUIDE = `# Working on this project
|
|
@@ -42332,11 +42389,11 @@ Thumbs.db
|
|
|
42332
42389
|
|
|
42333
42390
|
// src/commands/agent-guide.ts
|
|
42334
42391
|
var AGENT_GUIDE_FILE = "AGENTS.md";
|
|
42335
|
-
var GUIDES = [AGENT_GUIDE_FILE, "CLAUDE.md",
|
|
42392
|
+
var GUIDES = [AGENT_GUIDE_FILE, "CLAUDE.md", join4(".claude", "CLAUDE.md")];
|
|
42336
42393
|
async function agentGuidesIn(root) {
|
|
42337
42394
|
const found = [];
|
|
42338
42395
|
for (const name of GUIDES) {
|
|
42339
|
-
const there = await access(
|
|
42396
|
+
const there = await access(join4(root, name)).then(
|
|
42340
42397
|
() => true,
|
|
42341
42398
|
() => false
|
|
42342
42399
|
);
|
|
@@ -42349,18 +42406,18 @@ async function agentGuideIn(root) {
|
|
|
42349
42406
|
}
|
|
42350
42407
|
async function writeAgentGuide(root) {
|
|
42351
42408
|
const own2 = await agentGuideIn(root);
|
|
42352
|
-
if (!own2) await writeFile2(
|
|
42409
|
+
if (!own2) await writeFile2(join4(root, AGENT_GUIDE_FILE), AGENT_GUIDE);
|
|
42353
42410
|
return { name: own2 ?? AGENT_GUIDE_FILE, written: !own2 };
|
|
42354
42411
|
}
|
|
42355
42412
|
async function agentGuide(ctx, dir) {
|
|
42356
42413
|
const root = resolve3(dir);
|
|
42357
42414
|
const { name, written } = await writeAgentGuide(root);
|
|
42358
|
-
ctx.out.result({ path:
|
|
42415
|
+
ctx.out.result({ path: join4(root, name), written }, () => {
|
|
42359
42416
|
if (written) {
|
|
42360
|
-
ctx.out.line(`Wrote ${import_picocolors4.default.bold(
|
|
42417
|
+
ctx.out.line(`Wrote ${import_picocolors4.default.bold(join4(root, name))}, for coding agents working in this folder.`);
|
|
42361
42418
|
ctx.out.line(import_picocolors4.default.dim("Commit it: in a clone, pch publish refuses files that aren't."));
|
|
42362
42419
|
} else {
|
|
42363
|
-
ctx.out.line(`${
|
|
42420
|
+
ctx.out.line(`${join4(root, name)} is this folder's guide for agents, so it's left as it is.`);
|
|
42364
42421
|
ctx.out.line(
|
|
42365
42422
|
import_picocolors4.default.dim("pch guide has how to run, check and publish, to add to it if it helps.")
|
|
42366
42423
|
);
|
|
@@ -42413,7 +42470,7 @@ ${shown.join("\n")}
|
|
|
42413
42470
|
${said2.join("\n")}` : null;
|
|
42414
42471
|
}
|
|
42415
42472
|
async function gatherPictures(root, named, say) {
|
|
42416
|
-
const folder =
|
|
42473
|
+
const folder = join5(root, BRIEF_FOLDER);
|
|
42417
42474
|
for (const path2 of named) {
|
|
42418
42475
|
const from = resolve4(path2.trim().replace(/^["']|["']$/g, ""));
|
|
42419
42476
|
const found = await lstat(from).catch(() => null);
|
|
@@ -42421,7 +42478,7 @@ async function gatherPictures(root, named, say) {
|
|
|
42421
42478
|
say(`Left out ${path2}: pch takes a PNG, JPEG, GIF or WebP file it can read.`);
|
|
42422
42479
|
continue;
|
|
42423
42480
|
}
|
|
42424
|
-
if (
|
|
42481
|
+
if (dirname3(from) === folder) continue;
|
|
42425
42482
|
await mkdir2(folder, { recursive: true });
|
|
42426
42483
|
await copyAside(from, folder);
|
|
42427
42484
|
}
|
|
@@ -42433,7 +42490,7 @@ async function copyAside(from, folder) {
|
|
|
42433
42490
|
const stem = basename(from, extension);
|
|
42434
42491
|
for (let number4 = 1; ; number4++) {
|
|
42435
42492
|
const name = number4 === 1 ? `${stem}${extension}` : `${stem}-${number4}${extension}`;
|
|
42436
|
-
const copied = await copyFile(from,
|
|
42493
|
+
const copied = await copyFile(from, join5(folder, name), constants.COPYFILE_EXCL).then(
|
|
42437
42494
|
() => true,
|
|
42438
42495
|
(error62) => {
|
|
42439
42496
|
if (error62.code === "EEXIST") return false;
|
|
@@ -42457,7 +42514,7 @@ async function fromTerminal(ctx) {
|
|
|
42457
42514
|
}
|
|
42458
42515
|
async function agentBrief(ctx, dir) {
|
|
42459
42516
|
const root = resolve4(dir);
|
|
42460
|
-
const path2 =
|
|
42517
|
+
const path2 = join5(root, BRIEF_FILE);
|
|
42461
42518
|
const there = await access2(path2).then(
|
|
42462
42519
|
() => true,
|
|
42463
42520
|
() => false
|
|
@@ -42507,7 +42564,7 @@ async function agentBrief(ctx, dir) {
|
|
|
42507
42564
|
async function guidesNotNaming(root) {
|
|
42508
42565
|
const silent = [];
|
|
42509
42566
|
for (const name of await agentGuidesIn(root)) {
|
|
42510
|
-
const text = await readFile3(
|
|
42567
|
+
const text = await readFile3(join5(root, name), "utf8").catch(() => "");
|
|
42511
42568
|
if (!text.includes(BRIEF_FILE)) silent.push(name);
|
|
42512
42569
|
}
|
|
42513
42570
|
return silent;
|
|
@@ -42517,10 +42574,10 @@ async function guidesNotNaming(root) {
|
|
|
42517
42574
|
var import_picocolors6 = __toESM(require_picocolors(), 1);
|
|
42518
42575
|
init_errors();
|
|
42519
42576
|
import { spawnSync } from "node:child_process";
|
|
42520
|
-
import { existsSync } from "node:fs";
|
|
42577
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
42521
42578
|
import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
|
|
42522
42579
|
import { homedir as homedir2 } from "node:os";
|
|
42523
|
-
import { delimiter, dirname as
|
|
42580
|
+
import { delimiter, dirname as dirname4, join as join6, resolve as resolve5 } from "node:path";
|
|
42524
42581
|
var AGENT_IDS = ["claude", "codex", "cursor"];
|
|
42525
42582
|
var AGENT_NAMES = {
|
|
42526
42583
|
claude: "Claude Code",
|
|
@@ -42617,7 +42674,7 @@ async function installCursor(server) {
|
|
|
42617
42674
|
...settings.mcpServers,
|
|
42618
42675
|
[server.name]: { command: server.command, args: server.args }
|
|
42619
42676
|
};
|
|
42620
|
-
await mkdir3(
|
|
42677
|
+
await mkdir3(dirname4(path2), { recursive: true });
|
|
42621
42678
|
await writeFile4(path2, `${JSON.stringify(settings, null, 2)}
|
|
42622
42679
|
`);
|
|
42623
42680
|
return { agent: "cursor", state: had ? "updated" : "added", where: path2 };
|
|
@@ -42636,7 +42693,7 @@ function mcpServer({ target }, options = {}, found = {}) {
|
|
|
42636
42693
|
return {
|
|
42637
42694
|
name,
|
|
42638
42695
|
command: process.execPath,
|
|
42639
|
-
args: [...process.execArgv, resolve5(process.argv[1]), ...flags]
|
|
42696
|
+
args: isExecutable ? flags : [...process.execArgv, resolve5(process.argv[1]), ...flags]
|
|
42640
42697
|
};
|
|
42641
42698
|
}
|
|
42642
42699
|
function commandLine(server) {
|
|
@@ -42657,7 +42714,7 @@ function pchLogin({ target }) {
|
|
|
42657
42714
|
}
|
|
42658
42715
|
function isHere(agent2) {
|
|
42659
42716
|
if (onPath(agent2)) return true;
|
|
42660
|
-
if (agent2 === "cursor") return
|
|
42717
|
+
if (agent2 === "cursor") return existsSync2(join6(homedir2(), ".cursor"));
|
|
42661
42718
|
return false;
|
|
42662
42719
|
}
|
|
42663
42720
|
function codexHas(server) {
|
|
@@ -42666,7 +42723,7 @@ function codexHas(server) {
|
|
|
42666
42723
|
}
|
|
42667
42724
|
function onPath(bin) {
|
|
42668
42725
|
const names = process.platform === "win32" ? [`${bin}.cmd`, `${bin}.exe`, `${bin}.bat`, bin] : [bin];
|
|
42669
|
-
return (process.env.PATH ?? "").split(delimiter).some((folder) => folder && names.some((name) =>
|
|
42726
|
+
return (process.env.PATH ?? "").split(delimiter).some((folder) => folder && names.some((name) => existsSync2(join6(folder, name))));
|
|
42670
42727
|
}
|
|
42671
42728
|
function run(bin, args2) {
|
|
42672
42729
|
const shell = process.platform === "win32";
|
|
@@ -42678,7 +42735,7 @@ function run(bin, args2) {
|
|
|
42678
42735
|
var import_picocolors7 = __toESM(require_picocolors(), 1);
|
|
42679
42736
|
import { createHash as createHash2 } from "node:crypto";
|
|
42680
42737
|
import { mkdir as mkdir4, readFile as readFile5, stat as stat2, writeFile as writeFile5 } from "node:fs/promises";
|
|
42681
|
-
import { basename as basename2, dirname as
|
|
42738
|
+
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";
|
|
42682
42739
|
import { setTimeout as sleep2 } from "node:timers/promises";
|
|
42683
42740
|
init_errors();
|
|
42684
42741
|
var DEFAULT_WAIT_SECONDS = 300;
|
|
@@ -42784,7 +42841,7 @@ async function assetsAdd(ctx, ids, options) {
|
|
|
42784
42841
|
saved.push(path2);
|
|
42785
42842
|
}
|
|
42786
42843
|
if (!saved.length) continue;
|
|
42787
|
-
const holds = saved.length === 1 ? saved[0] :
|
|
42844
|
+
const holds = saved.length === 1 ? saved[0] : dirname5(saved[0]);
|
|
42788
42845
|
credits.push({
|
|
42789
42846
|
files: creditedFiles(holds, saved.length > 1, project2?.filesRoot ?? process.cwd()),
|
|
42790
42847
|
license: item.license.spdx,
|
|
@@ -42871,7 +42928,7 @@ function placeInFolder(item, file2, taken) {
|
|
|
42871
42928
|
taken.add(path2);
|
|
42872
42929
|
return path2;
|
|
42873
42930
|
}
|
|
42874
|
-
var forwardSlashes = (path2) => path2.split(
|
|
42931
|
+
var forwardSlashes = (path2) => path2.split(sep3).join("/");
|
|
42875
42932
|
var fromHere = (path2) => forwardSlashes(relative2(process.cwd(), path2));
|
|
42876
42933
|
function creditedFiles(path2, folder, filesRoot) {
|
|
42877
42934
|
const inRelease = relative2(filesRoot, path2);
|
|
@@ -42881,15 +42938,15 @@ function creditedFiles(path2, folder, filesRoot) {
|
|
|
42881
42938
|
}
|
|
42882
42939
|
async function projectFileFor(root) {
|
|
42883
42940
|
const here = process.cwd();
|
|
42884
|
-
for (let dir = root; ; dir =
|
|
42885
|
-
const path2 =
|
|
42941
|
+
for (let dir = root; ; dir = dirname5(dir)) {
|
|
42942
|
+
const path2 = join7(dir, "patch.json");
|
|
42886
42943
|
if (await stat2(path2).catch(() => null)) {
|
|
42887
42944
|
const contents = await readJson(path2);
|
|
42888
42945
|
if (!contents) return null;
|
|
42889
42946
|
const files = typeof contents.files === "string" ? contents.files : ".";
|
|
42890
42947
|
return { path: path2, filesRoot: resolve6(dir, files), contents };
|
|
42891
42948
|
}
|
|
42892
|
-
if (dir === here || relative2(here, dir).startsWith("..") || dir ===
|
|
42949
|
+
if (dir === here || relative2(here, dir).startsWith("..") || dir === dirname5(dir)) return null;
|
|
42893
42950
|
}
|
|
42894
42951
|
}
|
|
42895
42952
|
async function readJson(path2) {
|
|
@@ -42909,7 +42966,7 @@ async function addToCreditsFile(path2, lines) {
|
|
|
42909
42966
|
async function addToRecipe(project2, credits) {
|
|
42910
42967
|
const { recipe } = project2.contents;
|
|
42911
42968
|
const inFile = typeof recipe === "string";
|
|
42912
|
-
const path2 = inFile ? resolve6(
|
|
42969
|
+
const path2 = inFile ? resolve6(dirname5(project2.path), recipe) : project2.path;
|
|
42913
42970
|
const held = inFile ? await readJson(path2) : recipe ?? {};
|
|
42914
42971
|
if (!held || held.credits !== void 0 && !Array.isArray(held.credits)) return null;
|
|
42915
42972
|
const before = held.credits ?? [];
|
|
@@ -42922,14 +42979,14 @@ async function addToRecipe(project2, credits) {
|
|
|
42922
42979
|
}
|
|
42923
42980
|
function inside(root, relative3) {
|
|
42924
42981
|
const path2 = resolve6(root, relative3);
|
|
42925
|
-
if (!path2.startsWith(root +
|
|
42982
|
+
if (!path2.startsWith(root + sep3)) {
|
|
42926
42983
|
throw new CliError(EXIT.error, "asset_path_invalid", `${relative3} isn't inside ${root}.`);
|
|
42927
42984
|
}
|
|
42928
42985
|
return path2;
|
|
42929
42986
|
}
|
|
42930
42987
|
async function writeInside(root, relative3, contents) {
|
|
42931
42988
|
const path2 = inside(root, relative3);
|
|
42932
|
-
await mkdir4(
|
|
42989
|
+
await mkdir4(dirname5(path2), { recursive: true });
|
|
42933
42990
|
await writeFile5(path2, contents);
|
|
42934
42991
|
}
|
|
42935
42992
|
async function download(url2) {
|
|
@@ -42951,7 +43008,7 @@ async function saveChecked(url2, path2, sha256) {
|
|
|
42951
43008
|
`${url2} didn't download as Patch says it should: try again.`
|
|
42952
43009
|
);
|
|
42953
43010
|
}
|
|
42954
|
-
await mkdir4(
|
|
43011
|
+
await mkdir4(dirname5(path2), { recursive: true });
|
|
42955
43012
|
await writeFile5(path2, bytes);
|
|
42956
43013
|
}
|
|
42957
43014
|
|
|
@@ -43454,8 +43511,8 @@ async function diffOf(ctx, project2, request) {
|
|
|
43454
43511
|
}
|
|
43455
43512
|
async function standing(ctx, project2, request) {
|
|
43456
43513
|
const { diff, upToDate } = await diffOf(ctx, project2, request);
|
|
43457
|
-
const
|
|
43458
|
-
return { diff, fields: { up_to_date: upToDate, update } };
|
|
43514
|
+
const update2 = upToDate === false ? await updateSteps(ctx, project2, request).catch(() => null) : null;
|
|
43515
|
+
return { diff, fields: { up_to_date: upToDate, update: update2 } };
|
|
43459
43516
|
}
|
|
43460
43517
|
async function updateSteps(ctx, project2, request) {
|
|
43461
43518
|
const repo = await unwrap(
|
|
@@ -43803,17 +43860,17 @@ async function changeRequestWithdraw(ctx, ref) {
|
|
|
43803
43860
|
|
|
43804
43861
|
// src/commands/check.ts
|
|
43805
43862
|
var import_picocolors10 = __toESM(require_picocolors(), 1);
|
|
43806
|
-
import { existsSync as
|
|
43863
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
43807
43864
|
import { mkdir as mkdir6, mkdtemp as mkdtemp2 } from "node:fs/promises";
|
|
43808
43865
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
43809
|
-
import { basename as basename3, join as
|
|
43866
|
+
import { basename as basename3, join as join12, resolve as resolve8 } from "node:path";
|
|
43810
43867
|
|
|
43811
43868
|
// src/check/browser.ts
|
|
43812
43869
|
import { spawn as spawn3 } from "node:child_process";
|
|
43813
|
-
import { existsSync as
|
|
43870
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
43814
43871
|
import { chmod as chmod2, mkdir as mkdir5, mkdtemp, rename, rm, writeFile as writeFile6 } from "node:fs/promises";
|
|
43815
43872
|
import { tmpdir, userInfo } from "node:os";
|
|
43816
|
-
import { delimiter as delimiter2, dirname as
|
|
43873
|
+
import { delimiter as delimiter2, dirname as dirname6, join as join8, resolve as resolve7, sep as sep4 } from "node:path";
|
|
43817
43874
|
import { inflateRawSync } from "node:zlib";
|
|
43818
43875
|
init_errors();
|
|
43819
43876
|
var VERSIONS = "https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json";
|
|
@@ -43822,9 +43879,9 @@ function installedBrowsers(vars) {
|
|
|
43822
43879
|
if (process.platform === "win32") {
|
|
43823
43880
|
const places = [vars.ProgramFiles, vars["ProgramFiles(x86)"], vars.LOCALAPPDATA];
|
|
43824
43881
|
return places.filter((place) => Boolean(place)).flatMap((place) => [
|
|
43825
|
-
|
|
43826
|
-
|
|
43827
|
-
|
|
43882
|
+
join8(place, "Google", "Chrome", "Application", "chrome.exe"),
|
|
43883
|
+
join8(place, "Microsoft", "Edge", "Application", "msedge.exe"),
|
|
43884
|
+
join8(place, "Chromium", "Application", "chrome.exe")
|
|
43828
43885
|
]);
|
|
43829
43886
|
}
|
|
43830
43887
|
if (process.platform === "darwin") {
|
|
@@ -43835,7 +43892,7 @@ function installedBrowsers(vars) {
|
|
|
43835
43892
|
];
|
|
43836
43893
|
}
|
|
43837
43894
|
const names = ["google-chrome", "google-chrome-stable", "chromium", "chromium-browser", "msedge"];
|
|
43838
|
-
return (vars.PATH ?? "").split(delimiter2).filter(Boolean).flatMap((place) => names.map((name) =>
|
|
43895
|
+
return (vars.PATH ?? "").split(delimiter2).filter(Boolean).flatMap((place) => names.map((name) => join8(place, name)));
|
|
43839
43896
|
}
|
|
43840
43897
|
function downloadPlatform() {
|
|
43841
43898
|
const { platform, arch } = process;
|
|
@@ -43844,8 +43901,8 @@ function downloadPlatform() {
|
|
|
43844
43901
|
if (platform === "linux") return arch === "x64" ? "linux64" : null;
|
|
43845
43902
|
return null;
|
|
43846
43903
|
}
|
|
43847
|
-
var browsersFolder = () =>
|
|
43848
|
-
var shellFile = (platform) =>
|
|
43904
|
+
var browsersFolder = () => join8(dirname6(configPath()), "browser");
|
|
43905
|
+
var shellFile = (platform) => join8(
|
|
43849
43906
|
browsersFolder(),
|
|
43850
43907
|
platform,
|
|
43851
43908
|
`chrome-headless-shell-${platform}`,
|
|
@@ -43853,16 +43910,16 @@ var shellFile = (platform) => join7(
|
|
|
43853
43910
|
);
|
|
43854
43911
|
function findBrowser(named, vars = process.env) {
|
|
43855
43912
|
if (named) {
|
|
43856
|
-
if (!
|
|
43913
|
+
if (!existsSync3(named)) {
|
|
43857
43914
|
throw new CliError(EXIT.usage, "browser_missing", `There's no browser at ${named}.`);
|
|
43858
43915
|
}
|
|
43859
43916
|
return { path: resolve7(named), headlessShell: /headless[-_]shell/i.test(named), from: "named" };
|
|
43860
43917
|
}
|
|
43861
|
-
const installed = installedBrowsers(vars).find((path2) =>
|
|
43918
|
+
const installed = installedBrowsers(vars).find((path2) => existsSync3(path2));
|
|
43862
43919
|
if (installed) return { path: installed, headlessShell: false, from: "this PC" };
|
|
43863
43920
|
const platform = downloadPlatform();
|
|
43864
43921
|
const shell = platform && shellFile(platform);
|
|
43865
|
-
return shell &&
|
|
43922
|
+
return shell && existsSync3(shell) ? { path: shell, headlessShell: true, from: "downloaded" } : null;
|
|
43866
43923
|
}
|
|
43867
43924
|
async function downloadBrowser(out) {
|
|
43868
43925
|
const platform = downloadPlatform();
|
|
@@ -43873,7 +43930,7 @@ async function downloadBrowser(out) {
|
|
|
43873
43930
|
`There's no browser on this PC, and none to download for ${process.platform} ${process.arch}. Install Chrome or Chromium, or name one with --browser.`
|
|
43874
43931
|
);
|
|
43875
43932
|
}
|
|
43876
|
-
const folder =
|
|
43933
|
+
const folder = join8(browsersFolder(), platform);
|
|
43877
43934
|
const failed3 = (why) => new CliError(
|
|
43878
43935
|
EXIT.error,
|
|
43879
43936
|
"browser_download_failed",
|
|
@@ -43921,7 +43978,7 @@ async function downloadBrowser(out) {
|
|
|
43921
43978
|
await rm(folder, { recursive: true, force: true });
|
|
43922
43979
|
await rename(unpacking, folder);
|
|
43923
43980
|
const shell = shellFile(platform);
|
|
43924
|
-
if (!
|
|
43981
|
+
if (!existsSync3(shell)) throw failed3("the download didn't hold what it should");
|
|
43925
43982
|
return { path: shell, headlessShell: true, from: "downloaded" };
|
|
43926
43983
|
}
|
|
43927
43984
|
async function unzip(zip, into) {
|
|
@@ -43943,14 +44000,14 @@ async function unzip(zip, into) {
|
|
|
43943
44000
|
const name = zip.toString("utf8", at + 46, at + 46 + nameLength);
|
|
43944
44001
|
at += 46 + nameLength + extraLength + commentLength;
|
|
43945
44002
|
const target = resolve7(root, name);
|
|
43946
|
-
if (!target.startsWith(root +
|
|
44003
|
+
if (!target.startsWith(root + sep4)) throw new Error(`a path leaves the zip: ${name}`);
|
|
43947
44004
|
if (name.endsWith("/")) {
|
|
43948
44005
|
await mkdir5(target, { recursive: true });
|
|
43949
44006
|
continue;
|
|
43950
44007
|
}
|
|
43951
44008
|
const start = local + 30 + zip.readUInt16LE(local + 26) + zip.readUInt16LE(local + 28);
|
|
43952
44009
|
const body = zip.subarray(start, start + packed);
|
|
43953
|
-
await mkdir5(
|
|
44010
|
+
await mkdir5(dirname6(target), { recursive: true });
|
|
43954
44011
|
await writeFile6(target, method === 0 ? body : inflateRawSync(body));
|
|
43955
44012
|
if (mode & 73) await chmod2(target, 493);
|
|
43956
44013
|
}
|
|
@@ -44003,7 +44060,7 @@ var Browser = class _Browser {
|
|
|
44003
44060
|
);
|
|
44004
44061
|
}
|
|
44005
44062
|
static async launch(found, sandboxOff) {
|
|
44006
|
-
const profile = await mkdtemp(
|
|
44063
|
+
const profile = await mkdtemp(join8(tmpdir(), "pch-check-profile-"));
|
|
44007
44064
|
const child = spawn3(
|
|
44008
44065
|
found.path,
|
|
44009
44066
|
[
|
|
@@ -44196,7 +44253,7 @@ async function checkModel(file2) {
|
|
|
44196
44253
|
|
|
44197
44254
|
// src/check/play.ts
|
|
44198
44255
|
import { writeFile as writeFile7 } from "node:fs/promises";
|
|
44199
|
-
import { join as
|
|
44256
|
+
import { join as join10 } from "node:path";
|
|
44200
44257
|
import { setTimeout as wait } from "node:timers/promises";
|
|
44201
44258
|
|
|
44202
44259
|
// src/check/pixels.ts
|
|
@@ -44284,7 +44341,7 @@ function changed(a, b) {
|
|
|
44284
44341
|
import { createReadStream as createReadStream2 } from "node:fs";
|
|
44285
44342
|
import { stat as stat3 } from "node:fs/promises";
|
|
44286
44343
|
import { createServer } from "node:http";
|
|
44287
|
-
import { join as
|
|
44344
|
+
import { join as join9 } from "node:path";
|
|
44288
44345
|
var GAME_HOST_HEADERS = {
|
|
44289
44346
|
"Content-Security-Policy": [
|
|
44290
44347
|
"default-src 'self'",
|
|
@@ -44393,7 +44450,7 @@ async function serveGame(root, files) {
|
|
|
44393
44450
|
notFound();
|
|
44394
44451
|
return;
|
|
44395
44452
|
}
|
|
44396
|
-
const absolute =
|
|
44453
|
+
const absolute = join9(root, ...path2.split("/"));
|
|
44397
44454
|
const { type, encoding } = typeOf(path2);
|
|
44398
44455
|
const html = type.startsWith("text/html");
|
|
44399
44456
|
const headers = {
|
|
@@ -44700,7 +44757,7 @@ async function play(browser, options) {
|
|
|
44700
44757
|
if (!shot) return null;
|
|
44701
44758
|
const bytes = Buffer.from(shot.data, "base64");
|
|
44702
44759
|
if (keep) {
|
|
44703
|
-
const file2 =
|
|
44760
|
+
const file2 = join10(options.out, name);
|
|
44704
44761
|
await writeFile7(file2, bytes);
|
|
44705
44762
|
observed.screenshots.push(file2);
|
|
44706
44763
|
}
|
|
@@ -44824,7 +44881,7 @@ async function readRendering(browser, origin) {
|
|
|
44824
44881
|
|
|
44825
44882
|
// src/check/report.ts
|
|
44826
44883
|
import { readFile as readFile8 } from "node:fs/promises";
|
|
44827
|
-
import { join as
|
|
44884
|
+
import { join as join11 } from "node:path";
|
|
44828
44885
|
var NEEDS_GPU = /web ?gpu|web ?gl|graphics adapter|gpu adapter|no adapter/i;
|
|
44829
44886
|
var seconds = (ms) => ms < 1e3 ? `${ms} ms` : `${(ms / 1e3).toFixed(ms < 1e4 ? 1 : 0)} s`;
|
|
44830
44887
|
var limit = (ms) => `${ms / 1e3} s`;
|
|
@@ -44836,7 +44893,7 @@ async function pathsInText(root, files) {
|
|
|
44836
44893
|
const byLowerCase = new Map(files.map((file2) => [file2.toLowerCase(), file2]));
|
|
44837
44894
|
const found = [];
|
|
44838
44895
|
for (const file2 of files.filter((name) => TEXT_FILES.test(name))) {
|
|
44839
|
-
const text = await readFile8(
|
|
44896
|
+
const text = await readFile8(join11(root, ...file2.split("/")), "utf8").catch(() => "");
|
|
44840
44897
|
if (!text || text.length > TEXT_LIMIT) continue;
|
|
44841
44898
|
const folder = file2.includes("/") ? file2.slice(0, file2.lastIndexOf("/") + 1) : "";
|
|
44842
44899
|
for (const [, written] of text.matchAll(PATH_IN_TEXT)) {
|
|
@@ -45027,7 +45084,7 @@ var LABELS = {
|
|
|
45027
45084
|
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: pch test start.";
|
|
45028
45085
|
async function findGame(dir) {
|
|
45029
45086
|
const root = resolve8(dir);
|
|
45030
|
-
if (
|
|
45087
|
+
if (existsSync4(join12(root, "patch.json"))) {
|
|
45031
45088
|
const project2 = await loadProject(root);
|
|
45032
45089
|
if (project2.recipe.mode !== "browser") {
|
|
45033
45090
|
throw usage2(
|
|
@@ -45044,8 +45101,8 @@ async function findGame(dir) {
|
|
|
45044
45101
|
};
|
|
45045
45102
|
}
|
|
45046
45103
|
for (const folder of ["", "dist", "build", "public", "out"]) {
|
|
45047
|
-
if (
|
|
45048
|
-
const here =
|
|
45104
|
+
if (existsSync4(join12(root, folder, "index.html"))) {
|
|
45105
|
+
const here = join12(root, folder);
|
|
45049
45106
|
return { root: here, files: await listFiles(here), entry: "index.html", selfChecks: [] };
|
|
45050
45107
|
}
|
|
45051
45108
|
}
|
|
@@ -45072,9 +45129,9 @@ async function check2(ctx, dir, options) {
|
|
|
45072
45129
|
);
|
|
45073
45130
|
}
|
|
45074
45131
|
const models = options.models === true ? await Promise.all(
|
|
45075
|
-
game.files.filter((file2) => file2.toLowerCase().endsWith(".glb")).map((file2) => modelAt(
|
|
45132
|
+
game.files.filter((file2) => file2.toLowerCase().endsWith(".glb")).map((file2) => modelAt(join12(game.root, ...file2.split("/"))))
|
|
45076
45133
|
) : [];
|
|
45077
|
-
const folder = options.out ? resolve8(options.out) : await mkdtemp2(
|
|
45134
|
+
const folder = options.out ? resolve8(options.out) : await mkdtemp2(join12(tmpdir2(), `pch-check-${basename3(resolve8(dir))}-`));
|
|
45078
45135
|
await mkdir6(folder, { recursive: true });
|
|
45079
45136
|
const found = findBrowser(options.browser) ?? await downloadBrowser(out);
|
|
45080
45137
|
out.step(`Opening ${game.entry} from ${game.root}, muted, for ${seconds2} s or so`);
|
|
@@ -45217,7 +45274,7 @@ function printModels(ctx, models) {
|
|
|
45217
45274
|
// src/commands/connect.ts
|
|
45218
45275
|
var import_picocolors14 = __toESM(require_picocolors(), 1);
|
|
45219
45276
|
import { readFile as readFile12, writeFile as writeFile10 } from "node:fs/promises";
|
|
45220
|
-
import { join as
|
|
45277
|
+
import { join as join15, resolve as resolve11 } from "node:path";
|
|
45221
45278
|
import { setTimeout as sleep5 } from "node:timers/promises";
|
|
45222
45279
|
init_errors();
|
|
45223
45280
|
|
|
@@ -45265,7 +45322,7 @@ function sameName(a, b) {
|
|
|
45265
45322
|
var import_picocolors13 = __toESM(require_picocolors(), 1);
|
|
45266
45323
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
45267
45324
|
import { mkdir as mkdir7, readdir as readdir3, readFile as readFile11, rm as rm2, writeFile as writeFile9 } from "node:fs/promises";
|
|
45268
|
-
import { join as
|
|
45325
|
+
import { join as join14, resolve as resolve10 } from "node:path";
|
|
45269
45326
|
import { setTimeout as sleep4 } from "node:timers/promises";
|
|
45270
45327
|
init_errors();
|
|
45271
45328
|
init_guide_words();
|
|
@@ -45307,7 +45364,7 @@ function unknownLicence(name) {
|
|
|
45307
45364
|
);
|
|
45308
45365
|
}
|
|
45309
45366
|
async function licenceText(id, fill, texts = LICENCE_TEXTS) {
|
|
45310
|
-
const text = await readFile9(new URL(`${id}.txt`, texts), "utf8");
|
|
45367
|
+
const text = isExecutable && texts === LICENCE_TEXTS ? embeddedText(`licences/${id}.txt`) : await readFile9(new URL(`${id}.txt`, texts), "utf8");
|
|
45311
45368
|
return text.replace(
|
|
45312
45369
|
/^Copyright \(c\) <year> <(?:copyright holders|owner)>/m,
|
|
45313
45370
|
`Copyright (c) ${fill.year} ${fill.holder}`
|
|
@@ -45442,7 +45499,7 @@ async function versionTaken(ctx, ref, failure2) {
|
|
|
45442
45499
|
// src/commands/init.ts
|
|
45443
45500
|
var import_picocolors11 = __toESM(require_picocolors(), 1);
|
|
45444
45501
|
import { readFile as readFile10, stat as stat4, writeFile as writeFile8 } from "node:fs/promises";
|
|
45445
|
-
import { basename as basename4, join as
|
|
45502
|
+
import { basename as basename4, join as join13, resolve as resolve9 } from "node:path";
|
|
45446
45503
|
init_errors();
|
|
45447
45504
|
init_guide_words();
|
|
45448
45505
|
var FIRST_VERSION = "0.1.0";
|
|
@@ -45452,7 +45509,7 @@ function startingProjectFile(project2, version2 = FIRST_VERSION) {
|
|
|
45452
45509
|
}
|
|
45453
45510
|
async function init(ctx, dir, options) {
|
|
45454
45511
|
const root = resolve9(dir);
|
|
45455
|
-
const path2 =
|
|
45512
|
+
const path2 = join13(root, "patch.json");
|
|
45456
45513
|
if (!options.force && await readFile10(path2, "utf8").catch(() => null) !== null) {
|
|
45457
45514
|
throw new CliError(
|
|
45458
45515
|
EXIT.conflict,
|
|
@@ -45462,7 +45519,7 @@ async function init(ctx, dir, options) {
|
|
|
45462
45519
|
}
|
|
45463
45520
|
const name = await projectFor(ctx, root, options.project);
|
|
45464
45521
|
const files = options.files ?? await builtFolder(root);
|
|
45465
|
-
const hasRecipe = await stat4(
|
|
45522
|
+
const hasRecipe = await stat4(join13(root, "recipe.json")).then(
|
|
45466
45523
|
() => true,
|
|
45467
45524
|
() => false
|
|
45468
45525
|
);
|
|
@@ -45485,7 +45542,7 @@ async function init(ctx, dir, options) {
|
|
|
45485
45542
|
"pch validate",
|
|
45486
45543
|
"pch publish"
|
|
45487
45544
|
];
|
|
45488
|
-
const agentGuide2 = { path:
|
|
45545
|
+
const agentGuide2 = { path: join13(root, guide2.name), written: guide2.written };
|
|
45489
45546
|
const result = { path: path2, ...contents, on_patch: onPatch2, agent_guide: agentGuide2 };
|
|
45490
45547
|
ctx.out.result({ ...result, next, look_first: LOOK_FIRST }, () => {
|
|
45491
45548
|
ctx.out.line(`Wrote ${import_picocolors11.default.bold(path2)} for ${import_picocolors11.default.bold(name)}.`);
|
|
@@ -45527,9 +45584,9 @@ async function builtFolder(root) {
|
|
|
45527
45584
|
(found) => found.isFile(),
|
|
45528
45585
|
() => false
|
|
45529
45586
|
);
|
|
45530
|
-
if (await isFile(
|
|
45587
|
+
if (await isFile(join13(root, "index.html"))) return void 0;
|
|
45531
45588
|
for (const folder of ["dist", "build", "public", "out"]) {
|
|
45532
|
-
if (await isFile(
|
|
45589
|
+
if (await isFile(join13(root, folder, "index.html"))) return folder;
|
|
45533
45590
|
}
|
|
45534
45591
|
return void 0;
|
|
45535
45592
|
}
|
|
@@ -45775,7 +45832,7 @@ async function newRepo(ctx, ref, dir, options) {
|
|
|
45775
45832
|
lfs,
|
|
45776
45833
|
url: url2,
|
|
45777
45834
|
look_first: LOOK_FIRST,
|
|
45778
|
-
agent_guide: { path:
|
|
45835
|
+
agent_guide: { path: join14(folder, AGENT_GUIDE_FILE), written: true },
|
|
45779
45836
|
commits_as: commitsAs,
|
|
45780
45837
|
...options.visibility === "private" ? { who_can_see: PRIVATE_REPO } : {}
|
|
45781
45838
|
},
|
|
@@ -45815,14 +45872,14 @@ async function clearFolder(folder, keepIt) {
|
|
|
45815
45872
|
if (!keepIt) return rm2(folder, { recursive: true, force: true });
|
|
45816
45873
|
const entries = await readdir3(folder).catch(() => []);
|
|
45817
45874
|
await Promise.all(
|
|
45818
|
-
entries.map((entry) => rm2(
|
|
45875
|
+
entries.map((entry) => rm2(join14(folder, entry), { recursive: true, force: true }))
|
|
45819
45876
|
);
|
|
45820
45877
|
}
|
|
45821
45878
|
async function firstCommit(ctx, folder, name, files) {
|
|
45822
45879
|
await mkdir7(folder, { recursive: true });
|
|
45823
45880
|
await mustGit(["init", "-q", "-b", FIRST_BRANCH], { cwd: folder });
|
|
45824
45881
|
const commitsAs = await commitAsYourHandle(ctx, folder);
|
|
45825
|
-
for (const [path2, text] of Object.entries(files)) await writeFile9(
|
|
45882
|
+
for (const [path2, text] of Object.entries(files)) await writeFile9(join14(folder, path2), text);
|
|
45826
45883
|
const lfs = await hasLfs();
|
|
45827
45884
|
if (!lfs) ctx.out.step(NO_LFS);
|
|
45828
45885
|
else if (!await lfsSetUp()) await setUpLfsHere(ctx, folder);
|
|
@@ -46200,7 +46257,7 @@ async function originOnGithub(ctx, dir) {
|
|
|
46200
46257
|
);
|
|
46201
46258
|
}
|
|
46202
46259
|
async function readManifest(dir) {
|
|
46203
|
-
const path2 =
|
|
46260
|
+
const path2 = join15(dir, "patch.json");
|
|
46204
46261
|
let text;
|
|
46205
46262
|
try {
|
|
46206
46263
|
text = await readFile12(path2, "utf8");
|
|
@@ -46596,7 +46653,7 @@ async function nameInManifest(manifest, project2) {
|
|
|
46596
46653
|
// src/commands/covers.ts
|
|
46597
46654
|
var import_picocolors15 = __toESM(require_picocolors(), 1);
|
|
46598
46655
|
import { mkdir as mkdir8, readFile as readFile13, writeFile as writeFile11 } from "node:fs/promises";
|
|
46599
|
-
import { basename as basename5, extname as extname3, join as
|
|
46656
|
+
import { basename as basename5, extname as extname3, join as join16, resolve as resolve12 } from "node:path";
|
|
46600
46657
|
import { setTimeout as sleep7 } from "node:timers/promises";
|
|
46601
46658
|
init_errors();
|
|
46602
46659
|
|
|
@@ -46817,13 +46874,13 @@ async function follow2(ctx, project2, job, wait2) {
|
|
|
46817
46874
|
async function save(ctx, job, folder) {
|
|
46818
46875
|
const ready = job.takes.filter((take) => take.state === "ready" && take.url);
|
|
46819
46876
|
if (!ready.length || folder === void 0) return [];
|
|
46820
|
-
const dir = resolve12(folder ||
|
|
46877
|
+
const dir = resolve12(folder || join16("covers", job.id));
|
|
46821
46878
|
await mkdir8(dir, { recursive: true });
|
|
46822
46879
|
ctx.out.step(`Saving ${ready.length} take${ready.length === 1 ? "" : "s"} to ${dir}`);
|
|
46823
46880
|
const saved = [];
|
|
46824
46881
|
for (const take of ready) {
|
|
46825
|
-
const file2 =
|
|
46826
|
-
const preview =
|
|
46882
|
+
const file2 = join16(dir, `${take.shape}-${take.id}.jpg`);
|
|
46883
|
+
const preview = join16(dir, `${take.shape}-${take.id}-card.jpg`);
|
|
46827
46884
|
for (const [url2, path2] of [
|
|
46828
46885
|
[take.url, file2],
|
|
46829
46886
|
[take.preview_url, preview]
|
|
@@ -46941,7 +46998,7 @@ function outFolder(out, byDefault) {
|
|
|
46941
46998
|
// src/commands/git.ts
|
|
46942
46999
|
var import_picocolors16 = __toESM(require_picocolors(), 1);
|
|
46943
47000
|
import { stat as stat5 } from "node:fs/promises";
|
|
46944
|
-
import { basename as basename6, join as
|
|
47001
|
+
import { basename as basename6, join as join17, resolve as resolve13 } from "node:path";
|
|
46945
47002
|
|
|
46946
47003
|
// src/direct-push.ts
|
|
46947
47004
|
init_errors();
|
|
@@ -47706,7 +47763,7 @@ async function clone2(ctx, ref, dir) {
|
|
|
47706
47763
|
if (lfsForThisOne) await setUpLfsHere(ctx, into);
|
|
47707
47764
|
const commitsAs = target.token ? await commitAsYourHandle(ctx, into) : null;
|
|
47708
47765
|
const guide2 = await agentGuideIn(into);
|
|
47709
|
-
const brief = await stat5(
|
|
47766
|
+
const brief = await stat5(join17(into, BRIEF_FILE)).then(
|
|
47710
47767
|
(found) => found.isFile(),
|
|
47711
47768
|
() => false
|
|
47712
47769
|
);
|
|
@@ -48286,7 +48343,7 @@ var import_picocolors19 = __toESM(require_picocolors(), 1);
|
|
|
48286
48343
|
import { createHash as createHash3 } from "node:crypto";
|
|
48287
48344
|
import { mkdir as mkdir9, mkdtemp as mkdtemp3, writeFile as writeFile12 } from "node:fs/promises";
|
|
48288
48345
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
48289
|
-
import { join as
|
|
48346
|
+
import { join as join18, resolve as resolve15 } from "node:path";
|
|
48290
48347
|
import { setTimeout as sleep9 } from "node:timers/promises";
|
|
48291
48348
|
init_errors();
|
|
48292
48349
|
var HOLD_SECONDS = 50;
|
|
@@ -48467,7 +48524,7 @@ async function testFootage(ctx, ref, options) {
|
|
|
48467
48524
|
);
|
|
48468
48525
|
const sheets = data.length ? await saveSheets(
|
|
48469
48526
|
data,
|
|
48470
|
-
options.out ? resolve15(options.out) : await mkdtemp3(
|
|
48527
|
+
options.out ? resolve15(options.out) : await mkdtemp3(join18(tmpdir3(), "pch-footage-"))
|
|
48471
48528
|
) : [];
|
|
48472
48529
|
const next = data.length ? reviewCommand(name) : nextStep(name, "testing", test2).command;
|
|
48473
48530
|
out.result({ test: test2.id, state: test2.state, data: sheets, next }, () => {
|
|
@@ -48500,7 +48557,7 @@ async function saveSheets(sheets, folder) {
|
|
|
48500
48557
|
`Run ${sheet.run}'s sheet ${sheet.index} didn't download intact. Try again.`
|
|
48501
48558
|
);
|
|
48502
48559
|
}
|
|
48503
|
-
const path2 =
|
|
48560
|
+
const path2 = join18(folder, `run-${sheet.run}-sheet-${sheet.index}.jpg`);
|
|
48504
48561
|
await writeFile12(path2, bytes);
|
|
48505
48562
|
const { url: _url2, ...kept } = sheet;
|
|
48506
48563
|
saved.push({ ...kept, path: path2 });
|
|
@@ -48652,8 +48709,204 @@ async function categories(ctx, options) {
|
|
|
48652
48709
|
});
|
|
48653
48710
|
}
|
|
48654
48711
|
|
|
48655
|
-
// src/commands/
|
|
48712
|
+
// src/commands/update.ts
|
|
48656
48713
|
var import_picocolors21 = __toESM(require_picocolors(), 1);
|
|
48714
|
+
var import_semver3 = __toESM(require_semver2(), 1);
|
|
48715
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
48716
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
48717
|
+
import { mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
48718
|
+
import { chmod as chmod3, rename as rename2, rm as rm3, writeFile as writeFile13 } from "node:fs/promises";
|
|
48719
|
+
import { basename as basename8, dirname as dirname7, join as join19 } from "node:path";
|
|
48720
|
+
import { gunzipSync } from "node:zlib";
|
|
48721
|
+
init_errors();
|
|
48722
|
+
init_version();
|
|
48723
|
+
var DAY = 24 * 60 * 60 * 1e3;
|
|
48724
|
+
var QUIET = /* @__PURE__ */ new Set(["mcp", "git-credential", "update"]);
|
|
48725
|
+
async function latestVersion2(releases, signal) {
|
|
48726
|
+
const response = await fetch(`${releases}/latest.json`, { signal });
|
|
48727
|
+
if (!response.ok) throw new Error(`${releases}/latest.json answered ${response.status}`);
|
|
48728
|
+
const { version: version2 } = await response.json();
|
|
48729
|
+
if (typeof version2 !== "string" || !import_semver3.default.valid(version2)) {
|
|
48730
|
+
throw new Error(`${releases}/latest.json names no version`);
|
|
48731
|
+
}
|
|
48732
|
+
return version2;
|
|
48733
|
+
}
|
|
48734
|
+
function parseSums(text) {
|
|
48735
|
+
const sums = /* @__PURE__ */ new Map();
|
|
48736
|
+
for (const line of text.split(/\r?\n/)) {
|
|
48737
|
+
const match = /^([0-9a-f]{64}) [ *](.+)$/i.exec(line.trim());
|
|
48738
|
+
if (match) sums.set(match[2], match[1].toLowerCase());
|
|
48739
|
+
}
|
|
48740
|
+
return sums;
|
|
48741
|
+
}
|
|
48742
|
+
function notReplaceable(kind) {
|
|
48743
|
+
const command = updateCommand(kind);
|
|
48744
|
+
if (kind === "desktop")
|
|
48745
|
+
return { command, message: "This pch came with Patch Desktop, and updates with it." };
|
|
48746
|
+
if (command)
|
|
48747
|
+
return {
|
|
48748
|
+
command,
|
|
48749
|
+
message: `This pch was installed with ${kind}. To update it, run: ${command}`
|
|
48750
|
+
};
|
|
48751
|
+
return {
|
|
48752
|
+
command,
|
|
48753
|
+
message: `This pch runs from ${process.argv[1]}, which pch update doesn't manage. Update it the way you installed it.`
|
|
48754
|
+
};
|
|
48755
|
+
}
|
|
48756
|
+
async function download2(url2) {
|
|
48757
|
+
const response = await fetch(url2, { signal: AbortSignal.timeout(10 * 6e4) }).catch(
|
|
48758
|
+
(error62) => {
|
|
48759
|
+
throw new CliError(
|
|
48760
|
+
EXIT.error,
|
|
48761
|
+
"download_failed",
|
|
48762
|
+
`Couldn't download ${url2}: ${error62.message}`
|
|
48763
|
+
);
|
|
48764
|
+
}
|
|
48765
|
+
);
|
|
48766
|
+
if (!response.ok) {
|
|
48767
|
+
throw new CliError(EXIT.error, "download_failed", `${url2} answered ${response.status}.`);
|
|
48768
|
+
}
|
|
48769
|
+
return Buffer.from(await response.arrayBuffer());
|
|
48770
|
+
}
|
|
48771
|
+
async function update(ctx) {
|
|
48772
|
+
const kind = installKind();
|
|
48773
|
+
if (kind !== "executable") {
|
|
48774
|
+
const { command, message } = notReplaceable(kind);
|
|
48775
|
+
ctx.out.result(
|
|
48776
|
+
{ version: VERSION, install: kind, updated: false, command },
|
|
48777
|
+
() => ctx.out.line(message)
|
|
48778
|
+
);
|
|
48779
|
+
return EXIT.ok;
|
|
48780
|
+
}
|
|
48781
|
+
const releases = releasesUrl(ctx.target.env);
|
|
48782
|
+
const name = executableName();
|
|
48783
|
+
if (!releases || !name) {
|
|
48784
|
+
throw new CliError(
|
|
48785
|
+
EXIT.usage,
|
|
48786
|
+
"no_releases",
|
|
48787
|
+
releases ? `There's no pch executable for ${process.platform} ${process.arch}.` : `${ctx.target.env} has no releases of pch to update from.`
|
|
48788
|
+
);
|
|
48789
|
+
}
|
|
48790
|
+
ctx.out.step("Looking for a newer pch");
|
|
48791
|
+
const latest2 = await latestVersion2(releases, AbortSignal.timeout(15e3)).catch(
|
|
48792
|
+
(error62) => {
|
|
48793
|
+
throw new CliError(
|
|
48794
|
+
EXIT.error,
|
|
48795
|
+
"download_failed",
|
|
48796
|
+
`Couldn't check for a newer pch: ${error62.message}`
|
|
48797
|
+
);
|
|
48798
|
+
}
|
|
48799
|
+
);
|
|
48800
|
+
if (!import_semver3.default.gt(latest2, VERSION)) {
|
|
48801
|
+
ctx.out.result(
|
|
48802
|
+
{ version: VERSION, latest: latest2, install: kind, updated: false },
|
|
48803
|
+
() => ctx.out.line(`pch ${VERSION} is the newest.`)
|
|
48804
|
+
);
|
|
48805
|
+
return EXIT.ok;
|
|
48806
|
+
}
|
|
48807
|
+
const file2 = `${name}.gz`;
|
|
48808
|
+
ctx.out.step(`Downloading pch ${latest2}`);
|
|
48809
|
+
const [sums, packed] = await Promise.all([
|
|
48810
|
+
download2(`${releases}/${latest2}/SHA256SUMS`),
|
|
48811
|
+
download2(`${releases}/${latest2}/${file2}`)
|
|
48812
|
+
]);
|
|
48813
|
+
const expected = parseSums(sums.toString("utf8")).get(file2);
|
|
48814
|
+
const actual = createHash4("sha256").update(packed).digest("hex");
|
|
48815
|
+
if (!expected || expected !== actual) {
|
|
48816
|
+
throw new CliError(
|
|
48817
|
+
EXIT.error,
|
|
48818
|
+
"checksum_mismatch",
|
|
48819
|
+
`pch ${latest2} for this machine doesn't match its SHA-256, so it wasn't installed.`,
|
|
48820
|
+
{ file: file2, expected: expected ?? null, actual }
|
|
48821
|
+
);
|
|
48822
|
+
}
|
|
48823
|
+
const target = process.execPath;
|
|
48824
|
+
const fresh = `${target}.new`;
|
|
48825
|
+
await writeFile13(fresh, gunzipSync(packed), { mode: 493 });
|
|
48826
|
+
await chmod3(fresh, 493);
|
|
48827
|
+
const said2 = spawnSync2(fresh, ["--version"], { encoding: "utf8", timeout: 6e4 });
|
|
48828
|
+
if (said2.status !== 0 || said2.stdout.trim() !== latest2) {
|
|
48829
|
+
await rm3(fresh, { force: true });
|
|
48830
|
+
throw new CliError(
|
|
48831
|
+
EXIT.error,
|
|
48832
|
+
"update_failed",
|
|
48833
|
+
`pch ${latest2} didn't start on this machine, so pch ${VERSION} stays.`,
|
|
48834
|
+
{ stderr: (said2.stderr ?? said2.error?.message ?? "").trim().slice(-2e3) }
|
|
48835
|
+
);
|
|
48836
|
+
}
|
|
48837
|
+
await replace(target, fresh);
|
|
48838
|
+
ctx.out.result(
|
|
48839
|
+
{ version: latest2, previous: VERSION, install: kind, updated: true },
|
|
48840
|
+
() => ctx.out.line(`Updated pch from ${VERSION} to ${latest2}.`)
|
|
48841
|
+
);
|
|
48842
|
+
return EXIT.ok;
|
|
48843
|
+
}
|
|
48844
|
+
async function replace(target, fresh) {
|
|
48845
|
+
if (process.platform !== "win32") return rename2(fresh, target);
|
|
48846
|
+
const old = `${target}.${process.pid}.old`;
|
|
48847
|
+
await rename2(target, old);
|
|
48848
|
+
try {
|
|
48849
|
+
await rename2(fresh, target);
|
|
48850
|
+
} catch (error62) {
|
|
48851
|
+
await rename2(old, target);
|
|
48852
|
+
throw error62;
|
|
48853
|
+
}
|
|
48854
|
+
}
|
|
48855
|
+
function removeOldExecutables(target = process.execPath) {
|
|
48856
|
+
if (!isExecutable || process.platform !== "win32") return;
|
|
48857
|
+
const folder = dirname7(target);
|
|
48858
|
+
const old = new RegExp(`^${basename8(target).replace(/\./g, "\\.")}\\.\\d+\\.old$`, "i");
|
|
48859
|
+
try {
|
|
48860
|
+
for (const name of readdirSync(folder)) {
|
|
48861
|
+
if (old.test(name)) rmSync(join19(folder, name), { force: true });
|
|
48862
|
+
}
|
|
48863
|
+
} catch {
|
|
48864
|
+
}
|
|
48865
|
+
}
|
|
48866
|
+
function startUpdateNotice(ctx, command, now = Date.now()) {
|
|
48867
|
+
const how = updateCommand(installKind());
|
|
48868
|
+
const releases = releasesUrl(ctx.target.env);
|
|
48869
|
+
if (!how || !releases || ctx.out.json || QUIET.has(command)) return null;
|
|
48870
|
+
if (!process.stderr.isTTY || process.env.CI) return null;
|
|
48871
|
+
const stamp = join19(dirname7(configPath()), "update-check.json");
|
|
48872
|
+
try {
|
|
48873
|
+
const { checked_at } = JSON.parse(readFileSync(stamp, "utf8"));
|
|
48874
|
+
if (checked_at && now - Date.parse(checked_at) < DAY) return null;
|
|
48875
|
+
} catch {
|
|
48876
|
+
}
|
|
48877
|
+
try {
|
|
48878
|
+
mkdirSync(dirname7(stamp), { recursive: true, mode: 448 });
|
|
48879
|
+
writeFileSync(stamp, `${JSON.stringify({ checked_at: new Date(now).toISOString() })}
|
|
48880
|
+
`);
|
|
48881
|
+
} catch {
|
|
48882
|
+
return null;
|
|
48883
|
+
}
|
|
48884
|
+
const stop = new AbortController();
|
|
48885
|
+
const giveUp = setTimeout(() => stop.abort(), 2e3);
|
|
48886
|
+
giveUp.unref();
|
|
48887
|
+
const latest2 = latestVersion2(releases, stop.signal).catch(() => null).finally(() => clearTimeout(giveUp));
|
|
48888
|
+
return {
|
|
48889
|
+
async show() {
|
|
48890
|
+
let timer;
|
|
48891
|
+
const late = new Promise((done) => {
|
|
48892
|
+
timer = setTimeout(() => done(null), 500);
|
|
48893
|
+
});
|
|
48894
|
+
const version2 = await Promise.race([latest2, late]);
|
|
48895
|
+
clearTimeout(timer);
|
|
48896
|
+
stop.abort();
|
|
48897
|
+
if (version2 && import_semver3.default.gt(version2, VERSION)) {
|
|
48898
|
+
process.stderr.write(
|
|
48899
|
+
`
|
|
48900
|
+
${import_picocolors21.default.dim("\u203A")} pch ${version2} is out (you have ${VERSION}). To update, run: ${how}
|
|
48901
|
+
`
|
|
48902
|
+
);
|
|
48903
|
+
}
|
|
48904
|
+
}
|
|
48905
|
+
};
|
|
48906
|
+
}
|
|
48907
|
+
|
|
48908
|
+
// src/commands/validate.ts
|
|
48909
|
+
var import_picocolors22 = __toESM(require_picocolors(), 1);
|
|
48657
48910
|
init_errors();
|
|
48658
48911
|
async function validate2(ctx, dir) {
|
|
48659
48912
|
const project2 = await loadProject(dir);
|
|
@@ -48712,12 +48965,12 @@ async function validate2(ctx, dir) {
|
|
|
48712
48965
|
notes
|
|
48713
48966
|
};
|
|
48714
48967
|
ctx.out.result(result, () => {
|
|
48715
|
-
const mark = result.ok ?
|
|
48968
|
+
const mark = result.ok ? import_picocolors22.default.green("\u2713") : import_picocolors22.default.red("\u2717");
|
|
48716
48969
|
ctx.out.line(
|
|
48717
48970
|
`${mark} ${file2.project}@${file2.version} \xB7 ${project2.kind} \xB7 ${result.trust_tier} \xB7 ${project2.files.length} files, ${formatSize(total)}`
|
|
48718
48971
|
);
|
|
48719
|
-
for (const issue2 of issues) ctx.out.line(` ${
|
|
48720
|
-
for (const note2 of notes) ctx.out.line(` ${
|
|
48972
|
+
for (const issue2 of issues) ctx.out.line(` ${import_picocolors22.default.red(issue2.path || "\xB7")} ${issue2.message}`);
|
|
48973
|
+
for (const note2 of notes) ctx.out.line(` ${import_picocolors22.default.dim(note2)}`);
|
|
48721
48974
|
});
|
|
48722
48975
|
return result.ok ? EXIT.ok : EXIT.usage;
|
|
48723
48976
|
}
|
|
@@ -48768,21 +49021,32 @@ function action(run3) {
|
|
|
48768
49021
|
return async (...args2) => {
|
|
48769
49022
|
const command = args2.at(-1);
|
|
48770
49023
|
const globals = command.optsWithGlobals();
|
|
49024
|
+
let notice = null;
|
|
48771
49025
|
try {
|
|
48772
49026
|
const ctx = await createContext(globals);
|
|
49027
|
+
notice = startUpdateNotice(ctx, topLevel(command).name());
|
|
48773
49028
|
const code = await run3(ctx, ...args2.slice(0, -1));
|
|
48774
49029
|
process.exitCode = typeof code === "number" ? code : EXIT.ok;
|
|
48775
49030
|
} catch (error62) {
|
|
48776
49031
|
process.exitCode = new Output(Boolean(globals.json)).error(error62);
|
|
48777
49032
|
}
|
|
49033
|
+
await notice?.show();
|
|
48778
49034
|
};
|
|
48779
49035
|
}
|
|
49036
|
+
function topLevel(command) {
|
|
49037
|
+
let top = command;
|
|
49038
|
+
while (top.parent?.parent) top = top.parent;
|
|
49039
|
+
return top;
|
|
49040
|
+
}
|
|
48780
49041
|
var collect = (value, previous = []) => [...previous, value];
|
|
48781
49042
|
program2.command("login").description(
|
|
48782
49043
|
"Sign in through the browser (device code), and sign git in with it. It waits for a person, which can take a few minutes, so don't run it under a short timeout"
|
|
48783
49044
|
).option("--scope <scope>", "API key scope: read, publish, admin (repeatable)", collect).option("--no-browser", "print the link instead of opening a browser").action(action((ctx, options) => login(ctx, options)));
|
|
48784
49045
|
program2.command("logout").description("Revoke and forget the active login, and sign git out").action(action((ctx) => logout(ctx)));
|
|
48785
49046
|
program2.command("whoami").description("Who you're signed in as, and where").action(action((ctx) => whoami(ctx)));
|
|
49047
|
+
program2.command("update").description(
|
|
49048
|
+
"Update pch to the newest version, when the installer put it here; installed another way, it says how to update"
|
|
49049
|
+
).action(action((ctx) => update(ctx)));
|
|
48786
49050
|
program2.command("guide").description("How to make, test and publish on Patch, for you or your agent").action(action(async (ctx) => (await Promise.resolve().then(() => (init_mcp(), mcp_exports))).guide(ctx)));
|
|
48787
49051
|
program2.command("mcp").description(
|
|
48788
49052
|
"Patch's MCP server for your agent, signed in as pch is: add `pch mcp` to its servers"
|
|
@@ -49219,6 +49483,7 @@ var words = process.argv.slice(2);
|
|
|
49219
49483
|
if (!program2.commands.some((one) => [one.name(), ...one.aliases()].some((n) => words.includes(n)))) {
|
|
49220
49484
|
program2.version(VERSION);
|
|
49221
49485
|
}
|
|
49486
|
+
removeOldExecutables();
|
|
49222
49487
|
try {
|
|
49223
49488
|
await program2.parseAsync(process.argv);
|
|
49224
49489
|
} catch (error62) {
|