@riebeckite/honox 0.0.26 → 0.0.28
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/dist/index.js +832 -784
- package/dist/src/ssg/build_output.d.ts +15 -0
- package/dist/src/ssg/diagnostics.d.ts +24 -0
- package/dist/src/ssg/file_system.d.ts +4 -0
- package/dist/src/ssg/inventory.d.ts +5 -0
- package/dist/src/ssg/output_cache.d.ts +29 -0
- package/dist/src/ssg/runtime_metrics.d.ts +4 -0
- package/dist/src/ssg/types.d.ts +122 -0
- package/dist/src/ssg_plugin.d.ts +3 -37
- package/package.json +5 -5
- package/style.css +14 -0
package/dist/index.js
CHANGED
|
@@ -30,6 +30,7 @@ function siteRiebeckiteExternals(siteRoot) {
|
|
|
30
30
|
// src/workspace_packages.ts
|
|
31
31
|
import fs2 from "node:fs";
|
|
32
32
|
import path2 from "node:path";
|
|
33
|
+
import { escapeRegExp } from "@riebeckite/core";
|
|
33
34
|
function createWorkspacePackageAliases(workspaceRoot) {
|
|
34
35
|
const packagesRoot = path2.join(workspaceRoot, "packages");
|
|
35
36
|
if (!fs2.existsSync(packagesRoot)) return [];
|
|
@@ -127,9 +128,6 @@ function resolveExportTarget(target) {
|
|
|
127
128
|
}
|
|
128
129
|
return void 0;
|
|
129
130
|
}
|
|
130
|
-
function escapeRegExp(value) {
|
|
131
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
132
|
-
}
|
|
133
131
|
|
|
134
132
|
// src/resolved_config.ts
|
|
135
133
|
import path3 from "node:path";
|
|
@@ -284,21 +282,8 @@ function isJavaScriptModule(filePath) {
|
|
|
284
282
|
}
|
|
285
283
|
|
|
286
284
|
// src/ssg_plugin.ts
|
|
287
|
-
import {
|
|
288
|
-
import {
|
|
289
|
-
import {
|
|
290
|
-
copyFile,
|
|
291
|
-
lstat,
|
|
292
|
-
mkdir,
|
|
293
|
-
readdir,
|
|
294
|
-
readFile,
|
|
295
|
-
rename,
|
|
296
|
-
rm,
|
|
297
|
-
stat,
|
|
298
|
-
writeFile
|
|
299
|
-
} from "node:fs/promises";
|
|
300
|
-
import { dirname, join, relative, resolve } from "node:path";
|
|
301
|
-
import { performance } from "node:perf_hooks";
|
|
285
|
+
import { rm as rm3 } from "node:fs/promises";
|
|
286
|
+
import { join as join4, relative as relative3, resolve as resolve2 } from "node:path";
|
|
302
287
|
import { defaultExtensionMap, redirectPlugin, toSSG } from "hono/ssg";
|
|
303
288
|
import {
|
|
304
289
|
createServer
|
|
@@ -629,595 +614,211 @@ async function hasSiteOwnedFile(publicDir, logicalPath) {
|
|
|
629
614
|
}
|
|
630
615
|
}
|
|
631
616
|
|
|
632
|
-
// src/
|
|
633
|
-
|
|
617
|
+
// src/ssg/build_output.ts
|
|
618
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
619
|
+
import { mkdir, readdir, readFile, rename, rm, stat } from "node:fs/promises";
|
|
620
|
+
import { dirname, join } from "node:path";
|
|
621
|
+
|
|
622
|
+
// src/ssg/file_system.ts
|
|
623
|
+
import { relative, resolve } from "node:path";
|
|
624
|
+
function isMissingDirectoryError(error) {
|
|
625
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
626
|
+
}
|
|
627
|
+
function isNotFoundError(error) {
|
|
628
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
629
|
+
}
|
|
630
|
+
function isAbsolutePath(path11) {
|
|
631
|
+
return /^([a-z]:)?[\\/]/iu.test(path11);
|
|
632
|
+
}
|
|
633
|
+
function resolveWithinDirectory(directory, relativePath, label) {
|
|
634
|
+
if (!relativePath || relativePath.includes("\0") || isAbsolutePath(relativePath)) {
|
|
635
|
+
throw new Error(`Invalid ${label}: ${relativePath}`);
|
|
636
|
+
}
|
|
637
|
+
const file = resolve(directory, relativePath);
|
|
638
|
+
const relativePathFromDirectory = relative(directory, file);
|
|
639
|
+
if (relativePathFromDirectory.startsWith("..") || relativePathFromDirectory === "" || isAbsolutePath(relativePathFromDirectory)) {
|
|
640
|
+
throw new Error(`Unsafe ${label}: ${relativePath}`);
|
|
641
|
+
}
|
|
642
|
+
return file;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// src/ssg/build_output.ts
|
|
646
|
+
var CLOUDFLARE_WORKER_ASSET_LIMIT = 25 * 1024 * 1024;
|
|
647
|
+
async function findOversizedAssets(directory, relativeDirectory = "") {
|
|
648
|
+
const entries = await readdir(directory, { withFileTypes: true }).catch(
|
|
649
|
+
(error) => {
|
|
650
|
+
if (isMissingDirectoryError(error)) return [];
|
|
651
|
+
throw error;
|
|
652
|
+
}
|
|
653
|
+
);
|
|
654
|
+
const nestedAssets = await Promise.all(
|
|
655
|
+
entries.map(async (entry) => {
|
|
656
|
+
const path11 = join(directory, entry.name);
|
|
657
|
+
const relativePath = join(relativeDirectory, entry.name).replaceAll(
|
|
658
|
+
"\\",
|
|
659
|
+
"/"
|
|
660
|
+
);
|
|
661
|
+
if (entry.isDirectory()) {
|
|
662
|
+
return findOversizedAssets(path11, relativePath);
|
|
663
|
+
}
|
|
664
|
+
if (!entry.isFile()) return [];
|
|
665
|
+
const size = (await stat(path11)).size;
|
|
666
|
+
return size > CLOUDFLARE_WORKER_ASSET_LIMIT ? [{ path: relativePath, size }] : [];
|
|
667
|
+
})
|
|
668
|
+
);
|
|
669
|
+
return nestedAssets.flat();
|
|
670
|
+
}
|
|
671
|
+
function formatBytes(bytes) {
|
|
672
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
|
|
673
|
+
}
|
|
674
|
+
async function emptyBuildOutputDirectory(outDir) {
|
|
675
|
+
const entries = await readdir(outDir, { withFileTypes: true }).catch(
|
|
676
|
+
(error) => {
|
|
677
|
+
if (isMissingDirectoryError(error)) return [];
|
|
678
|
+
throw error;
|
|
679
|
+
}
|
|
680
|
+
);
|
|
681
|
+
for (const entry of entries) {
|
|
682
|
+
await rm(join(outDir, entry.name), { recursive: true, force: true });
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
async function preserveClientBuildArtifacts(outDir) {
|
|
686
|
+
const manifestPath = ".vite/manifest.json";
|
|
687
|
+
let manifestSource;
|
|
688
|
+
try {
|
|
689
|
+
manifestSource = await readFile(join(outDir, manifestPath), "utf8");
|
|
690
|
+
} catch (error) {
|
|
691
|
+
if (isNotFoundError(error)) return void 0;
|
|
692
|
+
throw error;
|
|
693
|
+
}
|
|
694
|
+
const paths = [manifestPath, ...collectClientAssetPaths(manifestSource)];
|
|
695
|
+
const stagingDirectory = join(
|
|
696
|
+
dirname(outDir),
|
|
697
|
+
".riebeckite",
|
|
698
|
+
`client-build-${randomUUID()}`
|
|
699
|
+
);
|
|
700
|
+
for (const relativePath of paths) {
|
|
701
|
+
const source = resolveBuildArtifactPath(outDir, relativePath);
|
|
702
|
+
const destination = resolveBuildArtifactPath(
|
|
703
|
+
stagingDirectory,
|
|
704
|
+
relativePath
|
|
705
|
+
);
|
|
706
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
707
|
+
await rename(source, destination);
|
|
708
|
+
}
|
|
634
709
|
return {
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
outputBytesHashed: 0
|
|
710
|
+
stagingDirectory,
|
|
711
|
+
paths,
|
|
712
|
+
manifestFingerprint: createHash("sha256").update(manifestSource).digest("hex")
|
|
639
713
|
};
|
|
640
714
|
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
715
|
+
async function restoreClientBuildArtifacts(outDir, artifacts) {
|
|
716
|
+
if (!artifacts) return;
|
|
717
|
+
for (const relativePath of artifacts.paths) {
|
|
718
|
+
const source = resolveBuildArtifactPath(
|
|
719
|
+
artifacts.stagingDirectory,
|
|
720
|
+
relativePath
|
|
721
|
+
);
|
|
722
|
+
const destination = resolveBuildArtifactPath(outDir, relativePath);
|
|
723
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
724
|
+
await rename(source, destination);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
async function discardClientBuildArtifacts(artifacts) {
|
|
728
|
+
if (!artifacts) return;
|
|
729
|
+
await rm(artifacts.stagingDirectory, { recursive: true, force: true });
|
|
730
|
+
}
|
|
731
|
+
function collectClientAssetPaths(manifestSource) {
|
|
732
|
+
let manifest;
|
|
733
|
+
try {
|
|
734
|
+
manifest = JSON.parse(manifestSource);
|
|
735
|
+
} catch (error) {
|
|
736
|
+
throw new Error(
|
|
737
|
+
`Vite client manifest could not be parsed: ${error instanceof Error ? error.message : String(error)}`
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
if (typeof manifest !== "object" || manifest === null || Array.isArray(manifest)) {
|
|
741
|
+
throw new Error("Vite client manifest must be an object.");
|
|
742
|
+
}
|
|
743
|
+
const paths = /* @__PURE__ */ new Set();
|
|
744
|
+
for (const entry of Object.values(manifest)) {
|
|
745
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
const assets = entry;
|
|
749
|
+
addClientAssetPath(paths, assets.file);
|
|
750
|
+
addClientAssetPaths(paths, assets.css);
|
|
751
|
+
addClientAssetPaths(paths, assets.assets);
|
|
752
|
+
}
|
|
753
|
+
return [...paths].sort();
|
|
754
|
+
}
|
|
755
|
+
function addClientAssetPath(paths, value) {
|
|
756
|
+
if (typeof value === "string") paths.add(value);
|
|
757
|
+
}
|
|
758
|
+
function addClientAssetPaths(paths, value) {
|
|
759
|
+
if (!Array.isArray(value)) return;
|
|
760
|
+
for (const path11 of value) addClientAssetPath(paths, path11);
|
|
761
|
+
}
|
|
762
|
+
function resolveBuildArtifactPath(directory, relativePath) {
|
|
763
|
+
return resolveWithinDirectory(
|
|
764
|
+
directory,
|
|
765
|
+
relativePath,
|
|
766
|
+
"Vite client manifest asset path"
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// src/ssg/diagnostics.ts
|
|
771
|
+
function formatSsrNoExternal() {
|
|
772
|
+
return "/^honox(?:\\/.*)?$/";
|
|
773
|
+
}
|
|
774
|
+
function logSsgDiagnostics(logger, message) {
|
|
775
|
+
if (process.env.RIEBECKITE_SSG_DIAGNOSTICS !== "1") return;
|
|
776
|
+
logger.info(`[riebeckite-ssg] ${message}`);
|
|
777
|
+
}
|
|
778
|
+
function logGeneratedHtmlAssetDiagnostics(logger, pages) {
|
|
779
|
+
if (process.env.RIEBECKITE_SSG_DIAGNOSTICS !== "1") return;
|
|
780
|
+
const renderedPages = pages.filter(
|
|
781
|
+
(page) => !/<meta\b[^>]*\bhttp-equiv=["']refresh["'][^>]*\bcontent=["'][^"']*\burl=/iu.test(
|
|
782
|
+
page.html
|
|
783
|
+
)
|
|
784
|
+
);
|
|
785
|
+
const cssPages = renderedPages.filter(
|
|
786
|
+
(page) => /<link\b[^>]*\brel=["'][^"']*\bstylesheet\b[^"']*["']/iu.test(page.html)
|
|
787
|
+
);
|
|
788
|
+
const scriptPages = renderedPages.filter(
|
|
789
|
+
(page) => /<script\b[^>]*\bsrc=["'][^"']+["']/iu.test(page.html)
|
|
790
|
+
);
|
|
791
|
+
const missingCss = renderedPages.filter((page) => !cssPages.includes(page)).map((page) => page.path);
|
|
792
|
+
const missingScript = renderedPages.filter((page) => !scriptPages.includes(page)).map((page) => page.path);
|
|
793
|
+
logger.info(
|
|
794
|
+
`[riebeckite-ssg] rendered HTML assets: ${pages.length} documents (${renderedPages.length} pages, ${pages.length - renderedPages.length} redirects) · CSS ${cssPages.length} · JavaScript ${scriptPages.length} · missing CSS ${missingCss.length} · missing JavaScript ${missingScript.length}`
|
|
795
|
+
);
|
|
796
|
+
if (missingCss.length > 0 || missingScript.length > 0) {
|
|
797
|
+
logger.info(
|
|
798
|
+
`[riebeckite-ssg] missing asset references: CSS ${missingCss.join(", ") || "none"} · JavaScript ${missingScript.join(", ") || "none"}`
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
function inspectGeneratedHtmlPages(module, pages, log) {
|
|
803
|
+
const plugins = module.config?.plugins ?? [];
|
|
804
|
+
for (const plugin of plugins) {
|
|
805
|
+
const inspector = plugin.inspectGeneratedHtml;
|
|
806
|
+
if (typeof inspector !== "function") continue;
|
|
807
|
+
for (const page of pages) {
|
|
808
|
+
for (const diagnostic of inspector(page) ?? []) {
|
|
809
|
+
const label = `[${plugin.name ?? "plugin"}] ${page.path}: ${diagnostic.message}`;
|
|
810
|
+
if (diagnostic.severity === "info") log.info(label);
|
|
811
|
+
else log.warn(label);
|
|
695
812
|
}
|
|
696
|
-
const timingsMs = process.env.RIEBECKITE_SSG_METRICS_FILE ? {} : void 0;
|
|
697
|
-
const outDir = resolve(config.root, config.build.outDir);
|
|
698
|
-
removeVirtualEntryChunk(bundle, resolvedVirtualId);
|
|
699
|
-
const server = await measure(
|
|
700
|
-
timingsMs,
|
|
701
|
-
"ssgSetup",
|
|
702
|
-
() => createServer({
|
|
703
|
-
root: config.root,
|
|
704
|
-
define: config.define,
|
|
705
|
-
resolve: {
|
|
706
|
-
...config.resolve,
|
|
707
|
-
builtins: [...config.resolve.builtins, /^node:/]
|
|
708
|
-
},
|
|
709
|
-
plugins: [],
|
|
710
|
-
build: { ssr: true },
|
|
711
|
-
environments: {
|
|
712
|
-
ssr: { resolve: { noExternal: [/^honox(?:\/.*)?$/] } }
|
|
713
|
-
},
|
|
714
|
-
mode: config.mode,
|
|
715
|
-
server: { watch: null }
|
|
716
|
-
})
|
|
717
|
-
);
|
|
718
|
-
try {
|
|
719
|
-
await restoreClientBuildArtifacts(outDir, clientBuildArtifacts);
|
|
720
|
-
logSsgDiagnostics(
|
|
721
|
-
this,
|
|
722
|
-
`SSR noExternal=${formatSsrNoExternal()} · client manifest ${clientBuildArtifacts ? "restored" : "not found"}`
|
|
723
|
-
);
|
|
724
|
-
const module = await measure(
|
|
725
|
-
timingsMs,
|
|
726
|
-
"ssrModuleLoad",
|
|
727
|
-
() => server.ssrLoadModule(entry)
|
|
728
|
-
);
|
|
729
|
-
const app = module.default;
|
|
730
|
-
if (!app) {
|
|
731
|
-
throw new Error(`Failed to find a default export from ${entry}.`);
|
|
732
|
-
}
|
|
733
|
-
const outputChangeSet = await measure(
|
|
734
|
-
timingsMs,
|
|
735
|
-
"outputDependencyResolution",
|
|
736
|
-
async () => module.content?.getOutputChangeSet()
|
|
737
|
-
);
|
|
738
|
-
const appFingerprint = await measure(
|
|
739
|
-
timingsMs,
|
|
740
|
-
"appFingerprint",
|
|
741
|
-
() => computeAppFingerprint(config.root, config.configFile)
|
|
742
|
-
);
|
|
743
|
-
const outputCachePath = join(
|
|
744
|
-
config.root,
|
|
745
|
-
".riebeckite",
|
|
746
|
-
"ssg-output-cache.json"
|
|
747
|
-
);
|
|
748
|
-
const previousOutputCache = await loadOutputCache(
|
|
749
|
-
outputCachePath,
|
|
750
|
-
timingsMs,
|
|
751
|
-
(message) => this.warn(message)
|
|
752
|
-
);
|
|
753
|
-
const siteOwnedOutputPaths = await measure(
|
|
754
|
-
timingsMs,
|
|
755
|
-
"siteOwnedOutputs",
|
|
756
|
-
() => collectSiteOwnedOutputPaths(config.publicDir)
|
|
757
|
-
);
|
|
758
|
-
const siteOutputCollisions = findBuildOutputSiteCollisions(
|
|
759
|
-
Object.keys(bundle),
|
|
760
|
-
siteOwnedOutputPaths
|
|
761
|
-
);
|
|
762
|
-
if (siteOutputCollisions.length > 0) {
|
|
763
|
-
this.warn(
|
|
764
|
-
`Build outputs take precedence over site public assets: ${siteOutputCollisions.join(", ")}`
|
|
765
|
-
);
|
|
766
|
-
}
|
|
767
|
-
const canUseIncremental = outputChangeSet && !outputChangeSet.fullRegenerationRequired && previousOutputCache && previousOutputCache.appFingerprint === appFingerprint && previousOutputCache.clientManifestFingerprint === clientBuildArtifacts?.manifestFingerprint && outputChangeSet.unchanged.every(
|
|
768
|
-
(output) => siteOwnedOutputPaths.has(output.path) || previousOutputCache.outputs[output.path]
|
|
769
|
-
);
|
|
770
|
-
const reusablePaths = new Set(
|
|
771
|
-
canUseIncremental ? outputChangeSet.unchanged.map((output) => output.path) : []
|
|
772
|
-
);
|
|
773
|
-
logSsgDiagnostics(
|
|
774
|
-
this,
|
|
775
|
-
`output cache ${canUseIncremental ? "hit" : "miss"} · ${reusablePaths.size} reusable outputs`
|
|
776
|
-
);
|
|
777
|
-
const shadowedOutputs = /* @__PURE__ */ new Set();
|
|
778
|
-
const skippedRouteCount = canUseIncremental ? outputChangeSet.unchanged.filter(
|
|
779
|
-
(output) => output.kind !== "generated"
|
|
780
|
-
).length : 0;
|
|
781
|
-
const generatedHtml = [];
|
|
782
|
-
const nextOutputCache = {
|
|
783
|
-
version: 4,
|
|
784
|
-
appFingerprint,
|
|
785
|
-
clientManifestFingerprint: clientBuildArtifacts?.manifestFingerprint,
|
|
786
|
-
outputs: {},
|
|
787
|
-
configFile: config.configFile
|
|
788
|
-
};
|
|
789
|
-
const metrics = {
|
|
790
|
-
processedContentCount: 0,
|
|
791
|
-
reusedContentCount: 0,
|
|
792
|
-
candidateOutputCount: outputChangeSet?.candidateOutputCount ?? 0,
|
|
793
|
-
affectedOutputCount: outputChangeSet?.affectedOutputCount ?? 0,
|
|
794
|
-
removedOutputCount: outputChangeSet?.removedOutputCount ?? 0,
|
|
795
|
-
unchangedOutputCount: outputChangeSet?.unchangedOutputCount ?? 0,
|
|
796
|
-
renderedOutputCount: 0,
|
|
797
|
-
emittedOutputCount: 0,
|
|
798
|
-
reusedOutputCount: 0,
|
|
799
|
-
deletedOutputCount: 0,
|
|
800
|
-
shadowedOutputCount: 0,
|
|
801
|
-
fullRegenerationRequired: !canUseIncremental,
|
|
802
|
-
htmlOutputCount: 0,
|
|
803
|
-
assetOutputCount: 0,
|
|
804
|
-
cacheFileSizeBytes: previousOutputCache?.fileSizeBytes ?? null,
|
|
805
|
-
cacheEntriesRead: previousOutputCache ? Object.keys(previousOutputCache.outputs).length : 0,
|
|
806
|
-
cacheEntriesSerialized: 0,
|
|
807
|
-
enumeratedRouteCount: 0,
|
|
808
|
-
renderedRouteCount: 0,
|
|
809
|
-
skippedRouteCount,
|
|
810
|
-
manifestHooksExecuted: 0,
|
|
811
|
-
manifestHooksReused: 0,
|
|
812
|
-
manifestHookCacheHit: 0,
|
|
813
|
-
manifestHookCacheMiss: 0,
|
|
814
|
-
manifestHookDurationMs: 0,
|
|
815
|
-
manifestHookPlugins: {},
|
|
816
|
-
timingsMs: timingsMs ?? {}
|
|
817
|
-
};
|
|
818
|
-
process.env.RIEBECKITE_SSG_FULL_REGENERATION = canUseIncremental ? "" : "1";
|
|
819
|
-
process.env.RIEBECKITE_SSG_SKIP_UNCHANGED = canUseIncremental ? "1" : "";
|
|
820
|
-
const result = await measure(
|
|
821
|
-
timingsMs,
|
|
822
|
-
"toSSG",
|
|
823
|
-
() => toSSG(
|
|
824
|
-
app,
|
|
825
|
-
{
|
|
826
|
-
writeFile: async (filePath, data) => {
|
|
827
|
-
const fileName = relative(outDir, filePath).replaceAll(
|
|
828
|
-
"\\",
|
|
829
|
-
"/"
|
|
830
|
-
);
|
|
831
|
-
if (siteOwnedOutputPaths.has(fileName)) {
|
|
832
|
-
shadowedOutputs.add(fileName);
|
|
833
|
-
return;
|
|
834
|
-
}
|
|
835
|
-
if (canUseIncremental && reusablePaths.has(fileName)) {
|
|
836
|
-
return;
|
|
837
|
-
}
|
|
838
|
-
if (fileName.endsWith(".html") && typeof data === "string") {
|
|
839
|
-
metrics.renderedOutputCount += 1;
|
|
840
|
-
metrics.renderedRouteCount += 1;
|
|
841
|
-
metrics.htmlOutputCount += 1;
|
|
842
|
-
generatedHtml.push({ path: fileName, html: data });
|
|
843
|
-
} else {
|
|
844
|
-
metrics.assetOutputCount += 1;
|
|
845
|
-
}
|
|
846
|
-
measureSync(
|
|
847
|
-
timingsMs,
|
|
848
|
-
"outputWrite",
|
|
849
|
-
() => this.emitFile({
|
|
850
|
-
type: "asset",
|
|
851
|
-
fileName,
|
|
852
|
-
source: data
|
|
853
|
-
})
|
|
854
|
-
);
|
|
855
|
-
metrics.emittedOutputCount += 1;
|
|
856
|
-
nextOutputCache.outputs[fileName] = {
|
|
857
|
-
cachePath: "",
|
|
858
|
-
checksum: ""
|
|
859
|
-
};
|
|
860
|
-
},
|
|
861
|
-
async mkdir() {
|
|
862
|
-
}
|
|
863
|
-
},
|
|
864
|
-
{
|
|
865
|
-
dir: outDir,
|
|
866
|
-
plugins: [redirectPlugin(), ...options.plugins ?? []],
|
|
867
|
-
extensionMap: options.extensionMap ?? defaultExtensionMap
|
|
868
|
-
}
|
|
869
|
-
)
|
|
870
|
-
);
|
|
871
|
-
delete process.env.RIEBECKITE_SSG_FULL_REGENERATION;
|
|
872
|
-
delete process.env.RIEBECKITE_SSG_SKIP_UNCHANGED;
|
|
873
|
-
metrics.enumeratedRouteCount = result.files.length;
|
|
874
|
-
if (!result.success) throw result.error;
|
|
875
|
-
metrics.processedContentCount = module.content?.getProcessedContentCount() ?? 0;
|
|
876
|
-
metrics.reusedContentCount = module.content?.getReusedContentCount() ?? 0;
|
|
877
|
-
const manifestHookMetrics = module.content?.getManifestHookMetrics();
|
|
878
|
-
metrics.manifestHooksExecuted = manifestHookMetrics?.manifestHooksExecuted ?? 0;
|
|
879
|
-
metrics.manifestHooksReused = manifestHookMetrics?.manifestHooksReused ?? 0;
|
|
880
|
-
metrics.manifestHookCacheHit = manifestHookMetrics?.manifestHookCacheHit ?? 0;
|
|
881
|
-
metrics.manifestHookCacheMiss = manifestHookMetrics?.manifestHookCacheMiss ?? 0;
|
|
882
|
-
metrics.manifestHookDurationMs = manifestHookMetrics?.manifestHookDurationMs ?? 0;
|
|
883
|
-
metrics.manifestHookPlugins = manifestHookMetrics?.byPlugin ?? {};
|
|
884
|
-
const emittedGeneratedOutputCount = await measure(
|
|
885
|
-
timingsMs,
|
|
886
|
-
"generatedOutputs",
|
|
887
|
-
() => emitGeneratedOutputs(
|
|
888
|
-
module,
|
|
889
|
-
(asset) => {
|
|
890
|
-
measureSync(
|
|
891
|
-
timingsMs,
|
|
892
|
-
"outputWrite",
|
|
893
|
-
() => this.emitFile(asset)
|
|
894
|
-
);
|
|
895
|
-
metrics.emittedOutputCount += 1;
|
|
896
|
-
nextOutputCache.outputs[asset.fileName] = {
|
|
897
|
-
cachePath: "",
|
|
898
|
-
checksum: ""
|
|
899
|
-
};
|
|
900
|
-
},
|
|
901
|
-
canUseIncremental ? new Set(
|
|
902
|
-
outputChangeSet.affected.filter((output) => output.kind === "generated").map((output) => output.path)
|
|
903
|
-
) : void 0,
|
|
904
|
-
siteOwnedOutputPaths,
|
|
905
|
-
(path11) => shadowedOutputs.add(path11)
|
|
906
|
-
)
|
|
907
|
-
);
|
|
908
|
-
if (!outputChangeSet)
|
|
909
|
-
metrics.candidateOutputCount = result.files.length + emittedGeneratedOutputCount;
|
|
910
|
-
if (canUseIncremental) {
|
|
911
|
-
await measure(timingsMs, "unchangedOutputReuse", async () => {
|
|
912
|
-
for (const output of outputChangeSet.unchanged) {
|
|
913
|
-
const path11 = output.path;
|
|
914
|
-
if (siteOwnedOutputPaths.has(path11)) {
|
|
915
|
-
shadowedOutputs.add(path11);
|
|
916
|
-
continue;
|
|
917
|
-
}
|
|
918
|
-
const cached = previousOutputCache.outputs[path11];
|
|
919
|
-
if (!cached) continue;
|
|
920
|
-
this.emitFile({
|
|
921
|
-
type: "asset",
|
|
922
|
-
fileName: path11,
|
|
923
|
-
source: await decodeOutput(outputCachePath, cached)
|
|
924
|
-
});
|
|
925
|
-
metrics.emittedOutputCount += 1;
|
|
926
|
-
metrics.reusedOutputCount += 1;
|
|
927
|
-
nextOutputCache.outputs[path11] = cached;
|
|
928
|
-
}
|
|
929
|
-
});
|
|
930
|
-
}
|
|
931
|
-
if (outputChangeSet) {
|
|
932
|
-
for (const output of outputChangeSet.removed) {
|
|
933
|
-
if (siteOwnedOutputPaths.has(output.path)) continue;
|
|
934
|
-
await rm(join(outDir, output.path), {
|
|
935
|
-
force: true
|
|
936
|
-
});
|
|
937
|
-
metrics.deletedOutputCount += 1;
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
metrics.shadowedOutputCount = shadowedOutputs.size;
|
|
941
|
-
if (shadowedOutputs.size > 0) {
|
|
942
|
-
this.warn(
|
|
943
|
-
`Site public assets take precedence over generated outputs: ${[
|
|
944
|
-
...shadowedOutputs
|
|
945
|
-
].sort().join(", ")}`
|
|
946
|
-
);
|
|
947
|
-
}
|
|
948
|
-
metrics.cacheEntriesSerialized = Object.keys(
|
|
949
|
-
nextOutputCache.outputs
|
|
950
|
-
).length;
|
|
951
|
-
pendingOutputCache = {
|
|
952
|
-
path: outputCachePath,
|
|
953
|
-
state: nextOutputCache,
|
|
954
|
-
metrics,
|
|
955
|
-
timingsMs,
|
|
956
|
-
outDir,
|
|
957
|
-
publicDir: config.publicDir,
|
|
958
|
-
configFile: config.configFile
|
|
959
|
-
};
|
|
960
|
-
this.info(
|
|
961
|
-
`SSG outputs: ${metrics.renderedOutputCount} rendered · ${metrics.reusedOutputCount} reused · ${metrics.deletedOutputCount} removed`
|
|
962
|
-
);
|
|
963
|
-
inspectGeneratedHtmlPages(module, generatedHtml, {
|
|
964
|
-
warn: (message) => this.warn(message),
|
|
965
|
-
info: (message) => this.info(message)
|
|
966
|
-
});
|
|
967
|
-
logGeneratedHtmlAssetDiagnostics(this, generatedHtml);
|
|
968
|
-
} finally {
|
|
969
|
-
await server.close();
|
|
970
|
-
await discardClientBuildArtifacts(clientBuildArtifacts);
|
|
971
|
-
clientBuildArtifacts = void 0;
|
|
972
|
-
}
|
|
973
|
-
},
|
|
974
|
-
closeBundle: {
|
|
975
|
-
order: "post",
|
|
976
|
-
async handler() {
|
|
977
|
-
if (!resolvedConfig) return;
|
|
978
|
-
const outDir = resolve(
|
|
979
|
-
resolvedConfig.root,
|
|
980
|
-
resolvedConfig.build.outDir
|
|
981
|
-
);
|
|
982
|
-
const oversizedAssets = await findOversizedAssets(outDir);
|
|
983
|
-
for (const asset of oversizedAssets) {
|
|
984
|
-
this.warn(
|
|
985
|
-
`Generated asset ${asset.path} is ${formatBytes(asset.size)}, exceeding Cloudflare Workers' ${formatBytes(cloudflareWorkerAssetLimit)} asset limit.`
|
|
986
|
-
);
|
|
987
|
-
}
|
|
988
|
-
const outputCache = pendingOutputCache;
|
|
989
|
-
if (!outputCache) return;
|
|
990
|
-
try {
|
|
991
|
-
outputCache.state.finalOutputInventory = await measure(
|
|
992
|
-
outputCache.timingsMs,
|
|
993
|
-
"outputInventory",
|
|
994
|
-
() => fingerprintOutputDirectory(outputCache.outDir)
|
|
995
|
-
);
|
|
996
|
-
const publicDir = outputCache.publicDir;
|
|
997
|
-
outputCache.state.publicDirectory = publicDir ? resolve(publicDir) : void 0;
|
|
998
|
-
outputCache.state.publicFingerprint = publicDir ? await measure(
|
|
999
|
-
outputCache.timingsMs,
|
|
1000
|
-
"publicFingerprint",
|
|
1001
|
-
() => fingerprintDirectory(resolve(publicDir))
|
|
1002
|
-
) : void 0;
|
|
1003
|
-
await waitForFaultInjection("build-state-before");
|
|
1004
|
-
await saveOutputCache(
|
|
1005
|
-
outputCache.path,
|
|
1006
|
-
outputCache.state,
|
|
1007
|
-
outputCache.outDir,
|
|
1008
|
-
outputCache.timingsMs
|
|
1009
|
-
);
|
|
1010
|
-
await waitForFaultInjection("build-state-committed");
|
|
1011
|
-
} catch (error) {
|
|
1012
|
-
this.warn(
|
|
1013
|
-
`SSG output cache could not be saved; the next build regenerates every output. ${error instanceof Error ? error.message : String(error)}`
|
|
1014
|
-
);
|
|
1015
|
-
} finally {
|
|
1016
|
-
await writeSsgOutputMetrics(outputCache.metrics);
|
|
1017
|
-
pendingOutputCache = void 0;
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
};
|
|
1022
|
-
}
|
|
1023
|
-
async function findOversizedAssets(directory, relativeDirectory = "") {
|
|
1024
|
-
const entries = await readdir(directory, { withFileTypes: true }).catch(
|
|
1025
|
-
(error) => {
|
|
1026
|
-
if (isMissingDirectoryError(error)) return [];
|
|
1027
|
-
throw error;
|
|
1028
|
-
}
|
|
1029
|
-
);
|
|
1030
|
-
const nestedAssets = await Promise.all(
|
|
1031
|
-
entries.map(async (entry) => {
|
|
1032
|
-
const path11 = join(directory, entry.name);
|
|
1033
|
-
const relativePath = join(relativeDirectory, entry.name).replaceAll(
|
|
1034
|
-
"\\",
|
|
1035
|
-
"/"
|
|
1036
|
-
);
|
|
1037
|
-
if (entry.isDirectory()) {
|
|
1038
|
-
return findOversizedAssets(path11, relativePath);
|
|
1039
|
-
}
|
|
1040
|
-
if (!entry.isFile()) return [];
|
|
1041
|
-
const size = (await stat(path11)).size;
|
|
1042
|
-
return size > cloudflareWorkerAssetLimit ? [{ path: relativePath, size }] : [];
|
|
1043
|
-
})
|
|
1044
|
-
);
|
|
1045
|
-
return nestedAssets.flat();
|
|
1046
|
-
}
|
|
1047
|
-
function isMissingDirectoryError(error) {
|
|
1048
|
-
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
1049
|
-
}
|
|
1050
|
-
function formatBytes(bytes) {
|
|
1051
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
|
|
1052
|
-
}
|
|
1053
|
-
async function emptyBuildOutputDirectory(outDir) {
|
|
1054
|
-
const entries = await readdir(outDir, { withFileTypes: true }).catch(
|
|
1055
|
-
(error) => {
|
|
1056
|
-
if (isMissingDirectoryError(error)) return [];
|
|
1057
|
-
throw error;
|
|
1058
|
-
}
|
|
1059
|
-
);
|
|
1060
|
-
for (const entry of entries) {
|
|
1061
|
-
await rm(join(outDir, entry.name), { recursive: true, force: true });
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
async function preserveClientBuildArtifacts(outDir) {
|
|
1065
|
-
const manifestPath = ".vite/manifest.json";
|
|
1066
|
-
let manifestSource;
|
|
1067
|
-
try {
|
|
1068
|
-
manifestSource = await readFile(join(outDir, manifestPath), "utf8");
|
|
1069
|
-
} catch (error) {
|
|
1070
|
-
if (isNotFoundError(error)) return void 0;
|
|
1071
|
-
throw error;
|
|
1072
|
-
}
|
|
1073
|
-
const paths = [manifestPath, ...collectClientAssetPaths(manifestSource)];
|
|
1074
|
-
const stagingDirectory = join(
|
|
1075
|
-
dirname(outDir),
|
|
1076
|
-
".riebeckite",
|
|
1077
|
-
`client-build-${randomUUID()}`
|
|
1078
|
-
);
|
|
1079
|
-
for (const relativePath of paths) {
|
|
1080
|
-
const source = resolveBuildArtifactPath(outDir, relativePath);
|
|
1081
|
-
const destination = resolveBuildArtifactPath(
|
|
1082
|
-
stagingDirectory,
|
|
1083
|
-
relativePath
|
|
1084
|
-
);
|
|
1085
|
-
await mkdir(dirname(destination), { recursive: true });
|
|
1086
|
-
await rename(source, destination);
|
|
1087
|
-
}
|
|
1088
|
-
return {
|
|
1089
|
-
stagingDirectory,
|
|
1090
|
-
paths,
|
|
1091
|
-
manifestFingerprint: createHash("sha256").update(manifestSource).digest("hex")
|
|
1092
|
-
};
|
|
1093
|
-
}
|
|
1094
|
-
async function restoreClientBuildArtifacts(outDir, artifacts) {
|
|
1095
|
-
if (!artifacts) return;
|
|
1096
|
-
for (const relativePath of artifacts.paths) {
|
|
1097
|
-
const source = resolveBuildArtifactPath(
|
|
1098
|
-
artifacts.stagingDirectory,
|
|
1099
|
-
relativePath
|
|
1100
|
-
);
|
|
1101
|
-
const destination = resolveBuildArtifactPath(outDir, relativePath);
|
|
1102
|
-
await mkdir(dirname(destination), { recursive: true });
|
|
1103
|
-
await rename(source, destination);
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
async function discardClientBuildArtifacts(artifacts) {
|
|
1107
|
-
if (!artifacts) return;
|
|
1108
|
-
await rm(artifacts.stagingDirectory, { recursive: true, force: true });
|
|
1109
|
-
}
|
|
1110
|
-
function collectClientAssetPaths(manifestSource) {
|
|
1111
|
-
let manifest;
|
|
1112
|
-
try {
|
|
1113
|
-
manifest = JSON.parse(manifestSource);
|
|
1114
|
-
} catch (error) {
|
|
1115
|
-
throw new Error(
|
|
1116
|
-
`Vite client manifest could not be parsed: ${error instanceof Error ? error.message : String(error)}`
|
|
1117
|
-
);
|
|
1118
|
-
}
|
|
1119
|
-
if (typeof manifest !== "object" || manifest === null || Array.isArray(manifest)) {
|
|
1120
|
-
throw new Error("Vite client manifest must be an object.");
|
|
1121
|
-
}
|
|
1122
|
-
const paths = /* @__PURE__ */ new Set();
|
|
1123
|
-
for (const entry of Object.values(manifest)) {
|
|
1124
|
-
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
1125
|
-
continue;
|
|
1126
|
-
}
|
|
1127
|
-
const assets = entry;
|
|
1128
|
-
addClientAssetPath(paths, assets.file);
|
|
1129
|
-
addClientAssetPaths(paths, assets.css);
|
|
1130
|
-
addClientAssetPaths(paths, assets.assets);
|
|
1131
|
-
}
|
|
1132
|
-
return [...paths].sort();
|
|
1133
|
-
}
|
|
1134
|
-
function addClientAssetPath(paths, value) {
|
|
1135
|
-
if (typeof value === "string") paths.add(value);
|
|
1136
|
-
}
|
|
1137
|
-
function addClientAssetPaths(paths, value) {
|
|
1138
|
-
if (!Array.isArray(value)) return;
|
|
1139
|
-
for (const path11 of value) addClientAssetPath(paths, path11);
|
|
1140
|
-
}
|
|
1141
|
-
function resolveBuildArtifactPath(directory, relativePath) {
|
|
1142
|
-
if (!relativePath || relativePath.startsWith("/") || relativePath.includes("\0")) {
|
|
1143
|
-
throw new Error(
|
|
1144
|
-
`Vite client manifest has an invalid asset path: ${relativePath}`
|
|
1145
|
-
);
|
|
1146
|
-
}
|
|
1147
|
-
const file = resolve(directory, relativePath);
|
|
1148
|
-
const relativePathFromDirectory = relative(directory, file);
|
|
1149
|
-
if (relativePathFromDirectory.startsWith("..") || relativePathFromDirectory === "" || isAbsolutePath(relativePathFromDirectory)) {
|
|
1150
|
-
throw new Error(
|
|
1151
|
-
`Vite client manifest has an unsafe asset path: ${relativePath}`
|
|
1152
|
-
);
|
|
1153
|
-
}
|
|
1154
|
-
return file;
|
|
1155
|
-
}
|
|
1156
|
-
function isAbsolutePath(path11) {
|
|
1157
|
-
return /^([a-z]:)?[\\/]/iu.test(path11);
|
|
1158
|
-
}
|
|
1159
|
-
function formatSsrNoExternal() {
|
|
1160
|
-
return "/^honox(?:\\/.*)?$/";
|
|
1161
|
-
}
|
|
1162
|
-
function logSsgDiagnostics(logger, message) {
|
|
1163
|
-
if (process.env.RIEBECKITE_SSG_DIAGNOSTICS !== "1") return;
|
|
1164
|
-
logger.info(`[riebeckite-ssg] ${message}`);
|
|
1165
|
-
}
|
|
1166
|
-
function logGeneratedHtmlAssetDiagnostics(logger, pages) {
|
|
1167
|
-
if (process.env.RIEBECKITE_SSG_DIAGNOSTICS !== "1") return;
|
|
1168
|
-
const renderedPages = pages.filter(
|
|
1169
|
-
(page) => !/<meta\b[^>]*\bhttp-equiv=["']refresh["'][^>]*\bcontent=["'][^"']*\burl=/iu.test(
|
|
1170
|
-
page.html
|
|
1171
|
-
)
|
|
1172
|
-
);
|
|
1173
|
-
const cssPages = renderedPages.filter(
|
|
1174
|
-
(page) => /<link\b[^>]*\brel=["'][^"']*\bstylesheet\b[^"']*["']/iu.test(page.html)
|
|
1175
|
-
);
|
|
1176
|
-
const scriptPages = renderedPages.filter(
|
|
1177
|
-
(page) => /<script\b[^>]*\bsrc=["'][^"']+["']/iu.test(page.html)
|
|
1178
|
-
);
|
|
1179
|
-
const missingCss = renderedPages.filter((page) => !cssPages.includes(page)).map((page) => page.path);
|
|
1180
|
-
const missingScript = renderedPages.filter((page) => !scriptPages.includes(page)).map((page) => page.path);
|
|
1181
|
-
logger.info(
|
|
1182
|
-
`[riebeckite-ssg] rendered HTML assets: ${pages.length} documents (${renderedPages.length} pages, ${pages.length - renderedPages.length} redirects) · CSS ${cssPages.length} · JavaScript ${scriptPages.length} · missing CSS ${missingCss.length} · missing JavaScript ${missingScript.length}`
|
|
1183
|
-
);
|
|
1184
|
-
if (missingCss.length > 0 || missingScript.length > 0) {
|
|
1185
|
-
logger.info(
|
|
1186
|
-
`[riebeckite-ssg] missing asset references: CSS ${missingCss.join(", ") || "none"} · JavaScript ${missingScript.join(", ") || "none"}`
|
|
1187
|
-
);
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
function shouldApplyRiebeckiteSsg(_config, env) {
|
|
1191
|
-
return env.command === "build" && env.mode !== "client";
|
|
1192
|
-
}
|
|
1193
|
-
function findBuildOutputSiteCollisions(buildOutputPaths, siteOwnedOutputPaths) {
|
|
1194
|
-
const collisions = /* @__PURE__ */ new Set();
|
|
1195
|
-
for (const path11 of buildOutputPaths) {
|
|
1196
|
-
if (siteOwnedOutputPaths.has(path11)) collisions.add(path11);
|
|
1197
|
-
}
|
|
1198
|
-
return [...collisions].sort();
|
|
1199
|
-
}
|
|
1200
|
-
async function emitGeneratedOutputs(module, emit, affectedPaths, shadowedPaths, onShadowed) {
|
|
1201
|
-
const content = module.content;
|
|
1202
|
-
if (!content) return 0;
|
|
1203
|
-
if (affectedPaths?.size === 0) return 0;
|
|
1204
|
-
const manifest = await content.getManifest();
|
|
1205
|
-
let count = 0;
|
|
1206
|
-
for (const output of manifest.generatedOutputs ?? []) {
|
|
1207
|
-
if (affectedPaths && !affectedPaths.has(output.path)) continue;
|
|
1208
|
-
if (shadowedPaths?.has(output.path)) {
|
|
1209
|
-
onShadowed?.(output.path);
|
|
1210
|
-
continue;
|
|
1211
813
|
}
|
|
1212
|
-
emit({
|
|
1213
|
-
type: "asset",
|
|
1214
|
-
fileName: output.path,
|
|
1215
|
-
source: output.content
|
|
1216
|
-
});
|
|
1217
|
-
count += 1;
|
|
1218
814
|
}
|
|
1219
|
-
return count;
|
|
1220
815
|
}
|
|
816
|
+
|
|
817
|
+
// src/ssg/inventory.ts
|
|
818
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
819
|
+
import { readdir as readdir2, readFile as readFile2 } from "node:fs/promises";
|
|
820
|
+
import { join as join2, relative as relative2 } from "node:path";
|
|
821
|
+
import { performance } from "node:perf_hooks";
|
|
1221
822
|
var APP_FINGERPRINT_IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
1222
823
|
"node_modules",
|
|
1223
824
|
".riebeckite",
|
|
@@ -1233,66 +834,30 @@ var APP_FINGERPRINT_CONFIG_FILES = [
|
|
|
1233
834
|
];
|
|
1234
835
|
async function computeAppFingerprint(root, configFile) {
|
|
1235
836
|
const files = [];
|
|
1236
|
-
await collectAppFiles(
|
|
837
|
+
await collectAppFiles(join2(root, "app"), files);
|
|
1237
838
|
if (configFile) files.push(configFile);
|
|
1238
839
|
for (const name of APP_FINGERPRINT_CONFIG_FILES) {
|
|
1239
|
-
files.push(
|
|
840
|
+
files.push(join2(root, name));
|
|
1240
841
|
}
|
|
1241
842
|
files.sort();
|
|
1242
|
-
const hash =
|
|
843
|
+
const hash = createHash2("sha256");
|
|
1243
844
|
for (const file of files) {
|
|
1244
845
|
let content;
|
|
1245
846
|
try {
|
|
1246
|
-
content = await
|
|
847
|
+
content = await readFile2(file);
|
|
1247
848
|
} catch {
|
|
1248
849
|
continue;
|
|
1249
850
|
}
|
|
1250
|
-
hash.update(
|
|
851
|
+
hash.update(relative2(root, file).replaceAll("\\", "/"));
|
|
1251
852
|
hash.update("\0");
|
|
1252
853
|
hash.update(content);
|
|
1253
854
|
hash.update("\0");
|
|
1254
855
|
}
|
|
1255
856
|
return hash.digest("hex");
|
|
1256
857
|
}
|
|
1257
|
-
async function verifyNoChangeFastPath(input) {
|
|
1258
|
-
const outputCachePath = join(
|
|
1259
|
-
input.root,
|
|
1260
|
-
".riebeckite",
|
|
1261
|
-
"ssg-output-cache.json"
|
|
1262
|
-
);
|
|
1263
|
-
const cache = await loadFastPathCache(fastPathCachePath(outputCachePath));
|
|
1264
|
-
if (!cache)
|
|
1265
|
-
return { used: false, blockReason: "output-cache-missing-or-invalid" };
|
|
1266
|
-
if (cache.outputCacheChecksum !== await checksumFile(outputCachePath)) {
|
|
1267
|
-
return { used: false, blockReason: "output-cache-missing-or-invalid" };
|
|
1268
|
-
}
|
|
1269
|
-
if (cache.appFingerprint !== await computeAppFingerprint(input.root, cache.configFile)) {
|
|
1270
|
-
return { used: false, blockReason: "app-fingerprint-changed" };
|
|
1271
|
-
}
|
|
1272
|
-
if (cache.publicDirectory && cache.publicFingerprint !== await fingerprintDirectory(cache.publicDirectory)) {
|
|
1273
|
-
return { used: false, blockReason: "static-assets-changed" };
|
|
1274
|
-
}
|
|
1275
|
-
if (input.expectedOutputPaths.some(
|
|
1276
|
-
(output) => !cache.outputPaths.includes(output) || !cache.finalOutputInventory[output]
|
|
1277
|
-
)) {
|
|
1278
|
-
return { used: false, blockReason: "output-cache-incomplete" };
|
|
1279
|
-
}
|
|
1280
|
-
const inventoryStartedAt = performance.now();
|
|
1281
|
-
const currentInventory = await fingerprintOutputDirectory(
|
|
1282
|
-
input.outputDirectory,
|
|
1283
|
-
"",
|
|
1284
|
-
input.metrics
|
|
1285
|
-
);
|
|
1286
|
-
if (input.metrics)
|
|
1287
|
-
input.metrics.outputInventoryValidationDurationMs += performance.now() - inventoryStartedAt;
|
|
1288
|
-
if (!inventoriesEqual(cache.finalOutputInventory, currentInventory)) {
|
|
1289
|
-
return { used: false, blockReason: "output-inventory-mismatch" };
|
|
1290
|
-
}
|
|
1291
|
-
return { used: true };
|
|
1292
|
-
}
|
|
1293
858
|
async function fingerprintDirectory(directory) {
|
|
1294
859
|
const inventory = await fingerprintOutputDirectory(directory);
|
|
1295
|
-
const hash =
|
|
860
|
+
const hash = createHash2("sha256");
|
|
1296
861
|
for (const [path11, checksum] of Object.entries(inventory).sort(
|
|
1297
862
|
([left], [right]) => left.localeCompare(right)
|
|
1298
863
|
)) {
|
|
@@ -1301,7 +866,7 @@ async function fingerprintDirectory(directory) {
|
|
|
1301
866
|
return hash.digest("hex");
|
|
1302
867
|
}
|
|
1303
868
|
async function fingerprintOutputDirectory(directory, relativeDirectory = "", metrics) {
|
|
1304
|
-
const entries = await
|
|
869
|
+
const entries = await readdir2(directory, { withFileTypes: true }).catch(
|
|
1305
870
|
(error) => {
|
|
1306
871
|
if (isMissingDirectoryError(error)) return void 0;
|
|
1307
872
|
throw error;
|
|
@@ -1310,8 +875,8 @@ async function fingerprintOutputDirectory(directory, relativeDirectory = "", met
|
|
|
1310
875
|
if (!entries) return {};
|
|
1311
876
|
const inventory = {};
|
|
1312
877
|
for (const entry of entries) {
|
|
1313
|
-
const file =
|
|
1314
|
-
const relativePath =
|
|
878
|
+
const file = join2(directory, entry.name);
|
|
879
|
+
const relativePath = join2(relativeDirectory, entry.name).replaceAll(
|
|
1315
880
|
"\\",
|
|
1316
881
|
"/"
|
|
1317
882
|
);
|
|
@@ -1324,8 +889,8 @@ async function fingerprintOutputDirectory(directory, relativeDirectory = "", met
|
|
|
1324
889
|
}
|
|
1325
890
|
if (entry.isFile()) {
|
|
1326
891
|
const hashedAt = performance.now();
|
|
1327
|
-
const content = await
|
|
1328
|
-
inventory[relativePath] =
|
|
892
|
+
const content = await readFile2(file);
|
|
893
|
+
inventory[relativePath] = createHash2("sha256").update(content).digest("hex");
|
|
1329
894
|
if (metrics) {
|
|
1330
895
|
metrics.outputHashValidationDurationMs += performance.now() - hashedAt;
|
|
1331
896
|
metrics.outputFilesHashed += 1;
|
|
@@ -1341,12 +906,12 @@ function inventoriesEqual(left, right) {
|
|
|
1341
906
|
return leftPaths.length === rightPaths.length && leftPaths.every((path11) => left[path11] === right[path11]);
|
|
1342
907
|
}
|
|
1343
908
|
async function collectAppFiles(directory, files) {
|
|
1344
|
-
const entries = await
|
|
909
|
+
const entries = await readdir2(directory, { withFileTypes: true }).catch(
|
|
1345
910
|
() => void 0
|
|
1346
911
|
);
|
|
1347
912
|
if (!entries) return;
|
|
1348
913
|
for (const entry of entries) {
|
|
1349
|
-
const full =
|
|
914
|
+
const full = join2(directory, entry.name);
|
|
1350
915
|
if (entry.isDirectory()) {
|
|
1351
916
|
if (APP_FINGERPRINT_IGNORED_DIRECTORIES.has(entry.name)) continue;
|
|
1352
917
|
await collectAppFiles(full, files);
|
|
@@ -1355,9 +920,114 @@ async function collectAppFiles(directory, files) {
|
|
|
1355
920
|
}
|
|
1356
921
|
}
|
|
1357
922
|
}
|
|
923
|
+
|
|
924
|
+
// src/ssg/output_cache.ts
|
|
925
|
+
import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
|
|
926
|
+
import { createReadStream } from "node:fs";
|
|
927
|
+
import {
|
|
928
|
+
copyFile,
|
|
929
|
+
lstat,
|
|
930
|
+
mkdir as mkdir2,
|
|
931
|
+
readdir as readdir3,
|
|
932
|
+
readFile as readFile3,
|
|
933
|
+
rename as rename2,
|
|
934
|
+
rm as rm2,
|
|
935
|
+
writeFile as writeFile2
|
|
936
|
+
} from "node:fs/promises";
|
|
937
|
+
import { dirname as dirname2, join as join3 } from "node:path";
|
|
938
|
+
import { performance as performance3 } from "node:perf_hooks";
|
|
939
|
+
|
|
940
|
+
// src/ssg/runtime_metrics.ts
|
|
941
|
+
import { writeFile } from "node:fs/promises";
|
|
942
|
+
import { performance as performance2 } from "node:perf_hooks";
|
|
943
|
+
async function measure(timingsMs, name, fn) {
|
|
944
|
+
if (!timingsMs) return await fn();
|
|
945
|
+
const start = performance2.now();
|
|
946
|
+
try {
|
|
947
|
+
return await fn();
|
|
948
|
+
} finally {
|
|
949
|
+
if (timingsMs)
|
|
950
|
+
timingsMs[name] = (timingsMs[name] ?? 0) + performance2.now() - start;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
function measureSync(timingsMs, name, fn) {
|
|
954
|
+
if (!timingsMs) return fn();
|
|
955
|
+
const start = performance2.now();
|
|
956
|
+
try {
|
|
957
|
+
return fn();
|
|
958
|
+
} finally {
|
|
959
|
+
if (timingsMs)
|
|
960
|
+
timingsMs[name] = (timingsMs[name] ?? 0) + performance2.now() - start;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
async function writeSsgOutputMetrics(metrics) {
|
|
964
|
+
const file = process.env.RIEBECKITE_SSG_METRICS_FILE;
|
|
965
|
+
if (!file) return;
|
|
966
|
+
await writeFile(file, `${JSON.stringify(metrics)}
|
|
967
|
+
`, "utf8");
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// src/ssg/output_cache.ts
|
|
971
|
+
var faultInjectionCounts = /* @__PURE__ */ new Map();
|
|
972
|
+
function decideOutputCacheRejection(decision) {
|
|
973
|
+
if (!decision.changeSetPresent) return "change-set-unavailable";
|
|
974
|
+
if (decision.changeSetRequiresFullRegeneration)
|
|
975
|
+
return "full-regeneration-required";
|
|
976
|
+
if (!decision.outputCacheLoaded) return "output-cache-missing-or-invalid";
|
|
977
|
+
if (!decision.appFingerprintMatch) return "app-fingerprint-changed";
|
|
978
|
+
if (!decision.clientManifestFingerprintMatch)
|
|
979
|
+
return "client-manifest-fingerprint-changed";
|
|
980
|
+
if (decision.uncoveredUnchangedCount > 0)
|
|
981
|
+
return "unchanged-output-not-cached";
|
|
982
|
+
return void 0;
|
|
983
|
+
}
|
|
984
|
+
function createNoChangeFastPathOutputMetrics() {
|
|
985
|
+
return {
|
|
986
|
+
outputInventoryValidationDurationMs: 0,
|
|
987
|
+
outputHashValidationDurationMs: 0,
|
|
988
|
+
outputFilesHashed: 0,
|
|
989
|
+
outputBytesHashed: 0
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
async function verifyNoChangeFastPath(input) {
|
|
993
|
+
const outputCachePath = join3(
|
|
994
|
+
input.root,
|
|
995
|
+
".riebeckite",
|
|
996
|
+
"ssg-output-cache.json"
|
|
997
|
+
);
|
|
998
|
+
const cache = await loadFastPathCache(fastPathCachePath(outputCachePath));
|
|
999
|
+
if (!cache)
|
|
1000
|
+
return { used: false, blockReason: "output-cache-missing-or-invalid" };
|
|
1001
|
+
if (cache.outputCacheChecksum !== await checksumFile(outputCachePath)) {
|
|
1002
|
+
return { used: false, blockReason: "output-cache-missing-or-invalid" };
|
|
1003
|
+
}
|
|
1004
|
+
if (cache.appFingerprint !== await computeAppFingerprint(input.root, cache.configFile)) {
|
|
1005
|
+
return { used: false, blockReason: "app-fingerprint-changed" };
|
|
1006
|
+
}
|
|
1007
|
+
if (cache.publicDirectory && cache.publicFingerprint !== await fingerprintDirectory(cache.publicDirectory)) {
|
|
1008
|
+
return { used: false, blockReason: "static-assets-changed" };
|
|
1009
|
+
}
|
|
1010
|
+
if (input.expectedOutputPaths.some(
|
|
1011
|
+
(output) => !cache.outputPaths.includes(output) || !cache.finalOutputInventory[output]
|
|
1012
|
+
)) {
|
|
1013
|
+
return { used: false, blockReason: "output-cache-incomplete" };
|
|
1014
|
+
}
|
|
1015
|
+
const inventoryStartedAt = performance3.now();
|
|
1016
|
+
const currentInventory = await fingerprintOutputDirectory(
|
|
1017
|
+
input.outputDirectory,
|
|
1018
|
+
"",
|
|
1019
|
+
input.metrics
|
|
1020
|
+
);
|
|
1021
|
+
if (input.metrics)
|
|
1022
|
+
input.metrics.outputInventoryValidationDurationMs += performance3.now() - inventoryStartedAt;
|
|
1023
|
+
if (!inventoriesEqual(cache.finalOutputInventory, currentInventory)) {
|
|
1024
|
+
return { used: false, blockReason: "output-inventory-mismatch" };
|
|
1025
|
+
}
|
|
1026
|
+
return { used: true };
|
|
1027
|
+
}
|
|
1358
1028
|
async function loadOutputCache(path11, timingsMs, log = () => {
|
|
1359
1029
|
}) {
|
|
1360
|
-
if (!await isSafeOutputCacheDirectory(
|
|
1030
|
+
if (!await isSafeOutputCacheDirectory(dirname2(path11))) {
|
|
1361
1031
|
log("SSG output cache directory is unsafe and will be ignored.");
|
|
1362
1032
|
return void 0;
|
|
1363
1033
|
}
|
|
@@ -1369,7 +1039,7 @@ async function loadOutputCache(path11, timingsMs, log = () => {
|
|
|
1369
1039
|
raw = await measure(
|
|
1370
1040
|
timingsMs,
|
|
1371
1041
|
"outputCacheRead",
|
|
1372
|
-
() =>
|
|
1042
|
+
() => readFile3(path11, "utf8")
|
|
1373
1043
|
);
|
|
1374
1044
|
} catch (error) {
|
|
1375
1045
|
if (isNotFoundError(error)) return void 0;
|
|
@@ -1385,44 +1055,26 @@ async function loadOutputCache(path11, timingsMs, log = () => {
|
|
|
1385
1055
|
log(
|
|
1386
1056
|
`SSG output cache is corrupted and will be rebuilt. ${error instanceof Error ? error.message : String(error)}`
|
|
1387
1057
|
);
|
|
1388
|
-
return void 0;
|
|
1389
|
-
}
|
|
1390
|
-
if (!isOutputCacheState(parsed)) {
|
|
1391
|
-
log("SSG output cache uses an unsupported format and will be rebuilt.");
|
|
1392
|
-
return void 0;
|
|
1393
|
-
}
|
|
1394
|
-
const entriesAreValid = await measure(
|
|
1395
|
-
timingsMs,
|
|
1396
|
-
"outputCacheEntriesValidate",
|
|
1397
|
-
() => validateOutputCacheEntries(path11, parsed.outputs, timingsMs)
|
|
1398
|
-
);
|
|
1399
|
-
if (!entriesAreValid) {
|
|
1400
|
-
log("SSG output cache entries are corrupted and will be rebuilt.");
|
|
1401
|
-
return void 0;
|
|
1402
|
-
}
|
|
1403
|
-
return timingsMs ? { ...parsed, fileSizeBytes: Buffer.byteLength(raw) } : parsed;
|
|
1404
|
-
}
|
|
1405
|
-
function isOutputCacheState(value) {
|
|
1406
|
-
if (typeof value !== "object" || value === null) return false;
|
|
1407
|
-
const candidate = value;
|
|
1408
|
-
return candidate.version === 4 && (candidate.appFingerprint === void 0 || typeof candidate.appFingerprint === "string") && (candidate.clientManifestFingerprint === void 0 || typeof candidate.clientManifestFingerprint === "string") && isOutputCacheEntries(candidate.outputs) && (candidate.finalOutputInventory === void 0 || isOutputInventory(candidate.finalOutputInventory)) && (candidate.publicDirectory === void 0 || typeof candidate.publicDirectory === "string") && (candidate.publicFingerprint === void 0 || typeof candidate.publicFingerprint === "string") && (candidate.configFile === void 0 || typeof candidate.configFile === "string");
|
|
1409
|
-
}
|
|
1410
|
-
function isOutputInventory(value) {
|
|
1411
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.entries(value).every(
|
|
1412
|
-
([outputPath, checksum]) => isSafeLogicalOutputPath(outputPath) && isChecksum(checksum)
|
|
1413
|
-
);
|
|
1414
|
-
}
|
|
1415
|
-
function isOutputCacheEntries(value) {
|
|
1416
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1417
|
-
return false;
|
|
1058
|
+
return void 0;
|
|
1418
1059
|
}
|
|
1419
|
-
|
|
1420
|
-
(
|
|
1060
|
+
if (!isOutputCacheState(parsed)) {
|
|
1061
|
+
log("SSG output cache uses an unsupported format and will be rebuilt.");
|
|
1062
|
+
return void 0;
|
|
1063
|
+
}
|
|
1064
|
+
const entriesAreValid = await measure(
|
|
1065
|
+
timingsMs,
|
|
1066
|
+
"outputCacheEntriesValidate",
|
|
1067
|
+
() => validateOutputCacheEntries(path11, parsed.outputs, timingsMs)
|
|
1421
1068
|
);
|
|
1069
|
+
if (!entriesAreValid) {
|
|
1070
|
+
log("SSG output cache entries are corrupted and will be rebuilt.");
|
|
1071
|
+
return void 0;
|
|
1072
|
+
}
|
|
1073
|
+
return timingsMs ? { ...parsed, fileSizeBytes: Buffer.byteLength(raw) } : parsed;
|
|
1422
1074
|
}
|
|
1423
1075
|
async function saveOutputCache(path11, state, outputDirectory, timingsMs) {
|
|
1424
|
-
const directory =
|
|
1425
|
-
await
|
|
1076
|
+
const directory = dirname2(path11);
|
|
1077
|
+
await mkdir2(directory, { recursive: true });
|
|
1426
1078
|
if (!await isSafeOutputCacheDirectory(directory)) {
|
|
1427
1079
|
throw new Error(`Unsafe SSG output cache directory: ${directory}`);
|
|
1428
1080
|
}
|
|
@@ -1430,7 +1082,7 @@ async function saveOutputCache(path11, state, outputDirectory, timingsMs) {
|
|
|
1430
1082
|
throw new Error(`Unsafe SSG output cache file: ${path11}`);
|
|
1431
1083
|
}
|
|
1432
1084
|
const entriesPath = outputCacheEntriesPath(path11);
|
|
1433
|
-
const temporaryPath = `${path11}.${
|
|
1085
|
+
const temporaryPath = `${path11}.${randomUUID2()}.tmp`;
|
|
1434
1086
|
try {
|
|
1435
1087
|
await waitForFaultInjection("metadata-save-start");
|
|
1436
1088
|
const outputs = await measure(
|
|
@@ -1453,7 +1105,7 @@ async function saveOutputCache(path11, state, outputDirectory, timingsMs) {
|
|
|
1453
1105
|
await measure(
|
|
1454
1106
|
timingsMs,
|
|
1455
1107
|
"outputCacheWrite",
|
|
1456
|
-
() =>
|
|
1108
|
+
() => writeFile2(temporaryPath, serialized, "utf8")
|
|
1457
1109
|
);
|
|
1458
1110
|
await measure(
|
|
1459
1111
|
timingsMs,
|
|
@@ -1461,7 +1113,7 @@ async function saveOutputCache(path11, state, outputDirectory, timingsMs) {
|
|
|
1461
1113
|
() => replaceFile(temporaryPath, path11)
|
|
1462
1114
|
);
|
|
1463
1115
|
await waitForFaultInjection("metadata-committed");
|
|
1464
|
-
const checksum =
|
|
1116
|
+
const checksum = createHash3("sha256").update(serialized).digest("hex");
|
|
1465
1117
|
if (state.finalOutputInventory && state.configFile) {
|
|
1466
1118
|
await saveFastPathCache(
|
|
1467
1119
|
fastPathCachePath(path11),
|
|
@@ -1476,10 +1128,28 @@ async function saveOutputCache(path11, state, outputDirectory, timingsMs) {
|
|
|
1476
1128
|
);
|
|
1477
1129
|
return checksum;
|
|
1478
1130
|
} catch (error) {
|
|
1479
|
-
await
|
|
1131
|
+
await rm2(temporaryPath, { force: true }).catch(() => void 0);
|
|
1480
1132
|
throw error;
|
|
1481
1133
|
}
|
|
1482
1134
|
}
|
|
1135
|
+
function isOutputCacheState(value) {
|
|
1136
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1137
|
+
const candidate = value;
|
|
1138
|
+
return candidate.version === 4 && (candidate.appFingerprint === void 0 || typeof candidate.appFingerprint === "string") && (candidate.clientManifestFingerprint === void 0 || typeof candidate.clientManifestFingerprint === "string") && isOutputCacheEntries(candidate.outputs) && (candidate.finalOutputInventory === void 0 || isOutputInventory(candidate.finalOutputInventory)) && (candidate.publicDirectory === void 0 || typeof candidate.publicDirectory === "string") && (candidate.publicFingerprint === void 0 || typeof candidate.publicFingerprint === "string") && (candidate.configFile === void 0 || typeof candidate.configFile === "string");
|
|
1139
|
+
}
|
|
1140
|
+
function isOutputInventory(value) {
|
|
1141
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.entries(value).every(
|
|
1142
|
+
([outputPath, checksum]) => isSafeLogicalOutputPath(outputPath) && isChecksum(checksum)
|
|
1143
|
+
);
|
|
1144
|
+
}
|
|
1145
|
+
function isOutputCacheEntries(value) {
|
|
1146
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1147
|
+
return false;
|
|
1148
|
+
}
|
|
1149
|
+
return Object.entries(value).every(
|
|
1150
|
+
([outputPath, entry]) => isSafeLogicalOutputPath(outputPath) && typeof entry === "object" && entry !== null && typeof entry.cachePath === "string" && entry.cachePath === outputCacheEntryName(outputPath) && isChecksum(entry.checksum)
|
|
1151
|
+
);
|
|
1152
|
+
}
|
|
1483
1153
|
function outputCacheEntriesPath(outputCachePath) {
|
|
1484
1154
|
return outputCachePath.replace(/\.json$/, ".entries");
|
|
1485
1155
|
}
|
|
@@ -1498,7 +1168,7 @@ async function saveOutputCacheEntries(directory, outputDirectory, outputs, final
|
|
|
1498
1168
|
}
|
|
1499
1169
|
const source = resolveOutputCacheFile(outputDirectory, outputPath);
|
|
1500
1170
|
const cachePath = outputCacheEntryName(outputPath);
|
|
1501
|
-
const destination =
|
|
1171
|
+
const destination = join3(directory, cachePath);
|
|
1502
1172
|
await waitForFaultInjection("entry-create-start");
|
|
1503
1173
|
await replaceOutputCacheEntry(source, destination, timingsMs);
|
|
1504
1174
|
const checksum = finalOutputInventory?.[outputPath];
|
|
@@ -1555,21 +1225,21 @@ function createFastPathCacheState(state, outputCacheChecksum) {
|
|
|
1555
1225
|
};
|
|
1556
1226
|
}
|
|
1557
1227
|
async function saveFastPathCache(path11, state, timingsMs) {
|
|
1558
|
-
const directory =
|
|
1559
|
-
await
|
|
1228
|
+
const directory = dirname2(path11);
|
|
1229
|
+
await mkdir2(directory, { recursive: true });
|
|
1560
1230
|
if (!await isSafeOutputCacheDirectory(directory)) {
|
|
1561
1231
|
throw new Error(`Unsafe SSG fast-path cache directory: ${directory}`);
|
|
1562
1232
|
}
|
|
1563
1233
|
if (!await isSafeCacheFileTarget(path11)) {
|
|
1564
1234
|
throw new Error(`Unsafe SSG fast-path cache file: ${path11}`);
|
|
1565
1235
|
}
|
|
1566
|
-
const temporaryPath = `${path11}.${
|
|
1236
|
+
const temporaryPath = `${path11}.${randomUUID2()}.tmp`;
|
|
1567
1237
|
try {
|
|
1568
1238
|
await waitForFaultInjection("sidecar-save-start");
|
|
1569
1239
|
await measure(
|
|
1570
1240
|
timingsMs,
|
|
1571
1241
|
"sidecarWrite",
|
|
1572
|
-
() =>
|
|
1242
|
+
() => writeFile2(temporaryPath, `${JSON.stringify(state)}
|
|
1573
1243
|
`, "utf8")
|
|
1574
1244
|
);
|
|
1575
1245
|
await measure(
|
|
@@ -1579,15 +1249,15 @@ async function saveFastPathCache(path11, state, timingsMs) {
|
|
|
1579
1249
|
);
|
|
1580
1250
|
await waitForFaultInjection("sidecar-committed");
|
|
1581
1251
|
} catch (error) {
|
|
1582
|
-
await
|
|
1252
|
+
await rm2(temporaryPath, { force: true }).catch(() => void 0);
|
|
1583
1253
|
throw error;
|
|
1584
1254
|
}
|
|
1585
1255
|
}
|
|
1586
1256
|
async function loadFastPathCache(path11) {
|
|
1587
1257
|
try {
|
|
1588
|
-
if (!await isSafeOutputCacheDirectory(
|
|
1258
|
+
if (!await isSafeOutputCacheDirectory(dirname2(path11))) return void 0;
|
|
1589
1259
|
if (!await isRegularFile(path11)) return void 0;
|
|
1590
|
-
const candidate = JSON.parse(await
|
|
1260
|
+
const candidate = JSON.parse(await readFile3(path11, "utf8"));
|
|
1591
1261
|
return isFastPathCacheState(candidate) ? candidate : void 0;
|
|
1592
1262
|
} catch {
|
|
1593
1263
|
return void 0;
|
|
@@ -1601,7 +1271,7 @@ function isFastPathCacheState(value) {
|
|
|
1601
1271
|
);
|
|
1602
1272
|
}
|
|
1603
1273
|
async function checksumFile(path11) {
|
|
1604
|
-
const hash =
|
|
1274
|
+
const hash = createHash3("sha256");
|
|
1605
1275
|
try {
|
|
1606
1276
|
for await (const chunk of createReadStream(path11)) hash.update(chunk);
|
|
1607
1277
|
return hash.digest("hex");
|
|
@@ -1611,11 +1281,11 @@ async function checksumFile(path11) {
|
|
|
1611
1281
|
}
|
|
1612
1282
|
async function replaceFile(temporaryPath, targetPath) {
|
|
1613
1283
|
try {
|
|
1614
|
-
await
|
|
1284
|
+
await rename2(temporaryPath, targetPath);
|
|
1615
1285
|
} catch (error) {
|
|
1616
1286
|
if (!isReplacementFailure(error)) throw error;
|
|
1617
|
-
await
|
|
1618
|
-
await
|
|
1287
|
+
await rm2(targetPath, { force: true });
|
|
1288
|
+
await rename2(temporaryPath, targetPath);
|
|
1619
1289
|
}
|
|
1620
1290
|
}
|
|
1621
1291
|
function isReplacementFailure(error) {
|
|
@@ -1625,31 +1295,24 @@ function isReplacementFailure(error) {
|
|
|
1625
1295
|
const code = String(error.code);
|
|
1626
1296
|
return code === "EPERM" || code === "EEXIST" || code === "EACCES";
|
|
1627
1297
|
}
|
|
1628
|
-
function isNotFoundError(error) {
|
|
1629
|
-
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
1630
|
-
}
|
|
1631
1298
|
async function decodeOutput(outputCachePath, entry) {
|
|
1632
1299
|
const file = resolveOutputCacheFile(
|
|
1633
1300
|
outputCacheEntriesPath(outputCachePath),
|
|
1634
1301
|
entry.cachePath
|
|
1635
1302
|
);
|
|
1636
|
-
const content = await
|
|
1637
|
-
const checksum =
|
|
1303
|
+
const content = await readFile3(file);
|
|
1304
|
+
const checksum = createHash3("sha256").update(content).digest("hex");
|
|
1638
1305
|
if (checksum !== entry.checksum) {
|
|
1639
1306
|
throw new Error("SSG output cache entry checksum mismatch.");
|
|
1640
1307
|
}
|
|
1641
1308
|
return content;
|
|
1642
1309
|
}
|
|
1643
1310
|
function resolveOutputCacheFile(directory, relativePath) {
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
if (relativePathFromDirectory.startsWith("..") || relativePathFromDirectory === "" || isAbsolutePath(relativePathFromDirectory)) {
|
|
1650
|
-
throw new Error(`Unsafe SSG output cache path: ${relativePath}`);
|
|
1651
|
-
}
|
|
1652
|
-
return file;
|
|
1311
|
+
return resolveWithinDirectory(
|
|
1312
|
+
directory,
|
|
1313
|
+
relativePath,
|
|
1314
|
+
"SSG output cache path"
|
|
1315
|
+
);
|
|
1653
1316
|
}
|
|
1654
1317
|
function isSafeLogicalOutputPath(path11) {
|
|
1655
1318
|
if (typeof path11 !== "string" || !path11 || path11.includes("\\") || path11.includes("\0") || isAbsolutePath(path11)) {
|
|
@@ -1668,13 +1331,13 @@ function assertSafeLogicalOutputPath(path11) {
|
|
|
1668
1331
|
}
|
|
1669
1332
|
}
|
|
1670
1333
|
function outputCacheEntryName(outputPath) {
|
|
1671
|
-
return
|
|
1334
|
+
return createHash3("sha256").update(outputPath).digest("hex");
|
|
1672
1335
|
}
|
|
1673
1336
|
function isChecksum(value) {
|
|
1674
1337
|
return typeof value === "string" && /^[a-f0-9]{64}$/u.test(value);
|
|
1675
1338
|
}
|
|
1676
1339
|
async function ensureOutputCacheEntriesDirectory(directory) {
|
|
1677
|
-
await
|
|
1340
|
+
await mkdir2(directory, { recursive: true });
|
|
1678
1341
|
if (!await isSafeOutputCacheEntriesDirectory(directory)) {
|
|
1679
1342
|
throw new Error(`Unsafe SSG output cache entries directory: ${directory}`);
|
|
1680
1343
|
}
|
|
@@ -1709,12 +1372,12 @@ async function replaceOutputCacheEntry(source, destination, timingsMs) {
|
|
|
1709
1372
|
if ((await lstat(destination).catch(() => void 0))?.isSymbolicLink()) {
|
|
1710
1373
|
throw new Error(`Unsafe SSG output cache entry: ${destination}`);
|
|
1711
1374
|
}
|
|
1712
|
-
const temporaryPath = `${destination}.${
|
|
1375
|
+
const temporaryPath = `${destination}.${randomUUID2()}.tmp`;
|
|
1713
1376
|
try {
|
|
1714
1377
|
await measure(
|
|
1715
1378
|
timingsMs,
|
|
1716
1379
|
"outputCacheEntryWrite",
|
|
1717
|
-
() =>
|
|
1380
|
+
() => writeFile2(temporaryPath, new Uint8Array())
|
|
1718
1381
|
);
|
|
1719
1382
|
await waitForFaultInjection("entry-body-write");
|
|
1720
1383
|
await measure(
|
|
@@ -1730,7 +1393,7 @@ async function replaceOutputCacheEntry(source, destination, timingsMs) {
|
|
|
1730
1393
|
);
|
|
1731
1394
|
await waitForFaultInjection("entry-committed");
|
|
1732
1395
|
} catch (error) {
|
|
1733
|
-
await
|
|
1396
|
+
await rm2(temporaryPath, { force: true }).catch(() => void 0);
|
|
1734
1397
|
throw error;
|
|
1735
1398
|
}
|
|
1736
1399
|
}
|
|
@@ -1744,84 +1407,469 @@ async function collectUnreferencedOutputCacheEntries(directory, outputs, timings
|
|
|
1744
1407
|
const entries = await measure(
|
|
1745
1408
|
timingsMs,
|
|
1746
1409
|
"outputCacheGcScan",
|
|
1747
|
-
() =>
|
|
1410
|
+
() => readdir3(directory, { withFileTypes: true })
|
|
1748
1411
|
);
|
|
1749
1412
|
for (const entry of entries) {
|
|
1750
1413
|
if (!/^[a-f0-9]{64}$/u.test(entry.name) || referenced.has(entry.name)) {
|
|
1751
1414
|
continue;
|
|
1752
1415
|
}
|
|
1753
|
-
const path11 =
|
|
1416
|
+
const path11 = join3(directory, entry.name);
|
|
1754
1417
|
if (await isRegularFile(path11)) {
|
|
1755
1418
|
await waitForFaultInjection("gc-entry-delete");
|
|
1756
1419
|
await measure(
|
|
1757
1420
|
timingsMs,
|
|
1758
|
-
"outputCacheGcDelete",
|
|
1759
|
-
() =>
|
|
1421
|
+
"outputCacheGcDelete",
|
|
1422
|
+
() => rm2(path11, { force: true })
|
|
1423
|
+
);
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
} catch {
|
|
1427
|
+
return;
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
async function waitForFaultInjection(point) {
|
|
1431
|
+
if (process.env.NODE_ENV !== "test" || process.env.RIEBECKITE_SSG_FAULT_INJECTION !== "1" || process.env.RIEBECKITE_SSG_FAULT_POINT !== point) {
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
const occurrence = Number.parseInt(
|
|
1435
|
+
process.env.RIEBECKITE_SSG_FAULT_OCCURRENCE ?? "1",
|
|
1436
|
+
10
|
|
1437
|
+
);
|
|
1438
|
+
const count = (faultInjectionCounts.get(point) ?? 0) + 1;
|
|
1439
|
+
faultInjectionCounts.set(point, count);
|
|
1440
|
+
if (!Number.isSafeInteger(occurrence) || occurrence < 1 || count !== occurrence)
|
|
1441
|
+
return;
|
|
1442
|
+
const signalFile = process.env.RIEBECKITE_SSG_FAULT_SIGNAL_FILE;
|
|
1443
|
+
if (!signalFile) return;
|
|
1444
|
+
await writeFile2(signalFile, `${point}
|
|
1445
|
+
`, "utf8");
|
|
1446
|
+
await new Promise(() => {
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
// src/ssg_plugin.ts
|
|
1451
|
+
var defaultEntry = "./src/index.tsx";
|
|
1452
|
+
function riebeckiteSsg(options = {}) {
|
|
1453
|
+
const virtualId = "virtual:riebeckite-ssg-void-entry";
|
|
1454
|
+
const resolvedVirtualId = `\0${virtualId}`;
|
|
1455
|
+
const entry = options.entry ?? defaultEntry;
|
|
1456
|
+
let resolvedConfig;
|
|
1457
|
+
let pendingOutputCache;
|
|
1458
|
+
let clientBuildArtifacts;
|
|
1459
|
+
return {
|
|
1460
|
+
name: "riebeckite-ssg",
|
|
1461
|
+
apply: shouldApplyRiebeckiteSsg,
|
|
1462
|
+
enforce: "post",
|
|
1463
|
+
config() {
|
|
1464
|
+
return {
|
|
1465
|
+
build: {
|
|
1466
|
+
rollupOptions: {
|
|
1467
|
+
input: [virtualId]
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
};
|
|
1471
|
+
},
|
|
1472
|
+
configResolved(config) {
|
|
1473
|
+
resolvedConfig = config;
|
|
1474
|
+
},
|
|
1475
|
+
async buildStart() {
|
|
1476
|
+
const config = resolvedConfig;
|
|
1477
|
+
if (!config) return;
|
|
1478
|
+
if (!config.plugins.some(
|
|
1479
|
+
(plugin) => plugin.name.startsWith("@hono/vite-build/")
|
|
1480
|
+
)) {
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1483
|
+
const outDir = resolve2(config.root, config.build.outDir);
|
|
1484
|
+
clientBuildArtifacts = await preserveClientBuildArtifacts(outDir);
|
|
1485
|
+
logSsgDiagnostics(
|
|
1486
|
+
this,
|
|
1487
|
+
`client manifest ${clientBuildArtifacts ? `preserved (${clientBuildArtifacts.paths.length} files)` : "not found"}`
|
|
1488
|
+
);
|
|
1489
|
+
await emptyBuildOutputDirectory(outDir);
|
|
1490
|
+
},
|
|
1491
|
+
resolveId(id) {
|
|
1492
|
+
return id === virtualId ? resolvedVirtualId : void 0;
|
|
1493
|
+
},
|
|
1494
|
+
load(id) {
|
|
1495
|
+
return id === resolvedVirtualId ? 'console.log("suppress empty chunk message")' : void 0;
|
|
1496
|
+
},
|
|
1497
|
+
async generateBundle(_outputOptions, bundle) {
|
|
1498
|
+
const config = resolvedConfig;
|
|
1499
|
+
if (!config) {
|
|
1500
|
+
throw new Error(
|
|
1501
|
+
"Riebeckite SSG could not resolve the Vite configuration."
|
|
1502
|
+
);
|
|
1503
|
+
}
|
|
1504
|
+
const timingsMs = process.env.RIEBECKITE_SSG_METRICS_FILE ? {} : void 0;
|
|
1505
|
+
const outDir = resolve2(config.root, config.build.outDir);
|
|
1506
|
+
removeVirtualEntryChunk(bundle, resolvedVirtualId);
|
|
1507
|
+
const server = await measure(
|
|
1508
|
+
timingsMs,
|
|
1509
|
+
"ssgSetup",
|
|
1510
|
+
() => createServer({
|
|
1511
|
+
root: config.root,
|
|
1512
|
+
define: config.define,
|
|
1513
|
+
resolve: {
|
|
1514
|
+
...config.resolve,
|
|
1515
|
+
builtins: [...config.resolve.builtins, /^node:/]
|
|
1516
|
+
},
|
|
1517
|
+
plugins: [],
|
|
1518
|
+
build: { ssr: true },
|
|
1519
|
+
environments: {
|
|
1520
|
+
ssr: { resolve: { noExternal: [/^honox(?:\/.*)?$/] } }
|
|
1521
|
+
},
|
|
1522
|
+
mode: config.mode,
|
|
1523
|
+
server: { watch: null }
|
|
1524
|
+
})
|
|
1525
|
+
);
|
|
1526
|
+
try {
|
|
1527
|
+
await restoreClientBuildArtifacts(outDir, clientBuildArtifacts);
|
|
1528
|
+
logSsgDiagnostics(
|
|
1529
|
+
this,
|
|
1530
|
+
`SSR noExternal=${formatSsrNoExternal()} · client manifest ${clientBuildArtifacts ? "restored" : "not found"}`
|
|
1531
|
+
);
|
|
1532
|
+
const module = await measure(
|
|
1533
|
+
timingsMs,
|
|
1534
|
+
"ssrModuleLoad",
|
|
1535
|
+
() => server.ssrLoadModule(entry)
|
|
1536
|
+
);
|
|
1537
|
+
const app = module.default;
|
|
1538
|
+
if (!app) {
|
|
1539
|
+
throw new Error(`Failed to find a default export from ${entry}.`);
|
|
1540
|
+
}
|
|
1541
|
+
const outputChangeSet = await measure(
|
|
1542
|
+
timingsMs,
|
|
1543
|
+
"outputDependencyResolution",
|
|
1544
|
+
async () => module.content?.getOutputChangeSet()
|
|
1545
|
+
);
|
|
1546
|
+
const appFingerprint = await measure(
|
|
1547
|
+
timingsMs,
|
|
1548
|
+
"appFingerprint",
|
|
1549
|
+
() => computeAppFingerprint(config.root, config.configFile)
|
|
1550
|
+
);
|
|
1551
|
+
const outputCachePath = join4(
|
|
1552
|
+
config.root,
|
|
1553
|
+
".riebeckite",
|
|
1554
|
+
"ssg-output-cache.json"
|
|
1555
|
+
);
|
|
1556
|
+
const previousOutputCache = await loadOutputCache(
|
|
1557
|
+
outputCachePath,
|
|
1558
|
+
timingsMs,
|
|
1559
|
+
(message) => this.warn(message)
|
|
1560
|
+
);
|
|
1561
|
+
const siteOwnedOutputPaths = await measure(
|
|
1562
|
+
timingsMs,
|
|
1563
|
+
"siteOwnedOutputs",
|
|
1564
|
+
() => collectSiteOwnedOutputPaths(config.publicDir)
|
|
1565
|
+
);
|
|
1566
|
+
const siteOutputCollisions = findBuildOutputSiteCollisions(
|
|
1567
|
+
Object.keys(bundle),
|
|
1568
|
+
siteOwnedOutputPaths
|
|
1569
|
+
);
|
|
1570
|
+
if (siteOutputCollisions.length > 0) {
|
|
1571
|
+
this.warn(
|
|
1572
|
+
`Build outputs take precedence over site public assets: ${siteOutputCollisions.join(", ")}`
|
|
1573
|
+
);
|
|
1574
|
+
}
|
|
1575
|
+
const uncoveredUnchangedCount = outputChangeSet ? outputChangeSet.unchanged.filter(
|
|
1576
|
+
(output) => !siteOwnedOutputPaths.has(output.path) && !previousOutputCache?.outputs[output.path]
|
|
1577
|
+
).length : 0;
|
|
1578
|
+
const outputCacheDecision = {
|
|
1579
|
+
changeSetPresent: outputChangeSet !== void 0,
|
|
1580
|
+
changeSetRequiresFullRegeneration: outputChangeSet?.fullRegenerationRequired ?? true,
|
|
1581
|
+
outputCacheLoaded: previousOutputCache !== void 0,
|
|
1582
|
+
appFingerprintMatch: previousOutputCache?.appFingerprint === appFingerprint,
|
|
1583
|
+
clientManifestFingerprintMatch: previousOutputCache?.clientManifestFingerprint === clientBuildArtifacts?.manifestFingerprint,
|
|
1584
|
+
uncoveredUnchangedCount
|
|
1585
|
+
};
|
|
1586
|
+
const outputCacheRejectionReason = decideOutputCacheRejection(outputCacheDecision);
|
|
1587
|
+
const canUseIncremental = outputCacheRejectionReason === void 0;
|
|
1588
|
+
const reusablePaths = new Set(
|
|
1589
|
+
canUseIncremental ? (outputChangeSet?.unchanged ?? []).map((output) => output.path) : []
|
|
1590
|
+
);
|
|
1591
|
+
logSsgDiagnostics(
|
|
1592
|
+
this,
|
|
1593
|
+
`output cache ${canUseIncremental ? "hit" : `miss (${outputCacheRejectionReason})`} · ${reusablePaths.size} reusable outputs`
|
|
1594
|
+
);
|
|
1595
|
+
const shadowedOutputs = /* @__PURE__ */ new Set();
|
|
1596
|
+
const skippedRouteCount = canUseIncremental ? (outputChangeSet?.unchanged ?? []).filter(
|
|
1597
|
+
(output) => output.kind !== "generated"
|
|
1598
|
+
).length : 0;
|
|
1599
|
+
const generatedHtml = [];
|
|
1600
|
+
const nextOutputCache = {
|
|
1601
|
+
version: 4,
|
|
1602
|
+
appFingerprint,
|
|
1603
|
+
clientManifestFingerprint: clientBuildArtifacts?.manifestFingerprint,
|
|
1604
|
+
outputs: {},
|
|
1605
|
+
configFile: config.configFile
|
|
1606
|
+
};
|
|
1607
|
+
const metrics = {
|
|
1608
|
+
processedContentCount: 0,
|
|
1609
|
+
reusedContentCount: 0,
|
|
1610
|
+
candidateOutputCount: outputChangeSet?.candidateOutputCount ?? 0,
|
|
1611
|
+
affectedOutputCount: outputChangeSet?.affectedOutputCount ?? 0,
|
|
1612
|
+
removedOutputCount: outputChangeSet?.removedOutputCount ?? 0,
|
|
1613
|
+
unchangedOutputCount: outputChangeSet?.unchangedOutputCount ?? 0,
|
|
1614
|
+
renderedOutputCount: 0,
|
|
1615
|
+
emittedOutputCount: 0,
|
|
1616
|
+
reusedOutputCount: 0,
|
|
1617
|
+
deletedOutputCount: 0,
|
|
1618
|
+
shadowedOutputCount: 0,
|
|
1619
|
+
fullRegenerationRequired: !canUseIncremental,
|
|
1620
|
+
htmlOutputCount: 0,
|
|
1621
|
+
assetOutputCount: 0,
|
|
1622
|
+
cacheFileSizeBytes: previousOutputCache?.fileSizeBytes ?? null,
|
|
1623
|
+
cacheEntriesRead: previousOutputCache ? Object.keys(previousOutputCache.outputs).length : 0,
|
|
1624
|
+
cacheEntriesSerialized: 0,
|
|
1625
|
+
enumeratedRouteCount: 0,
|
|
1626
|
+
renderedRouteCount: 0,
|
|
1627
|
+
skippedRouteCount,
|
|
1628
|
+
manifestHooksExecuted: 0,
|
|
1629
|
+
manifestHooksReused: 0,
|
|
1630
|
+
manifestHookCacheHit: 0,
|
|
1631
|
+
manifestHookCacheMiss: 0,
|
|
1632
|
+
manifestHookDurationMs: 0,
|
|
1633
|
+
manifestHookPlugins: {},
|
|
1634
|
+
outputCacheDecision,
|
|
1635
|
+
outputCacheRejectionReason: outputCacheRejectionReason ?? null,
|
|
1636
|
+
timingsMs: timingsMs ?? {}
|
|
1637
|
+
};
|
|
1638
|
+
process.env.RIEBECKITE_SSG_FULL_REGENERATION = canUseIncremental ? "" : "1";
|
|
1639
|
+
process.env.RIEBECKITE_SSG_SKIP_UNCHANGED = canUseIncremental ? "1" : "";
|
|
1640
|
+
const result = await measure(
|
|
1641
|
+
timingsMs,
|
|
1642
|
+
"toSSG",
|
|
1643
|
+
() => toSSG(
|
|
1644
|
+
app,
|
|
1645
|
+
{
|
|
1646
|
+
writeFile: async (filePath, data) => {
|
|
1647
|
+
const fileName = relative3(outDir, filePath).replaceAll(
|
|
1648
|
+
"\\",
|
|
1649
|
+
"/"
|
|
1650
|
+
);
|
|
1651
|
+
if (siteOwnedOutputPaths.has(fileName)) {
|
|
1652
|
+
shadowedOutputs.add(fileName);
|
|
1653
|
+
return;
|
|
1654
|
+
}
|
|
1655
|
+
if (canUseIncremental && reusablePaths.has(fileName)) {
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
if (fileName.endsWith(".html") && typeof data === "string") {
|
|
1659
|
+
metrics.renderedOutputCount += 1;
|
|
1660
|
+
metrics.renderedRouteCount += 1;
|
|
1661
|
+
metrics.htmlOutputCount += 1;
|
|
1662
|
+
generatedHtml.push({ path: fileName, html: data });
|
|
1663
|
+
} else {
|
|
1664
|
+
metrics.assetOutputCount += 1;
|
|
1665
|
+
}
|
|
1666
|
+
measureSync(
|
|
1667
|
+
timingsMs,
|
|
1668
|
+
"outputWrite",
|
|
1669
|
+
() => this.emitFile({
|
|
1670
|
+
type: "asset",
|
|
1671
|
+
fileName,
|
|
1672
|
+
source: data
|
|
1673
|
+
})
|
|
1674
|
+
);
|
|
1675
|
+
metrics.emittedOutputCount += 1;
|
|
1676
|
+
nextOutputCache.outputs[fileName] = {
|
|
1677
|
+
cachePath: "",
|
|
1678
|
+
checksum: ""
|
|
1679
|
+
};
|
|
1680
|
+
},
|
|
1681
|
+
async mkdir() {
|
|
1682
|
+
}
|
|
1683
|
+
},
|
|
1684
|
+
{
|
|
1685
|
+
dir: outDir,
|
|
1686
|
+
plugins: [redirectPlugin(), ...options.plugins ?? []],
|
|
1687
|
+
extensionMap: options.extensionMap ?? defaultExtensionMap
|
|
1688
|
+
}
|
|
1689
|
+
)
|
|
1690
|
+
);
|
|
1691
|
+
delete process.env.RIEBECKITE_SSG_FULL_REGENERATION;
|
|
1692
|
+
delete process.env.RIEBECKITE_SSG_SKIP_UNCHANGED;
|
|
1693
|
+
metrics.enumeratedRouteCount = result.files.length;
|
|
1694
|
+
if (!result.success) throw result.error;
|
|
1695
|
+
metrics.processedContentCount = module.content?.getProcessedContentCount() ?? 0;
|
|
1696
|
+
metrics.reusedContentCount = module.content?.getReusedContentCount() ?? 0;
|
|
1697
|
+
const manifestHookMetrics = module.content?.getManifestHookMetrics();
|
|
1698
|
+
metrics.manifestHooksExecuted = manifestHookMetrics?.manifestHooksExecuted ?? 0;
|
|
1699
|
+
metrics.manifestHooksReused = manifestHookMetrics?.manifestHooksReused ?? 0;
|
|
1700
|
+
metrics.manifestHookCacheHit = manifestHookMetrics?.manifestHookCacheHit ?? 0;
|
|
1701
|
+
metrics.manifestHookCacheMiss = manifestHookMetrics?.manifestHookCacheMiss ?? 0;
|
|
1702
|
+
metrics.manifestHookDurationMs = manifestHookMetrics?.manifestHookDurationMs ?? 0;
|
|
1703
|
+
metrics.manifestHookPlugins = manifestHookMetrics?.byPlugin ?? {};
|
|
1704
|
+
const emittedGeneratedOutputCount = await measure(
|
|
1705
|
+
timingsMs,
|
|
1706
|
+
"generatedOutputs",
|
|
1707
|
+
() => emitGeneratedOutputs(
|
|
1708
|
+
module,
|
|
1709
|
+
(asset) => {
|
|
1710
|
+
measureSync(
|
|
1711
|
+
timingsMs,
|
|
1712
|
+
"outputWrite",
|
|
1713
|
+
() => this.emitFile(asset)
|
|
1714
|
+
);
|
|
1715
|
+
metrics.emittedOutputCount += 1;
|
|
1716
|
+
nextOutputCache.outputs[asset.fileName] = {
|
|
1717
|
+
cachePath: "",
|
|
1718
|
+
checksum: ""
|
|
1719
|
+
};
|
|
1720
|
+
},
|
|
1721
|
+
canUseIncremental ? new Set(
|
|
1722
|
+
(outputChangeSet?.affected ?? []).filter((output) => output.kind === "generated").map((output) => output.path)
|
|
1723
|
+
) : void 0,
|
|
1724
|
+
siteOwnedOutputPaths,
|
|
1725
|
+
(path11) => shadowedOutputs.add(path11)
|
|
1726
|
+
)
|
|
1727
|
+
);
|
|
1728
|
+
if (!outputChangeSet)
|
|
1729
|
+
metrics.candidateOutputCount = result.files.length + emittedGeneratedOutputCount;
|
|
1730
|
+
if (canUseIncremental && outputChangeSet && previousOutputCache) {
|
|
1731
|
+
await measure(timingsMs, "unchangedOutputReuse", async () => {
|
|
1732
|
+
for (const output of outputChangeSet.unchanged) {
|
|
1733
|
+
const path11 = output.path;
|
|
1734
|
+
if (siteOwnedOutputPaths.has(path11)) {
|
|
1735
|
+
shadowedOutputs.add(path11);
|
|
1736
|
+
continue;
|
|
1737
|
+
}
|
|
1738
|
+
const cached = previousOutputCache.outputs[path11];
|
|
1739
|
+
if (!cached) continue;
|
|
1740
|
+
this.emitFile({
|
|
1741
|
+
type: "asset",
|
|
1742
|
+
fileName: path11,
|
|
1743
|
+
source: await decodeOutput(outputCachePath, cached)
|
|
1744
|
+
});
|
|
1745
|
+
metrics.emittedOutputCount += 1;
|
|
1746
|
+
metrics.reusedOutputCount += 1;
|
|
1747
|
+
nextOutputCache.outputs[path11] = cached;
|
|
1748
|
+
}
|
|
1749
|
+
});
|
|
1750
|
+
}
|
|
1751
|
+
if (outputChangeSet) {
|
|
1752
|
+
for (const output of outputChangeSet.removed) {
|
|
1753
|
+
if (siteOwnedOutputPaths.has(output.path)) continue;
|
|
1754
|
+
await rm3(join4(outDir, output.path), {
|
|
1755
|
+
force: true
|
|
1756
|
+
});
|
|
1757
|
+
metrics.deletedOutputCount += 1;
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
metrics.shadowedOutputCount = shadowedOutputs.size;
|
|
1761
|
+
if (shadowedOutputs.size > 0) {
|
|
1762
|
+
this.warn(
|
|
1763
|
+
`Site public assets take precedence over generated outputs: ${[
|
|
1764
|
+
...shadowedOutputs
|
|
1765
|
+
].sort().join(", ")}`
|
|
1766
|
+
);
|
|
1767
|
+
}
|
|
1768
|
+
metrics.cacheEntriesSerialized = Object.keys(
|
|
1769
|
+
nextOutputCache.outputs
|
|
1770
|
+
).length;
|
|
1771
|
+
pendingOutputCache = {
|
|
1772
|
+
path: outputCachePath,
|
|
1773
|
+
state: nextOutputCache,
|
|
1774
|
+
metrics,
|
|
1775
|
+
timingsMs,
|
|
1776
|
+
outDir,
|
|
1777
|
+
publicDir: config.publicDir,
|
|
1778
|
+
configFile: config.configFile
|
|
1779
|
+
};
|
|
1780
|
+
this.info(
|
|
1781
|
+
`SSG outputs: ${metrics.renderedOutputCount} rendered · ${metrics.reusedOutputCount} reused · ${metrics.deletedOutputCount} removed`
|
|
1782
|
+
);
|
|
1783
|
+
inspectGeneratedHtmlPages(module, generatedHtml, {
|
|
1784
|
+
warn: (message) => this.warn(message),
|
|
1785
|
+
info: (message) => this.info(message)
|
|
1786
|
+
});
|
|
1787
|
+
logGeneratedHtmlAssetDiagnostics(this, generatedHtml);
|
|
1788
|
+
} finally {
|
|
1789
|
+
await server.close();
|
|
1790
|
+
await discardClientBuildArtifacts(clientBuildArtifacts);
|
|
1791
|
+
clientBuildArtifacts = void 0;
|
|
1792
|
+
}
|
|
1793
|
+
},
|
|
1794
|
+
closeBundle: {
|
|
1795
|
+
order: "post",
|
|
1796
|
+
async handler() {
|
|
1797
|
+
if (!resolvedConfig) return;
|
|
1798
|
+
const outDir = resolve2(
|
|
1799
|
+
resolvedConfig.root,
|
|
1800
|
+
resolvedConfig.build.outDir
|
|
1760
1801
|
);
|
|
1802
|
+
const oversizedAssets = await findOversizedAssets(outDir);
|
|
1803
|
+
for (const asset of oversizedAssets) {
|
|
1804
|
+
this.warn(
|
|
1805
|
+
`Generated asset ${asset.path} is ${formatBytes(asset.size)}, exceeding Cloudflare Workers' ${formatBytes(CLOUDFLARE_WORKER_ASSET_LIMIT)} asset limit.`
|
|
1806
|
+
);
|
|
1807
|
+
}
|
|
1808
|
+
const outputCache = pendingOutputCache;
|
|
1809
|
+
if (!outputCache) return;
|
|
1810
|
+
try {
|
|
1811
|
+
outputCache.state.finalOutputInventory = await measure(
|
|
1812
|
+
outputCache.timingsMs,
|
|
1813
|
+
"outputInventory",
|
|
1814
|
+
() => fingerprintOutputDirectory(outputCache.outDir)
|
|
1815
|
+
);
|
|
1816
|
+
const publicDir = outputCache.publicDir;
|
|
1817
|
+
outputCache.state.publicDirectory = publicDir ? resolve2(publicDir) : void 0;
|
|
1818
|
+
outputCache.state.publicFingerprint = publicDir ? await measure(
|
|
1819
|
+
outputCache.timingsMs,
|
|
1820
|
+
"publicFingerprint",
|
|
1821
|
+
() => fingerprintDirectory(resolve2(publicDir))
|
|
1822
|
+
) : void 0;
|
|
1823
|
+
await waitForFaultInjection("build-state-before");
|
|
1824
|
+
await saveOutputCache(
|
|
1825
|
+
outputCache.path,
|
|
1826
|
+
outputCache.state,
|
|
1827
|
+
outputCache.outDir,
|
|
1828
|
+
outputCache.timingsMs
|
|
1829
|
+
);
|
|
1830
|
+
await waitForFaultInjection("build-state-committed");
|
|
1831
|
+
} catch (error) {
|
|
1832
|
+
this.warn(
|
|
1833
|
+
`SSG output cache could not be saved; the next build regenerates every output. ${error instanceof Error ? error.message : String(error)}`
|
|
1834
|
+
);
|
|
1835
|
+
} finally {
|
|
1836
|
+
await writeSsgOutputMetrics(outputCache.metrics);
|
|
1837
|
+
pendingOutputCache = void 0;
|
|
1838
|
+
}
|
|
1761
1839
|
}
|
|
1762
1840
|
}
|
|
1763
|
-
}
|
|
1764
|
-
return;
|
|
1765
|
-
}
|
|
1766
|
-
}
|
|
1767
|
-
async function waitForFaultInjection(point) {
|
|
1768
|
-
if (process.env.NODE_ENV !== "test" || process.env.RIEBECKITE_SSG_FAULT_INJECTION !== "1" || process.env.RIEBECKITE_SSG_FAULT_POINT !== point) {
|
|
1769
|
-
return;
|
|
1770
|
-
}
|
|
1771
|
-
const occurrence = Number.parseInt(
|
|
1772
|
-
process.env.RIEBECKITE_SSG_FAULT_OCCURRENCE ?? "1",
|
|
1773
|
-
10
|
|
1774
|
-
);
|
|
1775
|
-
const count = (faultInjectionCounts.get(point) ?? 0) + 1;
|
|
1776
|
-
faultInjectionCounts.set(point, count);
|
|
1777
|
-
if (!Number.isSafeInteger(occurrence) || occurrence < 1 || count !== occurrence)
|
|
1778
|
-
return;
|
|
1779
|
-
const signalFile = process.env.RIEBECKITE_SSG_FAULT_SIGNAL_FILE;
|
|
1780
|
-
if (!signalFile) return;
|
|
1781
|
-
await writeFile(signalFile, `${point}
|
|
1782
|
-
`, "utf8");
|
|
1783
|
-
await new Promise(() => {
|
|
1784
|
-
});
|
|
1785
|
-
}
|
|
1786
|
-
async function writeSsgOutputMetrics(metrics) {
|
|
1787
|
-
const file = process.env.RIEBECKITE_SSG_METRICS_FILE;
|
|
1788
|
-
if (!file) return;
|
|
1789
|
-
await writeFile(file, `${JSON.stringify(metrics)}
|
|
1790
|
-
`, "utf8");
|
|
1841
|
+
};
|
|
1791
1842
|
}
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
const start = performance.now();
|
|
1795
|
-
try {
|
|
1796
|
-
return await fn();
|
|
1797
|
-
} finally {
|
|
1798
|
-
if (timingsMs)
|
|
1799
|
-
timingsMs[name] = (timingsMs[name] ?? 0) + performance.now() - start;
|
|
1800
|
-
}
|
|
1843
|
+
function shouldApplyRiebeckiteSsg(_config, env) {
|
|
1844
|
+
return env.command === "build" && env.mode !== "client";
|
|
1801
1845
|
}
|
|
1802
|
-
function
|
|
1803
|
-
|
|
1804
|
-
const
|
|
1805
|
-
|
|
1806
|
-
return fn();
|
|
1807
|
-
} finally {
|
|
1808
|
-
if (timingsMs)
|
|
1809
|
-
timingsMs[name] = (timingsMs[name] ?? 0) + performance.now() - start;
|
|
1846
|
+
function findBuildOutputSiteCollisions(buildOutputPaths, siteOwnedOutputPaths) {
|
|
1847
|
+
const collisions = /* @__PURE__ */ new Set();
|
|
1848
|
+
for (const path11 of buildOutputPaths) {
|
|
1849
|
+
if (siteOwnedOutputPaths.has(path11)) collisions.add(path11);
|
|
1810
1850
|
}
|
|
1851
|
+
return [...collisions].sort();
|
|
1811
1852
|
}
|
|
1812
|
-
function
|
|
1813
|
-
const
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1853
|
+
async function emitGeneratedOutputs(module, emit, affectedPaths, shadowedPaths, onShadowed) {
|
|
1854
|
+
const content = module.content;
|
|
1855
|
+
if (!content) return 0;
|
|
1856
|
+
if (affectedPaths?.size === 0) return 0;
|
|
1857
|
+
const manifest = await content.getManifest();
|
|
1858
|
+
let count = 0;
|
|
1859
|
+
for (const output of manifest.generatedOutputs ?? []) {
|
|
1860
|
+
if (affectedPaths && !affectedPaths.has(output.path)) continue;
|
|
1861
|
+
if (shadowedPaths?.has(output.path)) {
|
|
1862
|
+
onShadowed?.(output.path);
|
|
1863
|
+
continue;
|
|
1823
1864
|
}
|
|
1865
|
+
emit({
|
|
1866
|
+
type: "asset",
|
|
1867
|
+
fileName: output.path,
|
|
1868
|
+
source: output.content
|
|
1869
|
+
});
|
|
1870
|
+
count += 1;
|
|
1824
1871
|
}
|
|
1872
|
+
return count;
|
|
1825
1873
|
}
|
|
1826
1874
|
function removeVirtualEntryChunk(bundle, virtualEntryId) {
|
|
1827
1875
|
for (const chunk of Object.values(bundle)) {
|
|
@@ -2068,9 +2116,9 @@ async function runTraced(tracer, name, buildTask) {
|
|
|
2068
2116
|
return tracer ? await tracer.span(name, {}, buildTask) : await buildTask();
|
|
2069
2117
|
}
|
|
2070
2118
|
async function waitForShutdown(server) {
|
|
2071
|
-
await new Promise((
|
|
2119
|
+
await new Promise((resolve3) => {
|
|
2072
2120
|
const close = () => {
|
|
2073
|
-
void server.close().finally(
|
|
2121
|
+
void server.close().finally(resolve3);
|
|
2074
2122
|
};
|
|
2075
2123
|
process.once("SIGINT", close);
|
|
2076
2124
|
process.once("SIGTERM", close);
|
|
@@ -2106,8 +2154,8 @@ async function findViteApplicationRootFrom(startDirectory, configRoot) {
|
|
|
2106
2154
|
return void 0;
|
|
2107
2155
|
}
|
|
2108
2156
|
function isWithin(parent, target) {
|
|
2109
|
-
const
|
|
2110
|
-
return
|
|
2157
|
+
const relative4 = path9.relative(parent, target);
|
|
2158
|
+
return relative4 === "" || !relative4.startsWith("..") && !path9.isAbsolute(relative4);
|
|
2111
2159
|
}
|
|
2112
2160
|
async function hasViteConfig(directory) {
|
|
2113
2161
|
for (const fileName of viteConfigFileNames) {
|