@riebeckite/honox 0.0.27 → 0.0.29

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 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 { createHash, randomUUID } from "node:crypto";
288
- import { createReadStream } from "node:fs";
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/ssg_plugin.ts
633
- function createNoChangeFastPathOutputMetrics() {
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
- outputInventoryValidationDurationMs: 0,
636
- outputHashValidationDurationMs: 0,
637
- outputFilesHashed: 0,
638
- outputBytesHashed: 0
710
+ stagingDirectory,
711
+ paths,
712
+ manifestFingerprint: createHash("sha256").update(manifestSource).digest("hex")
639
713
  };
640
714
  }
641
- var defaultEntry = "./src/index.tsx";
642
- var faultInjectionCounts = /* @__PURE__ */ new Map();
643
- var cloudflareWorkerAssetLimit = 25 * 1024 * 1024;
644
- function riebeckiteSsg(options = {}) {
645
- const virtualId = "virtual:riebeckite-ssg-void-entry";
646
- const resolvedVirtualId = `\0${virtualId}`;
647
- const entry = options.entry ?? defaultEntry;
648
- let resolvedConfig;
649
- let pendingOutputCache;
650
- let clientBuildArtifacts;
651
- return {
652
- name: "riebeckite-ssg",
653
- apply: shouldApplyRiebeckiteSsg,
654
- enforce: "post",
655
- config() {
656
- return {
657
- build: {
658
- rollupOptions: {
659
- input: [virtualId]
660
- }
661
- }
662
- };
663
- },
664
- configResolved(config) {
665
- resolvedConfig = config;
666
- },
667
- async buildStart() {
668
- const config = resolvedConfig;
669
- if (!config) return;
670
- if (!config.plugins.some(
671
- (plugin) => plugin.name.startsWith("@hono/vite-build/")
672
- )) {
673
- return;
674
- }
675
- const outDir = resolve(config.root, config.build.outDir);
676
- clientBuildArtifacts = await preserveClientBuildArtifacts(outDir);
677
- logSsgDiagnostics(
678
- this,
679
- `client manifest ${clientBuildArtifacts ? `preserved (${clientBuildArtifacts.paths.length} files)` : "not found"}`
680
- );
681
- await emptyBuildOutputDirectory(outDir);
682
- },
683
- resolveId(id) {
684
- return id === virtualId ? resolvedVirtualId : void 0;
685
- },
686
- load(id) {
687
- return id === resolvedVirtualId ? 'console.log("suppress empty chunk message")' : void 0;
688
- },
689
- async generateBundle(_outputOptions, bundle) {
690
- const config = resolvedConfig;
691
- if (!config) {
692
- throw new Error(
693
- "Riebeckite SSG could not resolve the Vite configuration."
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(join(root, "app"), files);
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(join(root, name));
840
+ files.push(join2(root, name));
1240
841
  }
1241
842
  files.sort();
1242
- const hash = createHash("sha256");
843
+ const hash = createHash2("sha256");
1243
844
  for (const file of files) {
1244
845
  let content;
1245
846
  try {
1246
- content = await readFile(file);
847
+ content = await readFile2(file);
1247
848
  } catch {
1248
849
  continue;
1249
850
  }
1250
- hash.update(relative(root, file).replaceAll("\\", "/"));
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 = createHash("sha256");
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 readdir(directory, { withFileTypes: true }).catch(
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 = join(directory, entry.name);
1314
- const relativePath = join(relativeDirectory, entry.name).replaceAll(
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 readFile(file);
1328
- inventory[relativePath] = createHash("sha256").update(content).digest("hex");
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 readdir(directory, { withFileTypes: true }).catch(
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 = join(directory, entry.name);
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(dirname(path11))) {
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
- () => readFile(path11, "utf8")
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
- return Object.entries(value).every(
1420
- ([outputPath, entry]) => isSafeLogicalOutputPath(outputPath) && typeof entry === "object" && entry !== null && typeof entry.cachePath === "string" && entry.cachePath === outputCacheEntryName(outputPath) && isChecksum(entry.checksum)
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 = dirname(path11);
1425
- await mkdir(directory, { recursive: true });
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}.${randomUUID()}.tmp`;
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
- () => writeFile(temporaryPath, serialized, "utf8")
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 = createHash("sha256").update(serialized).digest("hex");
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 rm(temporaryPath, { force: true }).catch(() => void 0);
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 = join(directory, cachePath);
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 = dirname(path11);
1559
- await mkdir(directory, { recursive: true });
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}.${randomUUID()}.tmp`;
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
- () => writeFile(temporaryPath, `${JSON.stringify(state)}
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 rm(temporaryPath, { force: true }).catch(() => void 0);
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(dirname(path11))) return void 0;
1258
+ if (!await isSafeOutputCacheDirectory(dirname2(path11))) return void 0;
1589
1259
  if (!await isRegularFile(path11)) return void 0;
1590
- const candidate = JSON.parse(await readFile(path11, "utf8"));
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 = createHash("sha256");
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 rename(temporaryPath, targetPath);
1284
+ await rename2(temporaryPath, targetPath);
1615
1285
  } catch (error) {
1616
1286
  if (!isReplacementFailure(error)) throw error;
1617
- await rm(targetPath, { force: true });
1618
- await rename(temporaryPath, targetPath);
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 readFile(file);
1637
- const checksum = createHash("sha256").update(content).digest("hex");
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
- if (!relativePath || relativePath.includes("\0") || isAbsolutePath(relativePath)) {
1645
- throw new Error(`Invalid SSG output cache path: ${relativePath}`);
1646
- }
1647
- const file = resolve(directory, relativePath);
1648
- const relativePathFromDirectory = relative(directory, file);
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 createHash("sha256").update(outputPath).digest("hex");
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 mkdir(directory, { recursive: true });
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}.${randomUUID()}.tmp`;
1375
+ const temporaryPath = `${destination}.${randomUUID2()}.tmp`;
1713
1376
  try {
1714
1377
  await measure(
1715
1378
  timingsMs,
1716
1379
  "outputCacheEntryWrite",
1717
- () => writeFile(temporaryPath, new Uint8Array())
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 rm(temporaryPath, { force: true }).catch(() => void 0);
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
- () => readdir(directory, { withFileTypes: true })
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 = join(directory, entry.name);
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
- () => rm(path11, { force: true })
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
- } catch {
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
- async function measure(timingsMs, name, fn) {
1793
- if (!timingsMs) return await fn();
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 measureSync(timingsMs, name, fn) {
1803
- if (!timingsMs) return fn();
1804
- const start = performance.now();
1805
- try {
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 inspectGeneratedHtmlPages(module, pages, log) {
1813
- const plugins = module.config?.plugins ?? [];
1814
- for (const plugin of plugins) {
1815
- const inspector = plugin.inspectGeneratedHtml;
1816
- if (typeof inspector !== "function") continue;
1817
- for (const page of pages) {
1818
- for (const diagnostic of inspector(page) ?? []) {
1819
- const label = `[${plugin.name ?? "plugin"}] ${page.path}: ${diagnostic.message}`;
1820
- if (diagnostic.severity === "info") log.info(label);
1821
- else log.warn(label);
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((resolve2) => {
2119
+ await new Promise((resolve3) => {
2072
2120
  const close = () => {
2073
- void server.close().finally(resolve2);
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 relative2 = path9.relative(parent, target);
2110
- return relative2 === "" || !relative2.startsWith("..") && !path9.isAbsolute(relative2);
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) {