backend-manager 5.0.65 → 5.0.67

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,6 +1,6 @@
1
1
  {
2
2
  "name": "backend-manager",
3
- "version": "5.0.65",
3
+ "version": "5.0.67",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {
@@ -11,7 +11,7 @@ const DEFAULT_EMULATOR_PORTS = {
11
11
  hosting: 5002,
12
12
  storage: 9199,
13
13
  pubsub: 8085,
14
- ui: 4000,
14
+ ui: 4050,
15
15
  };
16
16
 
17
17
  const REQUIRED_EMULATORS = {
@@ -22,7 +22,7 @@ const REQUIRED_EMULATORS = {
22
22
  hosting: { port: DEFAULT_EMULATOR_PORTS.hosting },
23
23
  storage: { port: DEFAULT_EMULATOR_PORTS.storage },
24
24
  pubsub: { port: DEFAULT_EMULATOR_PORTS.pubsub },
25
- ui: { enabled: true },
25
+ ui: { enabled: true, port: DEFAULT_EMULATOR_PORTS.ui },
26
26
  };
27
27
 
28
28
  class EmulatorsConfigTest extends BaseTest {
@@ -14,7 +14,7 @@ const FirebaseFunctionsTest = require('./firebase-functions');
14
14
  const BackendManagerTest = require('./backend-manager');
15
15
  const NpmProjectScriptsTest = require('./npm-project-scripts');
16
16
  const BemConfigTest = require('./bem-config');
17
- const BemConfigIdTest = require('./bem-config-id');
17
+ const ProjectIdConsistencyTest = require('./project-id-consistency');
18
18
  const ServiceAccountTest = require('./service-account');
19
19
  const GitignoreTest = require('./gitignore');
20
20
  const EnvFileTest = require('./env-file');
@@ -54,8 +54,8 @@ function getTests(context) {
54
54
  new BackendManagerTest(context),
55
55
  new NpmProjectScriptsTest(context),
56
56
  new BemConfigTest(context),
57
- new BemConfigIdTest(context),
58
57
  new ServiceAccountTest(context),
58
+ new ProjectIdConsistencyTest(context),
59
59
  new GitignoreTest(context),
60
60
  new EnvFileTest(context),
61
61
  new EnvRuntimeConfigDeprecatedTest(context),
@@ -0,0 +1,161 @@
1
+ const BaseTest = require('./base-test');
2
+ const jetpack = require('fs-jetpack');
3
+ const JSON5 = require('json5');
4
+ const chalk = require('chalk');
5
+
6
+ /**
7
+ * Ensures projectId is consistent across all configuration files:
8
+ * - .firebaserc (projects.default)
9
+ * - functions/backend-manager-config.json (firebaseConfig.projectId)
10
+ * - functions/service-account.json (project_id)
11
+ *
12
+ * Mismatches cause tests to fail when running emulators in separate terminals
13
+ * because different parts of the system connect to different Firestore databases.
14
+ */
15
+ class ProjectIdConsistencyTest extends BaseTest {
16
+ getName() {
17
+ return 'project IDs are consistent across all config files';
18
+ }
19
+
20
+ async run() {
21
+ const sources = this.getProjectIdSources();
22
+
23
+ // Check if we have at least .firebaserc (the source of truth)
24
+ if (!sources.firebaserc.exists) {
25
+ console.error(chalk.red('Missing .firebaserc file'));
26
+ return false;
27
+ }
28
+
29
+ if (!sources.firebaserc.projectId) {
30
+ console.error(chalk.red('Missing projects.default in .firebaserc'));
31
+ return false;
32
+ }
33
+
34
+ const expectedProjectId = sources.firebaserc.projectId;
35
+ const mismatches = [];
36
+
37
+ // Check backend-manager-config.json
38
+ if (sources.bemConfig.exists) {
39
+ if (!sources.bemConfig.projectId) {
40
+ mismatches.push({
41
+ file: 'backend-manager-config.json',
42
+ field: 'firebaseConfig.projectId',
43
+ expected: expectedProjectId,
44
+ actual: '(missing)',
45
+ });
46
+ } else if (sources.bemConfig.projectId !== expectedProjectId) {
47
+ mismatches.push({
48
+ file: 'backend-manager-config.json',
49
+ field: 'firebaseConfig.projectId',
50
+ expected: expectedProjectId,
51
+ actual: sources.bemConfig.projectId,
52
+ });
53
+ }
54
+ }
55
+
56
+ // Check service-account.json
57
+ if (sources.serviceAccount.exists) {
58
+ if (!sources.serviceAccount.projectId) {
59
+ mismatches.push({
60
+ file: 'service-account.json',
61
+ field: 'project_id',
62
+ expected: expectedProjectId,
63
+ actual: '(missing)',
64
+ });
65
+ } else if (sources.serviceAccount.projectId !== expectedProjectId) {
66
+ mismatches.push({
67
+ file: 'service-account.json',
68
+ field: 'project_id',
69
+ expected: expectedProjectId,
70
+ actual: sources.serviceAccount.projectId,
71
+ });
72
+ }
73
+ }
74
+
75
+ if (mismatches.length > 0) {
76
+ console.error(chalk.red('\nProject ID mismatches detected:'));
77
+ console.error(chalk.gray(` Source of truth: .firebaserc → ${expectedProjectId}\n`));
78
+
79
+ for (const mismatch of mismatches) {
80
+ console.error(chalk.red(` ${mismatch.file} (${mismatch.field})`));
81
+ console.error(chalk.gray(` Expected: ${mismatch.expected}`));
82
+ console.error(chalk.gray(` Actual: ${mismatch.actual}\n`));
83
+ }
84
+
85
+ return false;
86
+ }
87
+
88
+ return true;
89
+ }
90
+
91
+ getProjectIdSources() {
92
+ const projectPath = this.self.firebaseProjectPath;
93
+
94
+ // .firebaserc
95
+ const firebasercPath = `${projectPath}/.firebaserc`;
96
+ const firebasercContent = jetpack.read(firebasercPath);
97
+ const firebasercData = firebasercContent ? JSON5.parse(firebasercContent) : null;
98
+
99
+ // backend-manager-config.json
100
+ const bemConfigPath = `${projectPath}/functions/backend-manager-config.json`;
101
+ const bemConfigContent = jetpack.read(bemConfigPath);
102
+ const bemConfigData = bemConfigContent ? JSON5.parse(bemConfigContent) : null;
103
+
104
+ // service-account.json
105
+ const serviceAccountPath = `${projectPath}/functions/service-account.json`;
106
+ const serviceAccountContent = jetpack.read(serviceAccountPath);
107
+ const serviceAccountData = serviceAccountContent ? JSON5.parse(serviceAccountContent) : null;
108
+
109
+ return {
110
+ firebaserc: {
111
+ exists: !!firebasercContent,
112
+ projectId: firebasercData?.projects?.default || null,
113
+ },
114
+ bemConfig: {
115
+ exists: !!bemConfigContent,
116
+ projectId: bemConfigData?.firebaseConfig?.projectId || null,
117
+ },
118
+ serviceAccount: {
119
+ exists: !!serviceAccountContent,
120
+ projectId: serviceAccountData?.project_id || null,
121
+ },
122
+ };
123
+ }
124
+
125
+ async fix() {
126
+ const sources = this.getProjectIdSources();
127
+
128
+ if (!sources.firebaserc.projectId) {
129
+ console.log(chalk.red('Cannot fix: .firebaserc is missing or has no projects.default'));
130
+ console.log(chalk.yellow('Run: firebase use --add'));
131
+ throw new Error('Missing .firebaserc configuration');
132
+ }
133
+
134
+ const expectedProjectId = sources.firebaserc.projectId;
135
+
136
+ // Fix backend-manager-config.json
137
+ if (sources.bemConfig.exists && sources.bemConfig.projectId !== expectedProjectId) {
138
+ const bemConfigPath = `${this.self.firebaseProjectPath}/functions/backend-manager-config.json`;
139
+ const bemConfigContent = jetpack.read(bemConfigPath);
140
+ const bemConfigData = JSON5.parse(bemConfigContent);
141
+
142
+ bemConfigData.firebaseConfig = bemConfigData.firebaseConfig || {};
143
+ bemConfigData.firebaseConfig.projectId = expectedProjectId;
144
+
145
+ jetpack.write(bemConfigPath, JSON.stringify(bemConfigData, null, 2));
146
+ console.log(chalk.green(`Fixed: backend-manager-config.json → firebaseConfig.projectId = ${expectedProjectId}`));
147
+ }
148
+
149
+ // Cannot auto-fix service-account.json - must download correct one from Firebase Console
150
+ if (sources.serviceAccount.exists && sources.serviceAccount.projectId !== expectedProjectId) {
151
+ console.log(chalk.red(`\nCannot auto-fix service-account.json`));
152
+ console.log(chalk.yellow(` Current project_id: ${sources.serviceAccount.projectId}`));
153
+ console.log(chalk.yellow(` Expected project_id: ${expectedProjectId}`));
154
+ console.log(chalk.yellow(`\n Download the correct service account from:`));
155
+ console.log(chalk.cyan(` https://console.firebase.google.com/project/${expectedProjectId}/settings/serviceaccounts/adminsdk`));
156
+ throw new Error('service-account.json has wrong project_id - download correct one from Firebase Console');
157
+ }
158
+ }
159
+ }
160
+
161
+ module.exports = ProjectIdConsistencyTest;
@@ -1,6 +1,5 @@
1
1
  const BaseTest = require('./base-test');
2
2
  const jetpack = require('fs-jetpack');
3
- const JSON5 = require('json5');
4
3
  const chalk = require('chalk');
5
4
 
6
5
  class ServiceAccountTest extends BaseTest {
@@ -9,7 +8,7 @@ class ServiceAccountTest extends BaseTest {
9
8
  }
10
9
 
11
10
  async run() {
12
- let serviceAccount = jetpack.read(`${this.self.firebaseProjectPath}/functions/service-account.json`);
11
+ const serviceAccount = jetpack.read(`${this.self.firebaseProjectPath}/functions/service-account.json`);
13
12
 
14
13
  // Make sure the service account exists
15
14
  if (!serviceAccount) {
@@ -17,15 +16,6 @@ class ServiceAccountTest extends BaseTest {
17
16
  return false;
18
17
  }
19
18
 
20
- // Parse the service account
21
- serviceAccount = JSON5.parse(serviceAccount);
22
-
23
- // Check if project_id matches the project's ID
24
- if (this.self.projectId !== serviceAccount.project_id) {
25
- console.error(chalk.red('Mismatch between project name and service account project_id'));
26
- return false;
27
- }
28
-
29
19
  return true;
30
20
  }
31
21
 
@@ -63,10 +63,10 @@ module.exports = async ({ Manager, assistant, user, context, libraries }) => {
63
63
  // Create user doc
64
64
  batch.set(admin.firestore().doc(`users/${user.uid}`), userRecord);
65
65
 
66
- // Increment user count
67
- batch.update(admin.firestore().doc('meta/stats'), {
66
+ // Increment user count (use set+merge so doc is created if missing)
67
+ batch.set(admin.firestore().doc('meta/stats'), {
68
68
  'users.total': FieldValue.increment(1),
69
- });
69
+ }, { merge: true });
70
70
 
71
71
  await batch.commit();
72
72
  }, MAX_RETRIES, RETRY_DELAY_MS);
@@ -41,10 +41,10 @@ module.exports = async ({ Manager, assistant, user, context, libraries }) => {
41
41
  // Delete user doc
42
42
  batch.delete(admin.firestore().doc(`users/${user.uid}`));
43
43
 
44
- // Decrement user count
45
- batch.update(admin.firestore().doc('meta/stats'), {
44
+ // Decrement user count (use set+merge so doc is created if missing)
45
+ batch.set(admin.firestore().doc('meta/stats'), {
46
46
  'users.total': FieldValue.increment(-1),
47
- });
47
+ }, { merge: true });
48
48
 
49
49
  await batch.commit();
50
50
  }, MAX_RETRIES, RETRY_DELAY_MS);
@@ -35,9 +35,9 @@ module.exports = async ({ Manager, assistant, change, context, libraries }) => {
35
35
  // Delete event
36
36
  if (eventType === 'delete') {
37
37
  await admin.firestore().doc('meta/stats')
38
- .update({
38
+ .set({
39
39
  'notifications.total': FieldValue.increment(-1),
40
- });
40
+ }, { merge: true });
41
41
 
42
42
  Manager.Analytics({
43
43
  assistant: assistant,
@@ -60,9 +60,9 @@ module.exports = async ({ Manager, assistant, change, context, libraries }) => {
60
60
  // Create event
61
61
  if (eventType === 'create') {
62
62
  await admin.firestore().doc('meta/stats')
63
- .update({
63
+ .set({
64
64
  'notifications.total': FieldValue.increment(1),
65
- });
65
+ }, { merge: true });
66
66
 
67
67
  Manager.Analytics({
68
68
  assistant: assistant,
@@ -93,11 +93,11 @@ Module.prototype.main = function () {
93
93
  // Save to database only if there is a page token
94
94
  if (batch.pageToken) {
95
95
  await self.libraries.admin.firestore().doc(`meta/stats`)
96
- .update({
96
+ .set({
97
97
  syncUsers: {
98
98
  lastPageToken: batch.pageToken,
99
99
  }
100
- })
100
+ }, { merge: true })
101
101
  .then(r => {
102
102
  assistant.log(`Saved lastPageToken: ${batch.pageToken}`);
103
103
  })
@@ -184,8 +184,8 @@ Middleware.prototype.run = function (libPath, options) {
184
184
 
185
185
  // Build context object for route handler
186
186
  const context = {
187
- assistant: assistant,
188
187
  Manager: Manager,
188
+ assistant: assistant,
189
189
  user: assistant.getUser(),
190
190
  usage: assistant.usage,
191
191
  settings: assistant.settings,
@@ -94,11 +94,11 @@ module.exports = async ({ assistant, Manager, user, settings, analytics, librari
94
94
  // Save pageToken for resume
95
95
  if (batch.pageToken) {
96
96
  await admin.firestore().doc('meta/stats')
97
- .update({
97
+ .set({
98
98
  syncUsers: {
99
99
  lastPageToken: batch.pageToken,
100
100
  }
101
- })
101
+ }, { merge: true })
102
102
  .then(() => {
103
103
  assistant.log(`Saved lastPageToken: ${batch.pageToken}`);
104
104
  })
@@ -25,7 +25,7 @@ async function main() {
25
25
  // When running in emulator, we can initialize without credentials
26
26
  // The emulator environment variables tell it where to connect
27
27
  firebaseAdmin.initializeApp({
28
- projectId: process.env.GCLOUD_PROJECT || testConfig.appId,
28
+ projectId: process.env.GCLOUD_PROJECT || testConfig.projectId,
29
29
  });
30
30
  }
31
31
  admin = firebaseAdmin;
@@ -1,25 +0,0 @@
1
- const BaseTest = require('./base-test');
2
- const chalk = require('chalk');
3
-
4
- class BemConfigIdTest extends BaseTest {
5
- getName() {
6
- return 'has correct ID in backend-manager-config.json';
7
- }
8
-
9
- async run() {
10
- // Check if the project name matches the projectId
11
- if (this.self.projectId !== this.self.bemConfigJSON?.firebaseConfig?.projectId) {
12
- console.error(chalk.red('Mismatch between project name and firebaseConfig.projectId in backend-manager-config.json'));
13
- return false;
14
- }
15
-
16
- // Return pass
17
- return true;
18
- }
19
-
20
- async fix() {
21
- throw new Error('No automatic fix available for this test');
22
- }
23
- }
24
-
25
- module.exports = BemConfigIdTest;