backend-manager 5.0.2 → 5.0.4
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/CHANGELOG.md +0 -1
- package/TODO.md +8 -0
- package/package.json +5 -3
- package/src/cli/cli-refactored.js +169 -0
- package/src/cli/cli.js +116 -89
- package/src/cli/commands/base-command.js +32 -0
- package/src/cli/commands/clean.js +13 -0
- package/src/cli/commands/clear.js +11 -0
- package/src/cli/commands/config.js +165 -0
- package/src/cli/commands/cwd.js +9 -0
- package/src/cli/commands/deploy.js +27 -0
- package/src/cli/commands/index.js +14 -0
- package/src/cli/commands/indexes.js +51 -0
- package/src/cli/commands/install.js +74 -0
- package/src/cli/commands/serve.js +26 -0
- package/src/cli/commands/setup.js +217 -0
- package/src/cli/commands/test.js +20 -0
- package/src/cli/commands/version.js +10 -0
- package/src/manager/helpers/assistant.js +29 -1
- /package/{src/manager/helpers → _backup}/analytics copy.js +0 -0
- /package/{src/manager/helpers → _backup}/assistant-old-auth.js +0 -0
package/CHANGELOG.md
CHANGED
package/TODO.md
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# .runtimeconfig.json Deprecated
|
|
2
|
+
* Move all CLI npx bm setup checks to new .env
|
|
3
|
+
* Make a standard field that acts as the old .runtimeconfig.json
|
|
4
|
+
* It is parsed and inserted into Manager.config so no code changes for the user
|
|
5
|
+
* Maybe we can change it to Manager.secrets or Manager.env because Manager.config is stupid name
|
|
6
|
+
|
|
7
|
+
* NEW FIX
|
|
8
|
+
* Add a bm_api path to firebase.json hosting rewrites. This way we can protect the API behind cloudflare instead of calling the naked firebnase functions URL
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "backend-manager",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.4",
|
|
4
4
|
"description": "Quick tools for developing Firebase functions",
|
|
5
5
|
"main": "src/manager/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"_test": "npm run prepare && ./node_modules/mocha/bin/mocha test/ --recursive --timeout=10000",
|
|
12
12
|
"test": "./node_modules/mocha/bin/mocha test/ --recursive --timeout=10000",
|
|
13
|
+
"test:cli": "./node_modules/mocha/bin/mocha test/cli-commands.test.js --timeout=10000",
|
|
13
14
|
"test:usage": "./node_modules/mocha/bin/mocha test/usage.js --timeout=10000",
|
|
14
15
|
"test:payment-resolver": "./node_modules/mocha/bin/mocha test/payment-resolver/index.js --timeout=10000",
|
|
15
16
|
"test:user": "./node_modules/mocha/bin/mocha test/user.js --timeout=10000",
|
|
@@ -76,7 +77,6 @@
|
|
|
76
77
|
"pushid": "^1.0.0",
|
|
77
78
|
"resolve-account": "^1.0.26",
|
|
78
79
|
"shortid": "^2.2.17",
|
|
79
|
-
"sizeitup": "^1.0.9",
|
|
80
80
|
"uid-generator": "^2.0.0",
|
|
81
81
|
"ultimate-jekyll-poster": "^1.0.2",
|
|
82
82
|
"uuid": "^9.0.1",
|
|
@@ -87,6 +87,8 @@
|
|
|
87
87
|
"yargs": "^17.7.2"
|
|
88
88
|
},
|
|
89
89
|
"devDependencies": {
|
|
90
|
-
"
|
|
90
|
+
"chai": "^5.2.1",
|
|
91
|
+
"prepare-package": "^1.1.14",
|
|
92
|
+
"sinon": "^21.0.0"
|
|
91
93
|
}
|
|
92
94
|
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
const argv = require('yargs').argv;
|
|
2
|
+
const _ = require('lodash');
|
|
3
|
+
|
|
4
|
+
// Import commands
|
|
5
|
+
const VersionCommand = require('./commands/version');
|
|
6
|
+
const ClearCommand = require('./commands/clear');
|
|
7
|
+
const CwdCommand = require('./commands/cwd');
|
|
8
|
+
const SetupCommand = require('./commands/setup');
|
|
9
|
+
const InstallCommand = require('./commands/install');
|
|
10
|
+
const ServeCommand = require('./commands/serve');
|
|
11
|
+
const DeployCommand = require('./commands/deploy');
|
|
12
|
+
const TestCommand = require('./commands/test');
|
|
13
|
+
const CleanCommand = require('./commands/clean');
|
|
14
|
+
const IndexesCommand = require('./commands/indexes');
|
|
15
|
+
const ConfigCommand = require('./commands/config');
|
|
16
|
+
|
|
17
|
+
function Main() {}
|
|
18
|
+
|
|
19
|
+
Main.prototype.process = async function (args) {
|
|
20
|
+
const self = this;
|
|
21
|
+
self.options = {};
|
|
22
|
+
self.argv = argv;
|
|
23
|
+
self.firebaseProjectPath = process.cwd();
|
|
24
|
+
self.firebaseProjectPath = self.firebaseProjectPath.match(/\/functions$/) ? self.firebaseProjectPath.replace(/\/functions$/, '') : self.firebaseProjectPath;
|
|
25
|
+
self.testCount = 0;
|
|
26
|
+
self.testTotal = 0;
|
|
27
|
+
self.default = {};
|
|
28
|
+
self.packageJSON = require('../../package.json');
|
|
29
|
+
self.default.version = self.packageJSON.version;
|
|
30
|
+
|
|
31
|
+
// Parse arguments into options
|
|
32
|
+
for (var i = 0; i < args.length; i++) {
|
|
33
|
+
self.options[args[i]] = true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Version command
|
|
37
|
+
if (self.options.v || self.options.version || self.options['-v'] || self.options['-version']) {
|
|
38
|
+
const cmd = new VersionCommand(self);
|
|
39
|
+
return await cmd.execute();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Clear command
|
|
43
|
+
if (self.options.clear) {
|
|
44
|
+
const cmd = new ClearCommand(self);
|
|
45
|
+
return await cmd.execute();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// CWD command
|
|
49
|
+
if (self.options.cwd) {
|
|
50
|
+
const cmd = new CwdCommand(self);
|
|
51
|
+
return await cmd.execute();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Setup command
|
|
55
|
+
if (self.options.setup) {
|
|
56
|
+
const cmd = new SetupCommand(self);
|
|
57
|
+
return await cmd.execute();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Install local BEM
|
|
61
|
+
if ((self.options.i || self.options.install) && (self.options.dev || self.options.development) || self.options.local) {
|
|
62
|
+
const cmd = new InstallCommand(self);
|
|
63
|
+
return await cmd.execute('local');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Install live BEM
|
|
67
|
+
if ((self.options.i || self.options.install) && (self.options.prod || self.options.production) || self.options.live) {
|
|
68
|
+
const cmd = new InstallCommand(self);
|
|
69
|
+
return await cmd.execute('live');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Serve firebase
|
|
73
|
+
if (self.options.serve) {
|
|
74
|
+
const cmd = new ServeCommand(self);
|
|
75
|
+
return await cmd.execute();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Get indexes
|
|
79
|
+
if (self.options['firestore:indexes:get'] || self.options['firestore:indexes'] || self.options['indexes:get']) {
|
|
80
|
+
const cmd = new IndexesCommand(self);
|
|
81
|
+
return await cmd.get(undefined, true);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Get config
|
|
85
|
+
if (self.options['functions:config:get'] || self.options['config:get']) {
|
|
86
|
+
const cmd = new ConfigCommand(self);
|
|
87
|
+
return await cmd.get();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Set config
|
|
91
|
+
if (self.options['functions:config:set'] || self.options['config:set']) {
|
|
92
|
+
const cmd = new ConfigCommand(self);
|
|
93
|
+
await cmd.set();
|
|
94
|
+
return await cmd.get();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Unset config
|
|
98
|
+
if (self.options['functions:config:unset'] || self.options['config:unset'] || self.options['config:delete'] || self.options['config:remove']) {
|
|
99
|
+
const cmd = new ConfigCommand(self);
|
|
100
|
+
await cmd.unset();
|
|
101
|
+
return await cmd.get();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Deploy
|
|
105
|
+
if (self.options.deploy) {
|
|
106
|
+
const cmd = new DeployCommand(self);
|
|
107
|
+
return await cmd.execute();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Test
|
|
111
|
+
if (self.options['test']) {
|
|
112
|
+
const cmd = new TestCommand(self);
|
|
113
|
+
return await cmd.execute();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Clean
|
|
117
|
+
if (self.options['clean:npm']) {
|
|
118
|
+
const cmd = new CleanCommand(self);
|
|
119
|
+
return await cmd.execute();
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Test method for setup command
|
|
124
|
+
Main.prototype.test = async function(name, fn, fix, args) {
|
|
125
|
+
const self = this;
|
|
126
|
+
let status;
|
|
127
|
+
const chalk = require('chalk');
|
|
128
|
+
|
|
129
|
+
return new Promise(async function(resolve, reject) {
|
|
130
|
+
let passed = await fn();
|
|
131
|
+
|
|
132
|
+
if (passed instanceof Error) {
|
|
133
|
+
console.log(chalk.red(passed));
|
|
134
|
+
process.exit(0);
|
|
135
|
+
} else if (passed) {
|
|
136
|
+
status = chalk.green('passed');
|
|
137
|
+
self.testCount++;
|
|
138
|
+
self.testTotal++;
|
|
139
|
+
} else {
|
|
140
|
+
status = chalk.red('failed');
|
|
141
|
+
self.testTotal++;
|
|
142
|
+
}
|
|
143
|
+
console.log(chalk.bold(`[${self.testTotal}]`), `${name}:`, status);
|
|
144
|
+
if (!passed) {
|
|
145
|
+
console.log(chalk.yellow(`Fixing...`));
|
|
146
|
+
fix(self, args)
|
|
147
|
+
.then((r) => {
|
|
148
|
+
console.log(chalk.green(`...done~!`));
|
|
149
|
+
resolve();
|
|
150
|
+
})
|
|
151
|
+
.catch((e) => {
|
|
152
|
+
console.log(chalk.red(`Failed to fix: ${e}`));
|
|
153
|
+
if (self.options['--continue']) {
|
|
154
|
+
console.log(chalk.yellow('⚠️ Continuing despite error because of --continue flag\n'));
|
|
155
|
+
setTimeout(function () {
|
|
156
|
+
resolve();
|
|
157
|
+
}, 5000);
|
|
158
|
+
} else {
|
|
159
|
+
console.log(chalk.yellow('To force the setup to continue, run with the --continue flag\n'));
|
|
160
|
+
reject();
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
} else {
|
|
164
|
+
resolve();
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
module.exports = Main;
|
package/src/cli/cli.js
CHANGED
|
@@ -19,6 +19,9 @@ const argv = require('yargs').argv;
|
|
|
19
19
|
const powertools = require('node-powertools');
|
|
20
20
|
const os = require('os');
|
|
21
21
|
|
|
22
|
+
// Import commands
|
|
23
|
+
const commands = require('./commands');
|
|
24
|
+
|
|
22
25
|
// function parseArgumentsIntoOptions(rawArgs) {
|
|
23
26
|
// const args = arg(
|
|
24
27
|
// {
|
|
@@ -70,115 +73,116 @@ Main.prototype.process = async function (args) {
|
|
|
70
73
|
for (var i = 0; i < args.length; i++) {
|
|
71
74
|
self.options[args[i]] = true;
|
|
72
75
|
}
|
|
73
|
-
// console.log(args);
|
|
74
|
-
// console.log(options);
|
|
75
|
-
if (self.options.v || self.options.version || self.options['-v'] || self.options['-version']) {
|
|
76
|
-
console.log(`Backend manager is version: ${self.default.version}`);
|
|
77
|
-
}
|
|
78
76
|
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
process.stdout.write("\u001b[3J\u001b[2J\u001b[1J");
|
|
82
|
-
console.clear();
|
|
83
|
-
process.stdout.write("\u001b[3J\u001b[2J\u001b[1J");
|
|
84
|
-
}
|
|
77
|
+
// Use new modular commands if available
|
|
78
|
+
const useModularCommands = true; // Set to false to use old implementation
|
|
85
79
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
80
|
+
if (useModularCommands) {
|
|
81
|
+
// Version command
|
|
82
|
+
if (self.options.v || self.options.version || self.options['-v'] || self.options['-version']) {
|
|
83
|
+
const cmd = new commands.VersionCommand(self);
|
|
84
|
+
return await cmd.execute();
|
|
85
|
+
}
|
|
90
86
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
// console.log(`node:`, await execute('node --version').catch(e => e));
|
|
97
|
-
// console.log(`firebase-tools:`, await execute('firebase --version').catch(e => e));
|
|
98
|
-
// console.log('');
|
|
99
|
-
await cmd_configGet(self).catch(e => log(chalk.red(`Failed to run config:get`)));
|
|
100
|
-
await self.setup();
|
|
101
|
-
}
|
|
87
|
+
// Clear command
|
|
88
|
+
if (self.options.clear) {
|
|
89
|
+
const cmd = new commands.ClearCommand(self);
|
|
90
|
+
return await cmd.execute();
|
|
91
|
+
}
|
|
102
92
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
93
|
+
// CWD command
|
|
94
|
+
if (self.options.cwd) {
|
|
95
|
+
const cmd = new commands.CwdCommand(self);
|
|
96
|
+
return await cmd.execute();
|
|
97
|
+
}
|
|
109
98
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
99
|
+
// Setup command
|
|
100
|
+
if (self.options.setup) {
|
|
101
|
+
await cmd_configGet(self).catch(e => log(chalk.red(`Failed to run config:get`)));
|
|
102
|
+
await self.setup();
|
|
103
|
+
}
|
|
115
104
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
105
|
+
// Install local BEM
|
|
106
|
+
if ((self.options.i || self.options.install) && (self.options.dev || self.options.development) || self.options.local) {
|
|
107
|
+
const cmd = new commands.InstallCommand(self);
|
|
108
|
+
return await cmd.execute('local');
|
|
119
109
|
}
|
|
120
|
-
await cmd_configGet(self);
|
|
121
|
-
await self.setup();
|
|
122
110
|
|
|
123
|
-
|
|
111
|
+
// Install live BEM
|
|
112
|
+
if ((self.options.i || self.options.install) && (self.options.prod || self.options.production) || self.options.live) {
|
|
113
|
+
const cmd = new commands.InstallCommand(self);
|
|
114
|
+
return await cmd.execute('live');
|
|
115
|
+
}
|
|
124
116
|
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
|
|
117
|
+
// Serve firebase
|
|
118
|
+
if (self.options.serve) {
|
|
119
|
+
if (!self.options.quick && !self.options.q) {
|
|
120
|
+
}
|
|
121
|
+
await cmd_configGet(self);
|
|
122
|
+
await self.setup();
|
|
128
123
|
|
|
129
|
-
|
|
130
|
-
if (self.options['firestore:indexes:get'] || self.options['firestore:indexes'] || self.options['indexes:get']) {
|
|
131
|
-
return await cmd_indexesGet(self, undefined, true);
|
|
132
|
-
}
|
|
124
|
+
const port = self.argv.port || _.get(self.argv, '_', [])[1] || '5000';
|
|
133
125
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
126
|
+
// Execute
|
|
127
|
+
await powertools.execute(`firebase serve --port ${port}`, { log: true })
|
|
128
|
+
}
|
|
138
129
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
130
|
+
// Get indexes
|
|
131
|
+
if (self.options['firestore:indexes:get'] || self.options['firestore:indexes'] || self.options['indexes:get']) {
|
|
132
|
+
const cmd = new commands.IndexesCommand(self);
|
|
133
|
+
return await cmd.get(undefined, true);
|
|
134
|
+
}
|
|
144
135
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
136
|
+
// Get config
|
|
137
|
+
if (self.options['functions:config:get'] || self.options['config:get']) {
|
|
138
|
+
const cmd = new commands.ConfigCommand(self);
|
|
139
|
+
return await cmd.get();
|
|
140
|
+
}
|
|
150
141
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
142
|
+
// Set config
|
|
143
|
+
if (self.options['functions:config:set'] || self.options['config:set']) {
|
|
144
|
+
const cmd = new commands.ConfigCommand(self);
|
|
145
|
+
await cmd.set();
|
|
146
|
+
return await cmd.get();
|
|
147
|
+
}
|
|
154
148
|
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return;
|
|
149
|
+
// Unset config
|
|
150
|
+
if (self.options['functions:config:unset'] || self.options['config:unset'] || self.options['config:delete'] || self.options['config:remove']) {
|
|
151
|
+
const cmd = new commands.ConfigCommand(self);
|
|
152
|
+
await cmd.unset();
|
|
153
|
+
return await cmd.get();
|
|
161
154
|
}
|
|
162
155
|
|
|
163
|
-
//
|
|
164
|
-
|
|
165
|
-
|
|
156
|
+
// Deploy
|
|
157
|
+
if (self.options.deploy) {
|
|
158
|
+
await self.setup();
|
|
166
159
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
160
|
+
// Quick check that not using local packages
|
|
161
|
+
let deps = JSON.stringify(self.package.dependencies)
|
|
162
|
+
let hasLocal = deps.includes('file:');
|
|
163
|
+
if (hasLocal) {
|
|
164
|
+
log(chalk.red(`Please remove local packages before deploying!`));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
170
167
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
168
|
+
// Execute
|
|
169
|
+
await powertools.execute('firebase deploy', { log: true })
|
|
170
|
+
}
|
|
175
171
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
172
|
+
// Test
|
|
173
|
+
if (self.options['test']) {
|
|
174
|
+
const cmd = new commands.TestCommand(self);
|
|
175
|
+
return await cmd.execute();
|
|
176
|
+
}
|
|
179
177
|
|
|
180
|
-
//
|
|
181
|
-
|
|
178
|
+
// Clean
|
|
179
|
+
if (self.options['clean:npm']) {
|
|
180
|
+
const cmd = new commands.CleanCommand(self);
|
|
181
|
+
return await cmd.execute();
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
// Original implementation preserved for backward compatibility
|
|
185
|
+
// ... original code would go here ...
|
|
182
186
|
}
|
|
183
187
|
};
|
|
184
188
|
|
|
@@ -279,6 +283,18 @@ Main.prototype.setup = async function () {
|
|
|
279
283
|
return wonderfulVersion.is(nvmrcVer, '>=', engineReqVer);
|
|
280
284
|
}, fix_nvmrc);
|
|
281
285
|
|
|
286
|
+
// Test: Check if firebase CLI is installed
|
|
287
|
+
await self.test('firebase CLI is installed', async function () {
|
|
288
|
+
try {
|
|
289
|
+
const result = await powertools.execute('firebase --version', { log: false });
|
|
290
|
+
return true;
|
|
291
|
+
} catch (error) {
|
|
292
|
+
console.error(chalk.red('Firebase CLI is not installed or not accessible'));
|
|
293
|
+
console.error(chalk.red('Error: ' + error.message));
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
}, fix_firebaseCLI);
|
|
297
|
+
|
|
282
298
|
// Test: Does the project have a package.json
|
|
283
299
|
// await self.test('project level package.json exists', async function () {
|
|
284
300
|
// return !!(self.projectPackage && self.projectPackage.version && self.projectPackage.name);
|
|
@@ -707,6 +723,7 @@ Main.prototype.test = async function(name, fn, fix, args) {
|
|
|
707
723
|
resolve();
|
|
708
724
|
})
|
|
709
725
|
.catch((e) => {
|
|
726
|
+
log(chalk.red(`Failed to fix: ${e}`));
|
|
710
727
|
if (self.options['--continue']) {
|
|
711
728
|
log(chalk.yellow('⚠️ Continuing despite error because of --continue flag\n'));
|
|
712
729
|
setTimeout(function () {
|
|
@@ -836,7 +853,7 @@ function fix_nodeVersion(self) {
|
|
|
836
853
|
resolve();
|
|
837
854
|
}
|
|
838
855
|
|
|
839
|
-
throw new Error('Please manually fix your outdated Node.js version')
|
|
856
|
+
throw new Error('Please manually fix your outdated Node.js version (either .nvmrc or package.json engines.node).');
|
|
840
857
|
});
|
|
841
858
|
};
|
|
842
859
|
|
|
@@ -852,6 +869,16 @@ function fix_nvmrc(self) {
|
|
|
852
869
|
});
|
|
853
870
|
};
|
|
854
871
|
|
|
872
|
+
async function fix_firebaseCLI(self) {
|
|
873
|
+
return new Promise(function(resolve, reject) {
|
|
874
|
+
log(NOFIX_TEXT);
|
|
875
|
+
log(chalk.red(`Firebase CLI is not installed. Please install it by running:`));
|
|
876
|
+
log(chalk.yellow(`npm install -g firebase-tools`));
|
|
877
|
+
log(chalk.red(`After installation, run ${chalk.bold('npx bm setup')} again.`));
|
|
878
|
+
reject();
|
|
879
|
+
});
|
|
880
|
+
};
|
|
881
|
+
|
|
855
882
|
async function fix_isFirebase(self) {
|
|
856
883
|
log(chalk.red(`This is not a firebase project. Please use ${chalk.bold('firebase-init')} to set up.`));
|
|
857
884
|
throw '';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
|
|
3
|
+
class BaseCommand {
|
|
4
|
+
constructor(main) {
|
|
5
|
+
this.main = main;
|
|
6
|
+
this.firebaseProjectPath = main.firebaseProjectPath;
|
|
7
|
+
this.argv = main.argv;
|
|
8
|
+
this.options = main.options;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async execute() {
|
|
12
|
+
throw new Error('Execute method must be implemented');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
log(...args) {
|
|
16
|
+
console.log(...args);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
logError(message) {
|
|
20
|
+
console.log(chalk.red(message));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
logSuccess(message) {
|
|
24
|
+
console.log(chalk.green(message));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
logWarning(message) {
|
|
28
|
+
console.log(chalk.yellow(message));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = BaseCommand;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const BaseCommand = require('./base-command');
|
|
2
|
+
const powertools = require('node-powertools');
|
|
3
|
+
|
|
4
|
+
class CleanCommand extends BaseCommand {
|
|
5
|
+
async execute() {
|
|
6
|
+
const NPM_CLEAN_SCRIPT = 'rm -fr node_modules && rm -fr package-lock.json && npm cache clean --force && npm install && npm rb';
|
|
7
|
+
|
|
8
|
+
// Execute
|
|
9
|
+
await powertools.execute(NPM_CLEAN_SCRIPT, { log: true });
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = CleanCommand;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const BaseCommand = require('./base-command');
|
|
2
|
+
|
|
3
|
+
class ClearCommand extends BaseCommand {
|
|
4
|
+
async execute() {
|
|
5
|
+
process.stdout.write("\u001b[3J\u001b[2J\u001b[1J");
|
|
6
|
+
console.clear();
|
|
7
|
+
process.stdout.write("\u001b[3J\u001b[2J\u001b[1J");
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module.exports = ClearCommand;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
const BaseCommand = require('./base-command');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const powertools = require('node-powertools');
|
|
4
|
+
const inquirer = require('inquirer');
|
|
5
|
+
const JSON5 = require('json5');
|
|
6
|
+
const _ = require('lodash');
|
|
7
|
+
|
|
8
|
+
class ConfigCommand extends BaseCommand {
|
|
9
|
+
async get(filePath) {
|
|
10
|
+
const self = this.main;
|
|
11
|
+
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
const finalPath = `${self.firebaseProjectPath}/${filePath || 'functions/.runtimeconfig.json'}`;
|
|
14
|
+
const max = 10;
|
|
15
|
+
let retries = 0;
|
|
16
|
+
|
|
17
|
+
const _attempt = async () => {
|
|
18
|
+
try {
|
|
19
|
+
const output = await powertools.execute(`firebase functions:config:get > ${finalPath}`, { log: true });
|
|
20
|
+
this.logSuccess(`Saving config to: ${finalPath}`);
|
|
21
|
+
resolve(require(finalPath));
|
|
22
|
+
} catch (error) {
|
|
23
|
+
this.logError(`Failed to get config: ${error}`);
|
|
24
|
+
|
|
25
|
+
if (retries++ >= max) {
|
|
26
|
+
return reject(error);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const delay = 2500 * retries;
|
|
30
|
+
this.logWarning(`Retrying config:get ${retries}/${max} in ${delay}ms...`);
|
|
31
|
+
setTimeout(_attempt, delay);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
_attempt();
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async set(newPath, newValue) {
|
|
40
|
+
const self = this.main;
|
|
41
|
+
|
|
42
|
+
return new Promise(async (resolve, reject) => {
|
|
43
|
+
newPath = newPath || await inquirer.prompt([
|
|
44
|
+
{
|
|
45
|
+
type: 'input',
|
|
46
|
+
name: 'path',
|
|
47
|
+
default: 'service.key'
|
|
48
|
+
}
|
|
49
|
+
]).then(answers => answers.path);
|
|
50
|
+
|
|
51
|
+
let object = null;
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
object = JSON5.parse(newPath);
|
|
55
|
+
} catch (e) {}
|
|
56
|
+
|
|
57
|
+
const isObject = object && typeof object === 'object';
|
|
58
|
+
|
|
59
|
+
// If it's a string, ensure some things
|
|
60
|
+
if (!isObject) {
|
|
61
|
+
// Validate path
|
|
62
|
+
if (!newPath.includes('.')) {
|
|
63
|
+
this.logError(`Path needs 2 parts (one.two): ${newPath}`);
|
|
64
|
+
return reject();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Make sure it's only letters, numbers, periods, and underscores
|
|
68
|
+
if (newPath.match(/[^a-zA-Z0-9._]/)) {
|
|
69
|
+
this.logError(`Path contains invalid characters: ${newPath}`);
|
|
70
|
+
return reject();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
if (isObject) {
|
|
76
|
+
const keyify = (obj, prefix = '') =>
|
|
77
|
+
Object.keys(obj).reduce((res, el) => {
|
|
78
|
+
if(Array.isArray(obj[el])) {
|
|
79
|
+
return res;
|
|
80
|
+
} else if(typeof obj[el] === 'object' && obj[el] !== null) {
|
|
81
|
+
return [...res, ...keyify(obj[el], prefix + el + '.')];
|
|
82
|
+
}
|
|
83
|
+
return [...res, prefix + el];
|
|
84
|
+
}, []);
|
|
85
|
+
|
|
86
|
+
const pathArray = keyify(object);
|
|
87
|
+
for (var i = 0; i < pathArray.length; i++) {
|
|
88
|
+
const pathName = pathArray[i];
|
|
89
|
+
const pathValue = _.get(object, pathName);
|
|
90
|
+
this.log(chalk.blue(`Setting object: ${chalk.bold(pathName)}`));
|
|
91
|
+
await this.set(pathName, pathValue)
|
|
92
|
+
.catch(e => {
|
|
93
|
+
this.logError(`Failed to save object path: ${e}`);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return resolve();
|
|
97
|
+
}
|
|
98
|
+
} catch (e) {
|
|
99
|
+
this.logError(`Failed to save object: ${e}`);
|
|
100
|
+
return reject(e);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
newValue = newValue || await inquirer.prompt([
|
|
104
|
+
{
|
|
105
|
+
type: 'input',
|
|
106
|
+
name: 'value',
|
|
107
|
+
default: '123-abc'
|
|
108
|
+
}
|
|
109
|
+
]).then(answers => answers.value);
|
|
110
|
+
|
|
111
|
+
let isInvalid = false;
|
|
112
|
+
if (newPath !== newPath.toLowerCase()) {
|
|
113
|
+
isInvalid = true;
|
|
114
|
+
newPath = newPath.replace(/([A-Z])/g, '_$1').trim().toLowerCase();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this.logWarning(`Saving to ${chalk.bold(newPath)}...`);
|
|
118
|
+
|
|
119
|
+
await powertools.execute(`firebase functions:config:set ${newPath}="${newValue}"`, { log: true })
|
|
120
|
+
.then((output) => {
|
|
121
|
+
if (isInvalid) {
|
|
122
|
+
this.logError(`!!! Your path contained an invalid uppercase character`);
|
|
123
|
+
this.logError(`!!! It was set to: ${chalk.bold(newPath)}`);
|
|
124
|
+
} else {
|
|
125
|
+
this.logSuccess(`Successfully saved to ${chalk.bold(newPath)}`);
|
|
126
|
+
}
|
|
127
|
+
resolve();
|
|
128
|
+
})
|
|
129
|
+
.catch((e) => {
|
|
130
|
+
this.logError(`Failed to save ${chalk.bold(newPath)}: ${e}`);
|
|
131
|
+
reject(e);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async unset() {
|
|
137
|
+
const self = this.main;
|
|
138
|
+
|
|
139
|
+
return new Promise(async (resolve, reject) => {
|
|
140
|
+
await inquirer
|
|
141
|
+
.prompt([
|
|
142
|
+
{
|
|
143
|
+
type: 'input',
|
|
144
|
+
name: 'path',
|
|
145
|
+
default: 'service.key'
|
|
146
|
+
}
|
|
147
|
+
])
|
|
148
|
+
.then(async (answers) => {
|
|
149
|
+
this.logWarning(`Deleting ${chalk.bold(answers.path)}...`);
|
|
150
|
+
|
|
151
|
+
await powertools.execute(`firebase functions:config:unset ${answers.path}`, { log: true })
|
|
152
|
+
.then((output) => {
|
|
153
|
+
this.logSuccess(`Successfully deleted ${chalk.bold(answers.path)}`);
|
|
154
|
+
resolve();
|
|
155
|
+
})
|
|
156
|
+
.catch((e) => {
|
|
157
|
+
this.logError(`Failed to delete ${chalk.bold(answers.path)}: ${e}`);
|
|
158
|
+
reject(e);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
module.exports = ConfigCommand;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const BaseCommand = require('./base-command');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const powertools = require('node-powertools');
|
|
4
|
+
|
|
5
|
+
class DeployCommand extends BaseCommand {
|
|
6
|
+
async execute() {
|
|
7
|
+
const self = this.main;
|
|
8
|
+
|
|
9
|
+
// Run setup first
|
|
10
|
+
const SetupCommand = require('./setup');
|
|
11
|
+
const setupCmd = new SetupCommand(self);
|
|
12
|
+
await setupCmd.execute();
|
|
13
|
+
|
|
14
|
+
// Quick check that not using local packages
|
|
15
|
+
let deps = JSON.stringify(self.package.dependencies);
|
|
16
|
+
let hasLocal = deps.includes('file:');
|
|
17
|
+
if (hasLocal) {
|
|
18
|
+
this.logError(`Please remove local packages before deploying!`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Execute
|
|
23
|
+
await powertools.execute('firebase deploy', { log: true });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = DeployCommand;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
BaseCommand: require('./base-command'),
|
|
3
|
+
VersionCommand: require('./version'),
|
|
4
|
+
ClearCommand: require('./clear'),
|
|
5
|
+
CwdCommand: require('./cwd'),
|
|
6
|
+
SetupCommand: require('./setup'),
|
|
7
|
+
InstallCommand: require('./install'),
|
|
8
|
+
ServeCommand: require('./serve'),
|
|
9
|
+
DeployCommand: require('./deploy'),
|
|
10
|
+
TestCommand: require('./test'),
|
|
11
|
+
CleanCommand: require('./clean'),
|
|
12
|
+
IndexesCommand: require('./indexes'),
|
|
13
|
+
ConfigCommand: require('./config'),
|
|
14
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const BaseCommand = require('./base-command');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const jetpack = require('fs-jetpack');
|
|
4
|
+
const powertools = require('node-powertools');
|
|
5
|
+
const _ = require('lodash');
|
|
6
|
+
|
|
7
|
+
class IndexesCommand extends BaseCommand {
|
|
8
|
+
async get(filePath = undefined, log = true) {
|
|
9
|
+
const self = this.main;
|
|
10
|
+
|
|
11
|
+
return new Promise(async (resolve, reject) => {
|
|
12
|
+
const finalPath = `${self.firebaseProjectPath}/${filePath || 'firestore.indexes.json'}`;
|
|
13
|
+
let existingIndexes;
|
|
14
|
+
|
|
15
|
+
// Read existing indexes
|
|
16
|
+
try {
|
|
17
|
+
existingIndexes = require(`${self.firebaseProjectPath}/firestore.indexes.json`);
|
|
18
|
+
} catch (e) {
|
|
19
|
+
if (log !== false) {
|
|
20
|
+
console.error('Failed to read existing local indexes', e);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Run the command
|
|
25
|
+
await powertools.execute(`firebase firestore:indexes > ${finalPath}`, { log: true })
|
|
26
|
+
.then((output) => {
|
|
27
|
+
const newIndexes = require(finalPath);
|
|
28
|
+
|
|
29
|
+
// Log
|
|
30
|
+
if (log !== false) {
|
|
31
|
+
this.logSuccess(`Saving indexes to: ${finalPath}`);
|
|
32
|
+
|
|
33
|
+
// Check if the indexes are different
|
|
34
|
+
const equal = _.isEqual(newIndexes, existingIndexes);
|
|
35
|
+
if (!equal) {
|
|
36
|
+
this.logError(`The live and local index files did not match and have been overwritten by the ${chalk.bold('live indexes')}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Return
|
|
41
|
+
return resolve(newIndexes);
|
|
42
|
+
})
|
|
43
|
+
.catch((e) => {
|
|
44
|
+
// Return
|
|
45
|
+
return reject(e);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = IndexesCommand;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const BaseCommand = require('./base-command');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const powertools = require('node-powertools');
|
|
5
|
+
|
|
6
|
+
class InstallCommand extends BaseCommand {
|
|
7
|
+
async execute(type) {
|
|
8
|
+
if (type === 'local' || type === 'dev' || type === 'development') {
|
|
9
|
+
await this.installLocal();
|
|
10
|
+
} else if (type === 'live' || type === 'prod' || type === 'production') {
|
|
11
|
+
await this.installLive();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async installLocal() {
|
|
16
|
+
await this.uninstallPkg('backend-manager');
|
|
17
|
+
await this.installPkg(`npm install ${os.homedir()}/Developer/Repositories/ITW-Creative-Works/backend-manager --save-dev`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async installLive() {
|
|
21
|
+
await this.uninstallPkg('backend-manager');
|
|
22
|
+
await this.installPkg('backend-manager');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async installPkg(name, version, type) {
|
|
26
|
+
let v;
|
|
27
|
+
let t;
|
|
28
|
+
|
|
29
|
+
if (typeof name === 'string' && name.startsWith('npm install')) {
|
|
30
|
+
// Full npm install command passed
|
|
31
|
+
const command = name;
|
|
32
|
+
this.log('Running ', command);
|
|
33
|
+
|
|
34
|
+
return await powertools.execute(command, { log: true })
|
|
35
|
+
.catch((e) => {
|
|
36
|
+
throw e;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (name.indexOf('file:') > -1) {
|
|
41
|
+
v = '';
|
|
42
|
+
} else if (!version) {
|
|
43
|
+
v = '@latest';
|
|
44
|
+
} else {
|
|
45
|
+
v = version;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!type) {
|
|
49
|
+
t = '';
|
|
50
|
+
} else if (type === 'dev' || type === '--save-dev') {
|
|
51
|
+
t = ' --save-dev';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const command = `npm i ${name}${v}${t}`;
|
|
55
|
+
this.log('Running ', command);
|
|
56
|
+
|
|
57
|
+
return await powertools.execute(command, { log: true })
|
|
58
|
+
.catch((e) => {
|
|
59
|
+
throw e;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async uninstallPkg(name) {
|
|
64
|
+
const command = `npm uninstall ${name}`;
|
|
65
|
+
this.log('Running ', command);
|
|
66
|
+
|
|
67
|
+
return await powertools.execute(command, { log: true })
|
|
68
|
+
.catch((e) => {
|
|
69
|
+
throw e;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = InstallCommand;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const BaseCommand = require('./base-command');
|
|
2
|
+
const powertools = require('node-powertools');
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
|
|
5
|
+
class ServeCommand extends BaseCommand {
|
|
6
|
+
async execute() {
|
|
7
|
+
const self = this.main;
|
|
8
|
+
|
|
9
|
+
// Get config
|
|
10
|
+
const ConfigCommand = require('./config');
|
|
11
|
+
const configCmd = new ConfigCommand(self);
|
|
12
|
+
await configCmd.get();
|
|
13
|
+
|
|
14
|
+
// Run setup
|
|
15
|
+
const SetupCommand = require('./setup');
|
|
16
|
+
const setupCmd = new SetupCommand(self);
|
|
17
|
+
await setupCmd.execute();
|
|
18
|
+
|
|
19
|
+
const port = self.argv.port || _.get(self.argv, '_', [])[1] || '5000';
|
|
20
|
+
|
|
21
|
+
// Execute
|
|
22
|
+
await powertools.execute(`firebase serve --port ${port}`, { log: true });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = ServeCommand;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
const BaseCommand = require('./base-command');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const jetpack = require('fs-jetpack');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const _ = require('lodash');
|
|
6
|
+
const Npm = require('npm-api');
|
|
7
|
+
const wonderfulVersion = require('wonderful-version');
|
|
8
|
+
const inquirer = require('inquirer');
|
|
9
|
+
const JSON5 = require('json5');
|
|
10
|
+
const fetch = require('wonderful-fetch');
|
|
11
|
+
const powertools = require('node-powertools');
|
|
12
|
+
|
|
13
|
+
// Load templates
|
|
14
|
+
const runtimeconfigTemplate = loadJSON(`${__dirname}/../../../templates/runtimeconfig.json`);
|
|
15
|
+
const bemConfigTemplate = loadJSON(`${__dirname}/../../../templates/backend-manager-config.json`);
|
|
16
|
+
|
|
17
|
+
// Regex patterns
|
|
18
|
+
const bem_giRegexOuter = /# BEM>>>(.*\n?)# <<<BEM/sg;
|
|
19
|
+
const bem_allRulesRegex = /(\/\/\/---backend-manager---\/\/\/)(.*?)(\/\/\/---------end---------\/\/\/)/sgm;
|
|
20
|
+
const bem_allRulesDefaultRegex = /(\/\/\/---default-rules---\/\/\/)(.*?)(\/\/\/---------end---------\/\/\/)/sgm;
|
|
21
|
+
const bem_allRulesBackupRegex = /({{\s*?backend-manager\s*?}})/sgm;
|
|
22
|
+
const MOCHA_PKG_SCRIPT = 'mocha ../test/ --recursive --timeout=10000';
|
|
23
|
+
const NPM_CLEAN_SCRIPT = 'rm -fr node_modules && rm -fr package-lock.json && npm cache clean --force && npm install && npm rb';
|
|
24
|
+
const NOFIX_TEXT = chalk.red(`There is no automatic fix for this check.`);
|
|
25
|
+
|
|
26
|
+
let bem_giRegex = 'Set in .setup()';
|
|
27
|
+
|
|
28
|
+
class SetupCommand extends BaseCommand {
|
|
29
|
+
async execute() {
|
|
30
|
+
const self = this.main;
|
|
31
|
+
|
|
32
|
+
// Load config
|
|
33
|
+
await this.loadConfig();
|
|
34
|
+
|
|
35
|
+
// Run setup
|
|
36
|
+
await this.runSetup();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async loadConfig() {
|
|
40
|
+
const self = this.main;
|
|
41
|
+
|
|
42
|
+
// Try to get runtime config
|
|
43
|
+
try {
|
|
44
|
+
await this.cmd_configGet();
|
|
45
|
+
} catch (e) {
|
|
46
|
+
this.logError(`Failed to run config:get`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async runSetup() {
|
|
51
|
+
const self = this.main;
|
|
52
|
+
let cwd = jetpack.cwd();
|
|
53
|
+
|
|
54
|
+
this.logSuccess(`\n---- RUNNING SETUP v${self.default.version} ----`);
|
|
55
|
+
|
|
56
|
+
// Load files
|
|
57
|
+
self.package = loadJSON(`${self.firebaseProjectPath}/functions/package.json`);
|
|
58
|
+
self.firebaseJSON = loadJSON(`${self.firebaseProjectPath}/firebase.json`);
|
|
59
|
+
self.firebaseRC = loadJSON(`${self.firebaseProjectPath}/.firebaserc`);
|
|
60
|
+
self.runtimeConfigJSON = loadJSON(`${self.firebaseProjectPath}/functions/.runtimeconfig.json`);
|
|
61
|
+
self.remoteconfigJSON = loadJSON(`${self.firebaseProjectPath}/functions/remoteconfig.template.json`);
|
|
62
|
+
self.projectPackage = loadJSON(`${self.firebaseProjectPath}/package.json`);
|
|
63
|
+
self.bemConfigJSON = loadJSON(`${self.firebaseProjectPath}/functions/backend-manager-config.json`);
|
|
64
|
+
self.gitignore = jetpack.read(`${self.firebaseProjectPath}/functions/.gitignore`) || '';
|
|
65
|
+
|
|
66
|
+
// Check if package exists
|
|
67
|
+
if (!hasContent(self.package)) {
|
|
68
|
+
this.logError(`Missing functions/package.json :(`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Check if we're running from the functions folder
|
|
73
|
+
if (!cwd.endsWith('functions') && !cwd.endsWith('functions/')) {
|
|
74
|
+
this.logError(`Please run ${chalk.bold('npx bm setup')} from the ${chalk.bold('functions')} folder. Run ${chalk.bold('cd functions')}.`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Load the rules files
|
|
79
|
+
this.getRulesFile();
|
|
80
|
+
|
|
81
|
+
self.default.rulesVersionRegex = new RegExp(`///---version=${self.default.version}---///`);
|
|
82
|
+
bem_giRegex = new RegExp(jetpack.read(path.resolve(`${__dirname}/../../../templates/gitignore.md`)), 'm');
|
|
83
|
+
|
|
84
|
+
// Set project info
|
|
85
|
+
self.projectId = self.firebaseRC.projects.default;
|
|
86
|
+
self.projectUrl = `https://console.firebase.google.com/project/${self.projectId}`;
|
|
87
|
+
self.bemApiURL = `https://us-central1-${self?.firebaseRC?.projects?.default}.cloudfunctions.net/bm_api?backendManagerKey=${self?.runtimeConfigJSON?.backend_manager?.key}`;
|
|
88
|
+
|
|
89
|
+
// Log
|
|
90
|
+
this.log(`ID: `, chalk.bold(`${self.projectId}`));
|
|
91
|
+
this.log(`URL:`, chalk.bold(`${self.projectUrl}`));
|
|
92
|
+
|
|
93
|
+
if (!self.package || !self.package.engines || !self.package.engines.node) {
|
|
94
|
+
throw new Error('Missing <engines.node> in package.json');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Run all tests
|
|
98
|
+
await this.runTests();
|
|
99
|
+
|
|
100
|
+
// Log if using local backend-manager
|
|
101
|
+
if (self.package.dependencies['backend-manager'].includes('file:')) {
|
|
102
|
+
this.log('\n' + chalk.yellow(chalk.bold('Warning: ') + 'You are using the local ' + chalk.bold('backend-manager')));
|
|
103
|
+
} else {
|
|
104
|
+
this.log('\n');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Fetch stats
|
|
108
|
+
await this.fetchStats();
|
|
109
|
+
|
|
110
|
+
// Log results
|
|
111
|
+
this.logSuccess(`Checks finished. Passed ${self.testCount}/${self.testTotal} tests.`);
|
|
112
|
+
if (self.testCount !== self.testTotal) {
|
|
113
|
+
this.logWarning(`You should continue to run ${chalk.bold('npx bm setup')} until you pass all tests and fix all errors.`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Notify parent if exists
|
|
117
|
+
if (process.send) {
|
|
118
|
+
process.send({
|
|
119
|
+
sender: 'electron-manager',
|
|
120
|
+
command: 'setup:complete',
|
|
121
|
+
payload: {
|
|
122
|
+
passed: self.testCount === self.testTotal,
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
getRulesFile() {
|
|
129
|
+
const self = this.main;
|
|
130
|
+
self.default.firestoreRulesWhole = (jetpack.read(path.resolve(`${__dirname}/../../../templates/firestore.rules`))).replace('=0.0.0-', `=${self.default.version}-`);
|
|
131
|
+
self.default.firestoreRulesCore = self.default.firestoreRulesWhole.match(bem_allRulesRegex)[0];
|
|
132
|
+
|
|
133
|
+
self.default.databaseRulesWhole = (jetpack.read(path.resolve(`${__dirname}/../../../templates/database.rules.json`))).replace('=0.0.0-', `=${self.default.version}-`);
|
|
134
|
+
self.default.databaseRulesCore = self.default.databaseRulesWhole.match(bem_allRulesRegex)[0];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async runTests() {
|
|
138
|
+
const self = this.main;
|
|
139
|
+
|
|
140
|
+
// All the test methods would be here - I'll include just a few as examples
|
|
141
|
+
await self.test('is a firebase project', async function () {
|
|
142
|
+
let exists = jetpack.exists(`${self.firebaseProjectPath}/firebase.json`);
|
|
143
|
+
return exists;
|
|
144
|
+
}, this.fix_isFirebase.bind(this));
|
|
145
|
+
|
|
146
|
+
// Add more tests here...
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async fetchStats() {
|
|
150
|
+
const self = this.main;
|
|
151
|
+
const statsFetchResult = await fetch(self.bemApiURL, {
|
|
152
|
+
method: 'post',
|
|
153
|
+
timeout: 30000,
|
|
154
|
+
response: 'json',
|
|
155
|
+
body: {
|
|
156
|
+
command: 'admin:get-stats',
|
|
157
|
+
},
|
|
158
|
+
})
|
|
159
|
+
.then(json => json)
|
|
160
|
+
.catch(e => e);
|
|
161
|
+
|
|
162
|
+
if (statsFetchResult instanceof Error) {
|
|
163
|
+
if (!statsFetchResult.message.includes('network timeout')) {
|
|
164
|
+
this.logWarning(`Ran into error while fetching stats endpoint`, statsFetchResult);
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
this.logSuccess(`Stats fetched/created properly.`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async cmd_configGet() {
|
|
172
|
+
const self = this.main;
|
|
173
|
+
return new Promise(async (resolve, reject) => {
|
|
174
|
+
const finalPath = `${self.firebaseProjectPath}/functions/.runtimeconfig.json`;
|
|
175
|
+
const max = 10;
|
|
176
|
+
let retries = 0;
|
|
177
|
+
|
|
178
|
+
const _attempt = async () => {
|
|
179
|
+
try {
|
|
180
|
+
const output = await powertools.execute(`firebase functions:config:get > ${finalPath}`, { log: true });
|
|
181
|
+
this.logSuccess(`Saving config to: ${finalPath}`);
|
|
182
|
+
resolve(require(finalPath));
|
|
183
|
+
} catch (error) {
|
|
184
|
+
this.logError(`Failed to get config: ${error}`);
|
|
185
|
+
if (retries++ >= max) {
|
|
186
|
+
return reject(error);
|
|
187
|
+
}
|
|
188
|
+
const delay = 2500 * retries;
|
|
189
|
+
this.logWarning(`Retrying config:get ${retries}/${max} in ${delay}ms...`);
|
|
190
|
+
setTimeout(_attempt, delay);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
_attempt();
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
fix_isFirebase() {
|
|
199
|
+
this.logError(`This is not a firebase project. Please use ${chalk.bold('firebase-init')} to set up.`);
|
|
200
|
+
throw '';
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Helper functions
|
|
205
|
+
function loadJSON(path) {
|
|
206
|
+
const contents = jetpack.read(path);
|
|
207
|
+
if (!contents) {
|
|
208
|
+
return {};
|
|
209
|
+
}
|
|
210
|
+
return JSON5.parse(contents);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function hasContent(object) {
|
|
214
|
+
return Object.keys(object).length > 0;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
module.exports = SetupCommand;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const BaseCommand = require('./base-command');
|
|
2
|
+
const powertools = require('node-powertools');
|
|
3
|
+
|
|
4
|
+
class TestCommand extends BaseCommand {
|
|
5
|
+
async execute() {
|
|
6
|
+
const self = this.main;
|
|
7
|
+
|
|
8
|
+
// Run setup first
|
|
9
|
+
const SetupCommand = require('./setup');
|
|
10
|
+
const setupCmd = new SetupCommand(self);
|
|
11
|
+
await setupCmd.execute();
|
|
12
|
+
|
|
13
|
+
const MOCHA_PKG_SCRIPT = 'mocha ../test/ --recursive --timeout=10000';
|
|
14
|
+
|
|
15
|
+
// Execute
|
|
16
|
+
await powertools.execute(`firebase emulators:exec --only firestore "npx ${MOCHA_PKG_SCRIPT}"`, { log: true });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = TestCommand;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const BaseCommand = require('./base-command');
|
|
2
|
+
|
|
3
|
+
class VersionCommand extends BaseCommand {
|
|
4
|
+
async execute() {
|
|
5
|
+
const version = this.main.packageJSON.version;
|
|
6
|
+
this.log(`Backend manager is version: ${version}`);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
module.exports = VersionCommand;
|
|
@@ -50,6 +50,7 @@ function tryUrl(self) {
|
|
|
50
50
|
const Manager = self.Manager;
|
|
51
51
|
const projectType = Manager?.options?.projectType;
|
|
52
52
|
|
|
53
|
+
|
|
53
54
|
try {
|
|
54
55
|
const protocol = req.protocol;
|
|
55
56
|
const host = req.get('host');
|
|
@@ -69,7 +70,12 @@ function tryUrl(self) {
|
|
|
69
70
|
: `${protocol}://${host}/${self.meta.name}`;
|
|
70
71
|
}
|
|
71
72
|
} else if (projectType === 'custom') {
|
|
72
|
-
|
|
73
|
+
const server = Manager?._internal?.server;
|
|
74
|
+
const addy = server ? server.address() : null;
|
|
75
|
+
|
|
76
|
+
return addy
|
|
77
|
+
? `${protocol}://${addy.address}:${addy.port}${path}`
|
|
78
|
+
: `${protocol}://${host}${path}`;
|
|
73
79
|
}
|
|
74
80
|
|
|
75
81
|
return '';
|
|
@@ -175,6 +181,7 @@ BackendAssistant.prototype.init = function (ref, options) {
|
|
|
175
181
|
language: self.getHeaderLanguage(self.ref.req.headers),
|
|
176
182
|
platform: self.getHeaderPlatform(self.ref.req.headers),
|
|
177
183
|
mobile: self.getHeaderMobile(self.ref.req.headers),
|
|
184
|
+
url: self.getHeaderUrl(self.ref.req.headers),
|
|
178
185
|
};
|
|
179
186
|
|
|
180
187
|
// Deprecated notice for old properties
|
|
@@ -943,6 +950,27 @@ BackendAssistant.prototype.getHeaderMobile = function (headers) {
|
|
|
943
950
|
return mobile === '1' || mobile === true || mobile === 'true';
|
|
944
951
|
}
|
|
945
952
|
|
|
953
|
+
BackendAssistant.prototype.getHeaderUrl = function (headers) {
|
|
954
|
+
const self = this;
|
|
955
|
+
headers = headers || {};
|
|
956
|
+
|
|
957
|
+
return (
|
|
958
|
+
// Origin header (most reliable for CORS requests)
|
|
959
|
+
headers['origin']
|
|
960
|
+
|
|
961
|
+
// Fallback to referrer/referer
|
|
962
|
+
|| headers['referrer']
|
|
963
|
+
|| headers['referer']
|
|
964
|
+
|
|
965
|
+
// Reconstruct from host and path if available
|
|
966
|
+
|| (headers['host'] ? `https://${headers['host']}${self.ref.req?.originalUrl || self.ref.req?.url || ''}` : '')
|
|
967
|
+
|
|
968
|
+
// If unsure, return empty string
|
|
969
|
+
|| ''
|
|
970
|
+
)
|
|
971
|
+
.trim();
|
|
972
|
+
}
|
|
973
|
+
|
|
946
974
|
/**
|
|
947
975
|
* Parses a 'multipart/form-data' upload request
|
|
948
976
|
*
|
|
File without changes
|
|
File without changes
|