@maxceem/agw 0.1.5 → 0.1.7
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 +102 -31
- package/dist/agw.mjs +243 -37
- package/package.json +27 -5
- package/dist/release/0.1.5/console/.assetsignore +0 -1
- package/dist/release/0.1.5/console/assets/endpoints-DApt6mPC.js +0 -12
- package/dist/release/0.1.5/console/assets/index-DUtKG6Od.css +0 -2
- package/dist/release/0.1.5/console/assets/index-J1eP6WzX.js +0 -98
- package/dist/release/0.1.5/console/assets/usage-D26T_dIp.js +0 -42
- package/dist/release/0.1.5/console/favicon.svg +0 -22
- package/dist/release/0.1.5/console/index.html +0 -34
- package/dist/release/0.1.5/manifest.json +0 -27
- package/dist/release/0.1.5/migrations/0000_initial.sql +0 -314
- package/dist/release/0.1.5/worker/index.js +0 -86828
- package/dist/release/0.1.5/wrangler.json +0 -82
- package/dist/release-integrity.json +0 -1
package/README.md
CHANGED
|
@@ -1,63 +1,134 @@
|
|
|
1
1
|
# App AI Gateway CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`agw` sets up and manages [App AI Gateway](https://appaigateway.com/)
|
|
4
|
+
from a terminal or a coding agent: providers, apps, usage, and your own
|
|
5
|
+
deployment. Requires Node.js 22.19+.
|
|
4
6
|
|
|
5
7
|
```sh
|
|
6
|
-
|
|
7
|
-
npx @maxceem/agw app add --type ios --team-id ABCDE12345 --bundle-id com.example.app
|
|
8
|
+
npm install -g @maxceem/agw
|
|
8
9
|
```
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
## Quick start
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
Sign in with a management key. Create one in the console at
|
|
14
|
+
[appaigateway.com](https://appaigateway.com/): user menu, then **Management keys**.
|
|
13
15
|
|
|
14
16
|
```sh
|
|
15
|
-
agw
|
|
16
|
-
agw operation wait <operation-id> --timeout 300 --json
|
|
17
|
+
agw account login
|
|
17
18
|
```
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
No account yet? Skip login. A free anonymous account is created for you
|
|
21
|
+
automatically as soon as you add your first provider or app below. You can
|
|
22
|
+
claim it later with `agw account claim`.
|
|
20
23
|
|
|
21
|
-
|
|
24
|
+
Add a provider. Paste its API key into the hidden prompt.
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
```sh
|
|
27
|
+
agw provider add --type openai
|
|
28
|
+
```
|
|
26
29
|
|
|
27
|
-
|
|
30
|
+
Add an app. It uses every provider you have added.
|
|
28
31
|
|
|
29
|
-
|
|
32
|
+
```sh
|
|
33
|
+
agw app add --type ios --team-id ABCDE12345 --bundle-id com.example.app
|
|
34
|
+
```
|
|
30
35
|
|
|
31
|
-
|
|
36
|
+
Print the Swift code that calls the gateway from that app.
|
|
32
37
|
|
|
33
|
-
|
|
38
|
+
```sh
|
|
39
|
+
agw app snippet <app-id> --provider openai
|
|
40
|
+
```
|
|
34
41
|
|
|
35
|
-
|
|
42
|
+
## Self-host on Cloudflare
|
|
36
43
|
|
|
37
|
-
|
|
44
|
+
The package drives Wrangler and installs the gateway release of its own version.
|
|
45
|
+
No checkout needed. The gateway itself downloads once from the project's GitHub
|
|
46
|
+
release and is cached, after the CLI matches it against a digest built into the
|
|
47
|
+
executable.
|
|
38
48
|
|
|
39
49
|
```sh
|
|
40
|
-
agw deployment setup --name my-gateway --no-domain
|
|
41
|
-
agw deployment update
|
|
42
|
-
agw deployment domain --hostname ai.example.com
|
|
50
|
+
agw deployment setup --name my-gateway --no-domain # deploy and connect
|
|
51
|
+
agw deployment update # move to this CLI's release
|
|
52
|
+
agw deployment domain --hostname ai.example.com # attach a custom domain
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Setup signs in to Cloudflare through Wrangler, or uses `CLOUDFLARE_API_TOKEN`
|
|
56
|
+
if set. Add `--dry-run` to any of these to see what would happen. See the
|
|
57
|
+
[self-hosting guide](https://docs.appaigateway.com/self-hosting/deploy-to-cloudflare).
|
|
58
|
+
|
|
59
|
+
No route to GitHub from that machine? Fetch the `gateway-<version>.tar.gz` asset
|
|
60
|
+
elsewhere and pass `--release-archive <path>`.
|
|
61
|
+
|
|
62
|
+
## Commands
|
|
63
|
+
|
|
64
|
+
| Group | What it manages |
|
|
65
|
+
| --- | --- |
|
|
66
|
+
| `provider` | Provider credentials: `add`, `list`, `show`, `update`, `rotate-key`, `remove` |
|
|
67
|
+
| `provider-gateway` | Cloudflare AI Gateway in front of a provider |
|
|
68
|
+
| `app` | Apps and their config: `add`, `list`, `show`, `update`, `validate`, `check`, `snippet`, `remove`, `key` |
|
|
69
|
+
| `usage` | Spend and requests: `show`, `breakdown` |
|
|
70
|
+
| `account` | `status`, `claim`, `login`, `logout` |
|
|
71
|
+
| `deployment` | `setup`, `connect`, `status`, `update`, `domain` |
|
|
72
|
+
| `operation` | `status` and `wait` for browser handoffs |
|
|
73
|
+
|
|
74
|
+
`agw --help` lists every command; `agw <group> <command> --help` lists its flags.
|
|
75
|
+
|
|
76
|
+
## Using `agw` from an agent
|
|
77
|
+
|
|
78
|
+
Full guide: [docs.appaigateway.com/automation/cli](https://docs.appaigateway.com/automation/cli).
|
|
79
|
+
The whole documentation is also published as
|
|
80
|
+
[agents.md](https://docs.appaigateway.com/agents.md).
|
|
81
|
+
|
|
82
|
+
**Output.** Add `--json` for exactly one JSON document on stdout; progress goes
|
|
83
|
+
to stderr. Add `--no-input` to fail instead of prompting.
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{ "schemaVersion": 1, "ok": true, "context": { "url": "...", "accountId": "..." }, "result": { } }
|
|
87
|
+
{ "schemaVersion": 1, "ok": false, "error": { "code": "...", "message": "...", "nextAction": "...", "details": { } } }
|
|
43
88
|
```
|
|
44
89
|
|
|
45
|
-
|
|
90
|
+
Every error names a `code` and a `nextAction`. Follow the `nextAction`.
|
|
46
91
|
|
|
47
|
-
|
|
92
|
+
| Exit | Meaning |
|
|
93
|
+
| --- | --- |
|
|
94
|
+
| `0` | Success, including a browser handoff that is still pending |
|
|
95
|
+
| `2` | Invalid input or validation failure |
|
|
96
|
+
| `3` | Gateway unreachable, remote error, or failed handoff |
|
|
97
|
+
| `4` | Local state or authentication problem |
|
|
98
|
+
| `5` | `operation wait` timed out; the operation is still pending |
|
|
48
99
|
|
|
49
|
-
|
|
100
|
+
**Secrets.** Never put a key in a command argument. Either pipe it with
|
|
101
|
+
`--key-stdin`, or hand the browser to a human:
|
|
50
102
|
|
|
51
|
-
|
|
103
|
+
```sh
|
|
104
|
+
agw provider add --type openai --browser --no-open --json # prints a URL
|
|
105
|
+
agw operation wait <operation-id> --timeout 300 --json # resumes when done
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Share the URL with the person who holds the credential. Nothing typed on that
|
|
109
|
+
page reaches the CLI or its output.
|
|
52
110
|
|
|
53
|
-
|
|
111
|
+
**Editing apps.** Read before you write. `app show --json` returns the full
|
|
112
|
+
config; save `{name, config, status}` from `result.app`, edit it, and pass it
|
|
113
|
+
back with `app update <id> --file`. A concurrent change is rejected rather than
|
|
114
|
+
overwritten. `app validate --file` checks a file without touching the account.
|
|
54
115
|
|
|
55
|
-
|
|
116
|
+
**State.** Connection, credentials and pending operations live in
|
|
117
|
+
`~/.local/state/agw` (`$XDG_STATE_HOME/agw`, or `%LOCALAPPDATA%/agw` on
|
|
118
|
+
Windows), mode 0700. Do not delete it to fix an authentication error; run
|
|
119
|
+
`agw account login` instead. Output files are never overwritten.
|
|
56
120
|
|
|
57
|
-
|
|
121
|
+
## Contributing
|
|
58
122
|
|
|
59
|
-
|
|
123
|
+
The CLI lives in `cli/` of the
|
|
124
|
+
[main repository](https://github.com/maxceem/app-ai-gateway). From the repo root:
|
|
60
125
|
|
|
61
|
-
|
|
126
|
+
```sh
|
|
127
|
+
pnpm run cli:check # type-check and bundle
|
|
128
|
+
pnpm run cli:test # run the CLI tests
|
|
129
|
+
```
|
|
62
130
|
|
|
63
|
-
|
|
131
|
+
Both are part of `pnpm run verify`. Releasing is two steps in order:
|
|
132
|
+
`pnpm run cli:release:github` builds the gateway archive and uploads it to the
|
|
133
|
+
GitHub release, then `npm publish` from `cli/` ships the executable that
|
|
134
|
+
demands that exact archive. Licensed under Apache-2.0.
|
package/dist/agw.mjs
CHANGED
|
@@ -6,7 +6,7 @@ var __export = (target, all) => {
|
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
// <define:__AGW_MANIFEST__>
|
|
9
|
-
var define_AGW_MANIFEST_default = { version: "0.1.
|
|
9
|
+
var define_AGW_MANIFEST_default = { version: "0.1.7", wrangler: "4.96.0", upgradeFrom: ["0.1.7", "0.1.6", "0.1.5", "0.1.4", "0.1.3", "0.1.2", "0.1.1", "0.1.0"], release: { url: "https://github.com/maxceem/app-ai-gateway/releases/download/v0.1.7/gateway-0.1.7.tar.gz", sha256: "e9a154b8b0c71e38296995b8ae478f26b563d2eb7fb1111e575d3fde33815b88", manifest: "ac56329e6cc34a01ecb3c8820b671b74bd4fabfb69d2c9353307a5306332a663" } };
|
|
10
10
|
|
|
11
11
|
// src/main.ts
|
|
12
12
|
import { Writable } from "node:stream";
|
|
@@ -7649,8 +7649,8 @@ function ko_default() {
|
|
|
7649
7649
|
}
|
|
7650
7650
|
|
|
7651
7651
|
// ../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/lt.js
|
|
7652
|
-
var capitalizeFirstCharacter = (
|
|
7653
|
-
return
|
|
7652
|
+
var capitalizeFirstCharacter = (text2) => {
|
|
7653
|
+
return text2.charAt(0).toUpperCase() + text2.slice(1);
|
|
7654
7654
|
};
|
|
7655
7655
|
function getUnitTypeFromNumber(number4) {
|
|
7656
7656
|
const abs = Math.abs(number4);
|
|
@@ -15529,7 +15529,10 @@ var CliAccountSchema = external_exports.object({
|
|
|
15529
15529
|
claimed: external_exports.boolean(),
|
|
15530
15530
|
expiresAt: external_exports.string().nullable()
|
|
15531
15531
|
});
|
|
15532
|
-
var CliApprovalRefusalSchema = external_exports.enum([
|
|
15532
|
+
var CliApprovalRefusalSchema = external_exports.enum([
|
|
15533
|
+
"registration_required",
|
|
15534
|
+
"sign_out_required"
|
|
15535
|
+
]);
|
|
15533
15536
|
var CliViewerSchema = external_exports.object({
|
|
15534
15537
|
name: external_exports.string().nullable(),
|
|
15535
15538
|
email: external_exports.string().nullable()
|
|
@@ -15542,7 +15545,9 @@ var CliBrowserDetailsResponseSchema = external_exports.object({
|
|
|
15542
15545
|
/**
|
|
15543
15546
|
* What stands between this browser and the Approve button, or null when
|
|
15544
15547
|
* nothing does — which is every non-claim kind, since only a claim asks who
|
|
15545
|
-
* is holding the browser.
|
|
15548
|
+
* is holding the browser. One field decides the whole screen: each value
|
|
15549
|
+
* names the single thing the page may offer, so the page never has to
|
|
15550
|
+
* consult the handoff kind to know what to put in front of a person.
|
|
15546
15551
|
*/
|
|
15547
15552
|
blockedBy: CliApprovalRefusalSchema.nullable(),
|
|
15548
15553
|
googleEnabled: external_exports.boolean(),
|
|
@@ -15969,11 +15974,12 @@ function fromPackageJson() {
|
|
|
15969
15974
|
if (typeof packaged.version !== "string" || typeof wrangler !== "string") {
|
|
15970
15975
|
throw new Error("cli/package.json must declare a version and a pinned wrangler dependency");
|
|
15971
15976
|
}
|
|
15972
|
-
return { version: packaged.version, wrangler };
|
|
15977
|
+
return { version: packaged.version, wrangler, release: null };
|
|
15973
15978
|
}
|
|
15974
15979
|
var manifest = typeof define_AGW_MANIFEST_default === "object" && define_AGW_MANIFEST_default !== null ? define_AGW_MANIFEST_default : fromPackageJson();
|
|
15975
15980
|
var VERSION = manifest.version;
|
|
15976
15981
|
var WRANGLER_VERSION = manifest.wrangler;
|
|
15982
|
+
var RELEASE = manifest.release ?? null;
|
|
15977
15983
|
|
|
15978
15984
|
// src/common.ts
|
|
15979
15985
|
var CLOUD = "https://api.appaigateway.com";
|
|
@@ -16112,15 +16118,21 @@ var common = "json! no-input!";
|
|
|
16112
16118
|
var commands = {
|
|
16113
16119
|
"deployment setup": {
|
|
16114
16120
|
args: 0,
|
|
16115
|
-
flags: "name cloudflare-account-id domain no-domain! version dry-run! yes!"
|
|
16121
|
+
flags: "name cloudflare-account-id domain no-domain! version release-archive dry-run! yes!"
|
|
16116
16122
|
},
|
|
16117
16123
|
"deployment connect": {
|
|
16118
16124
|
args: 0,
|
|
16119
16125
|
flags: `url cloud! ${managementKey}`
|
|
16120
16126
|
},
|
|
16121
16127
|
"deployment status": { args: 0, flags: "" },
|
|
16122
|
-
"deployment update": {
|
|
16123
|
-
|
|
16128
|
+
"deployment update": {
|
|
16129
|
+
args: 0,
|
|
16130
|
+
flags: "version release-archive dry-run! yes!"
|
|
16131
|
+
},
|
|
16132
|
+
"deployment domain": {
|
|
16133
|
+
args: 0,
|
|
16134
|
+
flags: "hostname release-archive dry-run! yes!"
|
|
16135
|
+
},
|
|
16124
16136
|
"account status": { args: 0, flags: "" },
|
|
16125
16137
|
"account claim": { args: 0, flags: browser },
|
|
16126
16138
|
"account login": { args: 0, flags: managementKey },
|
|
@@ -16536,6 +16548,14 @@ function stateDirectory() {
|
|
|
16536
16548
|
"agw"
|
|
16537
16549
|
);
|
|
16538
16550
|
}
|
|
16551
|
+
function cacheDirectory() {
|
|
16552
|
+
if (platform() === "win32")
|
|
16553
|
+
return join(
|
|
16554
|
+
process.env["LOCALAPPDATA"] || join(homedir(), "AppData", "Local"),
|
|
16555
|
+
"agw"
|
|
16556
|
+
);
|
|
16557
|
+
return join(process.env["XDG_CACHE_HOME"] || join(homedir(), ".cache"), "agw");
|
|
16558
|
+
}
|
|
16539
16559
|
async function protectedDirectory(path) {
|
|
16540
16560
|
await mkdir(path, { recursive: true, mode: 448 });
|
|
16541
16561
|
const s = await lstat(path);
|
|
@@ -16574,14 +16594,14 @@ async function reserveOutput(path, { reservation, retain = false } = {}) {
|
|
|
16574
16594
|
},
|
|
16575
16595
|
async write(value) {
|
|
16576
16596
|
if (!handle) throw new Error("The reserved output is closed");
|
|
16577
|
-
const
|
|
16597
|
+
const text2 = typeof value === "string" ? value : JSON.stringify(value, null, 2) + "\n";
|
|
16578
16598
|
try {
|
|
16579
16599
|
if (retain) {
|
|
16580
16600
|
const existing = await handle.readFile("utf8");
|
|
16581
|
-
if (existing && !
|
|
16601
|
+
if (existing && !text2.startsWith(existing)) fail("output_changed", "The reserved output contains different data.", "Choose a new --key-output path; no existing data was overwritten.", 4);
|
|
16582
16602
|
await handle.truncate(0);
|
|
16583
|
-
await handle.write(
|
|
16584
|
-
} else await handle.writeFile(
|
|
16603
|
+
await handle.write(text2, 0, "utf8");
|
|
16604
|
+
} else await handle.writeFile(text2);
|
|
16585
16605
|
await handle.sync();
|
|
16586
16606
|
await handle.close();
|
|
16587
16607
|
handle = null;
|
|
@@ -17864,42 +17884,226 @@ async function openBrowser(url2) {
|
|
|
17864
17884
|
import { spawn as spawn2 } from "node:child_process";
|
|
17865
17885
|
import { createRequire as createRequire2 } from "node:module";
|
|
17866
17886
|
import { randomUUID } from "node:crypto";
|
|
17867
|
-
import { cp, writeFile, unlink as unlink2 } from "node:fs/promises";
|
|
17887
|
+
import { cp, writeFile as writeFile2, unlink as unlink2 } from "node:fs/promises";
|
|
17868
17888
|
import { join as join3 } from "node:path";
|
|
17869
17889
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
17870
17890
|
|
|
17871
17891
|
// src/release.ts
|
|
17872
|
-
import { readFile as readFile3, lstat as lstat2 } from "node:fs/promises";
|
|
17892
|
+
import { readFile as readFile3, lstat as lstat2, mkdir as mkdir2, rename as rename2, rm, writeFile } from "node:fs/promises";
|
|
17873
17893
|
import { join as join2, dirname } from "node:path";
|
|
17874
17894
|
import { fileURLToPath } from "node:url";
|
|
17875
|
-
import { createHash as createHash4 } from "node:crypto";
|
|
17876
|
-
|
|
17895
|
+
import { createHash as createHash4, randomBytes as randomBytes3 } from "node:crypto";
|
|
17896
|
+
|
|
17897
|
+
// src/archive.ts
|
|
17898
|
+
import { gunzipSync } from "node:zlib";
|
|
17899
|
+
var BLOCK = 512;
|
|
17900
|
+
var ARCHIVE_LIMIT = 64 * 1024 * 1024;
|
|
17901
|
+
function text(header, start, length) {
|
|
17902
|
+
const raw = header.subarray(start, start + length);
|
|
17903
|
+
const end = raw.indexOf(0);
|
|
17904
|
+
return raw.subarray(0, end === -1 ? raw.length : end).toString("latin1");
|
|
17905
|
+
}
|
|
17906
|
+
function octal(header, start, length, field) {
|
|
17907
|
+
const value = text(header, start, length).trim();
|
|
17908
|
+
if (!/^[0-7]+$/.test(value))
|
|
17909
|
+
fail("release_archive", `Release archive has an unreadable ${field} field.`);
|
|
17910
|
+
const parsed = Number.parseInt(value, 8);
|
|
17911
|
+
if (!Number.isSafeInteger(parsed))
|
|
17912
|
+
fail("release_archive", `Release archive has an out-of-range ${field} field.`);
|
|
17913
|
+
return parsed;
|
|
17914
|
+
}
|
|
17915
|
+
function checksumMatches(header) {
|
|
17916
|
+
const stored = octal(header, 148, 8, "checksum");
|
|
17917
|
+
let unsigned = 0;
|
|
17918
|
+
let signed = 0;
|
|
17919
|
+
for (let i = 0; i < BLOCK; i++) {
|
|
17920
|
+
const byte = i >= 148 && i < 156 ? 32 : header[i];
|
|
17921
|
+
unsigned += byte;
|
|
17922
|
+
signed += byte > 127 ? byte - 256 : byte;
|
|
17923
|
+
}
|
|
17924
|
+
return stored === unsigned || stored === signed;
|
|
17925
|
+
}
|
|
17926
|
+
function safePath(raw, directory) {
|
|
17927
|
+
const path = directory ? raw.replace(/\/+$/, "") : raw;
|
|
17928
|
+
if (!path || path.startsWith("/") || path.includes("\\") || path.split("/").some((segment) => segment === "" || segment === "." || segment === ".."))
|
|
17929
|
+
fail("release_archive", "Release archive contains an unsafe path.");
|
|
17930
|
+
return path;
|
|
17931
|
+
}
|
|
17932
|
+
function readArchive(gzipped, limit = ARCHIVE_LIMIT) {
|
|
17933
|
+
let tar;
|
|
17934
|
+
try {
|
|
17935
|
+
tar = gunzipSync(gzipped, { maxOutputLength: limit });
|
|
17936
|
+
} catch {
|
|
17937
|
+
fail("release_archive", "Release archive is not readable gzip within the size limit.");
|
|
17938
|
+
}
|
|
17939
|
+
if (tar.length % BLOCK !== 0)
|
|
17940
|
+
fail("release_archive", "Release archive is not a whole number of tar blocks.");
|
|
17941
|
+
const entries = [];
|
|
17942
|
+
let total = 0;
|
|
17943
|
+
let offset = 0;
|
|
17944
|
+
while (offset + BLOCK <= tar.length) {
|
|
17945
|
+
const header = tar.subarray(offset, offset + BLOCK);
|
|
17946
|
+
if (header.every((byte) => byte === 0)) {
|
|
17947
|
+
if (!tar.subarray(offset).every((byte) => byte === 0))
|
|
17948
|
+
fail("release_archive", "Release archive continues past its end marker.");
|
|
17949
|
+
return entries;
|
|
17950
|
+
}
|
|
17951
|
+
if (text(header, 257, 6).trim() !== "ustar")
|
|
17952
|
+
fail("release_archive", "Release archive is not in ustar format.");
|
|
17953
|
+
if (!checksumMatches(header))
|
|
17954
|
+
fail("release_archive", "Release archive has a corrupt header.");
|
|
17955
|
+
const type = String.fromCharCode(header[156]);
|
|
17956
|
+
if (type === "L" || type === "K")
|
|
17957
|
+
fail("release_archive", "Release archive uses GNU long-name extensions.");
|
|
17958
|
+
if (type !== "0" && type !== "\0" && type !== "5")
|
|
17959
|
+
fail(
|
|
17960
|
+
"release_archive",
|
|
17961
|
+
"Release archives may contain only regular files and directories."
|
|
17962
|
+
);
|
|
17963
|
+
const directory = type === "5";
|
|
17964
|
+
const prefix = text(header, 345, 155);
|
|
17965
|
+
const name = text(header, 0, 100);
|
|
17966
|
+
const path = safePath(prefix ? `${prefix}/${name}` : name, directory);
|
|
17967
|
+
const size = octal(header, 124, 12, "size");
|
|
17968
|
+
if (directory && size !== 0)
|
|
17969
|
+
fail("release_archive", "Release archive has a directory entry with contents.");
|
|
17970
|
+
total += size;
|
|
17971
|
+
if (total > limit) fail("release_archive", "Release archive exceeds its size limit.");
|
|
17972
|
+
const start = offset + BLOCK;
|
|
17973
|
+
if (start + size > tar.length)
|
|
17974
|
+
fail("release_archive", "Release archive is truncated.");
|
|
17975
|
+
entries.push({
|
|
17976
|
+
path,
|
|
17977
|
+
directory,
|
|
17978
|
+
data: directory ? Buffer.alloc(0) : Buffer.from(tar.subarray(start, start + size))
|
|
17979
|
+
});
|
|
17980
|
+
offset = start + Math.ceil(size / BLOCK) * BLOCK;
|
|
17981
|
+
}
|
|
17982
|
+
fail("release_archive", "Release archive has no end marker.");
|
|
17983
|
+
}
|
|
17984
|
+
|
|
17985
|
+
// src/release.ts
|
|
17986
|
+
var digest = (data) => createHash4("sha256").update(data).digest("hex");
|
|
17987
|
+
async function fetchArchive(url2) {
|
|
17988
|
+
let response;
|
|
17989
|
+
try {
|
|
17990
|
+
response = await fetch(url2, {
|
|
17991
|
+
redirect: "follow",
|
|
17992
|
+
signal: AbortSignal.timeout(3e5),
|
|
17993
|
+
headers: { "user-agent": `agw/${VERSION}` }
|
|
17994
|
+
});
|
|
17995
|
+
} catch {
|
|
17996
|
+
fail(
|
|
17997
|
+
"release_download",
|
|
17998
|
+
`Cannot download the gateway release from ${url2}.`,
|
|
17999
|
+
"Check network access, or pass --release-archive with a copy downloaded elsewhere.",
|
|
18000
|
+
3
|
|
18001
|
+
);
|
|
18002
|
+
}
|
|
18003
|
+
if (!response.ok)
|
|
18004
|
+
fail(
|
|
18005
|
+
"release_download",
|
|
18006
|
+
`Downloading the gateway release from ${url2} failed with HTTP ${response.status}.`,
|
|
18007
|
+
"Check network access, or pass --release-archive with a copy downloaded elsewhere.",
|
|
18008
|
+
3
|
|
18009
|
+
);
|
|
18010
|
+
const body = Buffer.from(await response.arrayBuffer());
|
|
18011
|
+
if (body.length > ARCHIVE_LIMIT)
|
|
18012
|
+
fail("release_download", "The downloaded gateway release exceeds its size limit.", void 0, 3);
|
|
18013
|
+
return body;
|
|
18014
|
+
}
|
|
18015
|
+
async function extract(archive, directory) {
|
|
18016
|
+
await mkdir2(directory, { recursive: true, mode: 448 });
|
|
18017
|
+
for (const entry of readArchive(archive)) {
|
|
18018
|
+
const path = join2(directory, entry.path);
|
|
18019
|
+
if (entry.directory) {
|
|
18020
|
+
await mkdir2(path, { recursive: true, mode: 448 });
|
|
18021
|
+
continue;
|
|
18022
|
+
}
|
|
18023
|
+
await mkdir2(dirname(path), { recursive: true, mode: 448 });
|
|
18024
|
+
await writeFile(path, entry.data, { mode: 384, flag: "wx" });
|
|
18025
|
+
}
|
|
18026
|
+
}
|
|
18027
|
+
async function cachedRelease(version2, integrity, source) {
|
|
18028
|
+
const root = source.cache ?? join2(cacheDirectory(), "releases");
|
|
18029
|
+
const directory = join2(root, version2);
|
|
18030
|
+
const cached2 = await readFile3(join2(directory, "manifest.json")).catch(() => null);
|
|
18031
|
+
if (cached2 && digest(cached2) === integrity.manifest) return directory;
|
|
18032
|
+
const supplied = source.archive ?? process.env["AGW_RELEASE_ARCHIVE"] ?? void 0;
|
|
18033
|
+
let archive;
|
|
18034
|
+
if (supplied) {
|
|
18035
|
+
archive = await readFile3(supplied).catch(
|
|
18036
|
+
() => fail(
|
|
18037
|
+
"release_unavailable",
|
|
18038
|
+
`Cannot read the gateway release archive at ${supplied}.`,
|
|
18039
|
+
"Supply the gateway-<version>.tar.gz asset published with this CLI release.",
|
|
18040
|
+
4
|
|
18041
|
+
)
|
|
18042
|
+
);
|
|
18043
|
+
} else {
|
|
18044
|
+
archive = await (source.download ?? fetchArchive)(integrity.url);
|
|
18045
|
+
}
|
|
18046
|
+
if (digest(archive) !== integrity.sha256)
|
|
18047
|
+
fail(
|
|
18048
|
+
"release_integrity",
|
|
18049
|
+
"The gateway release archive does not match the digest built into this CLI.",
|
|
18050
|
+
"Nothing was extracted. Obtain the archive published with this CLI version.",
|
|
18051
|
+
4
|
|
18052
|
+
);
|
|
18053
|
+
await protectedDirectory(root);
|
|
18054
|
+
const staging = join2(root, `.${version2}-${randomBytes3(8).toString("hex")}`);
|
|
18055
|
+
try {
|
|
18056
|
+
await extract(archive, staging);
|
|
18057
|
+
const manifest2 = await readFile3(join2(staging, "manifest.json")).catch(() => null);
|
|
18058
|
+
if (!manifest2 || digest(manifest2) !== integrity.manifest)
|
|
18059
|
+
fail(
|
|
18060
|
+
"release_integrity",
|
|
18061
|
+
"The gateway release archive does not contain the expected manifest.",
|
|
18062
|
+
"Nothing was installed. Obtain the archive published with this CLI version.",
|
|
18063
|
+
4
|
|
18064
|
+
);
|
|
18065
|
+
await rm(directory, { recursive: true, force: true });
|
|
18066
|
+
await rename2(staging, directory);
|
|
18067
|
+
} finally {
|
|
18068
|
+
await rm(staging, { recursive: true, force: true });
|
|
18069
|
+
}
|
|
18070
|
+
return directory;
|
|
18071
|
+
}
|
|
18072
|
+
async function release(requested = VERSION, base = dirname(fileURLToPath(import.meta.url)), source = {}) {
|
|
17877
18073
|
if (requested !== VERSION)
|
|
17878
18074
|
fail(
|
|
17879
18075
|
"unsupported_release",
|
|
17880
|
-
`This CLI
|
|
18076
|
+
`This CLI deploys gateway ${VERSION} only.`,
|
|
17881
18077
|
`Install @maxceem/agw@${requested} to use its matching release.`
|
|
17882
18078
|
);
|
|
17883
18079
|
const [major, minor] = process.versions.node.split(".").map(Number);
|
|
17884
18080
|
if (major === void 0 || minor === void 0 || major < 22 || major === 22 && minor < 19)
|
|
17885
18081
|
fail("unsupported_runtime", "Node.js 22.19.0 or newer is required.");
|
|
17886
|
-
const
|
|
18082
|
+
const integrity = source.integrity === void 0 ? RELEASE : source.integrity;
|
|
18083
|
+
const packaged = join2(base, "release", requested);
|
|
18084
|
+
const local = await lstat2(packaged).catch(() => null);
|
|
18085
|
+
let directory;
|
|
18086
|
+
if (local?.isDirectory()) directory = packaged;
|
|
18087
|
+
else if (integrity) directory = await cachedRelease(requested, integrity, source);
|
|
18088
|
+
else
|
|
18089
|
+
fail(
|
|
18090
|
+
"release_missing",
|
|
18091
|
+
"This CLI build does not know where to obtain its deployable release.",
|
|
18092
|
+
"Install a published release; repository contributors must run the CLI release build.",
|
|
18093
|
+
4
|
|
18094
|
+
);
|
|
17887
18095
|
let serialized;
|
|
17888
|
-
let integrity;
|
|
17889
18096
|
try {
|
|
17890
18097
|
serialized = await readFile3(join2(directory, "manifest.json"), "utf8");
|
|
17891
|
-
integrity = JSON.parse(
|
|
17892
|
-
await readFile3(join2(base, "release-integrity.json"), "utf8")
|
|
17893
|
-
);
|
|
17894
18098
|
} catch {
|
|
17895
18099
|
fail(
|
|
17896
18100
|
"release_missing",
|
|
17897
|
-
"The
|
|
17898
|
-
"Install a
|
|
18101
|
+
"The deployable release is missing its manifest.",
|
|
18102
|
+
"Install a published release; repository contributors must run the CLI release build.",
|
|
17899
18103
|
4
|
|
17900
18104
|
);
|
|
17901
18105
|
}
|
|
17902
|
-
if (
|
|
18106
|
+
if (integrity && digest(serialized) !== integrity.manifest)
|
|
17903
18107
|
fail("release_integrity", "Release manifest integrity check failed.");
|
|
17904
18108
|
const manifest2 = JSON.parse(serialized);
|
|
17905
18109
|
if (manifest2.format !== 1 || manifest2.version !== requested || manifest2.wrangler !== WRANGLER_VERSION || manifest2.schema !== 1)
|
|
@@ -17912,7 +18116,7 @@ async function release(requested = VERSION, base = dirname(fileURLToPath(import.
|
|
|
17912
18116
|
fail("release_integrity", "Release includes an unsafe path.");
|
|
17913
18117
|
const file2 = join2(directory, path);
|
|
17914
18118
|
const info = await lstat2(file2);
|
|
17915
|
-
if (!info.isFile() || info.isSymbolicLink() ||
|
|
18119
|
+
if (!info.isFile() || info.isSymbolicLink() || digest(await readFile3(file2)) !== hash2)
|
|
17916
18120
|
fail("release_integrity", "Release asset integrity check failed.");
|
|
17917
18121
|
}
|
|
17918
18122
|
return {
|
|
@@ -17928,7 +18132,7 @@ async function release(requested = VERSION, base = dirname(fileURLToPath(import.
|
|
|
17928
18132
|
var require2 = createRequire2(import.meta.url);
|
|
17929
18133
|
var OUTPUT_TAIL = 2e3;
|
|
17930
18134
|
function wranglerFailure(args, code, output, redact = []) {
|
|
17931
|
-
const tail = redact.filter((value) => value.length >= 8).reduce((
|
|
18135
|
+
const tail = redact.filter((value) => value.length >= 8).reduce((text2, value) => text2.split(value).join("[redacted]"), output).trim().slice(-OUTPUT_TAIL);
|
|
17932
18136
|
return new CliError(
|
|
17933
18137
|
"wrangler_failed",
|
|
17934
18138
|
`wrangler ${args[0] ?? ""} failed${code === null ? "" : ` (exit ${code})`}.`,
|
|
@@ -18115,7 +18319,7 @@ async function configFile(ctx, journal, artifact) {
|
|
|
18115
18319
|
} : {}
|
|
18116
18320
|
};
|
|
18117
18321
|
const path = join3(directory, "wrangler.json");
|
|
18118
|
-
await
|
|
18322
|
+
await writeFile2(path, JSON.stringify(config2, null, 2), { mode: 384 });
|
|
18119
18323
|
return { path, directory, config: config2 };
|
|
18120
18324
|
}
|
|
18121
18325
|
async function verifyDeployment(ctx, url2, id) {
|
|
@@ -18335,7 +18539,9 @@ async function matchExisting(ctx, cf, flags) {
|
|
|
18335
18539
|
return journal;
|
|
18336
18540
|
}
|
|
18337
18541
|
async function deploymentCommand(ctx, command, flags, cf = new Cloudflare(), loadRelease = release) {
|
|
18338
|
-
const artifact = await loadRelease(flags.version
|
|
18542
|
+
const artifact = await loadRelease(flags.version, void 0, {
|
|
18543
|
+
archive: flags["release-archive"]
|
|
18544
|
+
});
|
|
18339
18545
|
await cf.authenticate(flags);
|
|
18340
18546
|
if (command !== "deployment setup") {
|
|
18341
18547
|
const journal2 = await matchExisting(ctx, cf, flags);
|
|
@@ -18501,7 +18707,7 @@ async function deploymentCommand(ctx, command, flags, cf = new Cloudflare(), loa
|
|
|
18501
18707
|
SECRET_VAULT_LOCAL_KEK_V1: await ctx.store.vaultKey(journal.id)
|
|
18502
18708
|
};
|
|
18503
18709
|
const secretPath = join3(file2.directory, "secrets.json");
|
|
18504
|
-
await
|
|
18710
|
+
await writeFile2(secretPath, JSON.stringify(secrets), {
|
|
18505
18711
|
mode: 384
|
|
18506
18712
|
});
|
|
18507
18713
|
try {
|
|
@@ -18617,10 +18823,10 @@ function* strings(value) {
|
|
|
18617
18823
|
}
|
|
18618
18824
|
function reported(value, sent) {
|
|
18619
18825
|
if (typeof value !== "string") return "";
|
|
18620
|
-
let
|
|
18826
|
+
let text2 = value.replace(new RegExp("\\p{C}", "gu"), " ").replace(/\s+/g, " ").trim();
|
|
18621
18827
|
for (const secret3 of strings(sent))
|
|
18622
|
-
if (secret3.length >= 8)
|
|
18623
|
-
return
|
|
18828
|
+
if (secret3.length >= 8) text2 = text2.split(secret3).join("[redacted]");
|
|
18829
|
+
return text2.length > MESSAGE_LIMIT ? text2.slice(0, MESSAGE_LIMIT) + "\u2026" : text2;
|
|
18624
18830
|
}
|
|
18625
18831
|
var RECOVERABLE_RESOURCE_CODES = ["resource_receipt_expired", "resource_key_unavailable"];
|
|
18626
18832
|
var RECOVERABLE_RESOURCE_FIELDS = ["appId", "keyId", "providerId", "providerGatewayId"];
|
|
@@ -18815,16 +19021,16 @@ async function main(argv = process.argv.slice(2), {
|
|
|
18815
19021
|
};
|
|
18816
19022
|
const rendered = ctx.onboarding ? { ...result, ...ctx.onboarding } : result;
|
|
18817
19023
|
const output = { schemaVersion: 1, ok: true, context, result: rendered };
|
|
18818
|
-
const
|
|
19024
|
+
const text2 = json2 ? JSON.stringify(output) + "\n" : humanResult(parsed.command, rendered, context);
|
|
18819
19025
|
if (stdout instanceof Writable) {
|
|
18820
19026
|
await new Promise(
|
|
18821
19027
|
(resolve3, reject2) => stdout.write(
|
|
18822
|
-
|
|
19028
|
+
text2,
|
|
18823
19029
|
(error51) => error51 ? reject2(error51) : resolve3()
|
|
18824
19030
|
)
|
|
18825
19031
|
);
|
|
18826
19032
|
} else {
|
|
18827
|
-
stdout.write(
|
|
19033
|
+
stdout.write(text2);
|
|
18828
19034
|
}
|
|
18829
19035
|
await ctx.acknowledgeOutput();
|
|
18830
19036
|
return 0;
|