firebase-tools 14.18.0 → 14.19.1
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/README.md +11 -5
- package/lib/appUtils.js +230 -0
- package/lib/apphosting/localbuilds.js +23 -0
- package/lib/bin/mcp.js +16 -1
- package/lib/commands/index.js +8 -0
- package/lib/commands/init.js +0 -2
- package/lib/commands/remoteconfig-experiments-delete.js +32 -0
- package/lib/commands/remoteconfig-experiments-get.js +20 -0
- package/lib/commands/remoteconfig-experiments-list.js +38 -0
- package/lib/commands/remoteconfig-rollouts-delete.js +32 -0
- package/lib/commands/remoteconfig-rollouts-get.js +20 -0
- package/lib/commands/remoteconfig-rollouts-list.js +38 -0
- package/lib/config.js +4 -2
- package/lib/deploy/apphosting/deploy.js +26 -10
- package/lib/deploy/apphosting/prepare.js +23 -1
- package/lib/deploy/apphosting/release.js +5 -0
- package/lib/deploy/apphosting/util.js +4 -3
- package/lib/deploy/functions/deploy.js +5 -4
- package/lib/deploy/functions/params.js +2 -2
- package/lib/emulator/commandUtils.js +3 -3
- package/lib/emulator/controller.js +3 -2
- package/lib/gcp/cloudsql/cloudsqladmin.js +1 -1
- package/lib/gcp/storage.js +73 -20
- package/lib/init/features/dataconnect/index.js +18 -9
- package/lib/init/features/project.js +66 -75
- package/lib/management/projects.js +16 -4
- package/lib/mcp/index.js +9 -0
- package/lib/mcp/prompts/core/deploy.js +8 -8
- package/lib/mcp/prompts/core/init.js +15 -18
- package/lib/mcp/prompts/crashlytics/connect.js +2 -2
- package/lib/mcp/prompts/index.js +30 -9
- package/lib/mcp/resources/guides/init_ai.js +8 -0
- package/lib/mcp/resources/guides/init_auth.js +4 -0
- package/lib/mcp/resources/guides/init_firestore_rules.js +2 -0
- package/lib/mcp/resources/index.js +16 -1
- package/lib/mcp/tools/apphosting/list_backends.js +1 -1
- package/lib/mcp/tools/core/get_environment.js +34 -17
- package/lib/mcp/tools/core/init.js +2 -1
- package/lib/mcp/tools/core/logout.js +1 -2
- package/lib/mcp/tools/functions/get_logs.js +9 -7
- package/lib/mcp/tools/index.js +3 -2
- package/lib/remoteconfig/deleteExperiment.js +32 -0
- package/lib/remoteconfig/deleteRollout.js +33 -0
- package/lib/remoteconfig/getExperiment.js +52 -0
- package/lib/remoteconfig/getRollout.js +43 -0
- package/lib/remoteconfig/interfaces.js +3 -1
- package/lib/remoteconfig/listExperiments.js +72 -0
- package/lib/remoteconfig/listRollouts.js +72 -0
- package/lib/remoteconfig/options.js +4 -0
- package/lib/track.js +6 -4
- package/package.json +3 -1
- package/schema/firebase-config.json +6 -0
package/README.md
CHANGED
|
@@ -161,11 +161,17 @@ Detailed doc is [here](https://firebase.google.com/docs/cli/auth).
|
|
|
161
161
|
|
|
162
162
|
### Remote Config Commands
|
|
163
163
|
|
|
164
|
-
| Command
|
|
165
|
-
|
|
|
166
|
-
| **remoteconfig:get**
|
|
167
|
-
| **remoteconfig:versions:list**
|
|
168
|
-
| **remoteconfig:rollback**
|
|
164
|
+
| Command | Description |
|
|
165
|
+
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
166
|
+
| **remoteconfig:get** | Get a Firebase project's Remote Config template. |
|
|
167
|
+
| **remoteconfig:versions:list** | Get a list of the most recent Firebase Remote Config template versions that have been published. |
|
|
168
|
+
| **remoteconfig:rollback** | Roll back a project's published Remote Config template to the version provided by `--version_number` flag. |
|
|
169
|
+
| **remoteconfig:experiments:get** | Get a Remote Config experiment. |
|
|
170
|
+
| **remoteconfig:experiments:list** | Get a list of Remote Config experiments |
|
|
171
|
+
| **remoteconfig:experiments:delete** | Delete a Remote Config experiment. |
|
|
172
|
+
| **remoteconfig:rollouts:get** | Get a Remote Config rollout. |
|
|
173
|
+
| **remoteconfig:rollouts:list** | Get a list of Remote Config rollouts. |
|
|
174
|
+
| **remoteconfig:rollouts:delete** | Delete a Remote Config rollout. |
|
|
169
175
|
|
|
170
176
|
Use `firebase:deploy --only remoteconfig` to update and publish a project's Firebase Remote Config template.
|
|
171
177
|
|
package/lib/appUtils.js
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractAppIdentifiersAndroid = exports.extractAppIdentifierIos = exports.extractAppIdentifiersFlutter = exports.detectApps = exports.getPlatformsFromFolder = exports.appDescription = exports.Framework = exports.Platform = void 0;
|
|
4
|
+
const fs = require("fs-extra");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const glob_1 = require("glob");
|
|
7
|
+
var Platform;
|
|
8
|
+
(function (Platform) {
|
|
9
|
+
Platform["ANDROID"] = "ANDROID";
|
|
10
|
+
Platform["WEB"] = "WEB";
|
|
11
|
+
Platform["IOS"] = "IOS";
|
|
12
|
+
Platform["FLUTTER"] = "FLUTTER";
|
|
13
|
+
})(Platform = exports.Platform || (exports.Platform = {}));
|
|
14
|
+
var Framework;
|
|
15
|
+
(function (Framework) {
|
|
16
|
+
Framework["REACT"] = "REACT";
|
|
17
|
+
Framework["ANGULAR"] = "ANGULAR";
|
|
18
|
+
})(Framework = exports.Framework || (exports.Framework = {}));
|
|
19
|
+
function appDescription(a) {
|
|
20
|
+
return `${a.directory} (${a.platform.toLowerCase()})`;
|
|
21
|
+
}
|
|
22
|
+
exports.appDescription = appDescription;
|
|
23
|
+
async function getPlatformsFromFolder(dirPath) {
|
|
24
|
+
const apps = await detectApps(dirPath);
|
|
25
|
+
return [...new Set(apps.map((app) => app.platform))];
|
|
26
|
+
}
|
|
27
|
+
exports.getPlatformsFromFolder = getPlatformsFromFolder;
|
|
28
|
+
async function detectApps(dirPath) {
|
|
29
|
+
const packageJsonFiles = await detectFiles(dirPath, "package.json");
|
|
30
|
+
const pubSpecYamlFiles = await detectFiles(dirPath, "pubspec.yaml");
|
|
31
|
+
const srcMainFolders = await detectFiles(dirPath, "src/main/");
|
|
32
|
+
const xCodeProjects = await detectFiles(dirPath, "*.xcodeproj/");
|
|
33
|
+
const webApps = await Promise.all(packageJsonFiles.map((p) => packageJsonToWebApp(dirPath, p)));
|
|
34
|
+
const flutterAppPromises = await Promise.all(pubSpecYamlFiles.map((f) => processFlutterDir(dirPath, f)));
|
|
35
|
+
const flutterApps = flutterAppPromises.flat();
|
|
36
|
+
const androidAppPromises = await Promise.all(srcMainFolders.map((f) => processAndroidDir(dirPath, f)));
|
|
37
|
+
const androidApps = androidAppPromises
|
|
38
|
+
.flat()
|
|
39
|
+
.filter((a) => !flutterApps.some((f) => isPathInside(f.directory, a.directory)));
|
|
40
|
+
const iosAppPromises = await Promise.all(xCodeProjects.map((f) => processIosDir(dirPath, f)));
|
|
41
|
+
const iosApps = iosAppPromises
|
|
42
|
+
.flat()
|
|
43
|
+
.filter((a) => !flutterApps.some((f) => isPathInside(f.directory, a.directory)));
|
|
44
|
+
return [...webApps, ...flutterApps, ...androidApps, ...iosApps];
|
|
45
|
+
}
|
|
46
|
+
exports.detectApps = detectApps;
|
|
47
|
+
async function processIosDir(dirPath, filePath) {
|
|
48
|
+
const iosDir = path.dirname(filePath);
|
|
49
|
+
const iosAppIds = await detectAppIdsForPlatform(dirPath, Platform.IOS);
|
|
50
|
+
if (iosAppIds.length === 0) {
|
|
51
|
+
return [
|
|
52
|
+
{
|
|
53
|
+
platform: Platform.IOS,
|
|
54
|
+
directory: iosDir,
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
const iosApps = await Promise.all(iosAppIds.map((app) => ({
|
|
59
|
+
platform: Platform.IOS,
|
|
60
|
+
directory: iosDir,
|
|
61
|
+
appId: app.appId,
|
|
62
|
+
bundleId: app.bundleId,
|
|
63
|
+
})));
|
|
64
|
+
return iosApps.flat();
|
|
65
|
+
}
|
|
66
|
+
async function processAndroidDir(dirPath, filePath) {
|
|
67
|
+
const androidDir = path.dirname(path.dirname(filePath));
|
|
68
|
+
const androidAppIds = await detectAppIdsForPlatform(dirPath, Platform.ANDROID);
|
|
69
|
+
if (androidAppIds.length === 0) {
|
|
70
|
+
return [
|
|
71
|
+
{
|
|
72
|
+
platform: Platform.ANDROID,
|
|
73
|
+
directory: androidDir,
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
}
|
|
77
|
+
const androidApps = await Promise.all(androidAppIds.map((app) => ({
|
|
78
|
+
platform: Platform.ANDROID,
|
|
79
|
+
directory: androidDir,
|
|
80
|
+
appId: app.appId,
|
|
81
|
+
bundleId: app.bundleId,
|
|
82
|
+
})));
|
|
83
|
+
return androidApps.flat();
|
|
84
|
+
}
|
|
85
|
+
async function processFlutterDir(dirPath, filePath) {
|
|
86
|
+
const flutterDir = path.dirname(filePath);
|
|
87
|
+
const flutterAppIds = await detectAppIdsForPlatform(dirPath, Platform.FLUTTER);
|
|
88
|
+
if (flutterAppIds.length === 0) {
|
|
89
|
+
return [
|
|
90
|
+
{
|
|
91
|
+
platform: Platform.FLUTTER,
|
|
92
|
+
directory: flutterDir,
|
|
93
|
+
},
|
|
94
|
+
];
|
|
95
|
+
}
|
|
96
|
+
const flutterApps = await Promise.all(flutterAppIds.map((app) => {
|
|
97
|
+
const flutterApp = {
|
|
98
|
+
platform: Platform.FLUTTER,
|
|
99
|
+
directory: flutterDir,
|
|
100
|
+
appId: app.appId,
|
|
101
|
+
bundleId: app.bundleId,
|
|
102
|
+
};
|
|
103
|
+
return flutterApp;
|
|
104
|
+
}));
|
|
105
|
+
return flutterApps.flat();
|
|
106
|
+
}
|
|
107
|
+
function isPathInside(parent, child) {
|
|
108
|
+
const relativePath = path.relative(parent, child);
|
|
109
|
+
return !relativePath.startsWith(`..`);
|
|
110
|
+
}
|
|
111
|
+
async function packageJsonToWebApp(dirPath, packageJsonFile) {
|
|
112
|
+
const fullPath = path.join(dirPath, packageJsonFile);
|
|
113
|
+
const packageJson = JSON.parse((await fs.readFile(fullPath)).toString());
|
|
114
|
+
return {
|
|
115
|
+
platform: Platform.WEB,
|
|
116
|
+
directory: path.dirname(packageJsonFile),
|
|
117
|
+
frameworks: getFrameworksFromPackageJson(packageJson),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const WEB_FRAMEWORKS = Object.values(Framework);
|
|
121
|
+
const WEB_FRAMEWORKS_SIGNALS = {
|
|
122
|
+
REACT: ["react", "next"],
|
|
123
|
+
ANGULAR: ["@angular/core"],
|
|
124
|
+
};
|
|
125
|
+
async function detectAppIdsForPlatform(dirPath, platform) {
|
|
126
|
+
let appIdFiles;
|
|
127
|
+
let extractFunc;
|
|
128
|
+
switch (platform) {
|
|
129
|
+
case Platform.ANDROID:
|
|
130
|
+
appIdFiles = await detectFiles(dirPath, "google-services*.json*");
|
|
131
|
+
extractFunc = extractAppIdentifiersAndroid;
|
|
132
|
+
break;
|
|
133
|
+
case Platform.IOS:
|
|
134
|
+
appIdFiles = await detectFiles(dirPath, "GoogleService-*.plist*");
|
|
135
|
+
extractFunc = extractAppIdentifierIos;
|
|
136
|
+
break;
|
|
137
|
+
case Platform.FLUTTER:
|
|
138
|
+
appIdFiles = await detectFiles(dirPath, "firebase_options.dart");
|
|
139
|
+
extractFunc = extractAppIdentifiersFlutter;
|
|
140
|
+
break;
|
|
141
|
+
default:
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
const allAppIds = await Promise.all(appIdFiles.map(async (file) => {
|
|
145
|
+
const fileContent = (await fs.readFile(path.join(dirPath, file))).toString();
|
|
146
|
+
return extractFunc(fileContent);
|
|
147
|
+
}));
|
|
148
|
+
return allAppIds.flat();
|
|
149
|
+
}
|
|
150
|
+
function getFrameworksFromPackageJson(packageJson) {
|
|
151
|
+
var _a, _b;
|
|
152
|
+
const devDependencies = Object.keys((_a = packageJson.devDependencies) !== null && _a !== void 0 ? _a : {});
|
|
153
|
+
const dependencies = Object.keys((_b = packageJson.dependencies) !== null && _b !== void 0 ? _b : {});
|
|
154
|
+
const allDeps = Array.from(new Set([...devDependencies, ...dependencies]));
|
|
155
|
+
return WEB_FRAMEWORKS.filter((framework) => WEB_FRAMEWORKS_SIGNALS[framework].find((dep) => allDeps.includes(dep)));
|
|
156
|
+
}
|
|
157
|
+
function extractAppIdentifiersFlutter(fileContent) {
|
|
158
|
+
const optionsRegex = /FirebaseOptions\(([^)]*)\)/g;
|
|
159
|
+
const appIdRegex = /appId: '([^']*)'/;
|
|
160
|
+
const bundleIdRegex = /iosBundleId: '([^']*)'/;
|
|
161
|
+
const matches = fileContent.matchAll(optionsRegex);
|
|
162
|
+
const identifiers = [];
|
|
163
|
+
for (const match of matches) {
|
|
164
|
+
const optionsContent = match[1];
|
|
165
|
+
const appIdMatch = appIdRegex.exec(optionsContent);
|
|
166
|
+
const bundleIdMatch = bundleIdRegex.exec(optionsContent);
|
|
167
|
+
if (appIdMatch === null || appIdMatch === void 0 ? void 0 : appIdMatch[1]) {
|
|
168
|
+
identifiers.push({
|
|
169
|
+
appId: appIdMatch[1],
|
|
170
|
+
bundleId: bundleIdMatch === null || bundleIdMatch === void 0 ? void 0 : bundleIdMatch[1],
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return identifiers;
|
|
175
|
+
}
|
|
176
|
+
exports.extractAppIdentifiersFlutter = extractAppIdentifiersFlutter;
|
|
177
|
+
function extractAppIdentifierIos(fileContent) {
|
|
178
|
+
const appIdRegex = /<key>GOOGLE_APP_ID<\/key>\s*<string>([^<]*)<\/string>/;
|
|
179
|
+
const bundleIdRegex = /<key>BUNDLE_ID<\/key>\s*<string>([^<]*)<\/string>/;
|
|
180
|
+
const appIdMatch = fileContent.match(appIdRegex);
|
|
181
|
+
const bundleIdMatch = fileContent.match(bundleIdRegex);
|
|
182
|
+
if (appIdMatch === null || appIdMatch === void 0 ? void 0 : appIdMatch[1]) {
|
|
183
|
+
return [
|
|
184
|
+
{
|
|
185
|
+
appId: appIdMatch[1],
|
|
186
|
+
bundleId: bundleIdMatch === null || bundleIdMatch === void 0 ? void 0 : bundleIdMatch[1],
|
|
187
|
+
},
|
|
188
|
+
];
|
|
189
|
+
}
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
exports.extractAppIdentifierIos = extractAppIdentifierIos;
|
|
193
|
+
function extractAppIdentifiersAndroid(fileContent) {
|
|
194
|
+
var _a, _b;
|
|
195
|
+
const identifiers = [];
|
|
196
|
+
try {
|
|
197
|
+
const config = JSON.parse(fileContent);
|
|
198
|
+
if (config.client && Array.isArray(config.client)) {
|
|
199
|
+
for (const client of config.client) {
|
|
200
|
+
if ((_a = client.client_info) === null || _a === void 0 ? void 0 : _a.mobilesdk_app_id) {
|
|
201
|
+
identifiers.push({
|
|
202
|
+
appId: client.client_info.mobilesdk_app_id,
|
|
203
|
+
bundleId: (_b = client.client_info.android_client_info) === null || _b === void 0 ? void 0 : _b.package_name,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch (e) {
|
|
210
|
+
console.error("Error parsing google-services.json:", e);
|
|
211
|
+
}
|
|
212
|
+
return identifiers;
|
|
213
|
+
}
|
|
214
|
+
exports.extractAppIdentifiersAndroid = extractAppIdentifiersAndroid;
|
|
215
|
+
async function detectFiles(dirPath, filePattern) {
|
|
216
|
+
const options = {
|
|
217
|
+
cwd: dirPath,
|
|
218
|
+
ignore: [
|
|
219
|
+
"**/dataconnect*/**",
|
|
220
|
+
"**/node_modules/**",
|
|
221
|
+
"**/dist/**",
|
|
222
|
+
"**/build/**",
|
|
223
|
+
"**/out/**",
|
|
224
|
+
"**/.next/**",
|
|
225
|
+
"**/coverage/**",
|
|
226
|
+
],
|
|
227
|
+
absolute: false,
|
|
228
|
+
};
|
|
229
|
+
return (0, glob_1.glob)(`**/${filePattern}`, options);
|
|
230
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.localBuild = void 0;
|
|
4
|
+
const build_1 = require("@apphosting/build");
|
|
5
|
+
async function localBuild(projectRoot, framework) {
|
|
6
|
+
var _a, _b, _c;
|
|
7
|
+
const apphostingBuildOutput = await (0, build_1.localBuild)(projectRoot, framework);
|
|
8
|
+
const annotations = Object.fromEntries(Object.entries(apphostingBuildOutput.metadata).map(([key, value]) => [key, String(value)]));
|
|
9
|
+
const env = (_a = apphostingBuildOutput.runConfig.environmentVariables) === null || _a === void 0 ? void 0 : _a.map(({ variable, value, availability }) => ({
|
|
10
|
+
variable,
|
|
11
|
+
value,
|
|
12
|
+
availability,
|
|
13
|
+
}));
|
|
14
|
+
return {
|
|
15
|
+
outputFiles: (_c = (_b = apphostingBuildOutput.outputFiles) === null || _b === void 0 ? void 0 : _b.serverApp.include) !== null && _c !== void 0 ? _c : [],
|
|
16
|
+
annotations,
|
|
17
|
+
buildConfig: {
|
|
18
|
+
runCommand: apphostingBuildOutput.runConfig.runCommand,
|
|
19
|
+
env: env !== null && env !== void 0 ? env : [],
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
exports.localBuild = localBuild;
|
package/lib/bin/mcp.js
CHANGED
|
@@ -7,6 +7,8 @@ const index_1 = require("../mcp/index");
|
|
|
7
7
|
const util_1 = require("util");
|
|
8
8
|
const types_1 = require("../mcp/types");
|
|
9
9
|
const index_js_1 = require("../mcp/tools/index.js");
|
|
10
|
+
const index_js_2 = require("../mcp/prompts/index.js");
|
|
11
|
+
const index_js_3 = require("../mcp/resources/index.js");
|
|
10
12
|
const path_1 = require("path");
|
|
11
13
|
const STARTUP_MESSAGE = `
|
|
12
14
|
This is a running process of the Firebase MCP server. This command should only be executed by an MCP client. An example MCP client configuration might be:
|
|
@@ -26,13 +28,26 @@ async function mcp() {
|
|
|
26
28
|
only: { type: "string", default: "" },
|
|
27
29
|
dir: { type: "string" },
|
|
28
30
|
"generate-tool-list": { type: "boolean", default: false },
|
|
31
|
+
"generate-prompt-list": { type: "boolean", default: false },
|
|
32
|
+
"generate-resource-list": { type: "boolean", default: false },
|
|
29
33
|
},
|
|
30
34
|
allowPositionals: true,
|
|
31
35
|
});
|
|
36
|
+
let earlyExit = false;
|
|
32
37
|
if (values["generate-tool-list"]) {
|
|
33
38
|
console.log((0, index_js_1.markdownDocsOfTools)());
|
|
34
|
-
|
|
39
|
+
earlyExit = true;
|
|
40
|
+
}
|
|
41
|
+
if (values["generate-prompt-list"]) {
|
|
42
|
+
console.log((0, index_js_2.markdownDocsOfPrompts)());
|
|
43
|
+
earlyExit = true;
|
|
35
44
|
}
|
|
45
|
+
if (values["generate-resource-list"]) {
|
|
46
|
+
console.log((0, index_js_3.markdownDocsOfResources)());
|
|
47
|
+
earlyExit = true;
|
|
48
|
+
}
|
|
49
|
+
if (earlyExit)
|
|
50
|
+
return;
|
|
36
51
|
process.env.IS_FIREBASE_MCP = "true";
|
|
37
52
|
(0, logger_1.useFileLogger)();
|
|
38
53
|
const activeFeatures = (values.only || "")
|
package/lib/commands/index.js
CHANGED
|
@@ -208,6 +208,14 @@ function load(client) {
|
|
|
208
208
|
client.remoteconfig.rollback = loadCommand("remoteconfig-rollback");
|
|
209
209
|
client.remoteconfig.versions = {};
|
|
210
210
|
client.remoteconfig.versions.list = loadCommand("remoteconfig-versions-list");
|
|
211
|
+
client.remoteconfig.rollouts = {};
|
|
212
|
+
client.remoteconfig.rollouts.get = loadCommand("remoteconfig-rollouts-get");
|
|
213
|
+
client.remoteconfig.rollouts.list = loadCommand("remoteconfig-rollouts-list");
|
|
214
|
+
client.remoteconfig.rollouts.delete = loadCommand("remoteconfig-rollouts-delete");
|
|
215
|
+
client.remoteconfig.experiments = {};
|
|
216
|
+
client.remoteconfig.experiments.get = loadCommand("remoteconfig-experiments-get");
|
|
217
|
+
client.remoteconfig.experiments.list = loadCommand("remoteconfig-experiments-list");
|
|
218
|
+
client.remoteconfig.experiments.delete = loadCommand("remoteconfig-experiments-delete");
|
|
211
219
|
client.serve = loadCommand("serve");
|
|
212
220
|
client.setup = {};
|
|
213
221
|
client.setup.emulators = {};
|
package/lib/commands/init.js
CHANGED
|
@@ -10,7 +10,6 @@ const auth_1 = require("../auth");
|
|
|
10
10
|
const init_1 = require("../init");
|
|
11
11
|
const logger_1 = require("../logger");
|
|
12
12
|
const prompt_1 = require("../prompt");
|
|
13
|
-
const requireAuth_1 = require("../requireAuth");
|
|
14
13
|
const fsutils = require("../fsutils");
|
|
15
14
|
const utils = require("../utils");
|
|
16
15
|
const experiments_1 = require("../experiments");
|
|
@@ -132,7 +131,6 @@ ${[...featureNames]
|
|
|
132
131
|
exports.command = new command_1.Command("init [feature]")
|
|
133
132
|
.description("interactively configure the current directory as a Firebase project directory")
|
|
134
133
|
.help(HELP)
|
|
135
|
-
.before(requireAuth_1.requireAuth)
|
|
136
134
|
.action(initAction);
|
|
137
135
|
async function initAction(feature, options) {
|
|
138
136
|
var _a;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.command = void 0;
|
|
4
|
+
const command_1 = require("../command");
|
|
5
|
+
const requireAuth_1 = require("../requireAuth");
|
|
6
|
+
const requirePermissions_1 = require("../requirePermissions");
|
|
7
|
+
const logger_1 = require("../logger");
|
|
8
|
+
const projectUtils_1 = require("../projectUtils");
|
|
9
|
+
const interfaces_1 = require("../remoteconfig/interfaces");
|
|
10
|
+
const rcExperiment = require("../remoteconfig/deleteExperiment");
|
|
11
|
+
const getExperiment_1 = require("../remoteconfig/getExperiment");
|
|
12
|
+
const prompt_1 = require("../prompt");
|
|
13
|
+
exports.command = new command_1.Command("remoteconfig:experiments:delete <experimentId>")
|
|
14
|
+
.description("delete a Remote Config experiment.")
|
|
15
|
+
.before(requireAuth_1.requireAuth)
|
|
16
|
+
.before(requirePermissions_1.requirePermissions, [
|
|
17
|
+
"firebaseabt.experiments.delete",
|
|
18
|
+
"firebaseanalytics.resources.googleAnalyticsEdit",
|
|
19
|
+
])
|
|
20
|
+
.action(async (experimentId, options) => {
|
|
21
|
+
const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
|
|
22
|
+
const experiment = await (0, getExperiment_1.getExperiment)(projectNumber, interfaces_1.NAMESPACE_FIREBASE, experimentId);
|
|
23
|
+
logger_1.logger.info((0, getExperiment_1.parseExperiment)(experiment));
|
|
24
|
+
const confirmDeletion = await (0, prompt_1.confirm)({
|
|
25
|
+
message: "Are you sure you want to delete this experiment? This cannot be undone.",
|
|
26
|
+
default: false,
|
|
27
|
+
});
|
|
28
|
+
if (!confirmDeletion) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
logger_1.logger.info(await rcExperiment.deleteExperiment(projectNumber, interfaces_1.NAMESPACE_FIREBASE, experimentId));
|
|
32
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.command = void 0;
|
|
4
|
+
const command_1 = require("../command");
|
|
5
|
+
const requireAuth_1 = require("../requireAuth");
|
|
6
|
+
const requirePermissions_1 = require("../requirePermissions");
|
|
7
|
+
const logger_1 = require("../logger");
|
|
8
|
+
const projectUtils_1 = require("../projectUtils");
|
|
9
|
+
const interfaces_1 = require("../remoteconfig/interfaces");
|
|
10
|
+
const rcExperiment = require("../remoteconfig/getExperiment");
|
|
11
|
+
exports.command = new command_1.Command("remoteconfig:experiments:get <experimentId>")
|
|
12
|
+
.description("get a Remote Config experiment.")
|
|
13
|
+
.before(requireAuth_1.requireAuth)
|
|
14
|
+
.before(requirePermissions_1.requirePermissions, ["firebaseabt.experiments.get"])
|
|
15
|
+
.action(async (experimentId, options) => {
|
|
16
|
+
const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
|
|
17
|
+
const experiment = await rcExperiment.getExperiment(projectNumber, interfaces_1.NAMESPACE_FIREBASE, experimentId);
|
|
18
|
+
logger_1.logger.info(rcExperiment.parseExperiment(experiment));
|
|
19
|
+
return experiment;
|
|
20
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.command = void 0;
|
|
4
|
+
const rcExperiment = require("../remoteconfig/listExperiments");
|
|
5
|
+
const interfaces_1 = require("../remoteconfig/interfaces");
|
|
6
|
+
const command_1 = require("../command");
|
|
7
|
+
const requireAuth_1 = require("../requireAuth");
|
|
8
|
+
const requirePermissions_1 = require("../requirePermissions");
|
|
9
|
+
const logger_1 = require("../logger");
|
|
10
|
+
const projectUtils_1 = require("../projectUtils");
|
|
11
|
+
exports.command = new command_1.Command("remoteconfig:experiments:list")
|
|
12
|
+
.description("get a list of Remote Config experiments")
|
|
13
|
+
.option("--pageSize <pageSize>", "Maximum number of experiments to return per page. Defaults to 10. Pass '0' to fetch all experiments")
|
|
14
|
+
.option("--pageToken <pageToken>", "Token from a previous list operation to retrieve the next page of results. Listing starts from the beginning if omitted.")
|
|
15
|
+
.option("--filter <filter>", "Filters experiments by their full resource name. Format: `name:projects/{project_number}/namespaces/{namespace}/experiments/{experiment_id}`")
|
|
16
|
+
.before(requireAuth_1.requireAuth)
|
|
17
|
+
.before(requirePermissions_1.requirePermissions, [
|
|
18
|
+
"firebaseabt.experiments.list",
|
|
19
|
+
"firebaseanalytics.resources.googleAnalyticsReadAndAnalyze",
|
|
20
|
+
])
|
|
21
|
+
.action(async (options) => {
|
|
22
|
+
var _a;
|
|
23
|
+
const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
|
|
24
|
+
const listExperimentOptions = {
|
|
25
|
+
pageSize: (_a = options.pageSize) !== null && _a !== void 0 ? _a : interfaces_1.DEFAULT_PAGE_SIZE,
|
|
26
|
+
pageToken: options.pageToken,
|
|
27
|
+
filter: options.filter,
|
|
28
|
+
};
|
|
29
|
+
const { experiments, nextPageToken } = await rcExperiment.listExperiments(projectNumber, interfaces_1.NAMESPACE_FIREBASE, listExperimentOptions);
|
|
30
|
+
logger_1.logger.info(rcExperiment.parseExperimentList(experiments !== null && experiments !== void 0 ? experiments : []));
|
|
31
|
+
if (nextPageToken) {
|
|
32
|
+
logger_1.logger.info(`\nNext Page Token: \x1b[32m${nextPageToken}\x1b[0m\n`);
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
experiments,
|
|
36
|
+
nextPageToken,
|
|
37
|
+
};
|
|
38
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.command = void 0;
|
|
4
|
+
const command_1 = require("../command");
|
|
5
|
+
const requireAuth_1 = require("../requireAuth");
|
|
6
|
+
const requirePermissions_1 = require("../requirePermissions");
|
|
7
|
+
const logger_1 = require("../logger");
|
|
8
|
+
const projectUtils_1 = require("../projectUtils");
|
|
9
|
+
const interfaces_1 = require("../remoteconfig/interfaces");
|
|
10
|
+
const rcRollout = require("../remoteconfig/deleteRollout");
|
|
11
|
+
const getRollout_1 = require("../remoteconfig/getRollout");
|
|
12
|
+
const prompt_1 = require("../prompt");
|
|
13
|
+
exports.command = new command_1.Command("remoteconfig:rollouts:delete <rolloutId>")
|
|
14
|
+
.description("delete a Remote Config rollout.")
|
|
15
|
+
.before(requireAuth_1.requireAuth)
|
|
16
|
+
.before(requirePermissions_1.requirePermissions, [
|
|
17
|
+
"cloud.configs.update",
|
|
18
|
+
"firebaseanalytics.resources.googleAnalyticsEdit",
|
|
19
|
+
])
|
|
20
|
+
.action(async (rolloutId, options) => {
|
|
21
|
+
const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
|
|
22
|
+
const rollout = await (0, getRollout_1.getRollout)(projectNumber, interfaces_1.NAMESPACE_FIREBASE, rolloutId);
|
|
23
|
+
logger_1.logger.info((0, getRollout_1.parseRolloutIntoTable)(rollout));
|
|
24
|
+
const confirmDeletion = await (0, prompt_1.confirm)({
|
|
25
|
+
message: "Are you sure you want to delete this rollout? This cannot be undone.",
|
|
26
|
+
default: false,
|
|
27
|
+
});
|
|
28
|
+
if (!confirmDeletion) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
logger_1.logger.info(await rcRollout.deleteRollout(projectNumber, interfaces_1.NAMESPACE_FIREBASE, rolloutId));
|
|
32
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.command = void 0;
|
|
4
|
+
const command_1 = require("../command");
|
|
5
|
+
const requireAuth_1 = require("../requireAuth");
|
|
6
|
+
const requirePermissions_1 = require("../requirePermissions");
|
|
7
|
+
const logger_1 = require("../logger");
|
|
8
|
+
const projectUtils_1 = require("../projectUtils");
|
|
9
|
+
const interfaces_1 = require("../remoteconfig/interfaces");
|
|
10
|
+
const rcRollout = require("../remoteconfig/getRollout");
|
|
11
|
+
exports.command = new command_1.Command("remoteconfig:rollouts:get <rolloutId>")
|
|
12
|
+
.description("get a Remote Config rollout")
|
|
13
|
+
.before(requireAuth_1.requireAuth)
|
|
14
|
+
.before(requirePermissions_1.requirePermissions, ["cloud.configs.get"])
|
|
15
|
+
.action(async (rolloutId, options) => {
|
|
16
|
+
const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
|
|
17
|
+
const rollout = await rcRollout.getRollout(projectNumber, interfaces_1.NAMESPACE_FIREBASE, rolloutId);
|
|
18
|
+
logger_1.logger.info(rcRollout.parseRolloutIntoTable(rollout));
|
|
19
|
+
return rollout;
|
|
20
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.command = void 0;
|
|
4
|
+
const command_1 = require("../command");
|
|
5
|
+
const requireAuth_1 = require("../requireAuth");
|
|
6
|
+
const requirePermissions_1 = require("../requirePermissions");
|
|
7
|
+
const logger_1 = require("../logger");
|
|
8
|
+
const projectUtils_1 = require("../projectUtils");
|
|
9
|
+
const interfaces_1 = require("../remoteconfig/interfaces");
|
|
10
|
+
const rcRollout = require("../remoteconfig/listRollouts");
|
|
11
|
+
exports.command = new command_1.Command("remoteconfig:rollouts:list")
|
|
12
|
+
.description("get a list of Remote Config rollouts.")
|
|
13
|
+
.option("--pageSize <pageSize>", "Maximum number of rollouts to return per page. Defaults to 10. Pass '0' to fetch all rollouts")
|
|
14
|
+
.option("--pageToken <pageToken>", "Token from a previous list operation to retrieve the next page of results. Listing starts from the beginning if omitted.")
|
|
15
|
+
.option("--filter <filter>", "Filters rollouts by their full resource name. Format: `name:projects/{project_id}/namespaces/{namespace}/rollouts/{rollout_id}`")
|
|
16
|
+
.before(requireAuth_1.requireAuth)
|
|
17
|
+
.before(requirePermissions_1.requirePermissions, [
|
|
18
|
+
"cloud.configs.get",
|
|
19
|
+
"firebaseanalytics.resources.googleAnalyticsReadAndAnalyze",
|
|
20
|
+
])
|
|
21
|
+
.action(async (options) => {
|
|
22
|
+
var _a;
|
|
23
|
+
const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
|
|
24
|
+
const listRolloutOptions = {
|
|
25
|
+
pageSize: (_a = options.pageSize) !== null && _a !== void 0 ? _a : interfaces_1.DEFAULT_PAGE_SIZE,
|
|
26
|
+
pageToken: options.pageToken,
|
|
27
|
+
filter: options.filter,
|
|
28
|
+
};
|
|
29
|
+
const { rollouts, nextPageToken } = await rcRollout.listRollouts(projectNumber, interfaces_1.NAMESPACE_FIREBASE, listRolloutOptions);
|
|
30
|
+
logger_1.logger.info(rcRollout.parseRolloutList(rollouts !== null && rollouts !== void 0 ? rollouts : []));
|
|
31
|
+
if (nextPageToken) {
|
|
32
|
+
logger_1.logger.info(`\nNext Page Token: \x1b[32m${nextPageToken}\x1b[0m\n`);
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
rollouts,
|
|
36
|
+
nextPageToken,
|
|
37
|
+
};
|
|
38
|
+
});
|
package/lib/config.js
CHANGED
|
@@ -5,7 +5,6 @@ const _ = require("lodash");
|
|
|
5
5
|
const clc = require("colorette");
|
|
6
6
|
const fs = require("fs-extra");
|
|
7
7
|
const path = require("path");
|
|
8
|
-
const cjson = require("cjson");
|
|
9
8
|
const detectProjectRoot_1 = require("./detectProjectRoot");
|
|
10
9
|
const error_1 = require("./error");
|
|
11
10
|
const fsutils = require("./fsutils");
|
|
@@ -225,7 +224,10 @@ class Config {
|
|
|
225
224
|
if (pd) {
|
|
226
225
|
try {
|
|
227
226
|
const filePath = path.resolve(pd, path.basename(filename));
|
|
228
|
-
|
|
227
|
+
let data = {};
|
|
228
|
+
if (fs.statSync(filePath).size > 0) {
|
|
229
|
+
data = (0, loadCJSON_1.loadCJSON)(filePath);
|
|
230
|
+
}
|
|
229
231
|
const validator = (0, firebaseConfigValidate_1.getValidator)();
|
|
230
232
|
const valid = validator(data);
|
|
231
233
|
if (!valid && validator.errors) {
|
|
@@ -17,14 +17,20 @@ async function default_1(context, options) {
|
|
|
17
17
|
if (!context.backendConfigs) {
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
const bucketsPerLocation = {};
|
|
21
|
+
await Promise.all(Object.entries(context.backendLocations).map(async ([backendId, loc]) => {
|
|
22
|
+
const cfg = context.backendConfigs[backendId];
|
|
23
|
+
if (!cfg) {
|
|
24
|
+
throw new error_1.FirebaseError(`Failed to find config for backend ${backendId}. Please contact support with the contents of your firebase-debug.log to report your issue.`);
|
|
25
|
+
}
|
|
26
|
+
const baseName = `firebaseapphosting-sources-${options.projectNumber}-${loc.toLowerCase()}`;
|
|
27
|
+
const resolvedName = await gcs.upsertBucket({
|
|
23
28
|
product: "apphosting",
|
|
24
|
-
createMessage: `Creating Cloud Storage bucket in ${loc} to store App Hosting source code uploads at ${
|
|
29
|
+
createMessage: `Creating Cloud Storage bucket in ${loc} to store App Hosting source code uploads at ${baseName}...`,
|
|
25
30
|
projectId,
|
|
26
31
|
req: {
|
|
27
|
-
|
|
32
|
+
baseName,
|
|
33
|
+
purposeLabel: `apphosting-source-${loc.toLowerCase()}`,
|
|
28
34
|
location: loc,
|
|
29
35
|
lifecycle: {
|
|
30
36
|
rule: [
|
|
@@ -40,21 +46,31 @@ async function default_1(context, options) {
|
|
|
40
46
|
},
|
|
41
47
|
},
|
|
42
48
|
});
|
|
49
|
+
bucketsPerLocation[loc] = resolvedName;
|
|
43
50
|
}));
|
|
44
51
|
await Promise.all(Object.values(context.backendConfigs).map(async (cfg) => {
|
|
45
|
-
|
|
46
|
-
const
|
|
52
|
+
var _a;
|
|
53
|
+
const rootDir = (_a = options.projectRoot) !== null && _a !== void 0 ? _a : process.cwd();
|
|
54
|
+
let builtAppDir;
|
|
55
|
+
if (cfg.localBuild) {
|
|
56
|
+
builtAppDir = context.backendLocalBuilds[cfg.backendId].buildDir;
|
|
57
|
+
if (!builtAppDir) {
|
|
58
|
+
throw new error_1.FirebaseError(`No local build dir found for ${cfg.backendId}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const zippedSourcePath = await (0, util_1.createArchive)(cfg, rootDir, builtAppDir);
|
|
62
|
+
(0, utils_1.logLabeledBullet)("apphosting", `Zipped ${cfg.localBuild ? "built app" : "source"} for backend ${cfg.backendId}`);
|
|
47
63
|
const backendLocation = context.backendLocations[cfg.backendId];
|
|
48
64
|
if (!backendLocation) {
|
|
49
65
|
throw new error_1.FirebaseError(`Failed to find location for backend ${cfg.backendId}. Please contact support with the contents of your firebase-debug.log to report your issue.`);
|
|
50
66
|
}
|
|
51
|
-
(0, utils_1.logLabeledBullet)("apphosting", `Uploading
|
|
52
|
-
const bucketName =
|
|
67
|
+
(0, utils_1.logLabeledBullet)("apphosting", `Uploading ${cfg.localBuild ? "built app" : "source"} for backend ${cfg.backendId}...`);
|
|
68
|
+
const bucketName = bucketsPerLocation[backendLocation];
|
|
53
69
|
const { bucket, object } = await gcs.uploadObject({
|
|
54
70
|
file: zippedSourcePath,
|
|
55
71
|
stream: fs.createReadStream(zippedSourcePath),
|
|
56
72
|
}, bucketName);
|
|
57
|
-
(0, utils_1.logLabeledBullet)("apphosting", `
|
|
73
|
+
(0, utils_1.logLabeledBullet)("apphosting", `Uploaded at gs://${bucket}/${object}`);
|
|
58
74
|
context.backendStorageUris[cfg.backendId] =
|
|
59
75
|
`gs://${bucketName}/${path.basename(zippedSourcePath)}`;
|
|
60
76
|
}));
|