@ruvyxa/adapter-firebase 1.0.29 → 1.0.31
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.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +41 -11
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +83 -37
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import type { Adapter } from '@ruvyxa/core';
|
|
2
|
+
/** Node.js runtimes Cloud Functions (2nd gen) accepts. */
|
|
3
|
+
export type FirebaseRuntime = 'nodejs20' | 'nodejs22' | 'nodejs24';
|
|
2
4
|
/** Options for Firebase Hosting and Cloud Functions deployments. */
|
|
3
5
|
export interface FirebaseAdapterOptions {
|
|
4
6
|
/** Cloud Functions export and Hosting rewrite ID. @default "ruvyxaServer" */
|
|
5
7
|
functionName?: string;
|
|
6
8
|
/** Cloud Functions region colocated with Firebase Hosting. @default "us-central1" */
|
|
7
9
|
region?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Cloud Functions runtime. Node 24 is generally available on the
|
|
12
|
+
* second-generation functions this adapter emits; the older runtimes are
|
|
13
|
+
* here for a project pinned to one.
|
|
14
|
+
* @default "nodejs24"
|
|
15
|
+
*/
|
|
16
|
+
runtime?: FirebaseRuntime;
|
|
8
17
|
/**
|
|
9
18
|
* Emit a project-root `firebase.json`. Existing configuration is never overwritten.
|
|
10
19
|
* @default true
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAgD,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAgD,MAAM,cAAc,CAAA;AAYzF,0DAA0D;AAC1D,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,CAAA;AAElE,oEAAoE;AACpE,MAAM,WAAW,sBAAsB;IACrC,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,qFAAqF;IACrF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;;OAKG;IACH,OAAO,CAAC,EAAE,eAAe,CAAA;IACzB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB;AAED,sFAAsF;AACtF,wBAAgB,QAAQ,CAAC,OAAO,GAAE,sBAA2B,GAAG,OAAO,CAqJtE;eAuGc,QAAQ"}
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
import { CLIENT_BUNDLE_PREFIX, clientBuildOutput, IMMUTABLE_CACHE_CONTROL, PUBLIC_ASSET_CACHE_CONTROL, publicAssetGlobs, runtimeBuildPolicy, validateBuildContext, } from '@ruvyxa/core';
|
|
1
|
+
import { CLIENT_BUNDLE_PREFIX, clientBuildOutput, IMMUTABLE_CACHE_CONTROL, projectRelativeOutDir, PUBLIC_ASSET_CACHE_CONTROL, publicAssetGlobs, runtimeBuildPolicy, validateBuildContext, } from '@ruvyxa/core';
|
|
2
2
|
/** Create a Firebase Hosting adapter backed by a second-generation HTTPS function. */
|
|
3
3
|
export function firebase(options = {}) {
|
|
4
4
|
const functionName = options.functionName ?? 'ruvyxaServer';
|
|
5
5
|
const region = options.region ?? 'us-central1';
|
|
6
|
+
const runtime = options.runtime ?? 'nodejs24';
|
|
7
|
+
// The function package's `engines.node` has to name the same major as
|
|
8
|
+
// firebase.json, or the Firebase CLI rejects the deploy over the mismatch.
|
|
9
|
+
const nodeMajor = runtime.replace('nodejs', '');
|
|
6
10
|
if (!/^[A-Za-z_$][A-Za-z0-9_$]{0,62}$/.test(functionName)) {
|
|
7
11
|
throw new Error('[RUV2001] firebaseAdapter: "functionName" must be a valid JavaScript identifier');
|
|
8
12
|
}
|
|
9
|
-
if (
|
|
13
|
+
if (!['nodejs20', 'nodejs22', 'nodejs24'].includes(runtime)) {
|
|
14
|
+
throw new Error('[RUV2001] firebaseAdapter: "runtime" must be nodejs20, nodejs22, or nodejs24');
|
|
15
|
+
}
|
|
16
|
+
if (!/^[a-z]+-[a-z]+\d+$/.test(region)) {
|
|
10
17
|
throw new Error('[RUV2001] firebaseAdapter: "region" must be a Google Cloud region such as asia-east1');
|
|
11
18
|
}
|
|
12
19
|
return {
|
|
@@ -15,16 +22,23 @@ export function firebase(options = {}) {
|
|
|
15
22
|
supports: ['ssr', 'ssg', 'csr', 'isr', 'ppr', 'api'],
|
|
16
23
|
build(ctx) {
|
|
17
24
|
validateBuildContext(ctx, 'firebaseAdapter');
|
|
18
|
-
|
|
25
|
+
// `firebase deploy` resolves `functions.source` and `hosting.public`
|
|
26
|
+
// relative to the directory holding firebase.json. The project-root copy
|
|
27
|
+
// therefore has to name this build's `outDir` — hard-coding `.ruvyxa`
|
|
28
|
+
// pointed a project that configures `outDir` at a directory that does not
|
|
29
|
+
// exist — while the copy inside the deploy directory names its own
|
|
30
|
+
// siblings, so `firebase deploy` also works from there.
|
|
31
|
+
const relativeOutDir = projectRelativeOutDir(ctx);
|
|
32
|
+
const firebaseConfigFor = (functionsSource, hostingPublic) => JSON.stringify({
|
|
19
33
|
functions: [
|
|
20
34
|
{
|
|
21
|
-
source:
|
|
35
|
+
source: functionsSource,
|
|
22
36
|
codebase: 'ruvyxa',
|
|
23
|
-
runtime
|
|
37
|
+
runtime,
|
|
24
38
|
},
|
|
25
39
|
],
|
|
26
40
|
hosting: {
|
|
27
|
-
public:
|
|
41
|
+
public: hostingPublic,
|
|
28
42
|
ignore: ['firebase.json', '**/.*', '**/node_modules/**'],
|
|
29
43
|
headers: [
|
|
30
44
|
{
|
|
@@ -48,12 +62,14 @@ export function firebase(options = {}) {
|
|
|
48
62
|
],
|
|
49
63
|
},
|
|
50
64
|
}, null, 2);
|
|
65
|
+
const projectFirebaseConfig = firebaseConfigFor(`${relativeOutDir}/deploy/firebase/functions`, `${relativeOutDir}/deploy/firebase/public`);
|
|
66
|
+
const deployFirebaseConfig = firebaseConfigFor('functions', 'public');
|
|
51
67
|
const functionPackage = JSON.stringify({
|
|
52
68
|
name: 'ruvyxa-firebase-functions',
|
|
53
69
|
private: true,
|
|
54
70
|
type: 'module',
|
|
55
71
|
main: 'index.mjs',
|
|
56
|
-
engines: { node:
|
|
72
|
+
engines: { node: nodeMajor },
|
|
57
73
|
dependencies: { 'firebase-functions': '^7.3.0' },
|
|
58
74
|
}, null, 2);
|
|
59
75
|
return {
|
|
@@ -86,7 +102,7 @@ export function firebase(options = {}) {
|
|
|
86
102
|
{
|
|
87
103
|
kind: 'file',
|
|
88
104
|
path: 'deploy/firebase/firebase.json',
|
|
89
|
-
contents:
|
|
105
|
+
contents: deployFirebaseConfig + '\n',
|
|
90
106
|
},
|
|
91
107
|
{
|
|
92
108
|
kind: 'file',
|
|
@@ -103,7 +119,7 @@ export function firebase(options = {}) {
|
|
|
103
119
|
path: 'firebase.json',
|
|
104
120
|
scope: 'project',
|
|
105
121
|
skipIfExists: true,
|
|
106
|
-
contents:
|
|
122
|
+
contents: projectFirebaseConfig + '\n',
|
|
107
123
|
},
|
|
108
124
|
]),
|
|
109
125
|
],
|
|
@@ -115,11 +131,13 @@ export function firebase(options = {}) {
|
|
|
115
131
|
function firebaseHandlerSource(functionName, region, runtimePolicy) {
|
|
116
132
|
return `import { onRequest } from 'firebase-functions/v2/https';
|
|
117
133
|
import { createHandler, prerenderRelativePath } from './serverless-handler.mjs';
|
|
118
|
-
import { loadRouteModule } from './route-modules.mjs';
|
|
134
|
+
import { applyPluginHttp, loadActionModule, loadRouteModule } from './route-modules.mjs';
|
|
119
135
|
import manifest from './manifest.mjs';
|
|
120
136
|
import { readFileSync, writeFileSync, mkdirSync, statSync } from 'node:fs';
|
|
121
137
|
import os from 'node:os';
|
|
122
138
|
import path from 'node:path';
|
|
139
|
+
import { Readable } from 'node:stream';
|
|
140
|
+
import { pipeline } from 'node:stream/promises';
|
|
123
141
|
|
|
124
142
|
const runtimePolicy = ${JSON.stringify(runtimePolicy ?? {})};
|
|
125
143
|
|
|
@@ -138,6 +156,9 @@ const handler = createHandler({
|
|
|
138
156
|
i18n: manifest.i18n,
|
|
139
157
|
importPage: loadRouteModule,
|
|
140
158
|
importApi: loadRouteModule,
|
|
159
|
+
importAction: loadActionModule,
|
|
160
|
+
pluginHttp: applyPluginHttp,
|
|
161
|
+
security: runtimePolicy.security,
|
|
141
162
|
readPrerendered: (pathname, revalidate = 60) => {
|
|
142
163
|
const relative = prerenderRelativePath(pathname);
|
|
143
164
|
if (relative === null) return null;
|
|
@@ -188,7 +209,16 @@ export const ${functionName} = onRequest(
|
|
|
188
209
|
}
|
|
189
210
|
const cookies = response.headers.getSetCookie?.() ?? [];
|
|
190
211
|
if (cookies.length > 0) res.setHeader('set-cookie', cookies);
|
|
191
|
-
|
|
212
|
+
if (!response.body) {
|
|
213
|
+
res.end();
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
// Piped rather than collected into one buffer, matching the standalone
|
|
217
|
+
// server and the Vercel function: a second-generation function runs on
|
|
218
|
+
// Cloud Run, which forwards bytes as they are written, so buffering only
|
|
219
|
+
// added the whole response to this instance's memory and pushed the first
|
|
220
|
+
// byte back to the last.
|
|
221
|
+
await pipeline(Readable.fromWeb(response.body), res);
|
|
192
222
|
},
|
|
193
223
|
);
|
|
194
224
|
`;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,uBAAuB,EACvB,0BAA0B,EAC1B,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ruvyxa/adapter-firebase",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.31",
|
|
4
4
|
"description": "Deploy full-stack Ruvyxa applications to Firebase Hosting with CDN assets and a generated Cloud Functions v2 backend.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"homepage": "https://github.com/thirawat27/ruvyxa#readme",
|
|
30
30
|
"engines": {
|
|
31
|
-
"node": ">=
|
|
31
|
+
"node": ">=24.19.0"
|
|
32
32
|
},
|
|
33
33
|
"publishConfig": {
|
|
34
34
|
"access": "public"
|
|
@@ -40,12 +40,12 @@
|
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@ruvyxa/core": "^1.0.
|
|
43
|
+
"@ruvyxa/core": "^1.0.31"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "tsc -p tsconfig.json",
|
|
47
47
|
"check": "tsc -p tsconfig.check.json",
|
|
48
|
-
"test": "node
|
|
48
|
+
"test": "pnpm run build && node ../../../scripts/test-package.mjs adapter-firebase",
|
|
49
49
|
"format": "tsc -p tsconfig.check.json"
|
|
50
50
|
}
|
|
51
51
|
}
|
package/src/index.ts
CHANGED
|
@@ -3,18 +3,29 @@ import {
|
|
|
3
3
|
CLIENT_BUNDLE_PREFIX,
|
|
4
4
|
clientBuildOutput,
|
|
5
5
|
IMMUTABLE_CACHE_CONTROL,
|
|
6
|
+
projectRelativeOutDir,
|
|
6
7
|
PUBLIC_ASSET_CACHE_CONTROL,
|
|
7
8
|
publicAssetGlobs,
|
|
8
9
|
runtimeBuildPolicy,
|
|
9
10
|
validateBuildContext,
|
|
10
11
|
} from '@ruvyxa/core'
|
|
11
12
|
|
|
13
|
+
/** Node.js runtimes Cloud Functions (2nd gen) accepts. */
|
|
14
|
+
export type FirebaseRuntime = 'nodejs20' | 'nodejs22' | 'nodejs24'
|
|
15
|
+
|
|
12
16
|
/** Options for Firebase Hosting and Cloud Functions deployments. */
|
|
13
17
|
export interface FirebaseAdapterOptions {
|
|
14
18
|
/** Cloud Functions export and Hosting rewrite ID. @default "ruvyxaServer" */
|
|
15
19
|
functionName?: string
|
|
16
20
|
/** Cloud Functions region colocated with Firebase Hosting. @default "us-central1" */
|
|
17
21
|
region?: string
|
|
22
|
+
/**
|
|
23
|
+
* Cloud Functions runtime. Node 24 is generally available on the
|
|
24
|
+
* second-generation functions this adapter emits; the older runtimes are
|
|
25
|
+
* here for a project pinned to one.
|
|
26
|
+
* @default "nodejs24"
|
|
27
|
+
*/
|
|
28
|
+
runtime?: FirebaseRuntime
|
|
18
29
|
/**
|
|
19
30
|
* Emit a project-root `firebase.json`. Existing configuration is never overwritten.
|
|
20
31
|
* @default true
|
|
@@ -26,13 +37,20 @@ export interface FirebaseAdapterOptions {
|
|
|
26
37
|
export function firebase(options: FirebaseAdapterOptions = {}): Adapter {
|
|
27
38
|
const functionName = options.functionName ?? 'ruvyxaServer'
|
|
28
39
|
const region = options.region ?? 'us-central1'
|
|
40
|
+
const runtime = options.runtime ?? 'nodejs24'
|
|
41
|
+
// The function package's `engines.node` has to name the same major as
|
|
42
|
+
// firebase.json, or the Firebase CLI rejects the deploy over the mismatch.
|
|
43
|
+
const nodeMajor = runtime.replace('nodejs', '')
|
|
29
44
|
|
|
30
45
|
if (!/^[A-Za-z_$][A-Za-z0-9_$]{0,62}$/.test(functionName)) {
|
|
31
46
|
throw new Error(
|
|
32
47
|
'[RUV2001] firebaseAdapter: "functionName" must be a valid JavaScript identifier',
|
|
33
48
|
)
|
|
34
49
|
}
|
|
35
|
-
if (
|
|
50
|
+
if (!['nodejs20', 'nodejs22', 'nodejs24'].includes(runtime)) {
|
|
51
|
+
throw new Error('[RUV2001] firebaseAdapter: "runtime" must be nodejs20, nodejs22, or nodejs24')
|
|
52
|
+
}
|
|
53
|
+
if (!/^[a-z]+-[a-z]+\d+$/.test(region)) {
|
|
36
54
|
throw new Error(
|
|
37
55
|
'[RUV2001] firebaseAdapter: "region" must be a Google Cloud region such as asia-east1',
|
|
38
56
|
)
|
|
@@ -45,43 +63,57 @@ export function firebase(options: FirebaseAdapterOptions = {}): Adapter {
|
|
|
45
63
|
build(ctx: BuildContext): AdapterOutput {
|
|
46
64
|
validateBuildContext(ctx, 'firebaseAdapter')
|
|
47
65
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
ignore: ['firebase.json', '**/.*', '**/node_modules/**'],
|
|
60
|
-
headers: [
|
|
66
|
+
// `firebase deploy` resolves `functions.source` and `hosting.public`
|
|
67
|
+
// relative to the directory holding firebase.json. The project-root copy
|
|
68
|
+
// therefore has to name this build's `outDir` — hard-coding `.ruvyxa`
|
|
69
|
+
// pointed a project that configures `outDir` at a directory that does not
|
|
70
|
+
// exist — while the copy inside the deploy directory names its own
|
|
71
|
+
// siblings, so `firebase deploy` also works from there.
|
|
72
|
+
const relativeOutDir = projectRelativeOutDir(ctx)
|
|
73
|
+
const firebaseConfigFor = (functionsSource: string, hostingPublic: string) =>
|
|
74
|
+
JSON.stringify(
|
|
75
|
+
{
|
|
76
|
+
functions: [
|
|
61
77
|
{
|
|
62
|
-
source:
|
|
63
|
-
|
|
78
|
+
source: functionsSource,
|
|
79
|
+
codebase: 'ruvyxa',
|
|
80
|
+
runtime,
|
|
64
81
|
},
|
|
65
|
-
...publicAssetGlobs().map((glob) => ({
|
|
66
|
-
source: `**${glob}`,
|
|
67
|
-
headers: [{ key: 'Cache-Control', value: PUBLIC_ASSET_CACHE_CONTROL }],
|
|
68
|
-
})),
|
|
69
82
|
],
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
83
|
+
hosting: {
|
|
84
|
+
public: hostingPublic,
|
|
85
|
+
ignore: ['firebase.json', '**/.*', '**/node_modules/**'],
|
|
86
|
+
headers: [
|
|
87
|
+
{
|
|
88
|
+
source: `${CLIENT_BUNDLE_PREFIX}**`,
|
|
89
|
+
headers: [{ key: 'Cache-Control', value: IMMUTABLE_CACHE_CONTROL }],
|
|
77
90
|
},
|
|
78
|
-
|
|
79
|
-
|
|
91
|
+
...publicAssetGlobs().map((glob) => ({
|
|
92
|
+
source: `**${glob}`,
|
|
93
|
+
headers: [{ key: 'Cache-Control', value: PUBLIC_ASSET_CACHE_CONTROL }],
|
|
94
|
+
})),
|
|
95
|
+
],
|
|
96
|
+
rewrites: [
|
|
97
|
+
{
|
|
98
|
+
source: '**',
|
|
99
|
+
function: {
|
|
100
|
+
functionId: functionName,
|
|
101
|
+
region,
|
|
102
|
+
pinTag: true,
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
80
107
|
},
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
108
|
+
null,
|
|
109
|
+
2,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
const projectFirebaseConfig = firebaseConfigFor(
|
|
113
|
+
`${relativeOutDir}/deploy/firebase/functions`,
|
|
114
|
+
`${relativeOutDir}/deploy/firebase/public`,
|
|
84
115
|
)
|
|
116
|
+
const deployFirebaseConfig = firebaseConfigFor('functions', 'public')
|
|
85
117
|
|
|
86
118
|
const functionPackage = JSON.stringify(
|
|
87
119
|
{
|
|
@@ -89,7 +121,7 @@ export function firebase(options: FirebaseAdapterOptions = {}): Adapter {
|
|
|
89
121
|
private: true,
|
|
90
122
|
type: 'module',
|
|
91
123
|
main: 'index.mjs',
|
|
92
|
-
engines: { node:
|
|
124
|
+
engines: { node: nodeMajor },
|
|
93
125
|
dependencies: { 'firebase-functions': '^7.3.0' },
|
|
94
126
|
},
|
|
95
127
|
null,
|
|
@@ -126,7 +158,7 @@ export function firebase(options: FirebaseAdapterOptions = {}): Adapter {
|
|
|
126
158
|
{
|
|
127
159
|
kind: 'file',
|
|
128
160
|
path: 'deploy/firebase/firebase.json',
|
|
129
|
-
contents:
|
|
161
|
+
contents: deployFirebaseConfig + '\n',
|
|
130
162
|
},
|
|
131
163
|
{
|
|
132
164
|
kind: 'file',
|
|
@@ -144,7 +176,7 @@ export function firebase(options: FirebaseAdapterOptions = {}): Adapter {
|
|
|
144
176
|
path: 'firebase.json',
|
|
145
177
|
scope: 'project',
|
|
146
178
|
skipIfExists: true,
|
|
147
|
-
contents:
|
|
179
|
+
contents: projectFirebaseConfig + '\n',
|
|
148
180
|
} satisfies AdapterArtifact,
|
|
149
181
|
]),
|
|
150
182
|
],
|
|
@@ -161,11 +193,13 @@ function firebaseHandlerSource(
|
|
|
161
193
|
): string {
|
|
162
194
|
return `import { onRequest } from 'firebase-functions/v2/https';
|
|
163
195
|
import { createHandler, prerenderRelativePath } from './serverless-handler.mjs';
|
|
164
|
-
import { loadRouteModule } from './route-modules.mjs';
|
|
196
|
+
import { applyPluginHttp, loadActionModule, loadRouteModule } from './route-modules.mjs';
|
|
165
197
|
import manifest from './manifest.mjs';
|
|
166
198
|
import { readFileSync, writeFileSync, mkdirSync, statSync } from 'node:fs';
|
|
167
199
|
import os from 'node:os';
|
|
168
200
|
import path from 'node:path';
|
|
201
|
+
import { Readable } from 'node:stream';
|
|
202
|
+
import { pipeline } from 'node:stream/promises';
|
|
169
203
|
|
|
170
204
|
const runtimePolicy = ${JSON.stringify(runtimePolicy ?? {})};
|
|
171
205
|
|
|
@@ -184,6 +218,9 @@ const handler = createHandler({
|
|
|
184
218
|
i18n: manifest.i18n,
|
|
185
219
|
importPage: loadRouteModule,
|
|
186
220
|
importApi: loadRouteModule,
|
|
221
|
+
importAction: loadActionModule,
|
|
222
|
+
pluginHttp: applyPluginHttp,
|
|
223
|
+
security: runtimePolicy.security,
|
|
187
224
|
readPrerendered: (pathname, revalidate = 60) => {
|
|
188
225
|
const relative = prerenderRelativePath(pathname);
|
|
189
226
|
if (relative === null) return null;
|
|
@@ -234,7 +271,16 @@ export const ${functionName} = onRequest(
|
|
|
234
271
|
}
|
|
235
272
|
const cookies = response.headers.getSetCookie?.() ?? [];
|
|
236
273
|
if (cookies.length > 0) res.setHeader('set-cookie', cookies);
|
|
237
|
-
|
|
274
|
+
if (!response.body) {
|
|
275
|
+
res.end();
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
// Piped rather than collected into one buffer, matching the standalone
|
|
279
|
+
// server and the Vercel function: a second-generation function runs on
|
|
280
|
+
// Cloud Run, which forwards bytes as they are written, so buffering only
|
|
281
|
+
// added the whole response to this instance's memory and pushed the first
|
|
282
|
+
// byte back to the last.
|
|
283
|
+
await pipeline(Readable.fromWeb(response.body), res);
|
|
238
284
|
},
|
|
239
285
|
);
|
|
240
286
|
`
|