@michaelthielemann/kestrel-delivery-static 0.1.0 → 5.0.2

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/package.json CHANGED
@@ -1,50 +1,50 @@
1
1
  {
2
2
  "name": "@michaelthielemann/kestrel-delivery-static",
3
- "version": "0.1.0",
4
- "description": "The static delivery adapter: writes/prunes published snapshots through a StorageDriver (the build-time deploy target), plus the S3 static-output shipper.",
3
+ "version": "5.0.2",
4
+ "description": "Renders published documents per locale via renderer@1, stores them in blobstore@1 and tracks a publish status.",
5
+ "type": "module",
5
6
  "license": "Apache-2.0",
6
- "author": "Michael Thielemann <283621694+MichaelThielemann@users.noreply.github.com>",
7
- "homepage": "https://github.com/MichaelThielemann/kestrel#readme",
8
- "repository": {
9
- "type": "git",
10
- "url": "git+https://github.com/MichaelThielemann/kestrel.git",
11
- "directory": "packages/kestrel-delivery-static"
12
- },
13
- "bugs": "https://github.com/MichaelThielemann/kestrel/issues",
14
- "publishConfig": {
15
- "access": "public",
16
- "provenance": true
7
+ "engines": {
8
+ "node": ">=22.13.0"
17
9
  },
18
- "type": "module",
19
- "main": "./dist/index.js",
20
- "types": "./dist/index.d.ts",
21
10
  "exports": {
22
11
  ".": {
23
- "types": "./dist/index.d.ts",
24
- "import": "./dist/index.js"
12
+ "types": "./dist/module.d.ts",
13
+ "default": "./dist/module.js"
14
+ },
15
+ "./impl": {
16
+ "types": "./dist/impl.d.ts",
17
+ "default": "./dist/impl.js"
25
18
  }
26
19
  },
27
- "sideEffects": false,
28
20
  "files": [
29
- "dist"
21
+ "dist",
22
+ "README.md"
30
23
  ],
31
- "engines": {
32
- "node": ">=22.12.0"
24
+ "peerDependencies": {
25
+ "@michaelthielemann/kestrel": "^5.0.2",
26
+ "@michaelthielemann/kestrel-contracts": "^5.0.2"
33
27
  },
34
28
  "dependencies": {
35
- "@michaelthielemann/kestrel-contracts": "0.1.0",
36
- "@michaelthielemann/kestrel-publishing": "0.1.0",
37
- "@michaelthielemann/kestrel-core": "0.1.0",
38
- "@michaelthielemann/kestrel-delivery-live": "0.1.0"
29
+ "turndown": "^7.2.4",
30
+ "zod": "^3.23.8"
39
31
  },
40
32
  "devDependencies": {
41
- "@types/node": "^26.2.0",
42
- "typescript": "^6.0.3",
43
- "vitest": "^4.1.10"
33
+ "@types/turndown": "^5.0.6",
34
+ "vitest": "^3.2.0",
35
+ "@michaelthielemann/kestrel": "5.0.2",
36
+ "@michaelthielemann/kestrel-content-default": "5.0.2",
37
+ "@michaelthielemann/kestrel-contracts": "5.0.2",
38
+ "@michaelthielemann/kestrel-site-default": "5.0.2"
39
+ },
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/MichaelThielemann/kestrel.git",
43
+ "directory": "packages/delivery-static"
44
44
  },
45
+ "homepage": "https://github.com/MichaelThielemann/kestrel#readme",
46
+ "bugs": "https://github.com/MichaelThielemann/kestrel/issues",
45
47
  "scripts": {
46
- "build": "tsc -p tsconfig.json",
47
- "typecheck": "tsc --noEmit -p tsconfig.typecheck.json",
48
- "test": "vitest run --typecheck"
48
+ "build": "tsc -b tsconfig.json"
49
49
  }
50
50
  }
package/dist/index.d.ts DELETED
@@ -1,4 +0,0 @@
1
- export { createStaticDeliveryPort, deliveryPortFor } from './server/port.js';
2
- export { renderRoute } from './server/render-route.js';
3
- export { isEnvTrue, planS3Deploy, resolveOutputTarget, resolveOutputCreds, isCompressedSidecar, isPermanentError, deployStaticOutput, recordRouteDiscovery, readRouteDiscovery, type OutputTarget, type OutputConfig, type DeployResult, type DeployDriver, type RouteDiscovery, type DeployOptions, } from './server/deploy-output.js';
4
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAC5E,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACtD,OAAO,EACL,SAAS,EACT,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,EAClB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,aAAa,GACnB,MAAM,2BAA2B,CAAA"}
package/dist/index.js DELETED
@@ -1,4 +0,0 @@
1
- export { createStaticDeliveryPort, deliveryPortFor } from './server/port.js';
2
- export { renderRoute } from './server/render-route.js';
3
- export { isEnvTrue, planS3Deploy, resolveOutputTarget, resolveOutputCreds, isCompressedSidecar, isPermanentError, deployStaticOutput, recordRouteDiscovery, readRouteDiscovery, } from './server/deploy-output.js';
4
- //# sourceMappingURL=index.js.map
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAC5E,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACtD,OAAO,EACL,SAAS,EACT,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,GAOnB,MAAM,2BAA2B,CAAA"}
@@ -1,139 +0,0 @@
1
- import type { StorageDriver } from '@michaelthielemann/kestrel-core';
2
- /** A boolean env flag is on for any of the common truthy spellings (case-insensitive, trimmed).
3
- * @public */
4
- export declare function isEnvTrue(value: string | undefined): boolean;
5
- /** @public */
6
- export declare function planS3Deploy(opts: {
7
- isStaticGenerate: boolean;
8
- dryRun: boolean;
9
- bucket: string;
10
- accessKeyId: string;
11
- secretAccessKey: string;
12
- prefix?: string;
13
- /**
14
- * `output.auto` — the running server publishes to this bucket/prefix itself. Build-time route seeding is
15
- * then disabled (a prerendered file would shadow the live route), so `.output/public` holds only what
16
- * Nitro's crawler reached: shipping it would reconcile the server's live pages, sitemap and robots away.
17
- */
18
- autoPublish?: boolean;
19
- /** Bucket the S3 media driver writes live uploads to (empty when media isn't on S3 — then irrelevant). */
20
- mediaBucket?: string;
21
- /** Key prefix of those live media objects, to keep the reconcile from deleting them. */
22
- mediaPrefix?: string;
23
- }): 'skip' | 'deploy';
24
- /** @public */
25
- export interface OutputTarget {
26
- driver: 'local' | 's3';
27
- /** Non-secret S3 settings; credentials are resolved separately by `resolveOutputCreds`. */
28
- s3: {
29
- bucket: string;
30
- region: string;
31
- endpoint: string;
32
- prefix: string;
33
- };
34
- }
35
- /** @public */
36
- export type OutputConfig = {
37
- driver?: string;
38
- s3?: {
39
- bucket?: string;
40
- region?: string;
41
- endpoint?: string;
42
- prefix?: string;
43
- };
44
- };
45
- /**
46
- * Resolve where `nuxt generate`'s static output is shipped: a local directory (default) or an S3
47
- * bucket. Precedence per setting: `KESTREL_OUTPUT_*` env → config → default (the documented Kestrel
48
- * order, same as the runtime publisher). Pure, so the precedence is unit-testable; secrets are resolved
49
- * by `resolveOutputCreds`, never here.
50
- * @public
51
- */
52
- export declare function resolveOutputTarget(out?: OutputConfig, env?: Record<string, string | undefined>): OutputTarget;
53
- /**
54
- * S3 credentials for the output deploy: the output-specific `KESTREL_OUTPUT_S3_*`, falling back to the
55
- * shared media `KESTREL_S3_*` so a single S3 account "just works" — identical to the runtime publisher's
56
- * resolution. Required keys default to '' (not undefined) so `planS3Deploy` detects a missing credential.
57
- * @public
58
- */
59
- export declare function resolveOutputCreds(env?: Record<string, string | undefined>): {
60
- accessKeyId: string;
61
- secretAccessKey: string;
62
- sessionToken?: string;
63
- };
64
- /**
65
- * A `*.gz`/`*.br` file is a pre-compressed sidecar (from `nitro.compressPublicAssets`) when its
66
- * uncompressed base is also in the output. On a plain static S3 host these are dead weight — the host
67
- * does no `Accept-Encoding` negotiation, so nobody fetches `index.html.gz`, and storing it *as*
68
- * `index.html` with `Content-Encoding` would collide with the real file — so the deploy skips them.
69
- * A standalone `*.gz` archive (no base file) is genuine content and is kept.
70
- * @public
71
- */
72
- export declare function isCompressedSidecar(key: string, all: Set<string>): boolean;
73
- /** @public */
74
- export interface DeployResult {
75
- pruned: number;
76
- keys: string[];
77
- }
78
- /**
79
- * What build-time route discovery enumerated for this generate, plus WHY that enumeration is known to be
80
- * short of the site's pages (`incomplete` unset ⇒ it is complete). The runtime publisher's
81
- * `{ routes, failed }` is the same contract: an enumeration that came up short must never be the
82
- * authority for what gets deleted.
83
- */
84
- /** @public */
85
- export interface RouteDiscovery {
86
- routes: string[];
87
- incomplete?: string;
88
- }
89
- /** The prerender and deploy modules are loaded separately and share no module scope — but they are handed
90
- * the same Nuxt instance, so that object is what carries the signal from the one to the other.
91
- * @public */
92
- export declare function recordRouteDiscovery(nuxt: object, discovery: RouteDiscovery): void;
93
- /** `undefined` ⇒ discovery never reported anything, which is not itself evidence of a degraded build.
94
- * @public */
95
- export declare function readRouteDiscovery(nuxt: object): RouteDiscovery | undefined;
96
- /** The deploy needs `put` always, and `list`+`delete` for the reconcile; both are optional on the driver.
97
- * @public */
98
- export type DeployDriver = Pick<StorageDriver, 'put'> & Partial<Pick<StorageDriver, 'list' | 'delete'>>;
99
- /** @public */
100
- export interface DeployOptions {
101
- dryRun?: boolean;
102
- log?: (msg: string) => void;
103
- /** Max operations in flight at once (default 8). Bounded so a large tree never opens thousands of sockets. */
104
- concurrency?: number;
105
- /** Total attempts per operation before giving up (default 3); retries ride out transient S3 errors. */
106
- retries?: number;
107
- /** Sleep between retries (exponential backoff); injectable so tests run without real delay. */
108
- sleep?: (ms: number) => Promise<void>;
109
- /**
110
- * Why a build step REPORTED that this output tree is short of pages (routes that failed to prerender,
111
- * route discovery that could not read the DB). Set ⇒ upload but never reconcile: pages missing from the
112
- * input are indistinguishable from pages removed from the CMS, so the prune would delete live ones.
113
- * Only a step's own failure report belongs here: the SHAPE of the output cannot tell a degraded build
114
- * from a small site, so inferring it would strand the pages an editor deleted.
115
- */
116
- incomplete?: string;
117
- }
118
- /**
119
- * A permanent client error — bad/forbidden credentials, missing bucket, denied access (400/401/403/404).
120
- * These never become successful on a later attempt, so retrying only delays the inevitable loud failure.
121
- * @public
122
- */
123
- export declare function isPermanentError(err: unknown): boolean;
124
- /**
125
- * Upload every file under `dir` to `driver`, keyed by its POSIX-relative path, with a content type
126
- * inferred from the extension — the S3 equivalent of `aws s3 sync` (overwrites). Then reconciles: deletes
127
- * remote objects under the prefix that this generate didn't produce (`--delete`), so pages removed from
128
- * the CMS stop being served (output ≡ DB, no toggle). The reconcile is skipped when the driver can't
129
- * list+delete and when the caller flagged the tree `incomplete` — a build step reported it is short of
130
- * pages, so their absence says nothing about the CMS. Uploads still run then: shipping the pages that did
131
- * render is safe, deleting on their absence is not.
132
- * Uploads and deletes run with bounded concurrency and each is retried with
133
- * backoff, so a transient 5xx/throttle/dropped-connection no longer aborts the deploy mid-way and
134
- * half-updates the bucket; only an operation that fails every attempt rejects (true rollback isn't
135
- * possible on S3). With `dryRun`, walks/lists and reports without writing.
136
- * @public
137
- */
138
- export declare function deployStaticOutput(dir: string, driver: DeployDriver, opts?: DeployOptions): Promise<DeployResult>;
139
- //# sourceMappingURL=deploy-output.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"deploy-output.d.ts","sourceRoot":"","sources":["../../src/server/deploy-output.ts"],"names":[],"mappings":"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"}
@@ -1,220 +0,0 @@
1
- import { readdirSync, readFileSync } from 'node:fs';
2
- import { join, relative, sep } from 'node:path';
3
- import { setTimeout as sleepFor } from 'node:timers/promises';
4
- import { resolveS3Settings, contentTypeFor, cacheControlFor } from '@michaelthielemann/kestrel-core';
5
- /** A boolean env flag is on for any of the common truthy spellings (case-insensitive, trimmed).
6
- * @public */
7
- export function isEnvTrue(value) {
8
- const t = value?.trim().toLowerCase();
9
- return t === 'true' || t === '1' || t === 'yes' || t === 'on';
10
- }
11
- /**
12
- * Decide what the deploy hook should do, kept pure so the trigger guard is unit-testable. Returns
13
- * `'skip'` when this isn't a static `nuxt generate` — a plain `nuxt build` (or dev) must never deploy,
14
- * even with `driver: s3` configured — and when `output.auto` hands publishing to the running server
15
- * (see `autoPublish`). For a real (non-dry-run) static generate it throws when the
16
- * bucket or `KESTREL_S3_*` credentials are missing, so a misconfigured deploy fails the generate
17
- * loudly instead of warning and exiting 0 with a stale live bucket. A dry-run needs neither.
18
- */
19
- /** Strip leading/trailing slashes so prefixes compare on bare path segments. */
20
- const normPrefix = (p) => p.replace(/^\/+|\/+$/g, '');
21
- /**
22
- * Do two S3 key prefixes share keyspace on a path-segment boundary — i.e. is one equal to, or nested
23
- * under, the other? Catches both nesting directions: a prune of `a` reaches everything under `a/b`, and
24
- * a prune of `a/b` reaches the `a/b/…` slice of media stored under `a`. A pure-string collision like
25
- * `site` vs `siteother` is *not* an overlap. Empty prefixes never overlap (handled by their own guards).
26
- */
27
- function prefixesOverlap(a, b) {
28
- const x = normPrefix(a);
29
- const y = normPrefix(b);
30
- if (!x || !y)
31
- return false;
32
- return x === y || x.startsWith(`${y}/`) || y.startsWith(`${x}/`);
33
- }
34
- /** @public */
35
- export function planS3Deploy(opts) {
36
- if (!opts.isStaticGenerate)
37
- return 'skip';
38
- if (opts.autoPublish)
39
- return 'skip';
40
- if (!opts.dryRun) {
41
- const missing = [];
42
- if (!opts.bucket)
43
- missing.push('bucket (KESTREL_OUTPUT_S3_BUCKET)');
44
- if (!opts.accessKeyId)
45
- missing.push('KESTREL_OUTPUT_S3_ACCESS_KEY_ID / KESTREL_S3_ACCESS_KEY_ID');
46
- if (!opts.secretAccessKey)
47
- missing.push('KESTREL_OUTPUT_S3_SECRET_ACCESS_KEY / KESTREL_S3_SECRET_ACCESS_KEY');
48
- if (missing.length) {
49
- throw new Error(`[kestrel] output.driver=s3 but ${missing.join(', ')} ${missing.length > 1 ? 'are' : 'is'} missing`
50
- + ' — refusing to finish `nuxt generate` with a broken S3 deploy.');
51
- }
52
- // The deploy always reconciles (deletes every remote object under the prefix not in this generate —
53
- // output ≡ DB). With an empty prefix that's the whole bucket root, which may hold the media bucket's
54
- // objects too. Refuse rather than risk wiping it: an S3 deploy must target a dedicated prefix.
55
- if (!opts.prefix) {
56
- throw new Error('[kestrel] output.s3.prefix is empty — refusing to deploy (the always-on reconcile would delete the'
57
- + ' whole bucket root, and a shared/media bucket could be wiped). Set KESTREL_OUTPUT_S3_PREFIX to a'
58
- + ' prefix dedicated to the generated site.');
59
- }
60
- // Same bucket as the S3 media driver AND overlapping prefixes → the reconcile would delete live uploads.
61
- if (opts.mediaBucket && opts.mediaBucket === opts.bucket
62
- && opts.mediaPrefix && prefixesOverlap(opts.prefix, opts.mediaPrefix)) {
63
- throw new Error(`[kestrel] the output reconcile would delete live media: output prefix "${opts.prefix}" overlaps the`
64
- + ` S3 media prefix "${opts.mediaPrefix}" in the same bucket "${opts.bucket}". Give the generated`
65
- + ' site its own bucket or a non-overlapping KESTREL_OUTPUT_S3_PREFIX.');
66
- }
67
- }
68
- return 'deploy';
69
- }
70
- const clean = (v) => (typeof v === 'string' && v.trim() ? v.trim() : undefined);
71
- /**
72
- * Resolve where `nuxt generate`'s static output is shipped: a local directory (default) or an S3
73
- * bucket. Precedence per setting: `KESTREL_OUTPUT_*` env → config → default (the documented Kestrel
74
- * order, same as the runtime publisher). Pure, so the precedence is unit-testable; secrets are resolved
75
- * by `resolveOutputCreds`, never here.
76
- * @public
77
- */
78
- export function resolveOutputTarget(out = {}, env = {}) {
79
- const driver = (clean(env.KESTREL_OUTPUT_DRIVER) ?? clean(out.driver))?.toLowerCase() === 's3' ? 's3' : 'local';
80
- // Same non-secret resolver media uses, under the KESTREL_OUTPUT_S3_* prefix (own bucket, own creds).
81
- return { driver, s3: resolveS3Settings(out.s3, env, 'KESTREL_OUTPUT_S3') };
82
- }
83
- /**
84
- * S3 credentials for the output deploy: the output-specific `KESTREL_OUTPUT_S3_*`, falling back to the
85
- * shared media `KESTREL_S3_*` so a single S3 account "just works" — identical to the runtime publisher's
86
- * resolution. Required keys default to '' (not undefined) so `planS3Deploy` detects a missing credential.
87
- * @public
88
- */
89
- export function resolveOutputCreds(env = {}) {
90
- return {
91
- accessKeyId: clean(env.KESTREL_OUTPUT_S3_ACCESS_KEY_ID) ?? clean(env.KESTREL_S3_ACCESS_KEY_ID) ?? '',
92
- secretAccessKey: clean(env.KESTREL_OUTPUT_S3_SECRET_ACCESS_KEY) ?? clean(env.KESTREL_S3_SECRET_ACCESS_KEY) ?? '',
93
- sessionToken: clean(env.KESTREL_OUTPUT_S3_SESSION_TOKEN) ?? clean(env.KESTREL_S3_SESSION_TOKEN),
94
- };
95
- }
96
- /** Every file under `dir` as `{ abs, key }`, the key being its POSIX-relative path (stable order). */
97
- function listFiles(dir) {
98
- return readdirSync(dir, { recursive: true, withFileTypes: true })
99
- .filter((e) => e.isFile())
100
- .map((e) => {
101
- const abs = join(e.parentPath, e.name);
102
- return { abs, key: relative(dir, abs).split(sep).join('/') };
103
- });
104
- }
105
- /**
106
- * A `*.gz`/`*.br` file is a pre-compressed sidecar (from `nitro.compressPublicAssets`) when its
107
- * uncompressed base is also in the output. On a plain static S3 host these are dead weight — the host
108
- * does no `Accept-Encoding` negotiation, so nobody fetches `index.html.gz`, and storing it *as*
109
- * `index.html` with `Content-Encoding` would collide with the real file — so the deploy skips them.
110
- * A standalone `*.gz` archive (no base file) is genuine content and is kept.
111
- * @public
112
- */
113
- export function isCompressedSidecar(key, all) {
114
- const m = /\.(?:gz|br)$/.exec(key);
115
- return m ? all.has(key.slice(0, m.index)) : false;
116
- }
117
- /** The prerender and deploy modules are loaded separately and share no module scope — but they are handed
118
- * the same Nuxt instance, so that object is what carries the signal from the one to the other.
119
- * @public */
120
- export function recordRouteDiscovery(nuxt, discovery) {
121
- nuxt._kestrelRouteDiscovery = discovery;
122
- }
123
- /** `undefined` ⇒ discovery never reported anything, which is not itself evidence of a degraded build.
124
- * @public */
125
- export function readRouteDiscovery(nuxt) {
126
- return nuxt._kestrelRouteDiscovery;
127
- }
128
- async function runPool(items, concurrency, task) {
129
- let next = 0;
130
- const worker = async () => {
131
- while (next < items.length) {
132
- const i = next++;
133
- await task(items[i]);
134
- }
135
- };
136
- await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, worker));
137
- }
138
- /** HTTP statuses the S3 driver surfaces in its error message (e.g. `S3 put failed (403) for x`). */
139
- function statusFromError(err) {
140
- const m = /\((\d{3})\)/.exec(err?.message ?? '');
141
- return m ? Number(m[1]) : undefined;
142
- }
143
- /**
144
- * A permanent client error — bad/forbidden credentials, missing bucket, denied access (400/401/403/404).
145
- * These never become successful on a later attempt, so retrying only delays the inevitable loud failure.
146
- * @public
147
- */
148
- export function isPermanentError(err) {
149
- const s = statusFromError(err);
150
- return s === 400 || s === 401 || s === 403 || s === 404;
151
- }
152
- /** Retry `fn` with exponential backoff up to `retries` total attempts, surfacing the last error. */
153
- async function withRetry(fn, label, retries, sleep, log) {
154
- for (let attempt = 1;; attempt++) {
155
- try {
156
- await fn();
157
- return;
158
- }
159
- catch (err) {
160
- // A permanent 4xx can't be ridden out — re-throw at once so the deploy fails loudly, not slowly.
161
- if (isPermanentError(err) || attempt >= retries)
162
- throw err;
163
- log?.(`retry ${attempt}/${retries - 1} for ${label}: ${err.message}`);
164
- await sleep(100 * 2 ** (attempt - 1));
165
- }
166
- }
167
- }
168
- /**
169
- * Upload every file under `dir` to `driver`, keyed by its POSIX-relative path, with a content type
170
- * inferred from the extension — the S3 equivalent of `aws s3 sync` (overwrites). Then reconciles: deletes
171
- * remote objects under the prefix that this generate didn't produce (`--delete`), so pages removed from
172
- * the CMS stop being served (output ≡ DB, no toggle). The reconcile is skipped when the driver can't
173
- * list+delete and when the caller flagged the tree `incomplete` — a build step reported it is short of
174
- * pages, so their absence says nothing about the CMS. Uploads still run then: shipping the pages that did
175
- * render is safe, deleting on their absence is not.
176
- * Uploads and deletes run with bounded concurrency and each is retried with
177
- * backoff, so a transient 5xx/throttle/dropped-connection no longer aborts the deploy mid-way and
178
- * half-updates the bucket; only an operation that fails every attempt rejects (true rollback isn't
179
- * possible on S3). With `dryRun`, walks/lists and reports without writing.
180
- * @public
181
- */
182
- export async function deployStaticOutput(dir, driver, opts = {}) {
183
- const concurrency = Math.max(1, opts.concurrency ?? 8);
184
- const retries = Math.max(1, opts.retries ?? 3);
185
- const sleep = opts.sleep ?? ((ms) => sleepFor(ms));
186
- const all = listFiles(dir);
187
- const allKeys = new Set(all.map((f) => f.key));
188
- const files = all.filter((f) => !isCompressedSidecar(f.key, allKeys));
189
- const skipped = all.length - files.length;
190
- if (skipped)
191
- opts.log?.(`skipping ${skipped} pre-compressed sidecar(s) (.gz/.br)`);
192
- const keys = files.map((f) => f.key);
193
- if (!opts.dryRun) {
194
- // `files` already excludes pre-compressed sidecars (above), so anything ending `.gz`/`.br` here is a
195
- // standalone archive shipped with no `Content-Encoding` — keep its own extension in the type lookup.
196
- await runPool(files, concurrency, (f) => withRetry(() => driver.put(f.key, readFileSync(f.abs), contentTypeFor(f.key, false), { cacheControl: cacheControlFor(f.key) }), f.key, retries, sleep, opts.log));
197
- }
198
- opts.log?.(`${opts.dryRun ? 'Would deploy' : 'Deployed'} ${keys.length} file(s)`);
199
- // Reconcile (output ≡ DB) — skipped when the driver lacks list/delete (e.g. a put-only target) and when
200
- // a build step reported that this generate is short of pages.
201
- let pruned = 0;
202
- if (!driver.list || !driver.delete) {
203
- opts.log?.('driver cannot list/delete — skipping reconcile');
204
- }
205
- else if (opts.incomplete) {
206
- opts.log?.(`skipping reconcile — ${opts.incomplete}; the missing pages would read as stale and be deleted`);
207
- }
208
- else {
209
- const keep = new Set(keys);
210
- const stale = (await driver.list()).filter((k) => !keep.has(k));
211
- if (!opts.dryRun) {
212
- const del = driver.delete;
213
- await runPool(stale, concurrency, (k) => withRetry(() => del(k), `delete ${k}`, retries, sleep, opts.log));
214
- pruned = stale.length;
215
- }
216
- opts.log?.(`${opts.dryRun ? 'Would prune' : 'Pruned'} ${stale.length} stale object(s)`);
217
- }
218
- return { pruned, keys };
219
- }
220
- //# sourceMappingURL=deploy-output.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"deploy-output.js","sourceRoot":"","sources":["../../src/server/deploy-output.ts"],"names":[],"mappings":"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"}
@@ -1,21 +0,0 @@
1
- import type { DeliveryPort } from '@michaelthielemann/kestrel-contracts';
2
- import type { StorageDriver } from '@michaelthielemann/kestrel-core';
3
- /**
4
- * The static delivery adapter's `DeliveryPort` (ADR-0013 §3.3): writes/prunes files through a
5
- * `StorageDriver` (local dir or S3) — the same output the runtime publisher and the build-time deploy have
6
- * always written to. `publishSnapshot`/`rebuildAll` take the snapshot's `html` as-is (already fully
7
- * populated, media fixed by the producer at snapshot-creation time) and never re-render or touch the DB.
8
- * @public
9
- */
10
- export declare function createStaticDeliveryPort(driver: StorageDriver): DeliveryPort;
11
- /**
12
- * The `delivery` config selection seam (`kestrel.delivery: 'static' | 'live'`, default `'static'` —
13
- * see `resolveKestrel`): `'static'` returns the file-backed adapter above, `'live'` returns
14
- * `delivery-live`'s adapter (serves `published_snapshots` at request time instead of writing files —
15
- * see `createLiveDeliveryPort`'s own TSDoc). The runtime publisher/queue do not construct `DeliveryPort`s
16
- * directly yet (they still write through `StorageDriver` in `publisher.ts`, and that keeps running under
17
- * `'live'` too — see `KestrelConfig.delivery`), so nothing calls this seam in production yet.
18
- * @public
19
- */
20
- export declare function deliveryPortFor(delivery: 'static' | 'live', driver: StorageDriver): DeliveryPort;
21
- //# sourceMappingURL=port.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"port.d.ts","sourceRoot":"","sources":["../../src/server/port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAqB,MAAM,sCAAsC,CAAA;AAC3F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAA;AAKpE;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,aAAa,GAAG,YAAY,CAqB5E;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,YAAY,CAGhG"}
@@ -1,46 +0,0 @@
1
- import { contentTypeFor, cacheControlFor } from '@michaelthielemann/kestrel-core';
2
- import { createLiveDeliveryPort } from '@michaelthielemann/kestrel-delivery-live';
3
- import { htmlKeyForRoute } from '@michaelthielemann/kestrel-publishing';
4
- /**
5
- * The static delivery adapter's `DeliveryPort` (ADR-0013 §3.3): writes/prunes files through a
6
- * `StorageDriver` (local dir or S3) — the same output the runtime publisher and the build-time deploy have
7
- * always written to. `publishSnapshot`/`rebuildAll` take the snapshot's `html` as-is (already fully
8
- * populated, media fixed by the producer at snapshot-creation time) and never re-render or touch the DB.
9
- * @public
10
- */
11
- export function createStaticDeliveryPort(driver) {
12
- async function writeSnapshot(snapshot) {
13
- const key = htmlKeyForRoute(snapshot.route);
14
- await driver.put(key, Buffer.from(snapshot.html, 'utf8'), contentTypeFor(key), { cacheControl: cacheControlFor(key) });
15
- }
16
- return {
17
- async publishSnapshot(snapshot) {
18
- await writeSnapshot(snapshot);
19
- },
20
- async removeRoutes(routes) {
21
- for (const route of routes) {
22
- await driver.delete(htmlKeyForRoute(route), { pruneEmptyDirs: true });
23
- }
24
- },
25
- async rebuildAll(iter) {
26
- for await (const snapshot of iter) {
27
- await writeSnapshot(snapshot);
28
- }
29
- },
30
- };
31
- }
32
- /**
33
- * The `delivery` config selection seam (`kestrel.delivery: 'static' | 'live'`, default `'static'` —
34
- * see `resolveKestrel`): `'static'` returns the file-backed adapter above, `'live'` returns
35
- * `delivery-live`'s adapter (serves `published_snapshots` at request time instead of writing files —
36
- * see `createLiveDeliveryPort`'s own TSDoc). The runtime publisher/queue do not construct `DeliveryPort`s
37
- * directly yet (they still write through `StorageDriver` in `publisher.ts`, and that keeps running under
38
- * `'live'` too — see `KestrelConfig.delivery`), so nothing calls this seam in production yet.
39
- * @public
40
- */
41
- export function deliveryPortFor(delivery, driver) {
42
- if (delivery === 'live')
43
- return createLiveDeliveryPort();
44
- return createStaticDeliveryPort(driver);
45
- }
46
- //# sourceMappingURL=port.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"port.js","sourceRoot":"","sources":["../../src/server/port.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAA;AACjF,OAAO,EAAE,sBAAsB,EAAE,MAAM,0CAA0C,CAAA;AACjF,OAAO,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAA;AAEvE;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CAAC,MAAqB;IAC5D,KAAK,UAAU,aAAa,CAAC,QAA2B;QACtD,MAAM,GAAG,GAAG,eAAe,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;QAC3C,MAAM,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,cAAc,CAAC,GAAG,CAAC,EAAE,EAAE,YAAY,EAAE,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;IACxH,CAAC;IAED,OAAO;QACL,KAAK,CAAC,eAAe,CAAC,QAAQ;YAC5B,MAAM,aAAa,CAAC,QAAQ,CAAC,CAAA;QAC/B,CAAC;QACD,KAAK,CAAC,YAAY,CAAC,MAAM;YACvB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,MAAM,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAA;YACvE,CAAC;QACH,CAAC;QACD,KAAK,CAAC,UAAU,CAAC,IAAI;YACnB,IAAI,KAAK,EAAE,MAAM,QAAQ,IAAI,IAAI,EAAE,CAAC;gBAClC,MAAM,aAAa,CAAC,QAAQ,CAAC,CAAA;YAC/B,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAC,QAA2B,EAAE,MAAqB;IAChF,IAAI,QAAQ,KAAK,MAAM;QAAE,OAAO,sBAAsB,EAAE,CAAA;IACxD,OAAO,wBAAwB,CAAC,MAAM,CAAC,CAAA;AACzC,CAAC"}
@@ -1,17 +0,0 @@
1
- /**
2
- * The static delivery adapter's per-route content lookup: reads the route's current published snapshot
3
- * and hands back its HTML as a body/status pair, in the same shape the producer's live render uses —
4
- * so the rest of the publisher (write-to-driver, prune, meta) does not need to know which one produced it.
5
- * No live server round trip, no DB populate: a route with no current snapshot (never published, or
6
- * retracted — see `retractSnapshot`) is a plain 404, not an error.
7
- *
8
- * An intentional seam, not dead code: `publisher.ts` still writes files by rendering live and calling
9
- * `driver.put` directly rather than through this function, the same not-yet-wired state `deliveryPortFor`
10
- * documents for the port it belongs to.
11
- * @public
12
- */
13
- export declare function renderRoute(route: string): {
14
- body: Buffer | null;
15
- status: number;
16
- };
17
- //# sourceMappingURL=render-route.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"render-route.d.ts","sourceRoot":"","sources":["../../src/server/render-route.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAIlF"}
@@ -1,20 +0,0 @@
1
- import { currentSnapshot, usePublishingDb } from '@michaelthielemann/kestrel-publishing';
2
- /**
3
- * The static delivery adapter's per-route content lookup: reads the route's current published snapshot
4
- * and hands back its HTML as a body/status pair, in the same shape the producer's live render uses —
5
- * so the rest of the publisher (write-to-driver, prune, meta) does not need to know which one produced it.
6
- * No live server round trip, no DB populate: a route with no current snapshot (never published, or
7
- * retracted — see `retractSnapshot`) is a plain 404, not an error.
8
- *
9
- * An intentional seam, not dead code: `publisher.ts` still writes files by rendering live and calling
10
- * `driver.put` directly rather than through this function, the same not-yet-wired state `deliveryPortFor`
11
- * documents for the port it belongs to.
12
- * @public
13
- */
14
- export function renderRoute(route) {
15
- const snapshot = currentSnapshot(usePublishingDb().db, route);
16
- if (!snapshot)
17
- return { body: null, status: 404 };
18
- return { body: Buffer.from(snapshot.html, 'utf8'), status: 200 };
19
- }
20
- //# sourceMappingURL=render-route.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"render-route.js","sourceRoot":"","sources":["../../src/server/render-route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAA;AAExF;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,MAAM,QAAQ,GAAG,eAAe,CAAC,eAAe,EAAE,CAAC,EAAE,EAAE,KAAK,CAAC,CAAA;IAC7D,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAA;IACjD,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAA;AAClE,CAAC"}