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 +1 -1
- package/src/cli/commands/setup-tests/emulators-config.js +2 -2
- package/src/cli/commands/setup-tests/index.js +2 -2
- package/src/cli/commands/setup-tests/project-id-consistency.js +161 -0
- package/src/cli/commands/setup-tests/service-account.js +1 -11
- package/src/manager/events/auth/on-create.js +3 -3
- package/src/manager/events/auth/on-delete.js +3 -3
- package/src/manager/events/firestore/notifications/on-write.js +4 -4
- package/src/manager/functions/core/actions/api/admin/sync-users.js +2 -2
- package/src/manager/helpers/middleware.js +1 -1
- package/src/manager/routes/admin/users/sync/post.js +2 -2
- package/src/test/run-tests.js +1 -1
- package/src/cli/commands/setup-tests/bem-config-id.js +0 -25
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@ const DEFAULT_EMULATOR_PORTS = {
|
|
|
11
11
|
hosting: 5002,
|
|
12
12
|
storage: 9199,
|
|
13
13
|
pubsub: 8085,
|
|
14
|
-
ui:
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
})
|
package/src/test/run-tests.js
CHANGED
|
@@ -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.
|
|
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;
|