mnfst-publish 0.1.1 → 0.1.4
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/manifest.publish.mjs +49 -14
- package/package.json +1 -1
package/manifest.publish.mjs
CHANGED
|
@@ -109,6 +109,35 @@ function prerenderOutputDir(root) {
|
|
|
109
109
|
|
|
110
110
|
// --- MCP JSON-RPC over Streamable HTTP -------------------------------------
|
|
111
111
|
|
|
112
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
113
|
+
|
|
114
|
+
// Retry transient failures — a thrown fetch (network blip, worker cold-start /
|
|
115
|
+
// redeploy window) or a 5xx — with a short backoff, so a single hiccup doesn't
|
|
116
|
+
// surface as a raw "fetch failed". Publish/promote/upload are idempotent enough
|
|
117
|
+
// that a repeat is harmless. 4xx are NOT retried (they're real client errors).
|
|
118
|
+
async function fetchRetry(url, init, { tries = 4, label = 'the server' } = {}) {
|
|
119
|
+
let lastErr;
|
|
120
|
+
for (let attempt = 1; attempt <= tries; attempt++) {
|
|
121
|
+
try {
|
|
122
|
+
const res = await fetch(url, init);
|
|
123
|
+
if (res.status >= 500 && attempt < tries) {
|
|
124
|
+
log(` ${label} had a hiccup (HTTP ${res.status}) — retrying (${attempt}/${tries - 1})…`);
|
|
125
|
+
await sleep(500 * attempt);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
return res;
|
|
129
|
+
} catch (e) {
|
|
130
|
+
lastErr = e;
|
|
131
|
+
if (attempt < tries) {
|
|
132
|
+
log(` couldn't reach ${label} — retrying (${attempt}/${tries - 1})…`);
|
|
133
|
+
await sleep(500 * attempt);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
throw lastErr;
|
|
139
|
+
}
|
|
140
|
+
|
|
112
141
|
async function mcp(url, key, method, params, sessionId) {
|
|
113
142
|
const headers = {
|
|
114
143
|
'content-type': 'application/json',
|
|
@@ -116,7 +145,11 @@ async function mcp(url, key, method, params, sessionId) {
|
|
|
116
145
|
'x-api-key': key,
|
|
117
146
|
};
|
|
118
147
|
if (sessionId) headers['mcp-session-id'] = sessionId;
|
|
119
|
-
const res = await
|
|
148
|
+
const res = await fetchRetry(
|
|
149
|
+
url,
|
|
150
|
+
{ method: 'POST', headers, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }) },
|
|
151
|
+
{ label: 'Manifest' },
|
|
152
|
+
);
|
|
120
153
|
const sid = res.headers.get('mcp-session-id') || sessionId;
|
|
121
154
|
const text = await res.text();
|
|
122
155
|
// Response may be a JSON object or an SSE "data: {...}" line.
|
|
@@ -165,7 +198,6 @@ export function isExcludedPath(rel) {
|
|
|
165
198
|
const base = parts[parts.length - 1];
|
|
166
199
|
if (base === '.env' || base.startsWith('.env.')) return true; // .env, .env.local, .env.prod …
|
|
167
200
|
if (base === '.npmrc' || base === '.dev.vars' || base === 'id_rsa' || base === '.DS_Store') return true;
|
|
168
|
-
if (base === '.mnfst-render-complete') return true; // internal render sentinel — not part of the site
|
|
169
201
|
if (/\.(pem|key|p12|pfx)$/i.test(base)) return true;
|
|
170
202
|
return false;
|
|
171
203
|
}
|
|
@@ -298,16 +330,19 @@ export async function main() {
|
|
|
298
330
|
if (r.status !== 0) fail('render failed — fix the errors above and try again.');
|
|
299
331
|
}
|
|
300
332
|
|
|
301
|
-
// For a render project,
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
//
|
|
333
|
+
// For a render project, sanity-check there's actually a built site to ship —
|
|
334
|
+
// an output folder with an index.html. (When we just ran the render above, its
|
|
335
|
+
// exit code was already checked; this also covers --no-render and hand-built
|
|
336
|
+
// output.) Deliberately forgiving: no hard dependency on any marker file, so a
|
|
337
|
+
// good build always publishes regardless of which mnfst-render produced it.
|
|
305
338
|
if (source === 'render') {
|
|
306
339
|
const outDir = prerenderOutputDir(root);
|
|
307
|
-
if (!existsSync(join(root, outDir, '.
|
|
340
|
+
if (!existsSync(join(root, outDir, 'index.html'))) {
|
|
308
341
|
fail(
|
|
309
|
-
`the "${outDir}"
|
|
310
|
-
(opts.render === false
|
|
342
|
+
`the "${outDir}" folder doesn't have a built site yet (no index.html). ` +
|
|
343
|
+
(opts.render === false
|
|
344
|
+
? 'Remove --no-render so it builds first, or run `npx mnfst-render`, then publish again.'
|
|
345
|
+
: 'Run `npx mnfst-render` first, then publish again.'),
|
|
311
346
|
);
|
|
312
347
|
}
|
|
313
348
|
}
|
|
@@ -335,11 +370,11 @@ export async function main() {
|
|
|
335
370
|
const zip = buildZip(root, rels);
|
|
336
371
|
log(`Uploading ${rels.length} files (${(zip.length / 1048576).toFixed(1)} MB)…`);
|
|
337
372
|
|
|
338
|
-
const up = await
|
|
339
|
-
|
|
340
|
-
headers: { 'content-type': 'application/zip' },
|
|
341
|
-
|
|
342
|
-
|
|
373
|
+
const up = await fetchRetry(
|
|
374
|
+
uploadUrl,
|
|
375
|
+
{ method: 'POST', headers: { 'content-type': 'application/zip' }, body: zip },
|
|
376
|
+
{ label: 'the upload server' },
|
|
377
|
+
);
|
|
343
378
|
const upText = await up.text();
|
|
344
379
|
let upJson = null;
|
|
345
380
|
try {
|