mnfst-publish 0.1.6 → 0.1.8
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/bin/mnfst-publish.js +0 -0
- package/manifest.publish.mjs +66 -6
- package/package.json +1 -1
package/bin/mnfst-publish.js
CHANGED
|
File without changes
|
package/manifest.publish.mjs
CHANGED
|
@@ -30,6 +30,7 @@ function parseArgs(argv) {
|
|
|
30
30
|
else if (a === '--production' || a === '--prod') out.env = 'production';
|
|
31
31
|
else if (a === '--env') out.env = argv[++i];
|
|
32
32
|
else if (a === '--source') out.source = argv[++i];
|
|
33
|
+
else if (a === '--output-dir') out.outputDir = argv[++i];
|
|
33
34
|
else if (a === '--no-render') out.render = false;
|
|
34
35
|
else if (a === '--render') out.render = true;
|
|
35
36
|
else if (a === '--promote') out.promote = true;
|
|
@@ -259,11 +260,29 @@ export function loadPublishIgnore(root) {
|
|
|
259
260
|
return makePublishIgnore(patterns);
|
|
260
261
|
}
|
|
261
262
|
|
|
262
|
-
export function collectFiles(root) {
|
|
263
|
+
export function collectFiles(root, outputDir) {
|
|
263
264
|
const git = spawnSync('git', ['ls-files', '-co', '--exclude-standard'], { cwd: root, encoding: 'utf8' });
|
|
264
265
|
let rels;
|
|
265
266
|
if (git.status === 0) {
|
|
266
267
|
rels = git.stdout.split('\n').map((s) => s.trim()).filter(Boolean);
|
|
268
|
+
// Non-Manifest builds usually write to a GITIGNORED folder (dist/, build/…)
|
|
269
|
+
// — force-include the declared output dir or the publish ships no site.
|
|
270
|
+
if (outputDir && existsSync(join(root, outputDir))) {
|
|
271
|
+
const seen = new Set(rels);
|
|
272
|
+
const walkOut = (dir) => {
|
|
273
|
+
for (const name of readdirSync(dir)) {
|
|
274
|
+
if (EXCLUDED_DIRS.has(name)) continue;
|
|
275
|
+
const abs = join(dir, name);
|
|
276
|
+
const st = statSync(abs);
|
|
277
|
+
if (st.isDirectory()) walkOut(abs);
|
|
278
|
+
else {
|
|
279
|
+
const rel = relative(root, abs).split(sep).join('/');
|
|
280
|
+
if (!seen.has(rel)) { seen.add(rel); rels.push(rel); }
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
walkOut(join(root, outputDir));
|
|
285
|
+
}
|
|
267
286
|
} else {
|
|
268
287
|
// Not a git repo — walk, skipping excluded dirs as we go.
|
|
269
288
|
rels = [];
|
|
@@ -285,6 +304,44 @@ export function collectFiles(root) {
|
|
|
285
304
|
return rels.filter((r) => !isExcludedPath(r) && !publishIgnored(r));
|
|
286
305
|
}
|
|
287
306
|
|
|
307
|
+
// --- Component version stamp ------------------------------------------------
|
|
308
|
+
|
|
309
|
+
// Stamp each shipped manifest.json (project root, and the prerender output copy)
|
|
310
|
+
// with `deployment`: a content hash of its component HTML files. The components
|
|
311
|
+
// plugin appends it as ?v= to component fetches, so browser caches bust exactly
|
|
312
|
+
// when component markup changes and persist when it doesn't. Purely publish-time:
|
|
313
|
+
// the on-disk manifest.json is never modified.
|
|
314
|
+
export function stampManifests(root, rels) {
|
|
315
|
+
const overrides = new Map();
|
|
316
|
+
const relSet = new Set(rels);
|
|
317
|
+
const outDir = prerenderOutputDir(root);
|
|
318
|
+
for (const mfRel of ['manifest.json', outDir + '/manifest.json']) {
|
|
319
|
+
if (!relSet.has(mfRel)) continue;
|
|
320
|
+
let mf;
|
|
321
|
+
try {
|
|
322
|
+
mf = JSON.parse(readFileSync(join(root, mfRel), 'utf8'));
|
|
323
|
+
} catch {
|
|
324
|
+
continue; // invalid JSON — ship as-is
|
|
325
|
+
}
|
|
326
|
+
const dir = mfRel.includes('/') ? mfRel.slice(0, mfRel.lastIndexOf('/') + 1) : '';
|
|
327
|
+
const paths = [...(mf.preloadedComponents || []), ...(mf.components || [])]
|
|
328
|
+
.filter((p) => typeof p === 'string' && !p.startsWith('http'))
|
|
329
|
+
.map((p) => dir + p.replace(/^\/+/, ''))
|
|
330
|
+
.filter((p) => relSet.has(p))
|
|
331
|
+
.sort();
|
|
332
|
+
if (!paths.length) continue;
|
|
333
|
+
const hash = createHash('sha256');
|
|
334
|
+
for (const p of paths) {
|
|
335
|
+
hash.update(p + '\0');
|
|
336
|
+
hash.update(readFileSync(join(root, p)));
|
|
337
|
+
hash.update('\0');
|
|
338
|
+
}
|
|
339
|
+
mf.deployment = hash.digest('hex').slice(0, 12);
|
|
340
|
+
overrides.set(mfRel, Buffer.from(JSON.stringify(mf, null, 2) + '\n', 'utf8'));
|
|
341
|
+
}
|
|
342
|
+
return overrides;
|
|
343
|
+
}
|
|
344
|
+
|
|
288
345
|
// --- Minimal ZIP writer (DEFLATE), pure Node, no deps ----------------------
|
|
289
346
|
|
|
290
347
|
function crc32(buf) {
|
|
@@ -296,7 +353,7 @@ function crc32(buf) {
|
|
|
296
353
|
return (~c) >>> 0;
|
|
297
354
|
}
|
|
298
355
|
|
|
299
|
-
function buildZip(root, rels) {
|
|
356
|
+
function buildZip(root, rels, overrides = new Map()) {
|
|
300
357
|
// This packer writes classic (non-Zip64) ZIP records: file count is a uint16
|
|
301
358
|
// and offsets are uint32. Fail loudly rather than emit a silently-corrupt
|
|
302
359
|
// archive past those limits.
|
|
@@ -310,7 +367,7 @@ function buildZip(root, rels) {
|
|
|
310
367
|
if (offset > 0xffffffff) {
|
|
311
368
|
fail('project is too large to package (>4 GB). Contact support.');
|
|
312
369
|
}
|
|
313
|
-
const data = readFileSync(join(root, rel));
|
|
370
|
+
const data = overrides.get(rel) ?? readFileSync(join(root, rel));
|
|
314
371
|
const nameBuf = Buffer.from(rel, 'utf8');
|
|
315
372
|
const crc = crc32(data);
|
|
316
373
|
const deflated = deflateRawSync(data);
|
|
@@ -365,7 +422,7 @@ function buildZip(root, rels) {
|
|
|
365
422
|
export async function main() {
|
|
366
423
|
const opts = parseArgs(process.argv.slice(2));
|
|
367
424
|
if (opts.help) {
|
|
368
|
-
log('Usage: npx mnfst-publish [--staging|--production] [--no-render] [--promote]');
|
|
425
|
+
log('Usage: npx mnfst-publish [--staging|--production] [--source spa|render] [--output-dir <dir>] [--no-render] [--promote]');
|
|
369
426
|
log('Publishes the current Manifest project to managed hosting and prints the live URL.');
|
|
370
427
|
return;
|
|
371
428
|
}
|
|
@@ -446,9 +503,12 @@ export async function main() {
|
|
|
446
503
|
fail('the upload URL returned by the server was malformed.');
|
|
447
504
|
}
|
|
448
505
|
|
|
449
|
-
const rels = collectFiles(root);
|
|
506
|
+
const rels = collectFiles(root, opts.outputDir);
|
|
450
507
|
if (!rels.length) fail('nothing to publish (no files found).');
|
|
451
|
-
|
|
508
|
+
if (opts.outputDir && !existsSync(join(root, opts.outputDir))) {
|
|
509
|
+
fail(`the output folder "${opts.outputDir}" doesn't exist — run the project's build first, then publish again.`);
|
|
510
|
+
}
|
|
511
|
+
const zip = buildZip(root, rels, stampManifests(root, rels));
|
|
452
512
|
log(`Uploading ${rels.length} files (${(zip.length / 1048576).toFixed(1)} MB)…`);
|
|
453
513
|
|
|
454
514
|
const up = await fetchRetry(
|