@stati/core 1.27.2 → 1.28.0
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/config/errors.d.ts +10 -0
- package/dist/config/errors.d.ts.map +1 -0
- package/dist/config/errors.js +10 -0
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +15 -2
- package/dist/constants.d.ts +26 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +23 -0
- package/dist/core/build.d.ts.map +1 -1
- package/dist/core/build.js +96 -24
- package/dist/core/isg/builder.d.ts +14 -2
- package/dist/core/isg/builder.d.ts.map +1 -1
- package/dist/core/isg/builder.js +41 -7
- package/dist/core/isg/deps.d.ts +9 -0
- package/dist/core/isg/deps.d.ts.map +1 -1
- package/dist/core/isg/deps.js +30 -1
- package/dist/core/isg/hash.d.ts +10 -0
- package/dist/core/isg/hash.d.ts.map +1 -1
- package/dist/core/isg/hash.js +14 -0
- package/dist/core/isg/index.d.ts +2 -1
- package/dist/core/isg/index.d.ts.map +1 -1
- package/dist/core/isg/index.js +1 -1
- package/dist/core/isg/manifest.js +8 -0
- package/dist/core/markdown.d.ts +10 -1
- package/dist/core/markdown.d.ts.map +1 -1
- package/dist/core/markdown.js +13 -3
- package/dist/core/templates.d.ts +12 -1
- package/dist/core/templates.d.ts.map +1 -1
- package/dist/core/templates.js +61 -6
- package/dist/core/utils/fs.utils.d.ts +21 -1
- package/dist/core/utils/fs.utils.d.ts.map +1 -1
- package/dist/core/utils/fs.utils.js +61 -0
- package/dist/core/utils/index.d.ts +1 -1
- package/dist/core/utils/index.d.ts.map +1 -1
- package/dist/core/utils/index.js +1 -1
- package/dist/images/config.d.ts +52 -0
- package/dist/images/config.d.ts.map +1 -0
- package/dist/images/config.js +200 -0
- package/dist/images/index.d.ts +15 -0
- package/dist/images/index.d.ts.map +1 -0
- package/dist/images/index.js +11 -0
- package/dist/images/manifest.d.ts +36 -0
- package/dist/images/manifest.d.ts.map +1 -0
- package/dist/images/manifest.js +69 -0
- package/dist/images/markdown-rule.d.ts +26 -0
- package/dist/images/markdown-rule.d.ts.map +1 -0
- package/dist/images/markdown-rule.js +60 -0
- package/dist/images/markup.d.ts +39 -0
- package/dist/images/markup.d.ts.map +1 -0
- package/dist/images/markup.js +72 -0
- package/dist/images/og.d.ts +52 -0
- package/dist/images/og.d.ts.map +1 -0
- package/dist/images/og.js +165 -0
- package/dist/images/plan.d.ts +101 -0
- package/dist/images/plan.d.ts.map +1 -0
- package/dist/images/plan.js +128 -0
- package/dist/images/provider.d.ts +35 -0
- package/dist/images/provider.d.ts.map +1 -0
- package/dist/images/provider.js +70 -0
- package/dist/images/resolve.d.ts +45 -0
- package/dist/images/resolve.d.ts.map +1 -0
- package/dist/images/resolve.js +51 -0
- package/dist/images/runtime.d.ts +258 -0
- package/dist/images/runtime.d.ts.map +1 -0
- package/dist/images/runtime.js +659 -0
- package/dist/images/sources.d.ts +47 -0
- package/dist/images/sources.d.ts.map +1 -0
- package/dist/images/sources.js +97 -0
- package/dist/images/utils/image-probe.utils.d.ts +30 -0
- package/dist/images/utils/image-probe.utils.d.ts.map +1 -0
- package/dist/images/utils/image-probe.utils.js +307 -0
- package/dist/images/utils/index.d.ts +7 -0
- package/dist/images/utils/index.d.ts.map +1 -0
- package/dist/images/utils/index.js +5 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/metrics/noop.d.ts.map +1 -1
- package/dist/metrics/noop.js +3 -0
- package/dist/metrics/recorder.d.ts.map +1 -1
- package/dist/metrics/recorder.js +3 -0
- package/dist/metrics/types.d.ts +8 -0
- package/dist/metrics/types.d.ts.map +1 -1
- package/dist/seo/auto-inject.d.ts +3 -0
- package/dist/seo/auto-inject.d.ts.map +1 -1
- package/dist/seo/auto-inject.js +1 -0
- package/dist/seo/generator.d.ts +24 -0
- package/dist/seo/generator.d.ts.map +1 -1
- package/dist/seo/generator.js +65 -1
- package/dist/seo/index.d.ts +1 -1
- package/dist/seo/index.d.ts.map +1 -1
- package/dist/seo/index.js +1 -1
- package/dist/types/config.d.ts +5 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/content.d.ts +21 -2
- package/dist/types/content.d.ts.map +1 -1
- package/dist/types/images.d.ts +185 -0
- package/dist/types/images.d.ts.map +1 -0
- package/dist/types/images.js +8 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/isg.d.ts +7 -0
- package/dist/types/isg.d.ts.map +1 -1
- package/dist/types/seo.d.ts +9 -0
- package/dist/types/seo.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error thrown when the Stati configuration is invalid.
|
|
3
|
+
* `loadConfig()` always rethrows it, because the build cannot proceed with a broken config.
|
|
4
|
+
*/
|
|
5
|
+
export declare class ConfigValidationError extends Error {
|
|
6
|
+
constructor(message: string, options?: {
|
|
7
|
+
cause?: unknown;
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/config/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAI3D"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error thrown when the Stati configuration is invalid.
|
|
3
|
+
* `loadConfig()` always rethrows it, because the build cannot proceed with a broken config.
|
|
4
|
+
*/
|
|
5
|
+
export class ConfigValidationError extends Error {
|
|
6
|
+
constructor(message, options) {
|
|
7
|
+
super(message, options);
|
|
8
|
+
this.name = 'ConfigValidationError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AA+BrD;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC;AAqBD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAExD;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,UAAU,CAAC,GAAG,GAAE,MAAsB,GAAG,OAAO,CAAC,WAAW,CAAC,CA8HlF"}
|
package/dist/config/loader.js
CHANGED
|
@@ -2,6 +2,8 @@ import { existsSync, statSync } from 'node:fs';
|
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
4
|
import { validateISGConfig, ISGConfigurationError } from '../core/isg/validation.js';
|
|
5
|
+
import { validateImagesConfig } from '../images/config.js';
|
|
6
|
+
import { ConfigValidationError } from './errors.js';
|
|
5
7
|
import { compileStatiConfig, cleanupCompiledConfig } from '../core/utils/index.js';
|
|
6
8
|
import { DEFAULT_SRC_DIR, DEFAULT_OUT_DIR, DEFAULT_STATIC_DIR, DEFAULT_SITE_TITLE, DEFAULT_DEV_BASE_URL, DEFAULT_TTL_SECONDS, DEFAULT_MAX_AGE_CAP_DAYS, CONFIG_FILE_PATTERNS, } from '../constants.js';
|
|
7
9
|
/**
|
|
@@ -139,6 +141,16 @@ export async function loadConfig(cwd = process.cwd()) {
|
|
|
139
141
|
}
|
|
140
142
|
throw error; // Re-throw non-ISG errors
|
|
141
143
|
}
|
|
144
|
+
// Validate image configuration
|
|
145
|
+
try {
|
|
146
|
+
validateImagesConfig(mergedConfig.images);
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
if (error instanceof ConfigValidationError) {
|
|
150
|
+
throw new ConfigValidationError(`Invalid images configuration in ${configPath}:\n${error.message}`, { cause: error });
|
|
151
|
+
}
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
142
154
|
// Cache the loaded config with its modification time
|
|
143
155
|
try {
|
|
144
156
|
const mtime = statSync(resolvedConfigPath).mtimeMs;
|
|
@@ -154,8 +166,9 @@ export async function loadConfig(cwd = process.cwd()) {
|
|
|
154
166
|
return mergedConfig;
|
|
155
167
|
}
|
|
156
168
|
catch (error) {
|
|
157
|
-
if (error instanceof
|
|
158
|
-
|
|
169
|
+
if (error instanceof ConfigValidationError ||
|
|
170
|
+
(error instanceof Error && error.message.includes('Invalid ISG configuration'))) {
|
|
171
|
+
// Validation errors bubble up with context: the build cannot proceed with an invalid config
|
|
159
172
|
throw error;
|
|
160
173
|
}
|
|
161
174
|
console.error(`Error loading config from ${configPath}:`, error);
|
package/dist/constants.d.ts
CHANGED
|
@@ -82,4 +82,30 @@ export declare const DEFAULT_BUNDLES: readonly [{
|
|
|
82
82
|
readonly entryPoint: "main.ts";
|
|
83
83
|
readonly bundleName: "main";
|
|
84
84
|
}];
|
|
85
|
+
/** Output directory for generated and copied images, relative to the build output */
|
|
86
|
+
export declare const IMAGES_OUTPUT_DIR = "_img";
|
|
87
|
+
/** Subdirectory of {@link IMAGES_OUTPUT_DIR} for generated Open Graph images */
|
|
88
|
+
export declare const OG_IMAGES_SUBDIR = "og";
|
|
89
|
+
/** Cache subdirectory (inside `.stati`) holding the images manifest */
|
|
90
|
+
export declare const IMAGES_CACHE_SUBDIR = "images";
|
|
91
|
+
/** Package that implements responsive images and Open Graph image generation */
|
|
92
|
+
export declare const IMAGES_PACKAGE_NAME = "@stati/images";
|
|
93
|
+
/** Default candidate widths for responsive images */
|
|
94
|
+
export declare const DEFAULT_IMAGE_WIDTHS: readonly number[];
|
|
95
|
+
/** Default modern formats for responsive images */
|
|
96
|
+
export declare const DEFAULT_IMAGE_FORMATS: readonly ('avif' | 'webp')[];
|
|
97
|
+
/** Default encoder quality per format; PNG stays lossless unless configured */
|
|
98
|
+
export declare const DEFAULT_IMAGE_QUALITY: {
|
|
99
|
+
readonly avif: 50;
|
|
100
|
+
readonly webp: 75;
|
|
101
|
+
readonly jpeg: 80;
|
|
102
|
+
};
|
|
103
|
+
/** Default `sizes` attribute for responsive images */
|
|
104
|
+
export declare const DEFAULT_IMAGE_SIZES = "100vw";
|
|
105
|
+
/** Default Open Graph image template name */
|
|
106
|
+
export declare const DEFAULT_OG_TEMPLATE = "og.eta";
|
|
107
|
+
/** Default Open Graph image width in pixels */
|
|
108
|
+
export declare const DEFAULT_OG_WIDTH = 1200;
|
|
109
|
+
/** Default Open Graph image height in pixels */
|
|
110
|
+
export declare const DEFAULT_OG_HEIGHT = 630;
|
|
85
111
|
//# sourceMappingURL=constants.d.ts.map
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,iDAAiD;AACjD,eAAO,MAAM,eAAe,SAAS,CAAC;AAEtC,+CAA+C;AAC/C,eAAO,MAAM,eAAe,SAAS,CAAC;AAEtC,sCAAsC;AACtC,eAAO,MAAM,kBAAkB,WAAW,CAAC;AAE3C,iCAAiC;AACjC,eAAO,MAAM,cAAc,WAAW,CAAC;AAEvC,4DAA4D;AAC5D,eAAO,MAAM,YAAY,UAAU,CAAC;AAEpC,kCAAkC;AAClC,eAAO,MAAM,iBAAiB,kBAAkB,CAAC;AAIjD,qEAAqE;AACrE,eAAO,MAAM,sBAAsB,iCAAkC,CAAC;AAEtE,mCAAmC;AACnC,eAAO,MAAM,gBAAgB,iBAAiB,CAAC;AAE/C,2CAA2C;AAC3C,eAAO,MAAM,oBAAoB,UAEhC,CAAC;AAIF,sCAAsC;AACtC,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAErC,kCAAkC;AAClC,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAEzC,sCAAsC;AACtC,eAAO,MAAM,gBAAgB,cAAc,CAAC;AAE5C,0CAA0C;AAC1C,eAAO,MAAM,oBAAoB,0BAAmD,CAAC;AAIrF,wDAAwD;AACxD,eAAO,MAAM,mBAAmB,QAAQ,CAAC;AAEzC,+DAA+D;AAC/D,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAE5C,qEAAqE;AACrE,eAAO,MAAM,wBAAwB,QAAQ,CAAC;AAI9C,4BAA4B;AAC5B,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,0BAA0B;AAC1B,eAAO,MAAM,gBAAgB,QAA0B,CAAC;AAExD,yBAAyB;AACzB,eAAO,MAAM,eAAe,QAAwB,CAAC;AAErD,0BAA0B;AAC1B,eAAO,MAAM,gBAAgB,QAAsB,CAAC;AAEpD,8BAA8B;AAC9B,eAAO,MAAM,oBAAoB,QAAyB,CAAC;AAI3D,oDAAoD;AACpD,eAAO,MAAM,aAAa,QAAyB,CAAC;AAEpD,iDAAiD;AACjD,eAAO,MAAM,cAAc,QAA0B,CAAC;AAEtD,6CAA6C;AAC7C,eAAO,MAAM,UAAU,QAAmB,CAAC;AAE3C,iDAAiD;AACjD,eAAO,MAAM,WAAW,QAAuB,CAAC;AAEhD,uCAAuC;AACvC,eAAO,MAAM,WAAW,QAAsB,CAAC;AAE/C,2CAA2C;AAC3C,eAAO,MAAM,SAAS,QAAkB,CAAC;AAEzC,6CAA6C;AAC7C,eAAO,MAAM,UAAU,QAAmB,CAAC;AAI3C,kCAAkC;AAClC,eAAO,MAAM,kBAAkB,SAAS,CAAC;AAEzC,8BAA8B;AAC9B,eAAO,MAAM,kBAAkB,QAAQ,CAAC;AAExC,+BAA+B;AAC/B,eAAO,MAAM,eAAe,eAAgC,CAAC;AAE7D,mEAAmE;AACnE,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAIvC,yBAAyB;AACzB,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAElD,8CAA8C;AAC9C,eAAO,MAAM,cAAc,UAAU,CAAC;AAItC,0CAA0C;AAC1C,eAAO,MAAM,kBAAkB,QAAQ,CAAC;AAExC,qEAAqE;AACrE,eAAO,MAAM,kBAAkB,YAAY,CAAC;AAE5C;;;GAGG;AACH,eAAO,MAAM,eAAe;;;EAA2D,CAAC"}
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"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"}
|
package/dist/constants.js
CHANGED
|
@@ -88,3 +88,26 @@ export const DEFAULT_TS_OUT_DIR = '_assets';
|
|
|
88
88
|
* Creates a single global bundle from 'main.ts' entry point.
|
|
89
89
|
*/
|
|
90
90
|
export const DEFAULT_BUNDLES = [{ entryPoint: 'main.ts', bundleName: 'main' }];
|
|
91
|
+
// === Image Constants ===
|
|
92
|
+
/** Output directory for generated and copied images, relative to the build output */
|
|
93
|
+
export const IMAGES_OUTPUT_DIR = '_img';
|
|
94
|
+
/** Subdirectory of {@link IMAGES_OUTPUT_DIR} for generated Open Graph images */
|
|
95
|
+
export const OG_IMAGES_SUBDIR = 'og';
|
|
96
|
+
/** Cache subdirectory (inside `.stati`) holding the images manifest */
|
|
97
|
+
export const IMAGES_CACHE_SUBDIR = 'images';
|
|
98
|
+
/** Package that implements responsive images and Open Graph image generation */
|
|
99
|
+
export const IMAGES_PACKAGE_NAME = '@stati/images';
|
|
100
|
+
/** Default candidate widths for responsive images */
|
|
101
|
+
export const DEFAULT_IMAGE_WIDTHS = [480, 960, 1440];
|
|
102
|
+
/** Default modern formats for responsive images */
|
|
103
|
+
export const DEFAULT_IMAGE_FORMATS = ['avif', 'webp'];
|
|
104
|
+
/** Default encoder quality per format; PNG stays lossless unless configured */
|
|
105
|
+
export const DEFAULT_IMAGE_QUALITY = { avif: 50, webp: 75, jpeg: 80 };
|
|
106
|
+
/** Default `sizes` attribute for responsive images */
|
|
107
|
+
export const DEFAULT_IMAGE_SIZES = '100vw';
|
|
108
|
+
/** Default Open Graph image template name */
|
|
109
|
+
export const DEFAULT_OG_TEMPLATE = 'og.eta';
|
|
110
|
+
/** Default Open Graph image width in pixels */
|
|
111
|
+
export const DEFAULT_OG_WIDTH = 1200;
|
|
112
|
+
/** Default Open Graph image height in pixels */
|
|
113
|
+
export const DEFAULT_OG_HEIGHT = 630;
|
package/dist/core/build.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/core/build.ts"],"names":[],"mappings":"AA2DA,OAAO,KAAK,EAEV,UAAU,EACV,MAAM,EASP,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/core/build.ts"],"names":[],"mappings":"AA2DA,OAAO,KAAK,EAEV,UAAU,EACV,MAAM,EASP,MAAM,mBAAmB,CAAC;AAK3B,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGxE;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,gCAAgC;IAChC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,YAAY;IAC3B,iDAAiD;IACjD,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,iDAAiD;IACjD,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,0CAA0C;IAC1C,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,uCAAuC;IACvC,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACpC,qCAAqC;IACrC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,8BAA8B;IAC9B,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,+BAA+B;IAC/B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,iCAAiC;IACjC,OAAO,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IACrC,uEAAuE;IACvE,eAAe,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACtC,sGAAsG;IACtG,gBAAgB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,mFAAmF;IACnF,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACrC;AAED;;GAEG;AACH,MAAM,WAAW,WAAY,SAAQ,UAAU;IAC7C,wDAAwD;IACxD,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAkGD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAsB,KAAK,CAAC,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,CAsB5E"}
|
package/dist/core/build.js
CHANGED
|
@@ -12,6 +12,7 @@ import { generateRSSFeeds, validateRSSConfig } from '../rss/index.js';
|
|
|
12
12
|
import { computeSearchIndexFilename, generateSearchIndex, writeSearchIndex, autoInjectSearchMeta, } from '../search/index.js';
|
|
13
13
|
import { getEnv, isDevelopment } from '../env.js';
|
|
14
14
|
import { DEFAULT_OUT_DIR } from '../constants.js';
|
|
15
|
+
import { ImageRuntime } from '../images/runtime.js';
|
|
15
16
|
import { createMetricRecorder, noopMetricRecorder } from '../metrics/index.js';
|
|
16
17
|
/**
|
|
17
18
|
* Recursively calculates the total size of a directory in bytes.
|
|
@@ -203,10 +204,29 @@ async function loadContentAndBuildNavigation(config, options, logger) {
|
|
|
203
204
|
const eta = createTemplateEngine(config);
|
|
204
205
|
return { pages, navigation, md, eta, navigationHash };
|
|
205
206
|
}
|
|
207
|
+
/**
|
|
208
|
+
* Output file and ISG cache key of a page.
|
|
209
|
+
*/
|
|
210
|
+
function getPageOutput(page, outDir) {
|
|
211
|
+
let outputPath;
|
|
212
|
+
if (page.url === '/') {
|
|
213
|
+
outputPath = join(outDir, 'index.html');
|
|
214
|
+
}
|
|
215
|
+
else if (page.url.endsWith('/')) {
|
|
216
|
+
outputPath = join(outDir, page.url, 'index.html');
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
outputPath = join(outDir, `${page.url}.html`);
|
|
220
|
+
}
|
|
221
|
+
// Cache key is the output path relative to outDir
|
|
222
|
+
const relativePath = relative(outDir, outputPath).replace(/\\/g, '/');
|
|
223
|
+
const cacheKey = relativePath.startsWith('/') ? relativePath : `/${relativePath}`;
|
|
224
|
+
return { outputPath, cacheKey };
|
|
225
|
+
}
|
|
206
226
|
/**
|
|
207
227
|
* Processes pages with ISG caching logic.
|
|
208
228
|
*/
|
|
209
|
-
async function processPagesWithCache(pages, manifest, config, outDir, md, eta, navigation, buildTime, options, logger, compiledBundles, recorder = noopMetricRecorder, searchIndexFilename) {
|
|
229
|
+
async function processPagesWithCache(pages, manifest, config, outDir, md, eta, navigation, buildTime, options, logger, compiledBundles, recorder = noopMetricRecorder, searchIndexFilename, imageRuntime = null) {
|
|
210
230
|
let cacheHits = 0;
|
|
211
231
|
let cacheMisses = 0;
|
|
212
232
|
const searchablePages = [];
|
|
@@ -236,24 +256,14 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
|
|
|
236
256
|
const isDevMode = isDevelopment();
|
|
237
257
|
const pendingWrites = [];
|
|
238
258
|
const pendingCacheUpdates = [];
|
|
259
|
+
// Image settings that change generated HTML, part of every page's render inputs
|
|
260
|
+
const imageFingerprint = imageRuntime?.fingerprint();
|
|
239
261
|
for (let i = 0; i < pages.length; i++) {
|
|
240
262
|
const page = pages[i];
|
|
241
263
|
if (!page)
|
|
242
264
|
continue; // Safety check
|
|
243
|
-
// Determine output path
|
|
244
|
-
|
|
245
|
-
if (page.url === '/') {
|
|
246
|
-
outputPath = join(outDir, 'index.html');
|
|
247
|
-
}
|
|
248
|
-
else if (page.url.endsWith('/')) {
|
|
249
|
-
outputPath = join(outDir, page.url, 'index.html');
|
|
250
|
-
}
|
|
251
|
-
else {
|
|
252
|
-
outputPath = join(outDir, `${page.url}.html`);
|
|
253
|
-
}
|
|
254
|
-
// Get cache key (use output path relative to outDir)
|
|
255
|
-
const relativePath = relative(outDir, outputPath).replace(/\\/g, '/');
|
|
256
|
-
const cacheKey = relativePath.startsWith('/') ? relativePath : `/${relativePath}`;
|
|
265
|
+
// Determine output path and cache key
|
|
266
|
+
const { outputPath, cacheKey } = getPageOutput(page, outDir);
|
|
257
267
|
const existingEntry = manifest.entries[cacheKey];
|
|
258
268
|
const bundlePaths = getBundlePathsForPage(page.url, compiledBundles);
|
|
259
269
|
const assets = {
|
|
@@ -267,12 +277,16 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
|
|
|
267
277
|
...assets,
|
|
268
278
|
bundleAutoInject: config.typescript?.autoInject !== false,
|
|
269
279
|
searchAutoInject: config.search?.autoInjectMetaTag !== false,
|
|
280
|
+
// Omitted with images: false, so the hash matches builds without the image pipeline
|
|
281
|
+
...(imageFingerprint && { images: imageFingerprint }),
|
|
270
282
|
});
|
|
271
283
|
// Check if we should rebuild this page (considering ISG logic)
|
|
272
284
|
const shouldRebuildStart = performance.now();
|
|
273
285
|
const shouldRebuild = options.force ||
|
|
274
286
|
config.isg?.enabled === false ||
|
|
275
287
|
!(await pathExists(outputPath)) ||
|
|
288
|
+
// A cached page whose generated images are gone is rebuilt to regenerate them
|
|
289
|
+
(imageRuntime !== null && !imageRuntime.outputsExist(existingEntry?.images)) ||
|
|
276
290
|
(await shouldRebuildPage(page, existingEntry, config, pages, buildTime, renderInputsHash));
|
|
277
291
|
const shouldRebuildTime = performance.now() - shouldRebuildStart;
|
|
278
292
|
totalShouldRebuildTime += shouldRebuildTime;
|
|
@@ -302,12 +316,16 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
|
|
|
302
316
|
await config.hooks.beforeRender({ page, config });
|
|
303
317
|
recorder.addToPhase('hookBeforeRenderTotalMs', performance.now() - hookStart);
|
|
304
318
|
}
|
|
319
|
+
// Image state for this page: markup, dependencies and generated files
|
|
320
|
+
const pageImages = imageRuntime?.createPageContext(page);
|
|
305
321
|
// Render markdown to HTML with TOC extraction
|
|
306
322
|
const tocEnabled = config.markdown?.toc !== false;
|
|
307
|
-
const { html: htmlContent, toc } = renderMarkdown(page.content, md, tocEnabled
|
|
323
|
+
const { html: htmlContent, toc } = renderMarkdown(page.content, md, tocEnabled, {
|
|
324
|
+
images: pageImages,
|
|
325
|
+
});
|
|
308
326
|
// Render with template
|
|
309
327
|
const renderPageStart = performance.now();
|
|
310
|
-
const renderResult = await renderPage(page, htmlContent, config, eta, navigation, pages, assets, toc, logger);
|
|
328
|
+
const renderResult = await renderPage(page, htmlContent, config, eta, navigation, pages, assets, toc, logger, { images: pageImages });
|
|
311
329
|
const renderPageTime = performance.now() - renderPageStart;
|
|
312
330
|
totalRenderTime += renderPageTime;
|
|
313
331
|
let finalHtml = renderResult.html;
|
|
@@ -324,6 +342,9 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
|
|
|
324
342
|
if (config.seo?.debug !== undefined) {
|
|
325
343
|
injectOptions.debug = config.seo.debug;
|
|
326
344
|
}
|
|
345
|
+
if (renderResult.ogImage) {
|
|
346
|
+
injectOptions.generatedImage = renderResult.ogImage;
|
|
347
|
+
}
|
|
327
348
|
finalHtml = autoInjectSEO(finalHtml, injectOptions);
|
|
328
349
|
}
|
|
329
350
|
// Auto-inject search index meta tag if enabled (default: true)
|
|
@@ -336,6 +357,10 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
|
|
|
336
357
|
if (config.typescript?.autoInject !== false && assets.bundlePaths.length > 0) {
|
|
337
358
|
finalHtml = autoInjectBundles(finalHtml, assets.bundlePaths);
|
|
338
359
|
}
|
|
360
|
+
// Start the image jobs the final HTML needs and record its image files for the cache
|
|
361
|
+
const renderedAssets = pageImages && imageRuntime
|
|
362
|
+
? imageRuntime.commitPage(pageImages, finalHtml, { outputPath, cacheKey })
|
|
363
|
+
: undefined;
|
|
339
364
|
const renderTime = Math.round(performance.now() - startTime);
|
|
340
365
|
// Record page timing for rendered pages (includes template count)
|
|
341
366
|
recorder.recordPageTiming(page.url, renderTime, false, renderResult.templatesLoaded);
|
|
@@ -345,7 +370,7 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
|
|
|
345
370
|
// In dev mode, batch writes for parallel execution to avoid Windows filesystem slowdown
|
|
346
371
|
if (isDevMode) {
|
|
347
372
|
pendingWrites.push({ outputPath, content: finalHtml });
|
|
348
|
-
pendingCacheUpdates.push({ cacheKey, page, existingEntry, renderInputsHash });
|
|
373
|
+
pendingCacheUpdates.push({ cacheKey, page, existingEntry, renderInputsHash, renderedAssets });
|
|
349
374
|
}
|
|
350
375
|
else {
|
|
351
376
|
// Production mode: write immediately
|
|
@@ -357,10 +382,10 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
|
|
|
357
382
|
if (!options.skipCacheUpdate) {
|
|
358
383
|
const cacheEntryStart = performance.now();
|
|
359
384
|
if (existingEntry) {
|
|
360
|
-
manifest.entries[cacheKey] = await updateCacheEntry(existingEntry, page, config, pages, buildTime, false, renderInputsHash);
|
|
385
|
+
manifest.entries[cacheKey] = await updateCacheEntry(existingEntry, page, config, pages, buildTime, false, renderInputsHash, renderedAssets);
|
|
361
386
|
}
|
|
362
387
|
else {
|
|
363
|
-
manifest.entries[cacheKey] = await createCacheEntry(page, config, pages, buildTime, renderInputsHash);
|
|
388
|
+
manifest.entries[cacheKey] = await createCacheEntry(page, config, pages, buildTime, renderInputsHash, renderedAssets);
|
|
364
389
|
}
|
|
365
390
|
totalCacheEntryTime += performance.now() - cacheEntryStart;
|
|
366
391
|
}
|
|
@@ -390,14 +415,14 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
|
|
|
390
415
|
// This avoids expensive template dependency tracking on each incremental rebuild
|
|
391
416
|
if (!options.skipCacheUpdate) {
|
|
392
417
|
const cacheEntryStart = performance.now();
|
|
393
|
-
for (const { cacheKey, page, existingEntry, renderInputsHash } of pendingCacheUpdates) {
|
|
418
|
+
for (const { cacheKey, page, existingEntry, renderInputsHash, renderedAssets, } of pendingCacheUpdates) {
|
|
394
419
|
if (existingEntry) {
|
|
395
420
|
// Fast update: reuse existing deps, just update hashes and timestamp
|
|
396
421
|
manifest.entries[cacheKey] = await updateCacheEntry(existingEntry, page, config, pages, buildTime, true, // fastUpdate = true in dev mode
|
|
397
|
-
renderInputsHash);
|
|
422
|
+
renderInputsHash, renderedAssets);
|
|
398
423
|
}
|
|
399
424
|
else {
|
|
400
|
-
manifest.entries[cacheKey] = await createCacheEntry(page, config, pages, buildTime, renderInputsHash);
|
|
425
|
+
manifest.entries[cacheKey] = await createCacheEntry(page, config, pages, buildTime, renderInputsHash, renderedAssets);
|
|
401
426
|
}
|
|
402
427
|
}
|
|
403
428
|
totalCacheEntryTime = performance.now() - cacheEntryStart;
|
|
@@ -501,6 +526,14 @@ async function buildInternal(options = {}) {
|
|
|
501
526
|
await remove(cacheDir);
|
|
502
527
|
}
|
|
503
528
|
await ensureDir(outDir);
|
|
529
|
+
// Image pipeline; null with images: false. Fails here, before any page renders, when a
|
|
530
|
+
// feature needs @stati/images and it is missing or incompatible.
|
|
531
|
+
const imageRuntime = await ImageRuntime.create(config, {
|
|
532
|
+
outDir,
|
|
533
|
+
cacheDir,
|
|
534
|
+
logger,
|
|
535
|
+
development: isDevelopment(),
|
|
536
|
+
});
|
|
504
537
|
// Enable Tailwind class inventory tracking only if Tailwind is detected
|
|
505
538
|
const tailwindInitStart = performance.now();
|
|
506
539
|
const hasTailwind = await isTailwindUsed();
|
|
@@ -593,9 +626,26 @@ async function buildInternal(options = {}) {
|
|
|
593
626
|
}
|
|
594
627
|
const endPageRenderSpan = recorder.startSpan('pageRenderingMs');
|
|
595
628
|
const buildTime = new Date();
|
|
596
|
-
const pageProcessingResult = await processPagesWithCache(pages, manifest, config, outDir, md, eta, navigation, buildTime, options, logger, compiledBundles, recorder, searchIndexFilename);
|
|
629
|
+
const pageProcessingResult = await processPagesWithCache(pages, manifest, config, outDir, md, eta, navigation, buildTime, options, logger, compiledBundles, recorder, searchIndexFilename, imageRuntime);
|
|
597
630
|
endPageRenderSpan();
|
|
598
631
|
const { cacheHits, cacheMisses, searchablePages } = pageProcessingResult;
|
|
632
|
+
// Wait for image jobs; pages whose images failed are rewritten to show plain images
|
|
633
|
+
let imageFailures = [];
|
|
634
|
+
if (imageRuntime) {
|
|
635
|
+
const endImageSpan = recorder.startSpan('imageSettleMs');
|
|
636
|
+
const settled = await imageRuntime.settle(manifest);
|
|
637
|
+
await imageRuntime.save();
|
|
638
|
+
endImageSpan();
|
|
639
|
+
imageFailures = settled.failures;
|
|
640
|
+
const { generated, cached, failed } = imageRuntime.stats;
|
|
641
|
+
recorder.increment('imagesGenerated', generated);
|
|
642
|
+
recorder.increment('imagesCached', cached);
|
|
643
|
+
recorder.increment('imagesFailed', failed);
|
|
644
|
+
if (generated > 0 || failed > 0) {
|
|
645
|
+
logger.info('');
|
|
646
|
+
logger.info(`Images: ${generated} generated, ${cached} reused${failed > 0 ? `, ${failed} failed` : ''}`);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
599
649
|
// Generate search index if enabled
|
|
600
650
|
// Uses markdown content and TOC entries
|
|
601
651
|
let searchIndexMetadata;
|
|
@@ -638,6 +688,13 @@ async function buildInternal(options = {}) {
|
|
|
638
688
|
await saveCacheManifest(cacheDir, manifest);
|
|
639
689
|
}
|
|
640
690
|
endManifestSaveSpan();
|
|
691
|
+
// In the dev server a failed Open Graph image goes to the error overlay, like a template error
|
|
692
|
+
const ogFailures = imageFailures.filter((failure) => failure.og);
|
|
693
|
+
if (isDevMode && ogFailures.length > 0) {
|
|
694
|
+
throw new Error(ogFailures
|
|
695
|
+
.map((failure) => `Open Graph image failed for ${failure.label}: ${failure.message}`)
|
|
696
|
+
.join('\n'));
|
|
697
|
+
}
|
|
641
698
|
// Copy static assets and count them (skip for template-only changes in dev mode)
|
|
642
699
|
const endAssetSpan = recorder.startSpan('assetCopyMs');
|
|
643
700
|
let assetsCount = 0;
|
|
@@ -646,6 +703,21 @@ async function buildInternal(options = {}) {
|
|
|
646
703
|
}
|
|
647
704
|
endAssetSpan();
|
|
648
705
|
recorder.increment('assetsCopied', assetsCount);
|
|
706
|
+
// Delete generated images no page of this build uses. Production only: dev builds skip
|
|
707
|
+
// responsive and Open Graph images by default and must not delete them.
|
|
708
|
+
if (imageRuntime && getEnv() === 'production') {
|
|
709
|
+
const liveImages = new Set();
|
|
710
|
+
const liveSources = new Set();
|
|
711
|
+
for (const page of pages) {
|
|
712
|
+
const entry = manifest.entries[getPageOutput(page, outDir).cacheKey];
|
|
713
|
+
entry?.images?.forEach((image) => liveImages.add(image));
|
|
714
|
+
entry?.assetDeps?.forEach((source) => liveSources.add(source));
|
|
715
|
+
}
|
|
716
|
+
const removed = await imageRuntime.prune(liveImages, liveSources);
|
|
717
|
+
if (removed > 0) {
|
|
718
|
+
logger.info(`Removed ${removed} unused generated image${removed === 1 ? '' : 's'}`);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
649
721
|
// Get current environment
|
|
650
722
|
const currentEnv = getEnv();
|
|
651
723
|
// Generate sitemap if enabled (only in production mode)
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import type { PageModel, CacheEntry, StatiConfig } from '../../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Files a render discovered: the source images a page displays (`assetDeps`) and the
|
|
4
|
+
* generated images it references (`images`).
|
|
5
|
+
*/
|
|
6
|
+
export interface RenderedAssets {
|
|
7
|
+
/** Absolute paths of files whose content affects the page, such as source images */
|
|
8
|
+
assetDeps: readonly string[];
|
|
9
|
+
/** Generated image files the page references, relative to the output directory */
|
|
10
|
+
images: readonly string[];
|
|
11
|
+
}
|
|
2
12
|
/**
|
|
3
13
|
* Determines if a page should be rebuilt based on ISG logic.
|
|
4
14
|
* Checks content changes, dependency changes, TTL expiration, and freeze status.
|
|
@@ -29,6 +39,7 @@ export declare function shouldRebuildPage(page: PageModel, entry: CacheEntry | u
|
|
|
29
39
|
* @param allPages - All pages in the site, used to resolve the same layout as rendering
|
|
30
40
|
* @param renderedAt - When the page was rendered
|
|
31
41
|
* @param renderInputsHash - Optional hash of asset URLs and injection settings
|
|
42
|
+
* @param rendered - Files the render discovered, such as images
|
|
32
43
|
* @returns New cache entry
|
|
33
44
|
*
|
|
34
45
|
* @example
|
|
@@ -36,7 +47,7 @@ export declare function shouldRebuildPage(page: PageModel, entry: CacheEntry | u
|
|
|
36
47
|
* const entry = await createCacheEntry(page, config, pages, new Date());
|
|
37
48
|
* ```
|
|
38
49
|
*/
|
|
39
|
-
export declare function createCacheEntry(page: PageModel, config: StatiConfig, allPages: PageModel[], renderedAt: Date, renderInputsHash?: string): Promise<CacheEntry>;
|
|
50
|
+
export declare function createCacheEntry(page: PageModel, config: StatiConfig, allPages: PageModel[], renderedAt: Date, renderInputsHash?: string, rendered?: RenderedAssets): Promise<CacheEntry>;
|
|
40
51
|
/**
|
|
41
52
|
* Updates an existing cache entry with new information after rebuilding.
|
|
42
53
|
* In dev mode, this is optimized to reuse the existing deps array since
|
|
@@ -49,6 +60,7 @@ export declare function createCacheEntry(page: PageModel, config: StatiConfig, a
|
|
|
49
60
|
* @param renderedAt - When the page was rendered
|
|
50
61
|
* @param fastUpdate - If true, skip expensive dep tracking (dev mode optimization)
|
|
51
62
|
* @param renderInputsHash - Optional hash of asset URLs and injection settings
|
|
63
|
+
* @param rendered - Files the render discovered, such as images
|
|
52
64
|
* @returns Updated cache entry
|
|
53
65
|
*
|
|
54
66
|
* @example
|
|
@@ -56,5 +68,5 @@ export declare function createCacheEntry(page: PageModel, config: StatiConfig, a
|
|
|
56
68
|
* const updatedEntry = await updateCacheEntry(existingEntry, page, config, pages, new Date());
|
|
57
69
|
* ```
|
|
58
70
|
*/
|
|
59
|
-
export declare function updateCacheEntry(entry: CacheEntry, page: PageModel, config: StatiConfig, allPages: PageModel[], renderedAt: Date, fastUpdate?: boolean, renderInputsHash?: string): Promise<CacheEntry>;
|
|
71
|
+
export declare function updateCacheEntry(entry: CacheEntry, page: PageModel, config: StatiConfig, allPages: PageModel[], renderedAt: Date, fastUpdate?: boolean, renderInputsHash?: string, rendered?: RenderedAssets): Promise<CacheEntry>;
|
|
60
72
|
//# sourceMappingURL=builder.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../../src/core/isg/builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../../src/core/isg/builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAgF/E;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,oFAAoF;IACpF,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,kFAAkF;IAClF,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B;AA0BD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,SAAS,EACf,KAAK,EAAE,UAAU,GAAG,SAAS,EAC7B,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,SAAS,EAAE,EACrB,GAAG,EAAE,IAAI,EACT,gBAAgB,CAAC,EAAE,MAAM,GACxB,OAAO,CAAC,OAAO,CAAC,CAuKlB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,SAAS,EAAE,EACrB,UAAU,EAAE,IAAI,EAChB,gBAAgB,CAAC,EAAE,MAAM,EACzB,QAAQ,CAAC,EAAE,cAAc,GACxB,OAAO,CAAC,UAAU,CAAC,CAsFrB;AAkDD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,UAAU,EACjB,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,SAAS,EAAE,EACrB,UAAU,EAAE,IAAI,EAChB,UAAU,UAAQ,EAClB,gBAAgB,CAAC,EAAE,MAAM,EACzB,QAAQ,CAAC,EAAE,cAAc,GACxB,OAAO,CAAC,UAAU,CAAC,CA+DrB"}
|
package/dist/core/isg/builder.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { computeContentHash, computeFileHash, computeInputsHash } from './hash.js';
|
|
1
|
+
import { computeContentHash, computeFileHash, computeBinaryFileHash, computeInputsHash, } from './hash.js';
|
|
2
2
|
import { trackTemplateDependencies, resolveLayoutDependency } from './deps.js';
|
|
3
3
|
import { computeEffectiveTTL, computeNextRebuildAt, isPageFrozen } from './ttl.js';
|
|
4
4
|
import { validatePageISGOverrides, extractNumericOverride } from './validation.js';
|
|
@@ -55,8 +55,35 @@ function isValidCacheEntry(entry) {
|
|
|
55
55
|
if (entry.maxAgeCapDays !== undefined && typeof entry.maxAgeCapDays !== 'number') {
|
|
56
56
|
return false;
|
|
57
57
|
}
|
|
58
|
+
for (const list of [entry.assetDeps, entry.images]) {
|
|
59
|
+
if (list !== undefined && (!Array.isArray(list) || !list.every((v) => typeof v === 'string'))) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
58
63
|
return true;
|
|
59
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Hashes render-discovered dependencies by content. Missing files hash to a sentinel.
|
|
67
|
+
*/
|
|
68
|
+
async function hashAssetDeps(assetDeps) {
|
|
69
|
+
const hashes = [];
|
|
70
|
+
for (const assetPath of assetDeps ?? []) {
|
|
71
|
+
hashes.push(await computeBinaryFileHash(assetPath));
|
|
72
|
+
}
|
|
73
|
+
return hashes;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Returns the entry with its `assetDeps` and `images` replaced by this render's,
|
|
77
|
+
* dropping the fields when a render found none.
|
|
78
|
+
*/
|
|
79
|
+
function withRenderedAssets(entry, rendered) {
|
|
80
|
+
const { assetDeps: _assetDeps, images: _images, ...rest } = entry;
|
|
81
|
+
return {
|
|
82
|
+
...rest,
|
|
83
|
+
...(rendered && rendered.assetDeps.length > 0 && { assetDeps: [...rendered.assetDeps] }),
|
|
84
|
+
...(rendered && rendered.images.length > 0 && { images: [...rendered.images] }),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
60
87
|
/**
|
|
61
88
|
* Determines if a page should be rebuilt based on ISG logic.
|
|
62
89
|
* Checks content changes, dependency changes, TTL expiration, and freeze status.
|
|
@@ -133,6 +160,8 @@ export async function shouldRebuildPage(page, entry, config, allPages, now, rend
|
|
|
133
160
|
console.warn(`${dependencyErrors} dependency errors for ${page.url}, forcing rebuild`);
|
|
134
161
|
return true;
|
|
135
162
|
}
|
|
163
|
+
// Files found during the last render, such as images, are hashed by content
|
|
164
|
+
depsHashes.push(...(await hashAssetDeps(entry.assetDeps)));
|
|
136
165
|
const currentInputsHash = computeInputsHash(currentContentHash, depsHashes, renderInputsHash);
|
|
137
166
|
// If inputs changed, always rebuild
|
|
138
167
|
if (currentInputsHash !== entry.inputsHash) {
|
|
@@ -215,6 +244,7 @@ export async function shouldRebuildPage(page, entry, config, allPages, now, rend
|
|
|
215
244
|
* @param allPages - All pages in the site, used to resolve the same layout as rendering
|
|
216
245
|
* @param renderedAt - When the page was rendered
|
|
217
246
|
* @param renderInputsHash - Optional hash of asset URLs and injection settings
|
|
247
|
+
* @param rendered - Files the render discovered, such as images
|
|
218
248
|
* @returns New cache entry
|
|
219
249
|
*
|
|
220
250
|
* @example
|
|
@@ -222,7 +252,7 @@ export async function shouldRebuildPage(page, entry, config, allPages, now, rend
|
|
|
222
252
|
* const entry = await createCacheEntry(page, config, pages, new Date());
|
|
223
253
|
* ```
|
|
224
254
|
*/
|
|
225
|
-
export async function createCacheEntry(page, config, allPages, renderedAt, renderInputsHash) {
|
|
255
|
+
export async function createCacheEntry(page, config, allPages, renderedAt, renderInputsHash, rendered) {
|
|
226
256
|
const timings = {};
|
|
227
257
|
let start = performance.now();
|
|
228
258
|
// Validate page-level ISG overrides first
|
|
@@ -245,6 +275,7 @@ export async function createCacheEntry(page, config, allPages, renderedAt, rende
|
|
|
245
275
|
depsHashes.push(depHash);
|
|
246
276
|
}
|
|
247
277
|
}
|
|
278
|
+
depsHashes.push(...(await hashAssetDeps(rendered?.assetDeps)));
|
|
248
279
|
timings.depsHashes = performance.now() - start;
|
|
249
280
|
const inputsHash = computeInputsHash(contentHash, depsHashes, renderInputsHash);
|
|
250
281
|
// Extract tags from front matter
|
|
@@ -287,7 +318,7 @@ export async function createCacheEntry(page, config, allPages, renderedAt, rende
|
|
|
287
318
|
if (maxAgeCapDays !== undefined) {
|
|
288
319
|
cacheEntry.maxAgeCapDays = maxAgeCapDays;
|
|
289
320
|
}
|
|
290
|
-
return cacheEntry;
|
|
321
|
+
return withRenderedAssets(cacheEntry, rendered);
|
|
291
322
|
}
|
|
292
323
|
/**
|
|
293
324
|
* Checks if the template structure has changed by comparing the layout file's
|
|
@@ -345,6 +376,7 @@ async function hasTemplateStructureChanged(entry) {
|
|
|
345
376
|
* @param renderedAt - When the page was rendered
|
|
346
377
|
* @param fastUpdate - If true, skip expensive dep tracking (dev mode optimization)
|
|
347
378
|
* @param renderInputsHash - Optional hash of asset URLs and injection settings
|
|
379
|
+
* @param rendered - Files the render discovered, such as images
|
|
348
380
|
* @returns Updated cache entry
|
|
349
381
|
*
|
|
350
382
|
* @example
|
|
@@ -352,7 +384,7 @@ async function hasTemplateStructureChanged(entry) {
|
|
|
352
384
|
* const updatedEntry = await updateCacheEntry(existingEntry, page, config, pages, new Date());
|
|
353
385
|
* ```
|
|
354
386
|
*/
|
|
355
|
-
export async function updateCacheEntry(entry, page, config, allPages, renderedAt, fastUpdate = false, renderInputsHash) {
|
|
387
|
+
export async function updateCacheEntry(entry, page, config, allPages, renderedAt, fastUpdate = false, renderInputsHash, rendered) {
|
|
356
388
|
// In fast update mode (dev), reuse existing deps to avoid expensive tracking
|
|
357
389
|
if (fastUpdate && entry.deps.length > 0) {
|
|
358
390
|
// Detect if template structure has changed (new includes/extends added)
|
|
@@ -380,16 +412,18 @@ export async function updateCacheEntry(entry, page, config, allPages, renderedAt
|
|
|
380
412
|
depsHashes.push(depHash);
|
|
381
413
|
}
|
|
382
414
|
}
|
|
415
|
+
// Asset deps come from this render, not from the previous entry
|
|
416
|
+
depsHashes.push(...(await hashAssetDeps(rendered?.assetDeps)));
|
|
383
417
|
const inputsHash = computeInputsHash(contentHash, depsHashes, renderInputsHash);
|
|
384
|
-
return {
|
|
418
|
+
return withRenderedAssets({
|
|
385
419
|
...entry,
|
|
386
420
|
inputsHash,
|
|
387
421
|
renderedAt: renderedAt.toISOString(),
|
|
388
|
-
};
|
|
422
|
+
}, rendered);
|
|
389
423
|
}
|
|
390
424
|
}
|
|
391
425
|
// Full update - create new entry and preserve original publishedAt
|
|
392
|
-
const newEntry = await createCacheEntry(page, config, allPages, renderedAt, renderInputsHash);
|
|
426
|
+
const newEntry = await createCacheEntry(page, config, allPages, renderedAt, renderInputsHash, rendered);
|
|
393
427
|
// Preserve original publishedAt if no new one is specified
|
|
394
428
|
if (!newEntry.publishedAt && entry.publishedAt) {
|
|
395
429
|
newEntry.publishedAt = entry.publishedAt;
|
package/dist/core/isg/deps.d.ts
CHANGED
|
@@ -50,6 +50,15 @@ export declare function trackTemplateDependencies(page: PageModel, config: Stati
|
|
|
50
50
|
* @returns Absolute path to the layout file (POSIX format), or null if no layout applies
|
|
51
51
|
*/
|
|
52
52
|
export declare function resolveLayoutDependency(page: PageModel, config: StatiConfig, allPages: PageModel[]): Promise<string | null>;
|
|
53
|
+
/**
|
|
54
|
+
* Resolves the Open Graph template a page renders with, when Open Graph images are on and the
|
|
55
|
+
* page does not opt out. Pages that use the built-in template have no file dependency.
|
|
56
|
+
*
|
|
57
|
+
* @param page - The page model
|
|
58
|
+
* @param config - Stati configuration
|
|
59
|
+
* @returns Absolute path to the template (POSIX format), or null
|
|
60
|
+
*/
|
|
61
|
+
export declare function resolveOgTemplateDependency(page: PageModel, config: StatiConfig): Promise<string | null>;
|
|
53
62
|
/**
|
|
54
63
|
* Resolves a template name to its file path.
|
|
55
64
|
* Used for explicit layout specifications in front matter.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deps.d.ts","sourceRoot":"","sources":["../../../src/core/isg/deps.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"deps.d.ts","sourceRoot":"","sources":["../../../src/core/isg/deps.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAgBnE;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAI7C;AAED;;GAEG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;aAE9B,eAAe,EAAE,MAAM,EAAE;gBAAzB,eAAe,EAAE,MAAM,EAAE,EACzC,OAAO,EAAE,MAAM;CAKlB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,SAAS,EAAE,GACpB,OAAO,CAAC,MAAM,EAAE,CAAC,CA+CnB;AAED;;;;;;;;;GASG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,SAAS,EAAE,GACpB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAexB;AAED;;;;;;;GAOG;AACH,wBAAsB,2BAA2B,CAC/C,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAexB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CASxB"}
|