run402 1.43.0 → 1.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/sites.mjs +108 -35
- package/package.json +1 -1
- package/sdk/dist/namespaces/sites.d.ts +13 -21
- package/sdk/dist/namespaces/sites.d.ts.map +1 -1
- package/sdk/dist/namespaces/sites.js +9 -21
- package/sdk/dist/namespaces/sites.js.map +1 -1
- package/sdk/dist/node/canonicalize.d.ts +41 -0
- package/sdk/dist/node/canonicalize.d.ts.map +1 -0
- package/sdk/dist/node/canonicalize.js +62 -0
- package/sdk/dist/node/canonicalize.js.map +1 -0
- package/sdk/dist/node/index.d.ts +5 -1
- package/sdk/dist/node/index.d.ts.map +1 -1
- package/sdk/dist/node/index.js +4 -0
- package/sdk/dist/node/index.js.map +1 -1
- package/sdk/dist/node/sites-node.d.ts +116 -16
- package/sdk/dist/node/sites-node.d.ts.map +1 -1
- package/sdk/dist/node/sites-node.js +291 -27
- package/sdk/dist/node/sites-node.js.map +1 -1
package/lib/sites.mjs
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { readFileSync } from "fs";
|
|
2
|
-
import {
|
|
1
|
+
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
2
|
+
import { tmpdir } from "os";
|
|
3
|
+
import { dirname, join, resolve } from "path";
|
|
3
4
|
import { allowanceAuthHeaders, resolveProjectId, updateProject } from "./config.mjs";
|
|
4
5
|
import { resolveFilePathsInManifest } from "./manifest.mjs";
|
|
5
6
|
import { getSdk } from "./sdk.mjs";
|
|
6
7
|
import { reportSdkError } from "./sdk-errors.mjs";
|
|
7
8
|
|
|
8
|
-
const HELP = `run402 sites
|
|
9
|
+
const HELP = `run402 sites - Deploy and manage static sites
|
|
9
10
|
|
|
10
11
|
Usage:
|
|
11
12
|
run402 sites deploy --manifest <file> [--project <id>] [--target <target>]
|
|
12
|
-
run402 sites deploy-dir <path> --project <id> [--target <target>]
|
|
13
|
+
run402 sites deploy-dir <path> --project <id> [--target <target>]
|
|
13
14
|
run402 sites status <deployment_id>
|
|
14
15
|
cat manifest.json | run402 sites deploy
|
|
15
16
|
|
|
@@ -22,14 +23,13 @@ Options (deploy):
|
|
|
22
23
|
--manifest <file> Path to manifest JSON file (or read from stdin)
|
|
23
24
|
--project <id> Project ID (defaults to active project)
|
|
24
25
|
--target <target> Deployment target (e.g. 'production')
|
|
25
|
-
--inherit Copy unchanged files from the previous deployment (only upload changed files)
|
|
26
26
|
--help, -h Show this help message
|
|
27
27
|
|
|
28
28
|
Options (deploy-dir):
|
|
29
29
|
<path> Positional: local directory to deploy
|
|
30
30
|
--project <id> Project ID (defaults to active project)
|
|
31
31
|
--target <target> Deployment target (e.g. 'production')
|
|
32
|
-
--
|
|
32
|
+
--quiet Suppress progress events on stderr
|
|
33
33
|
|
|
34
34
|
Manifest format (JSON):
|
|
35
35
|
{
|
|
@@ -40,36 +40,40 @@ Manifest format (JSON):
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
Files can use either inline "data" or a local "path":
|
|
43
|
-
{ "file": "index.html", "data": "<html>...</html>" }
|
|
44
|
-
{ "file": "style.css", "path": "./dist/style.css" }
|
|
43
|
+
{ "file": "index.html", "data": "<html>...</html>" } <- inline content
|
|
44
|
+
{ "file": "style.css", "path": "./dist/style.css" } <- read from disk
|
|
45
45
|
Paths are resolved relative to the manifest file's directory.
|
|
46
46
|
Binary files (images, fonts, etc.) are auto-detected and base64-encoded.
|
|
47
47
|
|
|
48
48
|
Examples:
|
|
49
49
|
run402 sites deploy --manifest site.json
|
|
50
|
-
run402 sites deploy-dir ./my-site --project prj_abc
|
|
50
|
+
run402 sites deploy-dir ./my-site --project prj_abc
|
|
51
51
|
run402 sites status dpl_abc123
|
|
52
52
|
cat site.json | run402 sites deploy
|
|
53
53
|
|
|
54
54
|
Notes:
|
|
55
|
+
- Both deploy and deploy-dir use the v1.32 plan/commit transport: only
|
|
56
|
+
bytes the gateway doesn't already have are uploaded. Re-deploys of an
|
|
57
|
+
unchanged tree make no S3 PUTs.
|
|
55
58
|
- deploy-dir walks the directory, skips .git / node_modules / .DS_Store,
|
|
56
59
|
and auto-detects binary files. Symlinks are rejected.
|
|
57
|
-
-
|
|
60
|
+
- Progress events are emitted as JSON-line objects on stderr by default
|
|
61
|
+
(one object per line: {"phase":"plan",...}/{"phase":"upload",...}/...).
|
|
62
|
+
Final result envelope goes to stdout. Pass --quiet to silence stderr.
|
|
63
|
+
- Free with active tier - requires allowance auth
|
|
58
64
|
`;
|
|
59
65
|
|
|
60
66
|
const SUB_HELP = {
|
|
61
|
-
deploy: `run402 sites deploy
|
|
67
|
+
deploy: `run402 sites deploy - Deploy a static site from a manifest
|
|
62
68
|
|
|
63
69
|
Usage:
|
|
64
|
-
run402 sites deploy --manifest <file> [--project <id>] [--target <target>]
|
|
70
|
+
run402 sites deploy --manifest <file> [--project <id>] [--target <target>]
|
|
65
71
|
cat manifest.json | run402 sites deploy [--project <id>] [--target <target>]
|
|
66
72
|
|
|
67
73
|
Options:
|
|
68
74
|
--manifest <file> Path to manifest JSON file (or read from stdin)
|
|
69
75
|
--project <id> Project ID (defaults to the active project)
|
|
70
76
|
--target <target> Deployment target (e.g. 'production')
|
|
71
|
-
--inherit Copy unchanged files from the previous deployment
|
|
72
|
-
(only upload changed files)
|
|
73
77
|
|
|
74
78
|
Manifest format (JSON):
|
|
75
79
|
{
|
|
@@ -83,17 +87,17 @@ Manifest format (JSON):
|
|
|
83
87
|
|
|
84
88
|
Notes:
|
|
85
89
|
- Must include at least index.html in the files array
|
|
86
|
-
- Free with active tier
|
|
90
|
+
- Free with active tier - requires allowance auth
|
|
87
91
|
|
|
88
92
|
Examples:
|
|
89
93
|
run402 sites deploy --manifest site.json
|
|
90
|
-
run402 sites deploy --manifest site.json --target production
|
|
94
|
+
run402 sites deploy --manifest site.json --target production
|
|
91
95
|
cat site.json | run402 sites deploy
|
|
92
96
|
`,
|
|
93
|
-
"deploy-dir": `run402 sites deploy-dir
|
|
97
|
+
"deploy-dir": `run402 sites deploy-dir - Deploy a static site from a local directory
|
|
94
98
|
|
|
95
99
|
Usage:
|
|
96
|
-
run402 sites deploy-dir <path> [--project <id>] [--target <target>] [--
|
|
100
|
+
run402 sites deploy-dir <path> [--project <id>] [--target <target>] [--quiet]
|
|
97
101
|
|
|
98
102
|
Arguments:
|
|
99
103
|
<path> Local directory to deploy (positional, required)
|
|
@@ -101,23 +105,37 @@ Arguments:
|
|
|
101
105
|
Options:
|
|
102
106
|
--project <id> Project ID (defaults to the active project)
|
|
103
107
|
--target <target> Deployment target (e.g. 'production')
|
|
104
|
-
--
|
|
108
|
+
--quiet Suppress progress events on stderr (events are on by
|
|
109
|
+
default — see Progress events below)
|
|
105
110
|
|
|
106
111
|
Behavior:
|
|
107
112
|
- Walks <path> recursively, skips .git / node_modules / .DS_Store
|
|
108
|
-
-
|
|
113
|
+
- Computes per-file SHA-256 and uploads only bytes the gateway doesn't
|
|
114
|
+
already have (plan/commit transport, v1.32+)
|
|
109
115
|
- Symlinks are rejected (no following)
|
|
110
116
|
- Paths in the manifest are POSIX-style relative to <path>
|
|
111
117
|
|
|
118
|
+
Progress events:
|
|
119
|
+
By default, the CLI streams JSON-line events to stderr while the deploy
|
|
120
|
+
progresses. Each line is one JSON object terminated by \\n. Phases:
|
|
121
|
+
{"phase":"plan","manifest_size":N} - after POST /deploy/v1/plan
|
|
122
|
+
{"phase":"upload","file":"...","sha256":"...","done":k,"total":N}
|
|
123
|
+
- per uploaded file (k of N)
|
|
124
|
+
{"phase":"commit"} - before POST /deploy/v1/commit
|
|
125
|
+
{"phase":"poll","status":"copying","elapsed_ms":N}
|
|
126
|
+
- per Stage-2 copy poll tick
|
|
127
|
+
Stdout receives only the final result envelope. To consume both streams
|
|
128
|
+
separately: \`run402 sites deploy-dir ./dist --project p > result.json 2> events.log\`.
|
|
129
|
+
|
|
112
130
|
Notes:
|
|
113
|
-
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
- Free with active tier — requires allowance auth
|
|
131
|
+
- Re-deploying an unchanged tree makes no S3 PUTs (returns immediately
|
|
132
|
+
with bytes_uploaded: 0)
|
|
133
|
+
- Free with active tier - requires allowance auth
|
|
117
134
|
|
|
118
135
|
Examples:
|
|
119
136
|
run402 sites deploy-dir ./dist --project prj_abc
|
|
120
|
-
run402 sites deploy-dir ./my-site --project prj_abc --target production
|
|
137
|
+
run402 sites deploy-dir ./my-site --project prj_abc --target production
|
|
138
|
+
run402 sites deploy-dir ./dist --project prj_abc --quiet
|
|
121
139
|
`,
|
|
122
140
|
};
|
|
123
141
|
|
|
@@ -127,14 +145,58 @@ async function readStdin() {
|
|
|
127
145
|
return Buffer.concat(chunks).toString("utf-8");
|
|
128
146
|
}
|
|
129
147
|
|
|
148
|
+
/**
|
|
149
|
+
* Stage manifest files to a temp directory so the SDK's deployDir can walk
|
|
150
|
+
* them. The v1.32 SDK no longer accepts inline file bytes — every deploy
|
|
151
|
+
* goes through plan/commit and reads from a directory.
|
|
152
|
+
*/
|
|
153
|
+
function stageFilesToTempDir(files) {
|
|
154
|
+
const stage = mkdtempSync(join(tmpdir(), "run402-deploy-stage-"));
|
|
155
|
+
for (const f of files) {
|
|
156
|
+
if (typeof f.file !== "string" || typeof f.data !== "string") {
|
|
157
|
+
throw new Error("manifest entry missing required 'file' or 'data' string");
|
|
158
|
+
}
|
|
159
|
+
const target = join(stage, f.file);
|
|
160
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
161
|
+
const buf = (f.encoding ?? "utf-8") === "base64"
|
|
162
|
+
? Buffer.from(f.data, "base64")
|
|
163
|
+
: Buffer.from(f.data, "utf-8");
|
|
164
|
+
writeFileSync(target, buf);
|
|
165
|
+
}
|
|
166
|
+
return stage;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Returns an onEvent callback that writes each event as a single-line JSON
|
|
171
|
+
* object to stderr — or a no-op when --quiet was passed. The CLI is
|
|
172
|
+
* agent-first; structured stderr lets a piping agent stream progress with
|
|
173
|
+
* `2>events.log` while keeping stdout reserved for the final result envelope.
|
|
174
|
+
*
|
|
175
|
+
* Uses `console.error` so that test harnesses intercepting console output
|
|
176
|
+
* see each event line; `console.error` appends a newline by default.
|
|
177
|
+
*/
|
|
178
|
+
function makeStderrEventWriter(quiet) {
|
|
179
|
+
if (quiet) return undefined;
|
|
180
|
+
return (event) => {
|
|
181
|
+
console.error(JSON.stringify(event));
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
130
185
|
async function deploy(args) {
|
|
131
|
-
const opts = { manifest: null, project: undefined, target: undefined,
|
|
186
|
+
const opts = { manifest: null, project: undefined, target: undefined, quiet: false };
|
|
132
187
|
for (let i = 0; i < args.length; i++) {
|
|
133
188
|
if (args[i] === "--help" || args[i] === "-h") { console.log(HELP); process.exit(0); }
|
|
134
189
|
if (args[i] === "--manifest" && args[i + 1]) opts.manifest = args[++i];
|
|
135
190
|
if (args[i] === "--project" && args[i + 1]) opts.project = args[++i];
|
|
136
191
|
if (args[i] === "--target" && args[i + 1]) opts.target = args[++i];
|
|
137
|
-
if (args[i] === "--
|
|
192
|
+
if (args[i] === "--quiet") opts.quiet = true;
|
|
193
|
+
if (args[i] === "--inherit") {
|
|
194
|
+
console.error(JSON.stringify({
|
|
195
|
+
status: "error",
|
|
196
|
+
message: "--inherit is removed in v1.32; the SDK now uploads only changed files automatically.",
|
|
197
|
+
}));
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
138
200
|
}
|
|
139
201
|
const projectId = resolveProjectId(opts.project);
|
|
140
202
|
const raw = opts.manifest ? readFileSync(opts.manifest, "utf-8") : await readStdin();
|
|
@@ -142,13 +204,15 @@ async function deploy(args) {
|
|
|
142
204
|
if (opts.manifest) resolveFilePathsInManifest(manifest, dirname(resolve(opts.manifest)));
|
|
143
205
|
|
|
144
206
|
// Preserve the aggressive early exit when no allowance is configured.
|
|
145
|
-
allowanceAuthHeaders("/
|
|
207
|
+
allowanceAuthHeaders("/deploy/v1/plan");
|
|
146
208
|
|
|
209
|
+
const stage = stageFilesToTempDir(manifest.files || []);
|
|
147
210
|
try {
|
|
148
|
-
const data = await getSdk().sites.
|
|
149
|
-
|
|
211
|
+
const data = await getSdk().sites.deployDir({
|
|
212
|
+
project: projectId,
|
|
213
|
+
dir: stage,
|
|
150
214
|
target: opts.target,
|
|
151
|
-
|
|
215
|
+
onEvent: makeStderrEventWriter(opts.quiet),
|
|
152
216
|
});
|
|
153
217
|
if (data.deployment_id) {
|
|
154
218
|
updateProject(projectId, { last_deployment_id: data.deployment_id });
|
|
@@ -156,16 +220,25 @@ async function deploy(args) {
|
|
|
156
220
|
console.log(JSON.stringify(data, null, 2));
|
|
157
221
|
} catch (err) {
|
|
158
222
|
reportSdkError(err);
|
|
223
|
+
} finally {
|
|
224
|
+
rmSync(stage, { recursive: true, force: true });
|
|
159
225
|
}
|
|
160
226
|
}
|
|
161
227
|
|
|
162
228
|
async function deployDir(args) {
|
|
163
|
-
const opts = { dir: null, project: undefined, target: undefined,
|
|
229
|
+
const opts = { dir: null, project: undefined, target: undefined, quiet: false };
|
|
164
230
|
for (let i = 0; i < args.length; i++) {
|
|
165
231
|
if (args[i] === "--help" || args[i] === "-h") { console.log(SUB_HELP["deploy-dir"]); process.exit(0); }
|
|
166
232
|
if (args[i] === "--project" && args[i + 1]) { opts.project = args[++i]; continue; }
|
|
167
233
|
if (args[i] === "--target" && args[i + 1]) { opts.target = args[++i]; continue; }
|
|
168
|
-
if (args[i] === "--
|
|
234
|
+
if (args[i] === "--quiet") { opts.quiet = true; continue; }
|
|
235
|
+
if (args[i] === "--inherit") {
|
|
236
|
+
console.error(JSON.stringify({
|
|
237
|
+
status: "error",
|
|
238
|
+
message: "--inherit is removed in v1.32; the SDK now uploads only changed files automatically.",
|
|
239
|
+
}));
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
169
242
|
if (!args[i].startsWith("-") && opts.dir === null) { opts.dir = args[i]; continue; }
|
|
170
243
|
}
|
|
171
244
|
if (!opts.dir) {
|
|
@@ -175,14 +248,14 @@ async function deployDir(args) {
|
|
|
175
248
|
const projectId = resolveProjectId(opts.project);
|
|
176
249
|
|
|
177
250
|
// Preserve the aggressive early exit when no allowance is configured.
|
|
178
|
-
allowanceAuthHeaders("/
|
|
251
|
+
allowanceAuthHeaders("/deploy/v1/plan");
|
|
179
252
|
|
|
180
253
|
try {
|
|
181
254
|
const data = await getSdk().sites.deployDir({
|
|
182
255
|
project: projectId,
|
|
183
256
|
dir: opts.dir,
|
|
184
257
|
target: opts.target,
|
|
185
|
-
|
|
258
|
+
onEvent: makeStderrEventWriter(opts.quiet),
|
|
186
259
|
});
|
|
187
260
|
if (data.deployment_id) {
|
|
188
261
|
updateProject(projectId, { last_deployment_id: data.deployment_id });
|
package/package.json
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `sites` namespace — static site deployments
|
|
2
|
+
* `sites` namespace — static site deployments.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
4
|
+
* As of v1.32 the inline-bytes deploy path (`POST /deployments/v1` with
|
|
5
|
+
* base64 file blobs) is REMOVED at the gateway (returns 410 Gone). Callers
|
|
6
|
+
* migrate to `NodeSites.deployDir` from `@run402/sdk/node`, which uses the
|
|
7
|
+
* plan/commit transport over `/deploy/v1/plan` + `/deploy/v1/commit`.
|
|
8
|
+
*
|
|
9
|
+
* The isomorphic surface keeps only the public read-only `getDeployment`
|
|
10
|
+
* call. `SiteFile` is preserved for `apps.bundleDeploy` (separate `/deploy/v1`
|
|
11
|
+
* endpoint, unaffected by the v1.32 cutover).
|
|
7
12
|
*/
|
|
8
13
|
import type { Client } from "../kernel.js";
|
|
9
14
|
export interface SiteFile {
|
|
@@ -13,20 +18,13 @@ export interface SiteFile {
|
|
|
13
18
|
data: string;
|
|
14
19
|
encoding?: "utf-8" | "base64";
|
|
15
20
|
}
|
|
16
|
-
export interface SiteDeployOptions {
|
|
17
|
-
/** Files to deploy. Paths are relative to the site root. */
|
|
18
|
-
files: SiteFile[];
|
|
19
|
-
/** Deployment target label, e.g. `"production"`. */
|
|
20
|
-
target?: string;
|
|
21
|
-
/**
|
|
22
|
-
* When true, unchanged files are copied from the previous deployment —
|
|
23
|
-
* only changed/new files need to appear in `files`.
|
|
24
|
-
*/
|
|
25
|
-
inherit?: boolean;
|
|
26
|
-
}
|
|
27
21
|
export interface SiteDeployResult {
|
|
28
22
|
deployment_id: string;
|
|
29
23
|
url: string;
|
|
24
|
+
/** Total bytes across the manifest (present when reported by the gateway). */
|
|
25
|
+
bytes_total?: number;
|
|
26
|
+
/** Bytes uploaded in this deploy (0 on a no-op redeploy). */
|
|
27
|
+
bytes_uploaded?: number;
|
|
30
28
|
}
|
|
31
29
|
export interface DeploymentInfo {
|
|
32
30
|
id: string;
|
|
@@ -40,12 +38,6 @@ export interface DeploymentInfo {
|
|
|
40
38
|
export declare class Sites {
|
|
41
39
|
private readonly client;
|
|
42
40
|
constructor(client: Client);
|
|
43
|
-
/**
|
|
44
|
-
* Deploy a static site. Payment flows through the configured fetch wrapper
|
|
45
|
-
* (x402 in Node when a tier purchase is required; typically free with an
|
|
46
|
-
* active tier).
|
|
47
|
-
*/
|
|
48
|
-
deploy(projectId: string, opts: SiteDeployOptions): Promise<SiteDeployResult>;
|
|
49
41
|
/** Get deployment metadata by id. Public — no project auth. */
|
|
50
42
|
getDeployment(deploymentId: string): Promise<DeploymentInfo>;
|
|
51
43
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sites.d.ts","sourceRoot":"","sources":["../../src/namespaces/sites.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"sites.d.ts","sourceRoot":"","sources":["../../src/namespaces/sites.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,MAAM,WAAW,QAAQ;IACvB,sFAAsF;IACtF,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC/B;AAED,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6DAA6D;IAC7D,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,KAAK;IACJ,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,MAAM;IAE3C,+DAA+D;IACzD,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;CAMnE"}
|
|
@@ -1,32 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `sites` namespace — static site deployments
|
|
2
|
+
* `sites` namespace — static site deployments.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
4
|
+
* As of v1.32 the inline-bytes deploy path (`POST /deployments/v1` with
|
|
5
|
+
* base64 file blobs) is REMOVED at the gateway (returns 410 Gone). Callers
|
|
6
|
+
* migrate to `NodeSites.deployDir` from `@run402/sdk/node`, which uses the
|
|
7
|
+
* plan/commit transport over `/deploy/v1/plan` + `/deploy/v1/commit`.
|
|
8
|
+
*
|
|
9
|
+
* The isomorphic surface keeps only the public read-only `getDeployment`
|
|
10
|
+
* call. `SiteFile` is preserved for `apps.bundleDeploy` (separate `/deploy/v1`
|
|
11
|
+
* endpoint, unaffected by the v1.32 cutover).
|
|
7
12
|
*/
|
|
8
13
|
export class Sites {
|
|
9
14
|
client;
|
|
10
15
|
constructor(client) {
|
|
11
16
|
this.client = client;
|
|
12
17
|
}
|
|
13
|
-
/**
|
|
14
|
-
* Deploy a static site. Payment flows through the configured fetch wrapper
|
|
15
|
-
* (x402 in Node when a tier purchase is required; typically free with an
|
|
16
|
-
* active tier).
|
|
17
|
-
*/
|
|
18
|
-
async deploy(projectId, opts) {
|
|
19
|
-
const body = { project: projectId, files: opts.files };
|
|
20
|
-
if (opts.target !== undefined)
|
|
21
|
-
body.target = opts.target;
|
|
22
|
-
if (opts.inherit)
|
|
23
|
-
body.inherit = true;
|
|
24
|
-
return this.client.request("/deployments/v1", {
|
|
25
|
-
method: "POST",
|
|
26
|
-
body,
|
|
27
|
-
context: "deploying site",
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
18
|
/** Get deployment metadata by id. Public — no project auth. */
|
|
31
19
|
async getDeployment(deploymentId) {
|
|
32
20
|
return this.client.request(`/deployments/v1/${deploymentId}`, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sites.js","sourceRoot":"","sources":["../../src/namespaces/sites.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"sites.js","sourceRoot":"","sources":["../../src/namespaces/sites.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA+BH,MAAM,OAAO,KAAK;IACa;IAA7B,YAA6B,MAAc;QAAd,WAAM,GAAN,MAAM,CAAQ;IAAG,CAAC;IAE/C,+DAA+D;IAC/D,KAAK,CAAC,aAAa,CAAC,YAAoB;QACtC,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAiB,mBAAmB,YAAY,EAAE,EAAE;YAC5E,OAAO,EAAE,qBAAqB;YAC9B,QAAQ,EAAE,KAAK;SAChB,CAAC,CAAC;IACL,CAAC;CACF"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 8785 (JCS) canonical JSON for the v1.32 deploy-plan manifest digest.
|
|
3
|
+
*
|
|
4
|
+
* MUST stay byte-for-byte identical to the gateway's
|
|
5
|
+
* `services/deploy-plans.ts:canonicalizeJson`. A digest mismatch breaks
|
|
6
|
+
* idempotency: the SDK's hash won't match the gateway's, so retrying a
|
|
7
|
+
* plan creates a NEW plan instead of finding the existing one.
|
|
8
|
+
*
|
|
9
|
+
* Spec for our value domain:
|
|
10
|
+
* - object keys sorted ASCII-ascending
|
|
11
|
+
* - arrays comma-separated, no whitespace
|
|
12
|
+
* - strings/numbers via JSON.stringify (matches RFC 8785 for ASCII paths,
|
|
13
|
+
* hex sha256, integer sizes, ASCII content_types)
|
|
14
|
+
*/
|
|
15
|
+
export interface ManifestEntry {
|
|
16
|
+
path: string;
|
|
17
|
+
sha256: string;
|
|
18
|
+
size: number;
|
|
19
|
+
content_type: string;
|
|
20
|
+
}
|
|
21
|
+
export interface Manifest {
|
|
22
|
+
files: ManifestEntry[];
|
|
23
|
+
}
|
|
24
|
+
export declare function canonicalizeJson(value: unknown): string;
|
|
25
|
+
/**
|
|
26
|
+
* Build the canonical manifest object the gateway hashes: `{ files: [...] }`
|
|
27
|
+
* with entries sorted by path and only the four expected keys per entry.
|
|
28
|
+
* `content_type` defaults to `"application/octet-stream"` when absent.
|
|
29
|
+
*/
|
|
30
|
+
export declare function buildCanonicalManifest(entries: Array<{
|
|
31
|
+
path: string;
|
|
32
|
+
sha256: string;
|
|
33
|
+
size: number;
|
|
34
|
+
content_type?: string;
|
|
35
|
+
}>): Manifest;
|
|
36
|
+
/**
|
|
37
|
+
* Compute the hex SHA-256 of the canonical JSON encoding of a manifest.
|
|
38
|
+
* Matches the gateway's `computeManifestDigest`.
|
|
39
|
+
*/
|
|
40
|
+
export declare function computeManifestDigest(manifest: Manifest): Promise<string>;
|
|
41
|
+
//# sourceMappingURL=canonicalize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonicalize.d.ts","sourceRoot":"","sources":["../../src/node/canonicalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,aAAa,EAAE,CAAC;CACxB;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAavD;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GACpF,QAAQ,CAUV;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAO/E"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 8785 (JCS) canonical JSON for the v1.32 deploy-plan manifest digest.
|
|
3
|
+
*
|
|
4
|
+
* MUST stay byte-for-byte identical to the gateway's
|
|
5
|
+
* `services/deploy-plans.ts:canonicalizeJson`. A digest mismatch breaks
|
|
6
|
+
* idempotency: the SDK's hash won't match the gateway's, so retrying a
|
|
7
|
+
* plan creates a NEW plan instead of finding the existing one.
|
|
8
|
+
*
|
|
9
|
+
* Spec for our value domain:
|
|
10
|
+
* - object keys sorted ASCII-ascending
|
|
11
|
+
* - arrays comma-separated, no whitespace
|
|
12
|
+
* - strings/numbers via JSON.stringify (matches RFC 8785 for ASCII paths,
|
|
13
|
+
* hex sha256, integer sizes, ASCII content_types)
|
|
14
|
+
*/
|
|
15
|
+
export function canonicalizeJson(value) {
|
|
16
|
+
if (value === null)
|
|
17
|
+
return "null";
|
|
18
|
+
if (typeof value === "boolean")
|
|
19
|
+
return value ? "true" : "false";
|
|
20
|
+
if (typeof value === "number")
|
|
21
|
+
return JSON.stringify(value);
|
|
22
|
+
if (typeof value === "string")
|
|
23
|
+
return JSON.stringify(value);
|
|
24
|
+
if (Array.isArray(value))
|
|
25
|
+
return "[" + value.map(canonicalizeJson).join(",") + "]";
|
|
26
|
+
if (typeof value === "object") {
|
|
27
|
+
const obj = value;
|
|
28
|
+
const keys = Object.keys(obj).sort();
|
|
29
|
+
const pairs = keys.map((k) => JSON.stringify(k) + ":" + canonicalizeJson(obj[k]));
|
|
30
|
+
return "{" + pairs.join(",") + "}";
|
|
31
|
+
}
|
|
32
|
+
throw new Error("canonicalizeJson: unsupported value type");
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Build the canonical manifest object the gateway hashes: `{ files: [...] }`
|
|
36
|
+
* with entries sorted by path and only the four expected keys per entry.
|
|
37
|
+
* `content_type` defaults to `"application/octet-stream"` when absent.
|
|
38
|
+
*/
|
|
39
|
+
export function buildCanonicalManifest(entries) {
|
|
40
|
+
const files = entries
|
|
41
|
+
.map((e) => ({
|
|
42
|
+
path: e.path,
|
|
43
|
+
sha256: e.sha256,
|
|
44
|
+
size: e.size,
|
|
45
|
+
content_type: e.content_type ?? "application/octet-stream",
|
|
46
|
+
}))
|
|
47
|
+
.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
48
|
+
return { files };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Compute the hex SHA-256 of the canonical JSON encoding of a manifest.
|
|
52
|
+
* Matches the gateway's `computeManifestDigest`.
|
|
53
|
+
*/
|
|
54
|
+
export async function computeManifestDigest(manifest) {
|
|
55
|
+
const canonical = canonicalizeJson(manifest);
|
|
56
|
+
const bytes = new TextEncoder().encode(canonical);
|
|
57
|
+
const hash = await crypto.subtle.digest("SHA-256", bytes);
|
|
58
|
+
return Array.from(new Uint8Array(hash))
|
|
59
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
60
|
+
.join("");
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=canonicalize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonicalize.js","sourceRoot":"","sources":["../../src/node/canonicalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAaH,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAClC,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;IAChE,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC5D,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC5D,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;IACnF,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,GAAG,GAAG,KAAgC,CAAC;QAC7C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAClF,OAAO,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;IACrC,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;AAC9D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CACpC,OAAqF;IAErF,MAAM,KAAK,GAAoB,OAAO;SACnC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACX,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,YAAY,EAAE,CAAC,CAAC,YAAY,IAAI,0BAA0B;KAC3D,CAAC,CAAC;SACF,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACpE,OAAO,EAAE,KAAK,EAAE,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,QAAkB;IAC5D,MAAM,SAAS,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAClD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,KAAgC,CAAC,CAAC;IACrF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;SACpC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;SAC3C,IAAI,CAAC,EAAE,CAAC,CAAC;AACd,CAAC"}
|
package/sdk/dist/node/index.d.ts
CHANGED
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
* const project = await r.projects.provision({ tier: "prototype" });
|
|
15
15
|
* await r.sites.deployDir({ project: project.project_id, dir: "./my-site" });
|
|
16
16
|
* ```
|
|
17
|
+
*
|
|
18
|
+
* `deployDir` uses the v1.32 plan/commit transport and only uploads the
|
|
19
|
+
* bytes the gateway doesn't already have. Re-deploying an unchanged tree
|
|
20
|
+
* issues no S3 PUTs.
|
|
17
21
|
*/
|
|
18
22
|
import { Run402 } from "../index.js";
|
|
19
23
|
import { NodeSites } from "./sites-node.js";
|
|
@@ -49,7 +53,7 @@ export type NodeRun402 = Omit<Run402, "sites"> & {
|
|
|
49
53
|
*/
|
|
50
54
|
export declare function run402(opts?: NodeRun402Options): NodeRun402;
|
|
51
55
|
export { NodeSites } from "./sites-node.js";
|
|
52
|
-
export type { DeployDirOptions } from "./sites-node.js";
|
|
56
|
+
export type { DeployDirOptions, DeployEvent } from "./sites-node.js";
|
|
53
57
|
export { NodeCredentialsProvider } from "./credentials.js";
|
|
54
58
|
export { setupPaidFetch, createLazyPaidFetch } from "./paid-fetch.js";
|
|
55
59
|
export { Run402, Run402Error, PaymentRequired, ProjectNotFound, Unauthorized, ApiError, NetworkError, LocalError, } from "../index.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,EAAE,MAAM,EAAsB,MAAM,aAAa,CAAC;AAIzD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,WAAW,iBAAiB;IAChC,yFAAyF;IACzF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2EAA2E;IAC3E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,mFAAmF;IACnF,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACjC;AAED,4EAA4E;AAC5E,MAAM,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,CAAC;AAEtE;;;;;;;;;;GAUG;AACH,wBAAgB,MAAM,CAAC,IAAI,GAAE,iBAAsB,GAAG,UAAU,CAqB/D;AAED,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACrE,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEtE,OAAO,EACL,MAAM,EACN,WAAW,EACX,eAAe,EACf,eAAe,EACf,YAAY,EACZ,QAAQ,EACR,YAAY,EACZ,UAAU,GACX,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,aAAa,EACb,mBAAmB,EACnB,WAAW,EACX,cAAc,EACd,MAAM,GACP,MAAM,aAAa,CAAC"}
|
package/sdk/dist/node/index.js
CHANGED
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
* const project = await r.projects.provision({ tier: "prototype" });
|
|
15
15
|
* await r.sites.deployDir({ project: project.project_id, dir: "./my-site" });
|
|
16
16
|
* ```
|
|
17
|
+
*
|
|
18
|
+
* `deployDir` uses the v1.32 plan/commit transport and only uploads the
|
|
19
|
+
* bytes the gateway doesn't already have. Re-deploying an unchanged tree
|
|
20
|
+
* issues no S3 PUTs.
|
|
17
21
|
*/
|
|
18
22
|
import { getApiBase } from "../../core-dist/config.js";
|
|
19
23
|
import { Run402 } from "../index.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAE,MAAM,EAAsB,MAAM,aAAa,CAAC;AAEzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAqB5C;;;;;;;;;;GAUG;AACH,MAAM,UAAU,MAAM,CAAC,OAA0B,EAAE;IACjD,MAAM,OAAO,GAAkB;QAC7B,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,UAAU,EAAE;QACrC,WAAW,EAAE,IAAI,uBAAuB,CAAC;YACvC,aAAa,EAAE,IAAI,CAAC,aAAa;YACjC,YAAY,EAAE,IAAI,CAAC,YAAY;SAChC,CAAC;QACF,KAAK,EACH,IAAI,CAAC,KAAK;YACV,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,mBAAmB,EAAE,CAAC;KACtF,CAAC;IACF,MAAM,IAAI,GAAG,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC;IAEjC,yEAAyE;IACzE,0EAA0E;IAC1E,4EAA4E;IAC5E,2EAA2E;IAC3E,MAAM,MAAM,GAAI,IAAI,CAAC,KAAuC,CAAC,MAAM,CAAC;IACnE,IAAwC,CAAC,KAAK,GAAG,IAAI,SAAS,CAAC,MAAM,CAAC,CAAC;IAExE,OAAO,IAA6B,CAAC;AACvC,CAAC;AAED,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtE,6EAA6E;AAC7E,OAAO,EACL,MAAM,EACN,WAAW,EACX,eAAe,EACf,eAAe,EACf,YAAY,EACZ,QAAQ,EACR,YAAY,EACZ,UAAU,GACX,MAAM,aAAa,CAAC"}
|
|
@@ -1,23 +1,123 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Node-only augmentation of the `sites` namespace.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* `deployDir(dir)` walks a directory, computes per-file SHA-256 + size,
|
|
5
|
+
* builds a canonical manifest, and ships it via the v1.32 plan/commit
|
|
6
|
+
* transport:
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* 1. POST /deploy/v1/plan { manifest_digest, manifest } — returns per-file
|
|
9
|
+
* state (`present`, `satisfied_by_plan`, or `missing` + presigned URLs).
|
|
10
|
+
* 2. PUT each `missing` file's bytes to its presigned S3 URL(s),
|
|
11
|
+
* sending `x-amz-checksum-sha256` to satisfy the signed checksum
|
|
12
|
+
* algorithm. Single-PUT covers small files; multipart covers large
|
|
13
|
+
* files (the gateway picks per-part sizing).
|
|
14
|
+
* 3. POST /deploy/v1/commit { plan_id } — Stage 1 (DB) returns
|
|
15
|
+
* synchronously with `applied`, `noop`, `copying`, or `failed`.
|
|
16
|
+
* `copying` triggers a poll loop on `GET /deployments/v1/:id` until
|
|
17
|
+
* `ready` (or `failed`).
|
|
18
|
+
*
|
|
19
|
+
* The canonicalize used to compute `manifest_digest` MUST match the
|
|
20
|
+
* gateway's byte-for-byte (see `./canonicalize.ts`).
|
|
21
|
+
*
|
|
22
|
+
* Imports `node:fs/promises` and so cannot run in a V8 isolate. Wired into
|
|
23
|
+
* the SDK via the `@run402/sdk/node` entry point only.
|
|
10
24
|
*/
|
|
11
|
-
import { Sites, type
|
|
25
|
+
import { Sites, type SiteDeployResult } from "../namespaces/sites.js";
|
|
26
|
+
import { type ManifestEntry } from "./canonicalize.js";
|
|
12
27
|
export interface DeployDirOptions {
|
|
13
28
|
/** Project ID the deployment is linked to. */
|
|
14
29
|
project: string;
|
|
15
30
|
/** Local directory to walk. Paths in the manifest are relative to this root. */
|
|
16
31
|
dir: string;
|
|
17
|
-
/** When true, unchanged files are copied from the previous deployment server-side. */
|
|
18
|
-
inherit?: boolean;
|
|
19
32
|
/** Deployment target label, e.g. `"production"`. */
|
|
20
33
|
target?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Optional progress callback. Invoked synchronously at four phases of the
|
|
36
|
+
* deploy. Errors thrown from the callback are caught and dropped — a buggy
|
|
37
|
+
* consumer can't abort the deploy.
|
|
38
|
+
*/
|
|
39
|
+
onEvent?: (event: DeployEvent) => void;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Progress event emitted by {@link NodeSites.deployDir} when the caller
|
|
43
|
+
* supplies an `onEvent` callback. A discriminated union keyed by `phase`.
|
|
44
|
+
*/
|
|
45
|
+
export type DeployEvent =
|
|
46
|
+
/** `POST /deploy/v1/plan` returned. Fires once per deploy. */
|
|
47
|
+
{
|
|
48
|
+
phase: "plan";
|
|
49
|
+
manifest_size: number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* One file's bytes were successfully PUT to S3. Fires once per `missing`
|
|
53
|
+
* entry in the plan response — files reported as `present` or
|
|
54
|
+
* `satisfied_by_plan` do not trigger an upload event.
|
|
55
|
+
*/
|
|
56
|
+
| {
|
|
57
|
+
phase: "upload";
|
|
58
|
+
file: string;
|
|
59
|
+
sha256: string;
|
|
60
|
+
done: number;
|
|
61
|
+
total: number;
|
|
62
|
+
}
|
|
63
|
+
/** About to call `POST /deploy/v1/commit`. Fires once per deploy. */
|
|
64
|
+
| {
|
|
65
|
+
phase: "commit";
|
|
66
|
+
}
|
|
67
|
+
/** Stage-2 copy poll tick (`GET /deployments/v1/:id`). Fires per iteration when commit returned `copying`. */
|
|
68
|
+
| {
|
|
69
|
+
phase: "poll";
|
|
70
|
+
status: string;
|
|
71
|
+
elapsed_ms: number;
|
|
72
|
+
};
|
|
73
|
+
/** One walked file plus everything we need to hash, plan, and upload it. */
|
|
74
|
+
interface WalkedFile {
|
|
75
|
+
path: string;
|
|
76
|
+
size: number;
|
|
77
|
+
sha256: string;
|
|
78
|
+
content_type: string;
|
|
79
|
+
bytes: Buffer;
|
|
80
|
+
}
|
|
81
|
+
interface PlanFilePresent {
|
|
82
|
+
sha256: string;
|
|
83
|
+
present: true;
|
|
84
|
+
size: number;
|
|
85
|
+
content_type: string;
|
|
86
|
+
}
|
|
87
|
+
interface PlanFileSatisfied {
|
|
88
|
+
sha256: string;
|
|
89
|
+
satisfied_by_plan: true;
|
|
90
|
+
size: number;
|
|
91
|
+
content_type: string;
|
|
92
|
+
}
|
|
93
|
+
interface PlanFileMissing {
|
|
94
|
+
sha256: string;
|
|
95
|
+
missing: true;
|
|
96
|
+
upload_id: string;
|
|
97
|
+
mode: "single" | "multipart";
|
|
98
|
+
key: string;
|
|
99
|
+
staging_key: string;
|
|
100
|
+
part_size_bytes: number;
|
|
101
|
+
part_count: number;
|
|
102
|
+
parts: Array<{
|
|
103
|
+
part_number: number;
|
|
104
|
+
url: string;
|
|
105
|
+
byte_start: number;
|
|
106
|
+
byte_end: number;
|
|
107
|
+
}>;
|
|
108
|
+
expires_at: string;
|
|
109
|
+
}
|
|
110
|
+
type PlanFileResponse = PlanFilePresent | PlanFileSatisfied | PlanFileMissing;
|
|
111
|
+
interface PlanResponse {
|
|
112
|
+
plan_id: string;
|
|
113
|
+
files: PlanFileResponse[];
|
|
114
|
+
}
|
|
115
|
+
interface CommitResponse {
|
|
116
|
+
deployment_id: string;
|
|
117
|
+
url: string;
|
|
118
|
+
status: "applied" | "noop" | "copying" | "failed";
|
|
119
|
+
bytes_total?: number;
|
|
120
|
+
bytes_uploaded?: number;
|
|
21
121
|
}
|
|
22
122
|
/**
|
|
23
123
|
* Sites namespace enriched with the Node-only `deployDir` convenience.
|
|
@@ -25,23 +125,23 @@ export interface DeployDirOptions {
|
|
|
25
125
|
*/
|
|
26
126
|
export declare class NodeSites extends Sites {
|
|
27
127
|
/**
|
|
28
|
-
* Deploy every file under `dir` as a static site.
|
|
29
|
-
*
|
|
30
|
-
*
|
|
128
|
+
* Deploy every file under `dir` as a static site. Walks the tree, hashes
|
|
129
|
+
* each file, plans the deploy with the gateway (which dedupes against
|
|
130
|
+
* already-uploaded content), uploads only the missing bytes, then
|
|
131
|
+
* commits.
|
|
31
132
|
*
|
|
32
133
|
* Files named `.git`, `node_modules`, or `.DS_Store` are skipped at every
|
|
33
134
|
* depth. Symlinks cause a {@link LocalError} — they are not followed.
|
|
34
135
|
*/
|
|
35
136
|
deployDir(opts: DeployDirOptions): Promise<SiteDeployResult>;
|
|
36
137
|
}
|
|
37
|
-
/**
|
|
38
|
-
* Walk `root` and return a `SiteFile[]` with POSIX-style relative paths.
|
|
39
|
-
* Exported for tests; not part of the public SDK API.
|
|
40
|
-
*/
|
|
41
|
-
export declare function collectSiteFiles(root: string): Promise<SiteFile[]>;
|
|
42
138
|
/**
|
|
43
139
|
* Normalize a relative path to POSIX forward slashes. Exposed for tests;
|
|
44
140
|
* not part of the public SDK API.
|
|
45
141
|
*/
|
|
46
142
|
export declare function normalizeRelPath(rel: string): string;
|
|
143
|
+
/** @internal — exposed for tests, not part of the public SDK API. */
|
|
144
|
+
export declare function _collectFilesForTest(root: string): Promise<WalkedFile[]>;
|
|
145
|
+
/** @internal — exposed for tests, not part of the public SDK API. */
|
|
146
|
+
export type { WalkedFile, PlanResponse, CommitResponse, ManifestEntry };
|
|
47
147
|
//# sourceMappingURL=sites-node.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sites-node.d.ts","sourceRoot":"","sources":["../../src/node/sites-node.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"sites-node.d.ts","sourceRoot":"","sources":["../../src/node/sites-node.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,49 +1,260 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Node-only augmentation of the `sites` namespace.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* `deployDir(dir)` walks a directory, computes per-file SHA-256 + size,
|
|
5
|
+
* builds a canonical manifest, and ships it via the v1.32 plan/commit
|
|
6
|
+
* transport:
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* 1. POST /deploy/v1/plan { manifest_digest, manifest } — returns per-file
|
|
9
|
+
* state (`present`, `satisfied_by_plan`, or `missing` + presigned URLs).
|
|
10
|
+
* 2. PUT each `missing` file's bytes to its presigned S3 URL(s),
|
|
11
|
+
* sending `x-amz-checksum-sha256` to satisfy the signed checksum
|
|
12
|
+
* algorithm. Single-PUT covers small files; multipart covers large
|
|
13
|
+
* files (the gateway picks per-part sizing).
|
|
14
|
+
* 3. POST /deploy/v1/commit { plan_id } — Stage 1 (DB) returns
|
|
15
|
+
* synchronously with `applied`, `noop`, `copying`, or `failed`.
|
|
16
|
+
* `copying` triggers a poll loop on `GET /deployments/v1/:id` until
|
|
17
|
+
* `ready` (or `failed`).
|
|
18
|
+
*
|
|
19
|
+
* The canonicalize used to compute `manifest_digest` MUST match the
|
|
20
|
+
* gateway's byte-for-byte (see `./canonicalize.ts`).
|
|
21
|
+
*
|
|
22
|
+
* Imports `node:fs/promises` and so cannot run in a V8 isolate. Wired into
|
|
23
|
+
* the SDK via the `@run402/sdk/node` entry point only.
|
|
10
24
|
*/
|
|
11
25
|
import { readdir, readFile, lstat } from "node:fs/promises";
|
|
12
26
|
import { join, relative, sep } from "node:path";
|
|
13
27
|
import { Sites } from "../namespaces/sites.js";
|
|
14
|
-
import { LocalError } from "../errors.js";
|
|
28
|
+
import { ApiError, LocalError, Run402Error } from "../errors.js";
|
|
29
|
+
import { buildCanonicalManifest, computeManifestDigest, } from "./canonicalize.js";
|
|
15
30
|
const DEFAULT_IGNORE = new Set([".git", "node_modules", ".DS_Store"]);
|
|
16
31
|
const CONTEXT = "deploying directory";
|
|
32
|
+
/** Cap on total time spent waiting for Stage 2 copy to drain. */
|
|
33
|
+
const COPY_POLL_TIMEOUT_MS = 10 * 60 * 1000;
|
|
34
|
+
/** Initial poll interval; we hold this for ~30 s, then back off. */
|
|
35
|
+
const COPY_POLL_INITIAL_MS = 1_000;
|
|
36
|
+
const COPY_POLL_MAX_MS = 30_000;
|
|
37
|
+
/** Time after which we re-call /deploy/v1/plan to refresh presigned URLs (1 h TTL). */
|
|
38
|
+
const URL_REFRESH_AT_MS = 50 * 60 * 1000;
|
|
17
39
|
/**
|
|
18
40
|
* Sites namespace enriched with the Node-only `deployDir` convenience.
|
|
19
41
|
* All existing `Sites` methods are inherited unchanged.
|
|
20
42
|
*/
|
|
21
43
|
export class NodeSites extends Sites {
|
|
22
44
|
/**
|
|
23
|
-
* Deploy every file under `dir` as a static site.
|
|
24
|
-
*
|
|
25
|
-
*
|
|
45
|
+
* Deploy every file under `dir` as a static site. Walks the tree, hashes
|
|
46
|
+
* each file, plans the deploy with the gateway (which dedupes against
|
|
47
|
+
* already-uploaded content), uploads only the missing bytes, then
|
|
48
|
+
* commits.
|
|
26
49
|
*
|
|
27
50
|
* Files named `.git`, `node_modules`, or `.DS_Store` are skipped at every
|
|
28
51
|
* depth. Symlinks cause a {@link LocalError} — they are not followed.
|
|
29
52
|
*/
|
|
30
53
|
async deployDir(opts) {
|
|
31
|
-
const
|
|
54
|
+
const emit = (event) => {
|
|
55
|
+
if (!opts.onEvent)
|
|
56
|
+
return;
|
|
57
|
+
try {
|
|
58
|
+
opts.onEvent(event);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Swallow — a buggy consumer must not abort a deploy in progress.
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const files = await collectFiles(opts.dir);
|
|
32
65
|
if (files.length === 0) {
|
|
33
66
|
throw new LocalError(`directory ${opts.dir} contains no deployable files`, CONTEXT);
|
|
34
67
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
68
|
+
const manifest = buildCanonicalManifest(files.map((f) => ({
|
|
69
|
+
path: f.path,
|
|
70
|
+
sha256: f.sha256,
|
|
71
|
+
size: f.size,
|
|
72
|
+
content_type: f.content_type,
|
|
73
|
+
})));
|
|
74
|
+
const manifestDigest = await computeManifestDigest(manifest);
|
|
75
|
+
// Map sha → bytes so we can satisfy the plan response without re-walking.
|
|
76
|
+
// Multiple paths may share the same sha (identical files); any copy works.
|
|
77
|
+
const bytesBySha = new Map();
|
|
78
|
+
// Map sha → first manifest path with that content, so upload events can
|
|
79
|
+
// report a human-readable file path even when multiple paths dedupe.
|
|
80
|
+
const pathBySha = new Map();
|
|
81
|
+
for (const f of files) {
|
|
82
|
+
bytesBySha.set(f.sha256, f.bytes);
|
|
83
|
+
if (!pathBySha.has(f.sha256))
|
|
84
|
+
pathBySha.set(f.sha256, f.path);
|
|
85
|
+
}
|
|
86
|
+
// Reach through to the kernel client. `Sites.client` is `private` in TS
|
|
87
|
+
// but enumerable at runtime; the cast bypasses the visibility check.
|
|
88
|
+
const client = this.client;
|
|
89
|
+
const planClient = new ClientFromBase(client);
|
|
90
|
+
const plan = await planClient.requestPlan(opts.project, manifest, manifestDigest);
|
|
91
|
+
const planAt = Date.now();
|
|
92
|
+
emit({ phase: "plan", manifest_size: manifest.files.length });
|
|
93
|
+
const totalMissing = plan.files.filter(isMissing).length;
|
|
94
|
+
let doneCounter = 0;
|
|
95
|
+
let activePlan = plan;
|
|
96
|
+
let activePlanAt = planAt;
|
|
97
|
+
for (const entry of activePlan.files) {
|
|
98
|
+
if (!isMissing(entry))
|
|
99
|
+
continue;
|
|
100
|
+
// Refresh the plan if the URL TTL window is about to close.
|
|
101
|
+
if (Date.now() - activePlanAt > URL_REFRESH_AT_MS) {
|
|
102
|
+
activePlan = await planClient.requestPlan(opts.project, manifest, manifestDigest);
|
|
103
|
+
activePlanAt = Date.now();
|
|
104
|
+
}
|
|
105
|
+
const refreshed = activePlan.files.find((e) => e.sha256 === entry.sha256);
|
|
106
|
+
const target = refreshed && isMissing(refreshed) ? refreshed : entry;
|
|
107
|
+
const bytes = bytesBySha.get(target.sha256);
|
|
108
|
+
if (!bytes) {
|
|
109
|
+
throw new LocalError(`internal: no local bytes for sha ${target.sha256.slice(0, 12)}…`, CONTEXT);
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
await uploadOne(client.fetch, target, bytes);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
// S3 returns 403 when the presigned URL has expired. Refresh once.
|
|
116
|
+
if (err instanceof ApiError && err.status === 403) {
|
|
117
|
+
activePlan = await planClient.requestPlan(opts.project, manifest, manifestDigest);
|
|
118
|
+
activePlanAt = Date.now();
|
|
119
|
+
const fresh = activePlan.files.find((e) => e.sha256 === target.sha256);
|
|
120
|
+
if (fresh && isMissing(fresh)) {
|
|
121
|
+
await uploadOne(client.fetch, fresh, bytes);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
throw err;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
doneCounter += 1;
|
|
132
|
+
emit({
|
|
133
|
+
phase: "upload",
|
|
134
|
+
file: pathBySha.get(target.sha256) ?? target.sha256,
|
|
135
|
+
sha256: target.sha256,
|
|
136
|
+
done: doneCounter,
|
|
137
|
+
total: totalMissing,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
emit({ phase: "commit" });
|
|
141
|
+
const commit = await planClient.commit(opts.project, activePlan.plan_id);
|
|
142
|
+
if (commit.status === "applied" || commit.status === "noop") {
|
|
143
|
+
return shapeResult(commit);
|
|
144
|
+
}
|
|
145
|
+
if (commit.status === "copying") {
|
|
146
|
+
const final = await pollUntilReady(this, commit.deployment_id, emit);
|
|
147
|
+
return { deployment_id: commit.deployment_id, url: commit.url, ...final };
|
|
148
|
+
}
|
|
149
|
+
// status === "failed": stage 2 exhausted retries. Surface as ApiError so
|
|
150
|
+
// callers can decide whether to re-call deployDir (which re-commits).
|
|
151
|
+
throw new ApiError(`Deploy commit failed for plan ${activePlan.plan_id} after copy retries`, 500, commit, "committing deploy");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
class ClientFromBase {
|
|
155
|
+
client;
|
|
156
|
+
constructor(client) {
|
|
157
|
+
this.client = client;
|
|
158
|
+
}
|
|
159
|
+
async requestPlan(projectId, manifest, manifestDigest) {
|
|
160
|
+
return this.client.request("/deploy/v1/plan", {
|
|
161
|
+
method: "POST",
|
|
162
|
+
body: { project: projectId, manifest_digest: manifestDigest, manifest },
|
|
163
|
+
context: "planning deploy",
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
async commit(projectId, planId) {
|
|
167
|
+
return this.client.request("/deploy/v1/commit", {
|
|
168
|
+
method: "POST",
|
|
169
|
+
body: { project: projectId, plan_id: planId },
|
|
170
|
+
context: "committing deploy",
|
|
39
171
|
});
|
|
40
172
|
}
|
|
41
173
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
174
|
+
function isMissing(entry) {
|
|
175
|
+
return entry.missing === true;
|
|
176
|
+
}
|
|
177
|
+
function shapeResult(commit) {
|
|
178
|
+
const out = { deployment_id: commit.deployment_id, url: commit.url };
|
|
179
|
+
if (commit.bytes_total !== undefined)
|
|
180
|
+
out.bytes_total = commit.bytes_total;
|
|
181
|
+
if (commit.bytes_uploaded !== undefined)
|
|
182
|
+
out.bytes_uploaded = commit.bytes_uploaded;
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
// ─── Upload ──────────────────────────────────────────────────────────────────
|
|
186
|
+
async function uploadOne(fetchFn, entry, bytes) {
|
|
187
|
+
if (entry.mode === "single") {
|
|
188
|
+
if (entry.parts.length !== 1) {
|
|
189
|
+
throw new LocalError(`internal: single-mode upload for ${entry.sha256.slice(0, 12)}… returned ${entry.parts.length} parts`, CONTEXT);
|
|
190
|
+
}
|
|
191
|
+
const part = entry.parts[0];
|
|
192
|
+
const slice = bytes.subarray(part.byte_start, part.byte_end + 1);
|
|
193
|
+
// For single-PUT, the URL pre-commits the whole-object SHA — we send
|
|
194
|
+
// the same value (in base64, not hex) on the PUT.
|
|
195
|
+
const checksum = base64FromHex(entry.sha256);
|
|
196
|
+
await putToS3(fetchFn, part.url, slice, checksum, part.part_number);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
// multipart: each part PUT carries its own per-part SHA-256 base64.
|
|
200
|
+
for (const part of entry.parts) {
|
|
201
|
+
const slice = bytes.subarray(part.byte_start, part.byte_end + 1);
|
|
202
|
+
const checksum = await sha256Base64(slice);
|
|
203
|
+
await putToS3(fetchFn, part.url, slice, checksum, part.part_number);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
async function putToS3(fetchFn, url, body, checksumBase64, partNumber) {
|
|
207
|
+
let res;
|
|
208
|
+
try {
|
|
209
|
+
res = await fetchFn(url, {
|
|
210
|
+
method: "PUT",
|
|
211
|
+
headers: { "x-amz-checksum-sha256": checksumBase64 },
|
|
212
|
+
body: body,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
throw new ApiError(`S3 PUT failed for part ${partNumber}: ${err.message}`, 0, null, "uploading deploy bytes");
|
|
217
|
+
}
|
|
218
|
+
if (!res.ok) {
|
|
219
|
+
const text = await res.text().catch(() => "");
|
|
220
|
+
throw new ApiError(`S3 PUT failed for part ${partNumber} (HTTP ${res.status})${text ? ": " + text.slice(0, 200) : ""}`, res.status, text, "uploading deploy bytes");
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
async function pollUntilReady(sites, deploymentId, emit) {
|
|
224
|
+
const start = Date.now();
|
|
225
|
+
let interval = COPY_POLL_INITIAL_MS;
|
|
226
|
+
while (Date.now() - start < COPY_POLL_TIMEOUT_MS) {
|
|
227
|
+
await sleep(interval);
|
|
228
|
+
let info;
|
|
229
|
+
try {
|
|
230
|
+
info = await sites.getDeployment(deploymentId);
|
|
231
|
+
}
|
|
232
|
+
catch (err) {
|
|
233
|
+
// Transient lookup failure: keep polling unless we're a hard error.
|
|
234
|
+
if (err instanceof Run402Error && err.status !== null && err.status >= 500) {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
throw err;
|
|
238
|
+
}
|
|
239
|
+
emit({ phase: "poll", status: info.status, elapsed_ms: Date.now() - start });
|
|
240
|
+
if (info.status === "ready" || info.status === "applied") {
|
|
241
|
+
return { status: info.status, url: info.url };
|
|
242
|
+
}
|
|
243
|
+
if (info.status === "failed") {
|
|
244
|
+
throw new ApiError(`Deployment ${deploymentId} entered failed state during copy`, 500, info, "polling deploy");
|
|
245
|
+
}
|
|
246
|
+
// Hold the initial cadence for the first 30 s, then back off to a max.
|
|
247
|
+
if (Date.now() - start > 30_000) {
|
|
248
|
+
interval = Math.min(Math.floor(interval * 1.5), COPY_POLL_MAX_MS);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
throw new ApiError(`Timed out waiting for deployment ${deploymentId} to reach ready (${COPY_POLL_TIMEOUT_MS / 60_000} min)`, 504, null, "polling deploy");
|
|
252
|
+
}
|
|
253
|
+
function sleep(ms) {
|
|
254
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
255
|
+
}
|
|
256
|
+
// ─── Filesystem walk + hashing ───────────────────────────────────────────────
|
|
257
|
+
async function collectFiles(root) {
|
|
47
258
|
let rootStat;
|
|
48
259
|
try {
|
|
49
260
|
rootStat = await lstat(root);
|
|
@@ -89,7 +300,13 @@ async function walkInto(root, current, out) {
|
|
|
89
300
|
throw new LocalError(`cannot read file ${fullPath}: ${err.message}`, CONTEXT, err);
|
|
90
301
|
}
|
|
91
302
|
const rel = normalizeRelPath(relative(root, fullPath));
|
|
92
|
-
out.push(
|
|
303
|
+
out.push({
|
|
304
|
+
path: rel,
|
|
305
|
+
size: bytes.byteLength,
|
|
306
|
+
sha256: await sha256Hex(bytes),
|
|
307
|
+
content_type: guessContentType(rel),
|
|
308
|
+
bytes,
|
|
309
|
+
});
|
|
93
310
|
}
|
|
94
311
|
}
|
|
95
312
|
}
|
|
@@ -100,13 +317,60 @@ async function walkInto(root, current, out) {
|
|
|
100
317
|
export function normalizeRelPath(rel) {
|
|
101
318
|
return sep === "/" ? rel : rel.split(sep).join("/");
|
|
102
319
|
}
|
|
103
|
-
function
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
320
|
+
async function sha256Hex(bytes) {
|
|
321
|
+
const hash = await crypto.subtle.digest("SHA-256", bytes);
|
|
322
|
+
return Array.from(new Uint8Array(hash))
|
|
323
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
324
|
+
.join("");
|
|
325
|
+
}
|
|
326
|
+
async function sha256Base64(bytes) {
|
|
327
|
+
const hash = await crypto.subtle.digest("SHA-256", bytes);
|
|
328
|
+
return Buffer.from(hash).toString("base64");
|
|
329
|
+
}
|
|
330
|
+
function base64FromHex(hex) {
|
|
331
|
+
if (!/^[0-9a-f]*$/i.test(hex) || hex.length % 2 !== 0) {
|
|
332
|
+
throw new LocalError(`invalid hex sha256: ${hex}`, CONTEXT);
|
|
110
333
|
}
|
|
334
|
+
return Buffer.from(hex, "hex").toString("base64");
|
|
335
|
+
}
|
|
336
|
+
const CONTENT_TYPES = {
|
|
337
|
+
html: "text/html; charset=utf-8",
|
|
338
|
+
htm: "text/html; charset=utf-8",
|
|
339
|
+
css: "text/css; charset=utf-8",
|
|
340
|
+
js: "application/javascript; charset=utf-8",
|
|
341
|
+
mjs: "application/javascript; charset=utf-8",
|
|
342
|
+
json: "application/json; charset=utf-8",
|
|
343
|
+
svg: "image/svg+xml",
|
|
344
|
+
png: "image/png",
|
|
345
|
+
jpg: "image/jpeg",
|
|
346
|
+
jpeg: "image/jpeg",
|
|
347
|
+
gif: "image/gif",
|
|
348
|
+
webp: "image/webp",
|
|
349
|
+
ico: "image/x-icon",
|
|
350
|
+
woff: "font/woff",
|
|
351
|
+
woff2: "font/woff2",
|
|
352
|
+
ttf: "font/ttf",
|
|
353
|
+
eot: "application/vnd.ms-fontobject",
|
|
354
|
+
otf: "font/otf",
|
|
355
|
+
txt: "text/plain; charset=utf-8",
|
|
356
|
+
md: "text/markdown; charset=utf-8",
|
|
357
|
+
xml: "application/xml",
|
|
358
|
+
pdf: "application/pdf",
|
|
359
|
+
wasm: "application/wasm",
|
|
360
|
+
map: "application/json; charset=utf-8",
|
|
361
|
+
};
|
|
362
|
+
function guessContentType(path) {
|
|
363
|
+
const dot = path.lastIndexOf(".");
|
|
364
|
+
if (dot === -1)
|
|
365
|
+
return "application/octet-stream";
|
|
366
|
+
const ext = path.slice(dot + 1).toLowerCase();
|
|
367
|
+
return CONTENT_TYPES[ext] ?? "application/octet-stream";
|
|
368
|
+
}
|
|
369
|
+
// ─── Test-only exports ───────────────────────────────────────────────────────
|
|
370
|
+
// Kept exported so the unit tests can drive the walk + hash logic without
|
|
371
|
+
// having to spin up an HTTP mock for every concern.
|
|
372
|
+
/** @internal — exposed for tests, not part of the public SDK API. */
|
|
373
|
+
export async function _collectFilesForTest(root) {
|
|
374
|
+
return collectFiles(root);
|
|
111
375
|
}
|
|
112
376
|
//# sourceMappingURL=sites-node.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sites-node.js","sourceRoot":"","sources":["../../src/node/sites-node.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sites-node.js","sourceRoot":"","sources":["../../src/node/sites-node.ts"],"names":[],"mappings":"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"}
|