@rubytech/create-maxy-code 0.1.490 → 0.1.492
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/package.json +1 -1
- package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js +46 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/__tests__/pages-output-dir.test.d.ts +2 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/pages-output-dir.test.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/pages-output-dir.test.js +135 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/pages-output-dir.test.js.map +1 -0
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts +7 -1
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/cf-exec.js +26 -14
- package/payload/platform/lib/storage-broker/dist/cf-exec.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/pages-output-dir.d.ts +19 -0
- package/payload/platform/lib/storage-broker/dist/pages-output-dir.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/pages-output-dir.js +185 -0
- package/payload/platform/lib/storage-broker/dist/pages-output-dir.js.map +1 -0
- package/payload/platform/lib/storage-broker/src/__tests__/cf-exec.test.ts +65 -0
- package/payload/platform/lib/storage-broker/src/__tests__/pages-output-dir.test.ts +167 -0
- package/payload/platform/lib/storage-broker/src/cf-exec.ts +40 -16
- package/payload/platform/lib/storage-broker/src/pages-output-dir.ts +194 -0
- package/payload/platform/plugins/business-assistant/skills/e-sign/SKILL.md +30 -8
- package/payload/platform/plugins/cloudflare/mcp/__tests__/template-config.test.ts +8 -4
- package/payload/platform/plugins/cloudflare/references/hosting-sites.md +1 -1
- package/payload/platform/plugins/cloudflare/skills/data-portal/SKILL.md +4 -4
- package/payload/server/{chunk-KZFE3MOY.js → chunk-RPUTKSAJ.js} +134 -16
- package/payload/server/server.js +5 -5
- package/payload/server/{src-UYDFMWF4.js → src-JN6PIAJN.js} +1 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const WRANGLER_CONFIG_NAMES: readonly ["wrangler.json", "wrangler.jsonc", "wrangler.toml"];
|
|
2
|
+
export type UploadDirSource = (typeof WRANGLER_CONFIG_NAMES)[number] | "site-root";
|
|
3
|
+
export type UploadDirResolution = {
|
|
4
|
+
/** Absolute path to stage and hand wrangler as the asset root. */
|
|
5
|
+
uploadDir: string;
|
|
6
|
+
/** Which input decided it, so the caller logs the reason and not the value alone. */
|
|
7
|
+
source: UploadDirSource;
|
|
8
|
+
/** The verbatim declared value, present only when a config decided it. */
|
|
9
|
+
declared?: string;
|
|
10
|
+
};
|
|
11
|
+
/** Resolves the directory whose contents become the served root for `dir`.
|
|
12
|
+
*
|
|
13
|
+
* A tree that declares nothing keeps the site root, which is what every deploy
|
|
14
|
+
* did before Task 1917. A tree that declares an output dir gets that dir. A
|
|
15
|
+
* tree that declares one which cannot be resolved is an error naming the path:
|
|
16
|
+
* falling back to the site root here is exactly the defect this closes, because
|
|
17
|
+
* the deploy then succeeds while serving the wrong root. */
|
|
18
|
+
export declare function resolveUploadDir(dir: string): Promise<UploadDirResolution>;
|
|
19
|
+
//# sourceMappingURL=pages-output-dir.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pages-output-dir.d.ts","sourceRoot":"","sources":["../src/pages-output-dir.ts"],"names":[],"mappings":"AAqBA,eAAO,MAAM,qBAAqB,+DAIxB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,GAAG,WAAW,CAAC;AAEnF,MAAM,MAAM,mBAAmB,GAAG;IAChC,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;IAClB,qFAAqF;IACrF,MAAM,EAAE,eAAe,CAAC;IACxB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AA0FF;;;;;;6DAM6D;AAC7D,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA4DhF"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Where a Pages deploy's assets actually start. Task 1917 — `wrangler pages
|
|
3
|
+
// deploy <dir>` treats the positional argument as the asset root and overrides
|
|
4
|
+
// `pages_build_output_dir`, so handing it the site root publishes a declared
|
|
5
|
+
// output dir one level too deep and the served root 404s. Measured on glsmith
|
|
6
|
+
// 2026-07-22: two trees with identical shape and config, one deployed by house
|
|
7
|
+
// wrangler (document at the root, 200) and one by this broker (document
|
|
8
|
+
// reachable only under /public/, root 404).
|
|
9
|
+
//
|
|
10
|
+
// Only the one key is interpreted, in any of the three config formats. This is
|
|
11
|
+
// deliberately not a wrangler config parser: everything else in the file is
|
|
12
|
+
// wrangler's business and stays wrangler's business.
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.WRANGLER_CONFIG_NAMES = void 0;
|
|
15
|
+
exports.resolveUploadDir = resolveUploadDir;
|
|
16
|
+
const promises_1 = require("node:fs/promises");
|
|
17
|
+
const node_path_1 = require("node:path");
|
|
18
|
+
// The filenames `wrangler pages deploy` discovers as its own configuration, in
|
|
19
|
+
// wrangler's own precedence order (wrangler 4.112.0, wrangler-dist/cli.js:3189
|
|
20
|
+
// — json, then jsonc, then toml). The order is load-bearing twice: it decides
|
|
21
|
+
// which config declares the output dir here, and Task 1782 drops all three from
|
|
22
|
+
// the upload so a config is never served publicly at /wrangler.toml, where it
|
|
23
|
+
// would disclose the D1 database id and R2 bucket name.
|
|
24
|
+
exports.WRANGLER_CONFIG_NAMES = [
|
|
25
|
+
"wrangler.json",
|
|
26
|
+
"wrangler.jsonc",
|
|
27
|
+
"wrangler.toml",
|
|
28
|
+
];
|
|
29
|
+
const KEY = "pages_build_output_dir";
|
|
30
|
+
/** Strips line comments and block comments that sit outside string literals.
|
|
31
|
+
* JSON has no comments and jsonc does, so a jsonc file is de-commented before
|
|
32
|
+
* JSON.parse. Scanning character-wise rather than by regex is what keeps a
|
|
33
|
+
* `//` inside a value (a path like "a//b") from being eaten as a comment. */
|
|
34
|
+
function stripJsonComments(text) {
|
|
35
|
+
let out = "";
|
|
36
|
+
let inString = false;
|
|
37
|
+
let escaped = false;
|
|
38
|
+
for (let i = 0; i < text.length; i++) {
|
|
39
|
+
const c = text[i];
|
|
40
|
+
if (inString) {
|
|
41
|
+
out += c;
|
|
42
|
+
if (escaped)
|
|
43
|
+
escaped = false;
|
|
44
|
+
else if (c === "\\")
|
|
45
|
+
escaped = true;
|
|
46
|
+
else if (c === '"')
|
|
47
|
+
inString = false;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (c === '"') {
|
|
51
|
+
inString = true;
|
|
52
|
+
out += c;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (c === "/" && text[i + 1] === "/") {
|
|
56
|
+
while (i < text.length && text[i] !== "\n")
|
|
57
|
+
i++;
|
|
58
|
+
out += "\n";
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (c === "/" && text[i + 1] === "*") {
|
|
62
|
+
i += 2;
|
|
63
|
+
while (i < text.length && !(text[i] === "*" && text[i + 1] === "/"))
|
|
64
|
+
i++;
|
|
65
|
+
i++;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
out += c;
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
/** Reads the one key from a TOML file's top level. Top level means before the
|
|
73
|
+
* first `[section]` header: a key under `[env.preview]` is that environment's,
|
|
74
|
+
* not the deploy's, and reading it would publish the wrong root. Nothing else
|
|
75
|
+
* in the file is interpreted. */
|
|
76
|
+
function readTomlKey(text) {
|
|
77
|
+
for (const raw of text.split("\n")) {
|
|
78
|
+
const line = raw.trim();
|
|
79
|
+
if (line === "" || line.startsWith("#"))
|
|
80
|
+
continue;
|
|
81
|
+
if (line.startsWith("["))
|
|
82
|
+
return undefined;
|
|
83
|
+
const m = /^pages_build_output_dir\s*=\s*(?:"([^"]*)"|'([^']*)')\s*(?:#.*)?$/.exec(line);
|
|
84
|
+
if (m)
|
|
85
|
+
return m[1] ?? m[2];
|
|
86
|
+
}
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
/** Reads the one key from a JSON or JSONC file's top level. A present key whose
|
|
90
|
+
* value is not a string is an error rather than an ignore: the tree declared an
|
|
91
|
+
* output dir, and publishing its parent instead is the defect this closes. */
|
|
92
|
+
function readJsonKey(text, name, jsonc) {
|
|
93
|
+
let parsed;
|
|
94
|
+
try {
|
|
95
|
+
parsed = JSON.parse(jsonc ? stripJsonComments(text) : text);
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
throw new Error(`storage-broker: ${name} could not be parsed: ${String(err)}`);
|
|
99
|
+
}
|
|
100
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
101
|
+
throw new Error(`storage-broker: ${name} is not an object`);
|
|
102
|
+
}
|
|
103
|
+
const value = parsed[KEY];
|
|
104
|
+
if (value === undefined)
|
|
105
|
+
return undefined;
|
|
106
|
+
if (typeof value !== "string") {
|
|
107
|
+
throw new Error(`storage-broker: ${name} declares a non-string ${KEY}`);
|
|
108
|
+
}
|
|
109
|
+
return value;
|
|
110
|
+
}
|
|
111
|
+
/** True when `child` is not `root` or a descendant of it. */
|
|
112
|
+
function escapes(root, child) {
|
|
113
|
+
const rel = (0, node_path_1.relative)(root, child);
|
|
114
|
+
return rel.startsWith("..") || (0, node_path_1.isAbsolute)(rel);
|
|
115
|
+
}
|
|
116
|
+
function outsideError(name, declared, resolved, root) {
|
|
117
|
+
return new Error(`storage-broker: ${name} declares ${KEY}="${declared}", which resolves to ${resolved}, outside the site root ${root}`);
|
|
118
|
+
}
|
|
119
|
+
/** Resolves the directory whose contents become the served root for `dir`.
|
|
120
|
+
*
|
|
121
|
+
* A tree that declares nothing keeps the site root, which is what every deploy
|
|
122
|
+
* did before Task 1917. A tree that declares an output dir gets that dir. A
|
|
123
|
+
* tree that declares one which cannot be resolved is an error naming the path:
|
|
124
|
+
* falling back to the site root here is exactly the defect this closes, because
|
|
125
|
+
* the deploy then succeeds while serving the wrong root. */
|
|
126
|
+
async function resolveUploadDir(dir) {
|
|
127
|
+
for (const name of exports.WRANGLER_CONFIG_NAMES) {
|
|
128
|
+
let text;
|
|
129
|
+
try {
|
|
130
|
+
text = await (0, promises_1.readFile)((0, node_path_1.join)(dir, name), "utf8");
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
continue; // absent, or not readable as a file: the next name is wrangler's next choice
|
|
134
|
+
}
|
|
135
|
+
const declared = name === "wrangler.toml"
|
|
136
|
+
? readTomlKey(text)
|
|
137
|
+
: readJsonKey(text, name, name === "wrangler.jsonc");
|
|
138
|
+
// A config that declares nothing leaves the site root as the asset root,
|
|
139
|
+
// which is wrangler's own behaviour for a Pages project with no output dir.
|
|
140
|
+
if (declared === undefined)
|
|
141
|
+
return { uploadDir: dir, source: "site-root" };
|
|
142
|
+
const uploadDir = (0, node_path_1.resolve)(dir, declared);
|
|
143
|
+
// Confinement, lexically first. A declared value is file content, and
|
|
144
|
+
// resolving file content into a publish path through the route that holds
|
|
145
|
+
// the account-wide Cloudflare credential is the moment it has to be
|
|
146
|
+
// bounded. This pass costs no filesystem call and rejects the plain
|
|
147
|
+
// `"../.."` before anything is touched.
|
|
148
|
+
if (escapes(dir, uploadDir))
|
|
149
|
+
throw outsideError(name, declared, uploadDir, dir);
|
|
150
|
+
// lstat, not stat: the link itself is what matters here, and stat would
|
|
151
|
+
// report a link to a directory as a directory.
|
|
152
|
+
let info;
|
|
153
|
+
try {
|
|
154
|
+
info = await (0, promises_1.lstat)(uploadDir);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
throw new Error(`storage-broker: ${name} declares ${KEY}="${declared}" but ${uploadDir} does not exist`);
|
|
158
|
+
}
|
|
159
|
+
// A link is refused wherever it points. The staging copy takes the upload
|
|
160
|
+
// directory as its source and does not dereference, so a link source fails
|
|
161
|
+
// the copy outright; and a target that can change between this check and
|
|
162
|
+
// that copy is not something to publish from a route holding the
|
|
163
|
+
// account-wide credential.
|
|
164
|
+
if (info.isSymbolicLink()) {
|
|
165
|
+
throw new Error(`storage-broker: ${name} declares ${KEY}="${declared}" but ${uploadDir} is a symbolic link; the published root must be a real directory`);
|
|
166
|
+
}
|
|
167
|
+
if (!info.isDirectory()) {
|
|
168
|
+
throw new Error(`storage-broker: ${name} declares ${KEY}="${declared}" but ${uploadDir} is not a directory`);
|
|
169
|
+
}
|
|
170
|
+
// Confinement again, on the real paths. The lexical pass above is blind to
|
|
171
|
+
// symlinks in the declared value's own parent segments: a declared "a/b"
|
|
172
|
+
// whose `a` links out of the tree stays inside by string comparison while
|
|
173
|
+
// resolving anywhere on disk, and the deploy would then publish whatever it
|
|
174
|
+
// resolves to. Both sides are resolved because the site root itself commonly
|
|
175
|
+
// arrives through a symlinked prefix (macOS /var, a linked account
|
|
176
|
+
// directory), which would otherwise read as an escape on its own.
|
|
177
|
+
const realDir = await (0, promises_1.realpath)(dir);
|
|
178
|
+
const realUpload = await (0, promises_1.realpath)(uploadDir);
|
|
179
|
+
if (escapes(realDir, realUpload))
|
|
180
|
+
throw outsideError(name, declared, realUpload, realDir);
|
|
181
|
+
return { uploadDir, source: name, declared };
|
|
182
|
+
}
|
|
183
|
+
return { uploadDir: dir, source: "site-root" };
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=pages-output-dir.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pages-output-dir.js","sourceRoot":"","sources":["../src/pages-output-dir.ts"],"names":[],"mappings":";AAAA,4EAA4E;AAC5E,+EAA+E;AAC/E,6EAA6E;AAC7E,8EAA8E;AAC9E,+EAA+E;AAC/E,wEAAwE;AACxE,4CAA4C;AAC5C,EAAE;AACF,+EAA+E;AAC/E,4EAA4E;AAC5E,qDAAqD;;;AA2HrD,4CA4DC;AArLD,+CAA6D;AAC7D,yCAAgE;AAEhE,+EAA+E;AAC/E,+EAA+E;AAC/E,8EAA8E;AAC9E,gFAAgF;AAChF,8EAA8E;AAC9E,wDAAwD;AAC3C,QAAA,qBAAqB,GAAG;IACnC,eAAe;IACf,gBAAgB;IAChB,eAAe;CACP,CAAC;AAaX,MAAM,GAAG,GAAG,wBAAwB,CAAC;AAErC;;;8EAG8E;AAC9E,SAAS,iBAAiB,CAAC,IAAY;IACrC,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,IAAI,QAAQ,EAAE,CAAC;YACb,GAAG,IAAI,CAAC,CAAC;YACT,IAAI,OAAO;gBAAE,OAAO,GAAG,KAAK,CAAC;iBACxB,IAAI,CAAC,KAAK,IAAI;gBAAE,OAAO,GAAG,IAAI,CAAC;iBAC/B,IAAI,CAAC,KAAK,GAAG;gBAAE,QAAQ,GAAG,KAAK,CAAC;YACrC,SAAS;QACX,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;YACd,QAAQ,GAAG,IAAI,CAAC;YAChB,GAAG,IAAI,CAAC,CAAC;YACT,SAAS;QACX,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YACrC,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI;gBAAE,CAAC,EAAE,CAAC;YAChD,GAAG,IAAI,IAAI,CAAC;YACZ,SAAS;QACX,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YACrC,CAAC,IAAI,CAAC,CAAC;YACP,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC;gBAAE,CAAC,EAAE,CAAC;YACzE,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QACD,GAAG,IAAI,CAAC,CAAC;IACX,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;kCAGkC;AAClC,SAAS,WAAW,CAAC,IAAY;IAC/B,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QACxB,IAAI,IAAI,KAAK,EAAE,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAClD,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,SAAS,CAAC;QAC3C,MAAM,CAAC,GAAG,mEAAmE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzF,IAAI,CAAC;YAAE,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;+EAE+E;AAC/E,SAAS,WAAW,CAAC,IAAY,EAAE,IAAY,EAAE,KAAc;IAC7D,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC9D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,yBAAyB,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACjF,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,mBAAmB,CAAC,CAAC;IAC9D,CAAC;IACD,MAAM,KAAK,GAAI,MAAkC,CAAC,GAAG,CAAC,CAAC;IACvD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,0BAA0B,GAAG,EAAE,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,6DAA6D;AAC7D,SAAS,OAAO,CAAC,IAAY,EAAE,KAAa;IAC1C,MAAM,GAAG,GAAG,IAAA,oBAAQ,EAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAClC,OAAO,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,IAAA,sBAAU,EAAC,GAAG,CAAC,CAAC;AACjD,CAAC;AAED,SAAS,YAAY,CAAC,IAAY,EAAE,QAAgB,EAAE,QAAgB,EAAE,IAAY;IAClF,OAAO,IAAI,KAAK,CACd,mBAAmB,IAAI,aAAa,GAAG,KAAK,QAAQ,wBAAwB,QAAQ,2BAA2B,IAAI,EAAE,CACtH,CAAC;AACJ,CAAC;AAED;;;;;;6DAM6D;AACtD,KAAK,UAAU,gBAAgB,CAAC,GAAW;IAChD,KAAK,MAAM,IAAI,IAAI,6BAAqB,EAAE,CAAC;QACzC,IAAI,IAAY,CAAC;QACjB,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,IAAA,mBAAQ,EAAC,IAAA,gBAAI,EAAC,GAAG,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;QACjD,CAAC;QAAC,MAAM,CAAC;YACP,SAAS,CAAC,6EAA6E;QACzF,CAAC;QACD,MAAM,QAAQ,GACZ,IAAI,KAAK,eAAe;YACtB,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC;YACnB,CAAC,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,KAAK,gBAAgB,CAAC,CAAC;QACzD,yEAAyE;QACzE,4EAA4E;QAC5E,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;QAC3E,MAAM,SAAS,GAAG,IAAA,mBAAO,EAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QACzC,sEAAsE;QACtE,0EAA0E;QAC1E,oEAAoE;QACpE,oEAAoE;QACpE,wCAAwC;QACxC,IAAI,OAAO,CAAC,GAAG,EAAE,SAAS,CAAC;YAAE,MAAM,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;QAChF,wEAAwE;QACxE,+CAA+C;QAC/C,IAAI,IAAI,CAAC;QACT,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,IAAA,gBAAK,EAAC,SAAS,CAAC,CAAC;QAChC,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CACb,mBAAmB,IAAI,aAAa,GAAG,KAAK,QAAQ,SAAS,SAAS,iBAAiB,CACxF,CAAC;QACJ,CAAC;QACD,0EAA0E;QAC1E,2EAA2E;QAC3E,yEAAyE;QACzE,iEAAiE;QACjE,2BAA2B;QAC3B,IAAI,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CACb,mBAAmB,IAAI,aAAa,GAAG,KAAK,QAAQ,SAAS,SAAS,kEAAkE,CACzI,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CACb,mBAAmB,IAAI,aAAa,GAAG,KAAK,QAAQ,SAAS,SAAS,qBAAqB,CAC5F,CAAC;QACJ,CAAC;QACD,2EAA2E;QAC3E,yEAAyE;QACzE,0EAA0E;QAC1E,4EAA4E;QAC5E,6EAA6E;QAC7E,mEAAmE;QACnE,kEAAkE;QAClE,MAAM,OAAO,GAAG,MAAM,IAAA,mBAAQ,EAAC,GAAG,CAAC,CAAC;QACpC,MAAM,UAAU,GAAG,MAAM,IAAA,mBAAQ,EAAC,SAAS,CAAC,CAAC;QAC7C,IAAI,OAAO,CAAC,OAAO,EAAE,UAAU,CAAC;YAAE,MAAM,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;QAC1F,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAC/C,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;AACjD,CAAC"}
|
|
@@ -993,3 +993,68 @@ test("pagesDeploy pins wrangler's working directory to the deploy dir", async ()
|
|
|
993
993
|
"wrangler must run in the deploy dir so its scratch dir lands beside the tree it uploads",
|
|
994
994
|
);
|
|
995
995
|
});
|
|
996
|
+
|
|
997
|
+
// --- Task 1917: the asset root is the declared output dir, not its parent ---
|
|
998
|
+
|
|
999
|
+
const SIGN_TREE = {
|
|
1000
|
+
"wrangler.toml": 'name = "gls-sign"\npages_build_output_dir = "public"\n',
|
|
1001
|
+
"public/index.html": "<!doctype html>",
|
|
1002
|
+
"public/quote-patel.pdf": "%PDF-",
|
|
1003
|
+
"functions/api/sign.js": "export {}",
|
|
1004
|
+
};
|
|
1005
|
+
|
|
1006
|
+
test("pagesDeploy uploads the declared output dir, so its contents sit at the served root", async () => {
|
|
1007
|
+
const dir = siteTree(SIGN_TREE);
|
|
1008
|
+
const { run, calls } = snapshottingRun(DEPLOYED);
|
|
1009
|
+
const cf = makeCfExec(cred, run);
|
|
1010
|
+
const { uploadDir, uploadDirSource } = await cf.pagesDeploy(dir, "gls-sign-1848", "main");
|
|
1011
|
+
assert.deepEqual(
|
|
1012
|
+
calls[0].uploaded,
|
|
1013
|
+
["index.html", "quote-patel.pdf"],
|
|
1014
|
+
"the declared output dir's contents are the served root, not a /public/ subtree",
|
|
1015
|
+
);
|
|
1016
|
+
assert.equal(uploadDir, join(dir, "public"));
|
|
1017
|
+
assert.equal(uploadDirSource, "wrangler.toml");
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
test("pagesDeploy still runs wrangler in the site root when the output dir is declared", async () => {
|
|
1021
|
+
const dir = siteTree(SIGN_TREE);
|
|
1022
|
+
const { run, calls } = snapshottingRun(DEPLOYED);
|
|
1023
|
+
const cf = makeCfExec(cred, run);
|
|
1024
|
+
await cf.pagesDeploy(dir, "gls-sign-1848", "main");
|
|
1025
|
+
assert.equal(
|
|
1026
|
+
calls[0].cwd,
|
|
1027
|
+
dir,
|
|
1028
|
+
"cwd carries the config's bindings and wrangler's functions/ lookup; it is the site root either way",
|
|
1029
|
+
);
|
|
1030
|
+
});
|
|
1031
|
+
|
|
1032
|
+
test("pagesDeploy reports the site root as the source when nothing is declared", async () => {
|
|
1033
|
+
const dir = siteTree(PORTAL);
|
|
1034
|
+
const { run } = snapshottingRun(DEPLOYED);
|
|
1035
|
+
const cf = makeCfExec(cred, run);
|
|
1036
|
+
const { uploadDir, uploadDirSource } = await cf.pagesDeploy(dir, "kit-data", "main");
|
|
1037
|
+
assert.equal(uploadDir, dir);
|
|
1038
|
+
assert.equal(uploadDirSource, "site-root");
|
|
1039
|
+
});
|
|
1040
|
+
|
|
1041
|
+
test("pagesDeploy drops a wrangler config sitting inside the declared output dir", async () => {
|
|
1042
|
+
const dir = siteTree({ ...SIGN_TREE, "public/wrangler.toml": 'database_id = "secret-looking"\n' });
|
|
1043
|
+
const { run, calls } = snapshottingRun(DEPLOYED);
|
|
1044
|
+
const cf = makeCfExec(cred, run);
|
|
1045
|
+
const { excluded } = await cf.pagesDeploy(dir, "gls-sign-1848", "main");
|
|
1046
|
+
assert.deepEqual(calls[0].uploaded, ["index.html", "quote-patel.pdf"]);
|
|
1047
|
+
assert.deepEqual(excluded, ["wrangler.toml"]);
|
|
1048
|
+
});
|
|
1049
|
+
|
|
1050
|
+
test("pagesDeploy refuses to deploy when the declared output dir is absent", async () => {
|
|
1051
|
+
const dir = siteTree({ "wrangler.toml": 'pages_build_output_dir = "public"\n' });
|
|
1052
|
+
const { run, calls } = snapshottingRun(DEPLOYED);
|
|
1053
|
+
const cf = makeCfExec(cred, run);
|
|
1054
|
+
await assert.rejects(
|
|
1055
|
+
() => cf.pagesDeploy(dir, "gls-sign-1848", "main"),
|
|
1056
|
+
new RegExp(join(dir, "public")),
|
|
1057
|
+
"a declared-but-absent output dir names the path; falling back to the site root is the defect",
|
|
1058
|
+
);
|
|
1059
|
+
assert.equal(calls.length, 0, "no wrangler run may happen once resolution has failed");
|
|
1060
|
+
});
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtempSync, mkdirSync, writeFileSync, symlinkSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { resolveUploadDir } from "../pages-output-dir.js";
|
|
7
|
+
|
|
8
|
+
/** A site tree on disk. `dirs` are created empty; `files` are written. */
|
|
9
|
+
function tree(files: Record<string, string>, dirs: string[] = []): string {
|
|
10
|
+
const dir = mkdtempSync(join(tmpdir(), "pages-out-"));
|
|
11
|
+
for (const d of dirs) mkdirSync(join(dir, d), { recursive: true });
|
|
12
|
+
for (const [rel, body] of Object.entries(files)) writeFileSync(join(dir, rel), body);
|
|
13
|
+
return dir;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
test("no wrangler config at the root resolves to the site root", async () => {
|
|
17
|
+
const dir = tree({ "index.html": "<!doctype html>" });
|
|
18
|
+
assert.deepEqual(await resolveUploadDir(dir), { uploadDir: dir, source: "site-root" });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("a config declaring no output dir resolves to the site root", async () => {
|
|
22
|
+
const dir = tree({ "wrangler.toml": 'name = "kit-data"\n' });
|
|
23
|
+
assert.deepEqual(await resolveUploadDir(dir), { uploadDir: dir, source: "site-root" });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("a declared toml output dir resolves to that subdirectory", async () => {
|
|
27
|
+
const dir = tree({ "wrangler.toml": 'name = "gls"\npages_build_output_dir = "public"\n' }, [
|
|
28
|
+
"public",
|
|
29
|
+
]);
|
|
30
|
+
assert.deepEqual(await resolveUploadDir(dir), {
|
|
31
|
+
uploadDir: join(dir, "public"),
|
|
32
|
+
source: "wrangler.toml",
|
|
33
|
+
declared: "public",
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('a declared "." resolves to the site root and still names its config', async () => {
|
|
38
|
+
const dir = tree({ "wrangler.toml": 'pages_build_output_dir = "."\n' });
|
|
39
|
+
assert.deepEqual(await resolveUploadDir(dir), {
|
|
40
|
+
uploadDir: dir,
|
|
41
|
+
source: "wrangler.toml",
|
|
42
|
+
declared: ".",
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("a single-quoted toml value is read", async () => {
|
|
47
|
+
const dir = tree({ "wrangler.toml": "pages_build_output_dir = 'dist'\n" }, ["dist"]);
|
|
48
|
+
assert.equal((await resolveUploadDir(dir)).uploadDir, join(dir, "dist"));
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("a commented-out toml key is not read", async () => {
|
|
52
|
+
const dir = tree({ "wrangler.toml": '# pages_build_output_dir = "public"\n' }, ["public"]);
|
|
53
|
+
assert.equal((await resolveUploadDir(dir)).source, "site-root");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("a key inside a toml section is not read as the top-level key", async () => {
|
|
57
|
+
const dir = tree({ "wrangler.toml": '[env.preview]\npages_build_output_dir = "preview-out"\n' }, [
|
|
58
|
+
"preview-out",
|
|
59
|
+
]);
|
|
60
|
+
assert.equal((await resolveUploadDir(dir)).source, "site-root");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("a json config is read", async () => {
|
|
64
|
+
const dir = tree({ "wrangler.json": '{"pages_build_output_dir":"out"}' }, ["out"]);
|
|
65
|
+
assert.deepEqual(await resolveUploadDir(dir), {
|
|
66
|
+
uploadDir: join(dir, "out"),
|
|
67
|
+
source: "wrangler.json",
|
|
68
|
+
declared: "out",
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("a jsonc config is read past its comments", async () => {
|
|
73
|
+
const dir = tree(
|
|
74
|
+
{
|
|
75
|
+
"wrangler.jsonc":
|
|
76
|
+
'{\n // the served root\n "pages_build_output_dir": "out", /* trailing */\n "name": "x"\n}',
|
|
77
|
+
},
|
|
78
|
+
["out"],
|
|
79
|
+
);
|
|
80
|
+
assert.deepEqual(await resolveUploadDir(dir), {
|
|
81
|
+
uploadDir: join(dir, "out"),
|
|
82
|
+
source: "wrangler.jsonc",
|
|
83
|
+
declared: "out",
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("a comment marker inside a jsonc string is not stripped", async () => {
|
|
88
|
+
const dir = tree({ "wrangler.jsonc": '{"pages_build_output_dir":"a//b"}' }, ["a/b"]);
|
|
89
|
+
assert.equal((await resolveUploadDir(dir)).declared, "a//b");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("wrangler.json wins over wrangler.toml, matching wrangler's own precedence", async () => {
|
|
93
|
+
const dir = tree(
|
|
94
|
+
{
|
|
95
|
+
"wrangler.json": '{"pages_build_output_dir":"from-json"}',
|
|
96
|
+
"wrangler.toml": 'pages_build_output_dir = "from-toml"\n',
|
|
97
|
+
},
|
|
98
|
+
["from-json", "from-toml"],
|
|
99
|
+
);
|
|
100
|
+
assert.equal((await resolveUploadDir(dir)).source, "wrangler.json");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("an unparseable config throws naming the file, never a site-root fallback", async () => {
|
|
104
|
+
const dir = tree({ "wrangler.json": "{not json" });
|
|
105
|
+
await assert.rejects(() => resolveUploadDir(dir), /wrangler\.json/);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("a declared dir that does not exist throws naming the resolved path", async () => {
|
|
109
|
+
const dir = tree({ "wrangler.toml": 'pages_build_output_dir = "public"\n' });
|
|
110
|
+
await assert.rejects(() => resolveUploadDir(dir), new RegExp(join(dir, "public")));
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("a declared dir that is a file throws naming the resolved path", async () => {
|
|
114
|
+
const dir = tree({
|
|
115
|
+
"wrangler.toml": 'pages_build_output_dir = "public"\n',
|
|
116
|
+
public: "not a directory",
|
|
117
|
+
});
|
|
118
|
+
await assert.rejects(() => resolveUploadDir(dir), new RegExp(join(dir, "public")));
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("a declared value escaping the site root throws naming the resolved path", async () => {
|
|
122
|
+
const dir = tree({ "wrangler.toml": 'pages_build_output_dir = "../.."\n' });
|
|
123
|
+
await assert.rejects(() => resolveUploadDir(dir), /outside/);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("a non-string declared value throws naming the file", async () => {
|
|
127
|
+
const dir = tree({ "wrangler.json": '{"pages_build_output_dir":42}' });
|
|
128
|
+
await assert.rejects(() => resolveUploadDir(dir), /wrangler\.json/);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("a declared dir that is a symlink out of the site root is refused, naming the path", async () => {
|
|
132
|
+
const dir = tree({ "wrangler.toml": 'pages_build_output_dir = "public"\n' });
|
|
133
|
+
const outside = tree({ "SECRET.txt": "house credential" });
|
|
134
|
+
symlinkSync(outside, join(dir, "public"));
|
|
135
|
+
await assert.rejects(
|
|
136
|
+
() => resolveUploadDir(dir),
|
|
137
|
+
new RegExp(`symbolic link|${join(dir, "public")}`),
|
|
138
|
+
"a lexical containment check alone passes this and publishes whatever the link points at",
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("a declared value escaping through a symlinked parent segment throws as outside", async () => {
|
|
143
|
+
const dir = tree({ "wrangler.toml": 'pages_build_output_dir = "link/out"\n' });
|
|
144
|
+
const outside = tree({}, ["out"]);
|
|
145
|
+
symlinkSync(outside, join(dir, "link"));
|
|
146
|
+
await assert.rejects(
|
|
147
|
+
() => resolveUploadDir(dir),
|
|
148
|
+
/outside/,
|
|
149
|
+
"the leaf is a real directory here, so only the realpath comparison catches it",
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("a declared dir that is a symlink is refused even when it points inside the tree", async () => {
|
|
154
|
+
const dir = tree({ "wrangler.toml": 'pages_build_output_dir = "public"\n' }, ["real"]);
|
|
155
|
+
symlinkSync(join(dir, "real"), join(dir, "public"));
|
|
156
|
+
await assert.rejects(
|
|
157
|
+
() => resolveUploadDir(dir),
|
|
158
|
+
/symbolic link/,
|
|
159
|
+
"the staging copy cannot take a link as its source, and a link target can change between resolution and copy",
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("a declared dir reached through a symlinked parent that stays in the tree is accepted", async () => {
|
|
164
|
+
const dir = tree({ "wrangler.toml": 'pages_build_output_dir = "link/out"\n' }, ["real/out"]);
|
|
165
|
+
symlinkSync(join(dir, "real"), join(dir, "link"));
|
|
166
|
+
assert.equal((await resolveUploadDir(dir)).source, "wrangler.toml");
|
|
167
|
+
});
|
|
@@ -9,6 +9,11 @@ import { join, relative } from "node:path";
|
|
|
9
9
|
import type { HouseCredential } from "./house-credential.js";
|
|
10
10
|
import { readHouseCredential } from "./house-credential.js";
|
|
11
11
|
import { resolveHouseScopedToken } from "./house-scoped-token.js";
|
|
12
|
+
import {
|
|
13
|
+
resolveUploadDir,
|
|
14
|
+
WRANGLER_CONFIG_NAMES,
|
|
15
|
+
type UploadDirSource,
|
|
16
|
+
} from "./pages-output-dir.js";
|
|
12
17
|
|
|
13
18
|
export type RunFn = (
|
|
14
19
|
cmd: string,
|
|
@@ -76,22 +81,31 @@ export interface CfExec {
|
|
|
76
81
|
pagesProjectList(): Promise<{ name: string }[]>;
|
|
77
82
|
pagesProjectCreate(name: string, productionBranch: string): Promise<void>;
|
|
78
83
|
/** `excluded` names the files dropped from the upload set, so the caller can
|
|
79
|
-
* log what was published rather than assert it (Task 1782).
|
|
84
|
+
* log what was published rather than assert it (Task 1782). `uploadDir` and
|
|
85
|
+
* `uploadDirSource` name the asset root that was published and what decided
|
|
86
|
+
* it, so a root published one level too high is readable from the log
|
|
87
|
+
* without fetching the site (Task 1917). */
|
|
80
88
|
pagesDeploy(
|
|
81
89
|
dir: string,
|
|
82
90
|
project: string,
|
|
83
91
|
branch?: string,
|
|
84
|
-
): Promise<{
|
|
92
|
+
): Promise<{
|
|
93
|
+
url: string;
|
|
94
|
+
excluded: string[];
|
|
95
|
+
uploadDir: string;
|
|
96
|
+
uploadDirSource: UploadDirSource;
|
|
97
|
+
}>;
|
|
85
98
|
}
|
|
86
99
|
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
|
|
100
|
+
// WRANGLER_CONFIG_NAMES lives in ./pages-output-dir.js since Task 1917. It is
|
|
101
|
+
// the same list for two jobs that must not drift apart: the discovery order the
|
|
102
|
+
// output-dir resolver walks, and the drop-list this file applies to the upload.
|
|
103
|
+
// Task 1782 — the upload walk has a fixed ignore list with no config hook, so a
|
|
104
|
+
// config sitting in the deployed tree is served publicly at /wrangler.toml,
|
|
105
|
+
// disclosing the D1 database id and R2 bucket name, both of which embed the
|
|
106
|
+
// account id. All three names are dropped rather than only the .toml the
|
|
107
|
+
// data-portal template ships, so a tree that later gains a .json config cannot
|
|
108
|
+
// quietly start republishing it.
|
|
95
109
|
|
|
96
110
|
// An object key carries `/` as a real path separator (the data portal's
|
|
97
111
|
// per-person prefixes), so it is encoded segment-wise rather than whole:
|
|
@@ -455,16 +469,26 @@ export function makeCfExec(
|
|
|
455
469
|
// Measured on the house account 2026-07-20: a brand-new project deployed
|
|
456
470
|
// with the config outside the upload received the same bindings and the
|
|
457
471
|
// same config hash as the control that uploaded it.
|
|
472
|
+
// Task 1917 — the positional argument IS wrangler's asset root and
|
|
473
|
+
// overrides `pages_build_output_dir`, so the directory to stage is the
|
|
474
|
+
// declared output dir when the tree declares one. Resolution happens
|
|
475
|
+
// before any staging: a declared dir that cannot be resolved aborts the
|
|
476
|
+
// deploy rather than falling back to the site root and publishing one
|
|
477
|
+
// level too high, which is what left gls-sign-1848 answering only under
|
|
478
|
+
// /public/ while its root 404'd.
|
|
479
|
+
const { uploadDir, source: uploadDirSource } = await resolveUploadDir(dir);
|
|
458
480
|
const staged = await mkdtemp(join(tmpdir(), "maxy-pages-upload-"));
|
|
459
481
|
const excluded: string[] = [];
|
|
460
482
|
try {
|
|
461
|
-
await cp(
|
|
483
|
+
await cp(uploadDir, staged, {
|
|
462
484
|
recursive: true,
|
|
463
|
-
// Only a config at the root of
|
|
464
|
-
//
|
|
485
|
+
// Only a config at the root of what is uploaded is dropped. That root
|
|
486
|
+
// is the served root, which is the surface Task 1782 protects. A file
|
|
487
|
+
// of the same name anywhere below it is site content and stays
|
|
488
|
+
// published.
|
|
465
489
|
filter: (src) => {
|
|
466
|
-
const rel = relative(
|
|
467
|
-
if (!WRANGLER_CONFIG_NAMES.includes(rel)) return true;
|
|
490
|
+
const rel = relative(uploadDir, src);
|
|
491
|
+
if (!(WRANGLER_CONFIG_NAMES as readonly string[]).includes(rel)) return true;
|
|
468
492
|
excluded.push(rel);
|
|
469
493
|
return false;
|
|
470
494
|
},
|
|
@@ -497,7 +521,7 @@ export function makeCfExec(
|
|
|
497
521
|
if (!url) {
|
|
498
522
|
throw new Error(`storage-broker: pages deploy returned no deployment url: ${stdout}`);
|
|
499
523
|
}
|
|
500
|
-
return { url, excluded: excluded.sort() };
|
|
524
|
+
return { url, excluded: excluded.sort(), uploadDir, uploadDirSource };
|
|
501
525
|
} finally {
|
|
502
526
|
// A failed deploy must not strand a second copy of the site on disk.
|
|
503
527
|
await rm(staged, { recursive: true, force: true });
|