@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 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
@@ -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;AAWzF,oEAAoE;AACpE,MAAM,WAAW,sBAAsB;IACrC,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,qFAAqF;IACrF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB;AAED,sFAAsF;AACtF,wBAAgB,QAAQ,CAAC,OAAO,GAAE,sBAA2B,GAAG,OAAO,CAgItE;eAyFc,QAAQ"}
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 (!/^[a-z]+-[a-z]+[0-9]+$/.test(region)) {
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
- const firebaseConfig = JSON.stringify({
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: '.ruvyxa/deploy/firebase/functions',
35
+ source: functionsSource,
22
36
  codebase: 'ruvyxa',
23
- runtime: 'nodejs22',
37
+ runtime,
24
38
  },
25
39
  ],
26
40
  hosting: {
27
- public: '.ruvyxa/deploy/firebase/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: '22' },
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: firebaseConfig + '\n',
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: firebaseConfig + '\n',
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
- res.send(Buffer.from(await response.arrayBuffer()));
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":"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"}
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.29",
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": ">=22.13.0"
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.29"
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 --test \"../../../tests/packages/adapter-firebase/**/*.test.ts\"",
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 (!/^[a-z]+-[a-z]+[0-9]+$/.test(region)) {
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
- const firebaseConfig = JSON.stringify(
49
- {
50
- functions: [
51
- {
52
- source: '.ruvyxa/deploy/firebase/functions',
53
- codebase: 'ruvyxa',
54
- runtime: 'nodejs22',
55
- },
56
- ],
57
- hosting: {
58
- public: '.ruvyxa/deploy/firebase/public',
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: `${CLIENT_BUNDLE_PREFIX}**`,
63
- headers: [{ key: 'Cache-Control', value: IMMUTABLE_CACHE_CONTROL }],
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
- rewrites: [
71
- {
72
- source: '**',
73
- function: {
74
- functionId: functionName,
75
- region,
76
- pinTag: true,
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
- null,
83
- 2,
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: '22' },
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: firebaseConfig + '\n',
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: firebaseConfig + '\n',
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
- res.send(Buffer.from(await response.arrayBuffer()));
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
  `