firebase-tools 12.3.1 → 12.4.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/lib/api/frameworks.js +21 -0
- package/lib/api.js +4 -3
- package/lib/appdistribution/client.js +52 -0
- package/lib/auth.js +3 -3
- package/lib/command.js +15 -4
- package/lib/commands/appdistribution-group-create.js +19 -0
- package/lib/commands/appdistribution-group-delete.js +24 -0
- package/lib/commands/appdistribution-testers-add.js +6 -1
- package/lib/commands/appdistribution-testers-remove.js +20 -13
- package/lib/commands/experiments-describe.js +1 -1
- package/lib/commands/ext-install.js +10 -4
- package/lib/commands/index.js +5 -0
- package/lib/commands/init.js +8 -0
- package/lib/commands/internaltesting-frameworks-init.js +14 -0
- package/lib/config.js +1 -0
- package/lib/deploy/extensions/prepare.js +1 -0
- package/lib/deploy/extensions/release.js +11 -1
- package/lib/deploy/functions/checkIam.js +4 -1
- package/lib/deploy/functions/prepare.js +2 -1
- package/lib/deploy/functions/runtimes/discovery/index.js +6 -0
- package/lib/deploy/functions/runtimes/node/index.js +12 -4
- package/lib/deploy/functions/runtimes/python/index.js +19 -25
- package/lib/deploy/hosting/deploy.js +0 -6
- package/lib/deploy/hosting/prepare.js +7 -1
- package/lib/deploy/index.js +11 -4
- package/lib/deploy/lifecycleHooks.js +3 -0
- package/lib/deploy/storage/prepare.js +1 -1
- package/lib/detectProjectRoot.js +4 -1
- package/lib/dynamicImport.js +11 -1
- package/lib/emulator/commandUtils.js +4 -4
- package/lib/emulator/controller.js +9 -7
- package/lib/emulator/downloadableEmulators.js +3 -3
- package/lib/emulator/functionsEmulator.js +1 -2
- package/lib/emulator/storage/index.js +6 -0
- package/lib/emulator/storage/rules/manager.js +0 -4
- package/lib/emulator/storage/server.js +52 -0
- package/lib/ensureApiEnabled.js +3 -1
- package/lib/experiments.js +5 -0
- package/lib/extensions/paramHelper.js +0 -5
- package/lib/frameworks/compose/discover/filesystem.js +52 -0
- package/lib/frameworks/compose/discover/frameworkMatcher.js +76 -0
- package/lib/frameworks/compose/discover/frameworkSpec.js +39 -0
- package/lib/frameworks/compose/discover/types.js +2 -0
- package/lib/frameworks/constants.js +2 -15
- package/lib/frameworks/index.js +13 -8
- package/lib/frameworks/utils.js +50 -20
- package/lib/functionsConfig.js +2 -2
- package/lib/gcp/cloudbuild.js +50 -0
- package/lib/gcp/storage.js +6 -5
- package/lib/init/features/composer/repo.js +121 -0
- package/lib/init/features/frameworks/constants.js +7 -0
- package/lib/init/features/frameworks/index.js +36 -0
- package/lib/init/features/index.js +3 -1
- package/lib/init/index.js +4 -0
- package/lib/management/projects.js +5 -1
- package/lib/monospace/index.js +82 -0
- package/lib/monospace/interfaces.js +2 -0
- package/lib/requireAuth.js +8 -0
- package/lib/track.js +91 -52
- package/lib/utils.js +6 -1
- package/package.json +1 -1
- package/schema/extension-yaml.json +432 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createBuild = exports.createStack = exports.API_VERSION = void 0;
|
|
4
|
+
const apiv2_1 = require("../apiv2");
|
|
5
|
+
const api_1 = require("../api");
|
|
6
|
+
exports.API_VERSION = "v1";
|
|
7
|
+
const client = new apiv2_1.Client({
|
|
8
|
+
urlPrefix: api_1.frameworksOrigin,
|
|
9
|
+
auth: true,
|
|
10
|
+
apiVersion: exports.API_VERSION,
|
|
11
|
+
});
|
|
12
|
+
async function createStack(projectId, location, stackId, stack) {
|
|
13
|
+
const res = await client.post(`projects/${projectId}/locations/${location}/stacks`, stack, { queryParams: { stackId } });
|
|
14
|
+
return res.body;
|
|
15
|
+
}
|
|
16
|
+
exports.createStack = createStack;
|
|
17
|
+
async function createBuild(projectId, location, stackId, buildId, build) {
|
|
18
|
+
const res = await client.post(`projects/${projectId}/locations/${location}/stacks/${stackId}/builds`, build, { queryParams: { buildId } });
|
|
19
|
+
return res.body;
|
|
20
|
+
}
|
|
21
|
+
exports.createBuild = createBuild;
|
package/lib/api.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.setScopes = exports.getScopes = exports.githubClientSecret = exports.githubClientId = void 0;
|
|
3
|
+
exports.githubApiOrigin = exports.githubOrigin = exports.frameworksOrigin = exports.serviceUsageOrigin = exports.cloudRunApiOrigin = exports.hostingApiOrigin = exports.firebaseStorageOrigin = exports.storageOrigin = exports.runtimeconfigOrigin = exports.rulesOrigin = exports.resourceManagerOrigin = exports.remoteConfigApiOrigin = exports.rtdbMetadataOrigin = exports.rtdbManagementOrigin = exports.realtimeOrigin = exports.extensionsTOSOrigin = exports.extensionsPublisherOrigin = exports.extensionsOrigin = exports.iamOrigin = exports.identityOrigin = exports.hostingOrigin = exports.googleOrigin = exports.pubsubOrigin = exports.cloudTasksOrigin = exports.cloudschedulerOrigin = exports.cloudbuildOrigin = exports.functionsDefaultRegion = exports.runOrigin = exports.functionsV2Origin = exports.functionsOrigin = exports.firestoreOrigin = exports.firestoreOriginOrEmulator = exports.firedataOrigin = exports.firebaseExtensionsRegistryOrigin = exports.firebaseApiOrigin = exports.eventarcOrigin = exports.dynamicLinksKey = exports.dynamicLinksOrigin = exports.deployOrigin = exports.consoleOrigin = exports.authOrigin = exports.appDistributionOrigin = exports.artifactRegistryDomain = exports.containerRegistryDomain = exports.cloudMonitoringOrigin = exports.cloudloggingOrigin = exports.cloudbillingOrigin = exports.clientSecret = exports.clientId = exports.authProxyOrigin = void 0;
|
|
4
|
+
exports.setScopes = exports.getScopes = exports.githubClientSecret = exports.githubClientId = exports.secretManagerOrigin = void 0;
|
|
5
5
|
const constants_1 = require("./emulator/constants");
|
|
6
6
|
const logger_1 = require("./logger");
|
|
7
7
|
const scopes = require("./scopes");
|
|
@@ -16,7 +16,6 @@ exports.cloudMonitoringOrigin = utils.envOverride("CLOUD_MONITORING_URL", "https
|
|
|
16
16
|
exports.containerRegistryDomain = utils.envOverride("CONTAINER_REGISTRY_DOMAIN", "gcr.io");
|
|
17
17
|
exports.artifactRegistryDomain = utils.envOverride("ARTIFACT_REGISTRY_DOMAIN", "https://artifactregistry.googleapis.com");
|
|
18
18
|
exports.appDistributionOrigin = utils.envOverride("FIREBASE_APP_DISTRIBUTION_URL", "https://firebaseappdistribution.googleapis.com");
|
|
19
|
-
exports.appengineOrigin = utils.envOverride("FIREBASE_APPENGINE_URL", "https://appengine.googleapis.com");
|
|
20
19
|
exports.authOrigin = utils.envOverride("FIREBASE_AUTH_URL", "https://accounts.google.com");
|
|
21
20
|
exports.consoleOrigin = utils.envOverride("FIREBASE_CONSOLE_URL", "https://console.firebase.google.com");
|
|
22
21
|
exports.deployOrigin = utils.envOverride("FIREBASE_DEPLOY_URL", utils.envOverride("FIREBASE_UPLOAD_URL", "https://deploy.firebase.com"));
|
|
@@ -37,6 +36,7 @@ exports.functionsOrigin = utils.envOverride("FIREBASE_FUNCTIONS_URL", "https://c
|
|
|
37
36
|
exports.functionsV2Origin = utils.envOverride("FIREBASE_FUNCTIONS_V2_URL", "https://cloudfunctions.googleapis.com");
|
|
38
37
|
exports.runOrigin = utils.envOverride("CLOUD_RUN_URL", "https://run.googleapis.com");
|
|
39
38
|
exports.functionsDefaultRegion = utils.envOverride("FIREBASE_FUNCTIONS_DEFAULT_REGION", "us-central1");
|
|
39
|
+
exports.cloudbuildOrigin = utils.envOverride("FIREBASE_CLOUDBUILD_URL", "https://cloudbuild.googleapis.com");
|
|
40
40
|
exports.cloudschedulerOrigin = utils.envOverride("FIREBASE_CLOUDSCHEDULER_URL", "https://cloudscheduler.googleapis.com");
|
|
41
41
|
exports.cloudTasksOrigin = utils.envOverride("FIREBASE_CLOUD_TAKS_URL", "https://cloudtasks.googleapis.com");
|
|
42
42
|
exports.pubsubOrigin = utils.envOverride("FIREBASE_PUBSUB_URL", "https://pubsub.googleapis.com");
|
|
@@ -59,6 +59,7 @@ exports.firebaseStorageOrigin = utils.envOverride("FIREBASE_FIREBASESTORAGE_URL"
|
|
|
59
59
|
exports.hostingApiOrigin = utils.envOverride("FIREBASE_HOSTING_API_URL", "https://firebasehosting.googleapis.com");
|
|
60
60
|
exports.cloudRunApiOrigin = utils.envOverride("CLOUD_RUN_API_URL", "https://run.googleapis.com");
|
|
61
61
|
exports.serviceUsageOrigin = utils.envOverride("FIREBASE_SERVICE_USAGE_URL", "https://serviceusage.googleapis.com");
|
|
62
|
+
exports.frameworksOrigin = utils.envOverride("FRAMEWORKS_URL", "https://placeholder.googleapis.com");
|
|
62
63
|
exports.githubOrigin = utils.envOverride("GITHUB_URL", "https://github.com");
|
|
63
64
|
exports.githubApiOrigin = utils.envOverride("GITHUB_API_URL", "https://api.github.com");
|
|
64
65
|
exports.secretManagerOrigin = utils.envOverride("CLOUD_SECRET_MANAGER_URL", "https://secretmanager.googleapis.com");
|
|
@@ -137,5 +137,57 @@ class AppDistributionClient {
|
|
|
137
137
|
}
|
|
138
138
|
return apiResponse.body;
|
|
139
139
|
}
|
|
140
|
+
async createGroup(projectName, displayName, alias) {
|
|
141
|
+
let apiResponse;
|
|
142
|
+
try {
|
|
143
|
+
apiResponse = await this.appDistroV2Client.request({
|
|
144
|
+
method: "POST",
|
|
145
|
+
path: alias === undefined ? `${projectName}/groups` : `${projectName}/groups?groupId=${alias}`,
|
|
146
|
+
body: { displayName: displayName },
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
throw new error_1.FirebaseError(`Failed to create group ${err}`);
|
|
151
|
+
}
|
|
152
|
+
return apiResponse.body;
|
|
153
|
+
}
|
|
154
|
+
async deleteGroup(groupName) {
|
|
155
|
+
try {
|
|
156
|
+
await this.appDistroV2Client.request({
|
|
157
|
+
method: "DELETE",
|
|
158
|
+
path: groupName,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
throw new error_1.FirebaseError(`Failed to delete group ${err}`);
|
|
163
|
+
}
|
|
164
|
+
utils.logSuccess(`Group deleted successfully`);
|
|
165
|
+
}
|
|
166
|
+
async addTestersToGroup(groupName, emails) {
|
|
167
|
+
try {
|
|
168
|
+
await this.appDistroV2Client.request({
|
|
169
|
+
method: "POST",
|
|
170
|
+
path: `${groupName}:batchJoin`,
|
|
171
|
+
body: { emails: emails },
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
throw new error_1.FirebaseError(`Failed to add testers to group ${err}`);
|
|
176
|
+
}
|
|
177
|
+
utils.logSuccess(`Testers added to group successfully`);
|
|
178
|
+
}
|
|
179
|
+
async removeTestersFromGroup(groupName, emails) {
|
|
180
|
+
try {
|
|
181
|
+
await this.appDistroV2Client.request({
|
|
182
|
+
method: "POST",
|
|
183
|
+
path: `${groupName}:batchLeave`,
|
|
184
|
+
body: { emails: emails },
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
throw new error_1.FirebaseError(`Failed to remove testers from group ${err}`);
|
|
189
|
+
}
|
|
190
|
+
utils.logSuccess(`Testers removed from group successfully`);
|
|
191
|
+
}
|
|
140
192
|
}
|
|
141
193
|
exports.AppDistributionClient = AppDistributionClient;
|
package/lib/auth.js
CHANGED
|
@@ -307,7 +307,7 @@ async function loginRemotely() {
|
|
|
307
307
|
});
|
|
308
308
|
try {
|
|
309
309
|
const tokens = await getTokensFromAuthorizationCode(code, `${api_1.authProxyOrigin}/complete`, codeVerifier);
|
|
310
|
-
void (0, track_1.
|
|
310
|
+
void (0, track_1.trackGA4)("login", { method: "google_remote" });
|
|
311
311
|
return {
|
|
312
312
|
user: jwt.decode(tokens.id_token),
|
|
313
313
|
tokens: tokens,
|
|
@@ -323,7 +323,7 @@ async function loginWithLocalhostGoogle(port, userHint) {
|
|
|
323
323
|
const authUrl = getLoginUrl(callbackUrl, userHint);
|
|
324
324
|
const successTemplate = "../templates/loginSuccess.html";
|
|
325
325
|
const tokens = await loginWithLocalhost(port, callbackUrl, authUrl, successTemplate, getTokensFromAuthorizationCode);
|
|
326
|
-
void (0, track_1.
|
|
326
|
+
void (0, track_1.trackGA4)("login", { method: "google_localhost" });
|
|
327
327
|
return {
|
|
328
328
|
user: jwt.decode(tokens.id_token),
|
|
329
329
|
tokens: tokens,
|
|
@@ -335,7 +335,7 @@ async function loginWithLocalhostGitHub(port) {
|
|
|
335
335
|
const authUrl = getGithubLoginUrl(callbackUrl);
|
|
336
336
|
const successTemplate = "../templates/loginSuccessGithub.html";
|
|
337
337
|
const tokens = await loginWithLocalhost(port, callbackUrl, authUrl, successTemplate, getGithubTokensFromAuthorizationCode);
|
|
338
|
-
void (0, track_1.
|
|
338
|
+
void (0, track_1.trackGA4)("login", { method: "github_localhost" });
|
|
339
339
|
return tokens;
|
|
340
340
|
}
|
|
341
341
|
async function loginWithLocalhost(port, callbackUrl, authUrl, successTemplate, getTokens) {
|
package/lib/command.js
CHANGED
|
@@ -89,7 +89,12 @@ class Command {
|
|
|
89
89
|
}, null, 2));
|
|
90
90
|
}
|
|
91
91
|
const duration = Math.floor((process.uptime() - start) * 1000);
|
|
92
|
-
const trackSuccess = (0, track_1.
|
|
92
|
+
const trackSuccess = (0, track_1.trackGA4)("command_execution", {
|
|
93
|
+
command_name: this.name,
|
|
94
|
+
result: "success",
|
|
95
|
+
duration,
|
|
96
|
+
interactive: (0, utils_1.getInheritedOption)(options, "nonInteractive") ? "false" : "true",
|
|
97
|
+
});
|
|
93
98
|
if (!isEmulator) {
|
|
94
99
|
await (0, utils_1.withTimeout)(5000, trackSuccess);
|
|
95
100
|
}
|
|
@@ -113,8 +118,11 @@ class Command {
|
|
|
113
118
|
}
|
|
114
119
|
const duration = Math.floor((process.uptime() - start) * 1000);
|
|
115
120
|
await (0, utils_1.withTimeout)(5000, Promise.all([
|
|
116
|
-
(0, track_1.
|
|
117
|
-
|
|
121
|
+
(0, track_1.trackGA4)("command_execution", {
|
|
122
|
+
command_name: this.name,
|
|
123
|
+
result: "error",
|
|
124
|
+
interactive: (0, utils_1.getInheritedOption)(options, "nonInteractive") ? "false" : "true",
|
|
125
|
+
}, duration),
|
|
118
126
|
isEmulator
|
|
119
127
|
? (0, track_1.trackEmulator)("command_error", {
|
|
120
128
|
command_name: this.name,
|
|
@@ -223,7 +231,10 @@ function validateProjectId(project) {
|
|
|
223
231
|
if (PROJECT_ID_REGEX.test(project)) {
|
|
224
232
|
return;
|
|
225
233
|
}
|
|
226
|
-
(0, track_1.
|
|
234
|
+
(0, track_1.trackGA4)("error", {
|
|
235
|
+
error_type: "Error (User)",
|
|
236
|
+
details: "Invalid project ID",
|
|
237
|
+
});
|
|
227
238
|
const invalidMessage = "Invalid project id: " + clc.bold(project) + ".";
|
|
228
239
|
if (project.toLowerCase() !== project) {
|
|
229
240
|
throw new error_1.FirebaseError(invalidMessage + "\nNote: Project id must be all lowercase.");
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.command = void 0;
|
|
4
|
+
const command_1 = require("../command");
|
|
5
|
+
const utils = require("../utils");
|
|
6
|
+
const requireAuth_1 = require("../requireAuth");
|
|
7
|
+
const client_1 = require("../appdistribution/client");
|
|
8
|
+
const options_parser_util_1 = require("../appdistribution/options-parser-util");
|
|
9
|
+
exports.command = new command_1.Command("appdistribution:group:create <displayName> [alias]")
|
|
10
|
+
.description("create group in project")
|
|
11
|
+
.before(requireAuth_1.requireAuth)
|
|
12
|
+
.action(async (displayName, alias, options) => {
|
|
13
|
+
const projectName = await (0, options_parser_util_1.getProjectName)(options);
|
|
14
|
+
const appDistroClient = new client_1.AppDistributionClient();
|
|
15
|
+
utils.logBullet(`Creating group in project`);
|
|
16
|
+
const group = await appDistroClient.createGroup(projectName, displayName, alias);
|
|
17
|
+
alias = group.name.split("/").pop();
|
|
18
|
+
utils.logSuccess(`Group '${group.displayName}' (alias: ${alias}) created successfully`);
|
|
19
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.command = void 0;
|
|
4
|
+
const command_1 = require("../command");
|
|
5
|
+
const utils = require("../utils");
|
|
6
|
+
const requireAuth_1 = require("../requireAuth");
|
|
7
|
+
const error_1 = require("../error");
|
|
8
|
+
const client_1 = require("../appdistribution/client");
|
|
9
|
+
const options_parser_util_1 = require("../appdistribution/options-parser-util");
|
|
10
|
+
exports.command = new command_1.Command("appdistribution:group:delete <alias>")
|
|
11
|
+
.description("delete group from a project")
|
|
12
|
+
.before(requireAuth_1.requireAuth)
|
|
13
|
+
.action(async (alias, options) => {
|
|
14
|
+
const projectName = await (0, options_parser_util_1.getProjectName)(options);
|
|
15
|
+
const appDistroClient = new client_1.AppDistributionClient();
|
|
16
|
+
try {
|
|
17
|
+
utils.logBullet(`Deleting group from project`);
|
|
18
|
+
await appDistroClient.deleteGroup(`${projectName}/groups/${alias}`);
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
throw new error_1.FirebaseError(`Failed to delete group ${err}`);
|
|
22
|
+
}
|
|
23
|
+
utils.logSuccess(`Group ${alias} has successfully been deleted`);
|
|
24
|
+
});
|
|
@@ -7,8 +7,9 @@ const requireAuth_1 = require("../requireAuth");
|
|
|
7
7
|
const client_1 = require("../appdistribution/client");
|
|
8
8
|
const options_parser_util_1 = require("../appdistribution/options-parser-util");
|
|
9
9
|
exports.command = new command_1.Command("appdistribution:testers:add [emails...]")
|
|
10
|
-
.description("add testers to project")
|
|
10
|
+
.description("add testers to project (and possibly group)")
|
|
11
11
|
.option("--file <file>", "a path to a file containing a list of tester emails to be added")
|
|
12
|
+
.option("--group-alias <group-alias>", "if specified, the testers are also added to the group identified by this alias")
|
|
12
13
|
.before(requireAuth_1.requireAuth)
|
|
13
14
|
.action(async (emails, options) => {
|
|
14
15
|
const projectName = await (0, options_parser_util_1.getProjectName)(options);
|
|
@@ -16,4 +17,8 @@ exports.command = new command_1.Command("appdistribution:testers:add [emails...]
|
|
|
16
17
|
const emailsToAdd = (0, options_parser_util_1.getEmails)(emails, options.file);
|
|
17
18
|
utils.logBullet(`Adding ${emailsToAdd.length} testers to project`);
|
|
18
19
|
await appDistroClient.addTesters(projectName, emailsToAdd);
|
|
20
|
+
if (options.groupAlias) {
|
|
21
|
+
utils.logBullet(`Adding ${emailsToAdd.length} testers to group`);
|
|
22
|
+
await appDistroClient.addTestersToGroup(`${projectName}/groups/${options.groupAlias}`, emailsToAdd);
|
|
23
|
+
}
|
|
19
24
|
});
|
|
@@ -9,25 +9,32 @@ const client_1 = require("../appdistribution/client");
|
|
|
9
9
|
const options_parser_util_1 = require("../appdistribution/options-parser-util");
|
|
10
10
|
const logger_1 = require("../logger");
|
|
11
11
|
exports.command = new command_1.Command("appdistribution:testers:remove [emails...]")
|
|
12
|
-
.description("remove testers from a project")
|
|
12
|
+
.description("remove testers from a project (or group)")
|
|
13
13
|
.option("--file <file>", "a path to a file containing a list of tester emails to be removed")
|
|
14
|
+
.option("--group-alias <group-alias>", "if specified, the testers are only removed from the group identified by this alias, but not the project")
|
|
14
15
|
.before(requireAuth_1.requireAuth)
|
|
15
16
|
.action(async (emails, options) => {
|
|
16
17
|
const projectName = await (0, options_parser_util_1.getProjectName)(options);
|
|
17
18
|
const appDistroClient = new client_1.AppDistributionClient();
|
|
18
19
|
const emailsArr = (0, options_parser_util_1.getEmails)(emails, options.file);
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
deleteResponse = await appDistroClient.removeTesters(projectName, emailsArr);
|
|
20
|
+
if (options.groupAlias) {
|
|
21
|
+
utils.logBullet(`Removing ${emailsArr.length} testers from group`);
|
|
22
|
+
await appDistroClient.removeTestersFromGroup(`${projectName}/groups/${options.groupAlias}`, emailsArr);
|
|
23
23
|
}
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
else {
|
|
25
|
+
let deleteResponse;
|
|
26
|
+
try {
|
|
27
|
+
utils.logBullet(`Deleting ${emailsArr.length} testers from project`);
|
|
28
|
+
deleteResponse = await appDistroClient.removeTesters(projectName, emailsArr);
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
throw new error_1.FirebaseError(`Failed to remove testers ${err}`);
|
|
32
|
+
}
|
|
33
|
+
if (!deleteResponse.emails) {
|
|
34
|
+
utils.logSuccess(`Testers did not exist`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
logger_1.logger.debug(`Testers: ${deleteResponse.emails}, have been successfully deleted`);
|
|
38
|
+
utils.logSuccess(`${deleteResponse.emails.length} testers have successfully been deleted`);
|
|
26
39
|
}
|
|
27
|
-
if (!deleteResponse.emails) {
|
|
28
|
-
utils.logSuccess(`Testers did not exist`);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
logger_1.logger.debug(`Testers: ${deleteResponse.emails}, have been successfully deleted`);
|
|
32
|
-
utils.logSuccess(`${deleteResponse.emails.length} testers have successfully been deleted`);
|
|
33
40
|
});
|
|
@@ -8,7 +8,7 @@ const experiments = require("../experiments");
|
|
|
8
8
|
const logger_1 = require("../logger");
|
|
9
9
|
const utils_1 = require("../utils");
|
|
10
10
|
exports.command = new command_1.Command("experiments:describe <experiment>")
|
|
11
|
-
.description("
|
|
11
|
+
.description("describe what an experiment does when enabled")
|
|
12
12
|
.action((experiment) => {
|
|
13
13
|
if (!experiments.isValidExperiment(experiment)) {
|
|
14
14
|
let message = `Cannot find experiment ${(0, colorette_1.bold)(experiment)}`;
|
|
@@ -35,7 +35,7 @@ exports.command = new command_1.Command("ext:install [extensionName]")
|
|
|
35
35
|
.before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extMinVersion")
|
|
36
36
|
.before(extensionsHelper_1.diagnoseAndFixProject)
|
|
37
37
|
.action(async (extensionName, options) => {
|
|
38
|
-
var _a;
|
|
38
|
+
var _a, _b;
|
|
39
39
|
const projectId = (0, projectUtils_1.getProjectId)(options);
|
|
40
40
|
const paramsEnvPath = "";
|
|
41
41
|
let learnMore = false;
|
|
@@ -61,12 +61,18 @@ exports.command = new command_1.Command("ext:install [extensionName]")
|
|
|
61
61
|
if ((0, extensionsHelper_1.isLocalPath)(extensionName)) {
|
|
62
62
|
source = await (0, extensionsHelper_1.createSourceFromLocation)((0, projectUtils_1.needProjectId)({ projectId }), extensionName);
|
|
63
63
|
await (0, displayExtensionInfo_1.displayExtInfo)(extensionName, "", source.spec);
|
|
64
|
-
void (0, track_1.
|
|
64
|
+
void (0, track_1.trackGA4)("extension_added_to_manifest", {
|
|
65
|
+
published: "local",
|
|
66
|
+
interactive: options.nonInteractive ? "false" : "true",
|
|
67
|
+
});
|
|
65
68
|
}
|
|
66
69
|
else {
|
|
67
|
-
void (0, track_1.track)("Extension Install", "Install by Extension Ref", options.interactive ? 1 : 0);
|
|
68
70
|
extensionName = await (0, extensionsHelper_1.canonicalizeRefInput)(extensionName);
|
|
69
71
|
extensionVersion = await extensionsApi.getExtensionVersion(extensionName);
|
|
72
|
+
void (0, track_1.trackGA4)("extension_added_to_manifest", {
|
|
73
|
+
published: ((_a = extensionVersion.listing) === null || _a === void 0 ? void 0 : _a.state) === "APPROVED" ? "published" : "uploaded",
|
|
74
|
+
interactive: options.nonInteractive ? "false" : "true",
|
|
75
|
+
});
|
|
70
76
|
await infoExtensionVersion({
|
|
71
77
|
extensionName,
|
|
72
78
|
extensionVersion,
|
|
@@ -82,7 +88,7 @@ exports.command = new command_1.Command("ext:install [extensionName]")
|
|
|
82
88
|
if (!source && !extensionVersion) {
|
|
83
89
|
throw new error_1.FirebaseError("Could not find a source. Please specify a valid source to continue.");
|
|
84
90
|
}
|
|
85
|
-
const spec = (
|
|
91
|
+
const spec = (_b = source === null || source === void 0 ? void 0 : source.spec) !== null && _b !== void 0 ? _b : extensionVersion === null || extensionVersion === void 0 ? void 0 : extensionVersion.spec;
|
|
86
92
|
if (!spec) {
|
|
87
93
|
throw new error_1.FirebaseError(`Could not find the extension.yaml for extension '${clc.bold(extensionName)}'. Please make sure this is a valid extension and try again.`);
|
|
88
94
|
}
|
package/lib/commands/index.js
CHANGED
|
@@ -19,6 +19,9 @@ function load(client) {
|
|
|
19
19
|
client.appdistribution.testers = {};
|
|
20
20
|
client.appdistribution.testers.add = loadCommand("appdistribution-testers-add");
|
|
21
21
|
client.appdistribution.testers.delete = loadCommand("appdistribution-testers-remove");
|
|
22
|
+
client.appdistribution.group = {};
|
|
23
|
+
client.appdistribution.group.create = loadCommand("appdistribution-group-create");
|
|
24
|
+
client.appdistribution.group.delete = loadCommand("appdistribution-group-delete");
|
|
22
25
|
client.apps = {};
|
|
23
26
|
client.apps.create = loadCommand("apps-create");
|
|
24
27
|
client.apps.list = loadCommand("apps-list");
|
|
@@ -141,6 +144,8 @@ function load(client) {
|
|
|
141
144
|
client.internaltesting.frameworks.compose = loadCommand("internaltesting-frameworks-compose");
|
|
142
145
|
client.internaltesting.functions = {};
|
|
143
146
|
client.internaltesting.functions.discover = loadCommand("internaltesting-functions-discover");
|
|
147
|
+
client.internaltesting.frameworks = {};
|
|
148
|
+
client.internaltesting.frameworks.init = loadCommand("internaltesting-frameworks-init");
|
|
144
149
|
}
|
|
145
150
|
client.login = loadCommand("login");
|
|
146
151
|
client.login.add = loadCommand("login-add");
|
package/lib/commands/init.js
CHANGED
|
@@ -14,6 +14,7 @@ const prompt_1 = require("../prompt");
|
|
|
14
14
|
const requireAuth_1 = require("../requireAuth");
|
|
15
15
|
const fsutils = require("../fsutils");
|
|
16
16
|
const utils = require("../utils");
|
|
17
|
+
const experiments_1 = require("../experiments");
|
|
17
18
|
const homeDir = os.homedir();
|
|
18
19
|
const TEMPLATE_ROOT = path.resolve(__dirname, "../../templates/");
|
|
19
20
|
const BANNER_TEXT = fs.readFileSync(path.join(TEMPLATE_ROOT, "banner.txt"), "utf8");
|
|
@@ -68,6 +69,13 @@ const choices = [
|
|
|
68
69
|
checked: false,
|
|
69
70
|
},
|
|
70
71
|
];
|
|
72
|
+
if ((0, experiments_1.isEnabled)("frameworks")) {
|
|
73
|
+
choices.push({
|
|
74
|
+
value: "frameworks",
|
|
75
|
+
name: "Frameworks: Get started with Frameworks projects.",
|
|
76
|
+
checked: false,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
71
79
|
const featureNames = choices.map((choice) => choice.value);
|
|
72
80
|
const DESCRIPTION = `Interactively configure the current directory as a Firebase project or initialize new features in an already configured Firebase project directory.
|
|
73
81
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.command = void 0;
|
|
4
|
+
const command_1 = require("../command");
|
|
5
|
+
const repo_1 = require("../init/features/composer/repo");
|
|
6
|
+
const projectUtils_1 = require("../projectUtils");
|
|
7
|
+
const requireInteractive_1 = require("../requireInteractive");
|
|
8
|
+
exports.command = new command_1.Command("internaltesting:frameworks:init")
|
|
9
|
+
.description("connect github repo to cloud build")
|
|
10
|
+
.before(requireInteractive_1.default)
|
|
11
|
+
.action(async (options) => {
|
|
12
|
+
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
13
|
+
await (0, repo_1.linkGitHubRepository)(projectId, "us-central2", "stack0");
|
|
14
|
+
});
|
package/lib/config.js
CHANGED
|
@@ -18,6 +18,7 @@ const v2FunctionHelper_1 = require("./v2FunctionHelper");
|
|
|
18
18
|
const tos_1 = require("../../extensions/tos");
|
|
19
19
|
async function prepare(context, options, payload) {
|
|
20
20
|
var _a, _b;
|
|
21
|
+
context.extensionsStartTime = Date.now();
|
|
21
22
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
22
23
|
const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
|
|
23
24
|
const aliases = (0, projectUtils_1.getAliases)(options, projectId);
|
|
@@ -8,8 +8,9 @@ const error_1 = require("../../error");
|
|
|
8
8
|
const errors_1 = require("./errors");
|
|
9
9
|
const projectUtils_1 = require("../../projectUtils");
|
|
10
10
|
const etags_1 = require("../../extensions/etags");
|
|
11
|
+
const track_1 = require("../../track");
|
|
11
12
|
async function release(context, options, payload) {
|
|
12
|
-
var _a, _b, _c, _d;
|
|
13
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
13
14
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
14
15
|
const errorHandler = new errors_1.ErrorHandler();
|
|
15
16
|
const deploymentQueue = new queue_1.default({
|
|
@@ -37,6 +38,15 @@ async function release(context, options, payload) {
|
|
|
37
38
|
deploymentQueue.process();
|
|
38
39
|
deploymentQueue.close();
|
|
39
40
|
await deploymentPromise;
|
|
41
|
+
const duration = context.extensionsStartTime ? Date.now() - context.extensionsStartTime : 1;
|
|
42
|
+
await (0, track_1.trackGA4)("extensions_deploy", {
|
|
43
|
+
extension_instance_created: (_f = (_e = payload.instancesToCreate) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : 0,
|
|
44
|
+
extension_instance_updated: (_h = (_g = payload.instancesToUpdate) === null || _g === void 0 ? void 0 : _g.length) !== null && _h !== void 0 ? _h : 0,
|
|
45
|
+
extension_instance_configured: (_k = (_j = payload.instancesToConfigure) === null || _j === void 0 ? void 0 : _j.length) !== null && _k !== void 0 ? _k : 0,
|
|
46
|
+
extension_instance_deleted: (_m = (_l = payload.instancesToDelete) === null || _l === void 0 ? void 0 : _l.length) !== null && _m !== void 0 ? _m : 0,
|
|
47
|
+
errors: (_o = errorHandler.errors.length) !== null && _o !== void 0 ? _o : 0,
|
|
48
|
+
interactive: options.nonInteractive ? "false" : "true",
|
|
49
|
+
}, duration);
|
|
40
50
|
const newHave = await planner.have(projectId);
|
|
41
51
|
(0, etags_1.saveEtags)(options.rc, projectId, newHave);
|
|
42
52
|
if (errorHandler.hasErrors()) {
|
|
@@ -59,7 +59,10 @@ async function checkHttpIam(context, options, payload) {
|
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
61
|
if (!passed) {
|
|
62
|
-
void (0, track_1.
|
|
62
|
+
void (0, track_1.trackGA4)("error", {
|
|
63
|
+
error_type: "Error (User)",
|
|
64
|
+
details: "deploy:functions:http_create_missing_iam",
|
|
65
|
+
});
|
|
63
66
|
throw new error_1.FirebaseError(`Missing required permission on project ${(0, colorette_1.bold)(context.projectId)} to deploy new HTTPS functions. The permission ${(0, colorette_1.bold)(PERMISSION)} is required to deploy the following functions:\n\n- ` +
|
|
64
67
|
newHttpsEndpoints.map((func) => func.id).join("\n- ") +
|
|
65
68
|
`\n\nTo address this error, please ask a project Owner to assign your account the "Cloud Functions Admin" role at the following URL:\n\nhttps://console.cloud.google.com/iam-admin/iam?project=${context.projectId}`);
|
|
@@ -301,7 +301,8 @@ async function loadCodebases(config, options, firebaseConfig, runtimeConfig, fil
|
|
|
301
301
|
logger_1.logger.debug(`Building ${runtimeDelegate.name} source`);
|
|
302
302
|
await runtimeDelegate.build();
|
|
303
303
|
const firebaseEnvs = functionsEnv.loadFirebaseEnvs(firebaseConfig, projectId);
|
|
304
|
-
|
|
304
|
+
(0, utils_1.logLabeledBullet)("functions", `Loading and anaylzing source code for codebase ${codebase} to determine what to deploy`);
|
|
305
|
+
wantBuilds[codebase] = await runtimeDelegate.discoverBuild(runtimeConfig, Object.assign(Object.assign({}, firebaseEnvs), { GOOGLE_CLOUD_QUOTA_PROJECT: projectId }));
|
|
305
306
|
}
|
|
306
307
|
return wantBuilds;
|
|
307
308
|
}
|
|
@@ -64,6 +64,12 @@ async function detectFromPort(port, project, runtime, timeout = 10000) {
|
|
|
64
64
|
throw err;
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
+
if (res.status !== 200) {
|
|
68
|
+
const text = await res.text();
|
|
69
|
+
logger_1.logger.debug(`Got response code ${res.status}; body ${text}`);
|
|
70
|
+
throw new error_1.FirebaseError("Functions codebase could not be analyzed successfully. " +
|
|
71
|
+
"It may have a syntax or runtime error");
|
|
72
|
+
}
|
|
67
73
|
const text = await res.text();
|
|
68
74
|
logger_1.logger.debug("Got response from /__/functions.yaml", text);
|
|
69
75
|
let parsed;
|
|
@@ -92,7 +92,7 @@ class Delegate {
|
|
|
92
92
|
return Promise.resolve(() => Promise.resolve());
|
|
93
93
|
}
|
|
94
94
|
serveAdmin(port, config, envs) {
|
|
95
|
-
var _a;
|
|
95
|
+
var _a, _b;
|
|
96
96
|
const env = Object.assign(Object.assign({}, envs), { PORT: port, FUNCTIONS_CONTROL_API: "true", HOME: process.env.HOME, PATH: process.env.PATH, NODE_ENV: process.env.NODE_ENV, __FIREBASE_FRAMEWORKS_ENTRY__: process.env.__FIREBASE_FRAMEWORKS_ENTRY__ });
|
|
97
97
|
if (Object.keys(config || {}).length) {
|
|
98
98
|
env.CLOUD_RUNTIME_CONFIG = JSON.stringify(config);
|
|
@@ -114,17 +114,25 @@ class Delegate {
|
|
|
114
114
|
const childProcess = spawn(binPath, [this.sourceDir], {
|
|
115
115
|
env,
|
|
116
116
|
cwd: this.sourceDir,
|
|
117
|
-
stdio: ["ignore", "pipe", "
|
|
117
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
118
118
|
});
|
|
119
119
|
(_a = childProcess.stdout) === null || _a === void 0 ? void 0 : _a.on("data", (chunk) => {
|
|
120
|
-
logger_1.logger.
|
|
120
|
+
logger_1.logger.info(chunk.toString("utf8"));
|
|
121
|
+
});
|
|
122
|
+
(_b = childProcess.stderr) === null || _b === void 0 ? void 0 : _b.on("data", (chunk) => {
|
|
123
|
+
logger_1.logger.error(chunk.toString("utf8"));
|
|
121
124
|
});
|
|
122
125
|
return Promise.resolve(async () => {
|
|
123
126
|
const p = new Promise((resolve, reject) => {
|
|
124
127
|
childProcess.once("exit", resolve);
|
|
125
128
|
childProcess.once("error", reject);
|
|
126
129
|
});
|
|
127
|
-
|
|
130
|
+
try {
|
|
131
|
+
await (0, node_fetch_1.default)(`http://localhost:${port}/__/quitquitquit`);
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
logger_1.logger.debug("Failed to call quitquitquit. This often means the server failed to start", e);
|
|
135
|
+
}
|
|
128
136
|
setTimeout(() => {
|
|
129
137
|
if (!childProcess.killed) {
|
|
130
138
|
childProcess.kill("SIGKILL");
|
|
@@ -11,7 +11,6 @@ const discovery = require("../discovery");
|
|
|
11
11
|
const logger_1 = require("../../../../logger");
|
|
12
12
|
const python_1 = require("../../../../functions/python");
|
|
13
13
|
const error_1 = require("../../../../error");
|
|
14
|
-
const utils_1 = require("../../../../utils");
|
|
15
14
|
exports.LATEST_VERSION = "python311";
|
|
16
15
|
async function tryCreateDelegate(context) {
|
|
17
16
|
const requirementsTextPath = path.join(context.sourceDir, "requirements.txt");
|
|
@@ -108,32 +107,31 @@ class Delegate {
|
|
|
108
107
|
const modulesDir = await this.modulesDir();
|
|
109
108
|
const envWithAdminPort = Object.assign(Object.assign({}, envs), { ADMIN_PORT: port.toString() });
|
|
110
109
|
const args = [this.bin, `"${path.join(modulesDir, "private", "serving.py")}"`];
|
|
111
|
-
const stdout = [];
|
|
112
|
-
const stderr = [];
|
|
113
110
|
logger_1.logger.debug(`Running admin server with args: ${JSON.stringify(args)} and env: ${JSON.stringify(envWithAdminPort)} in ${this.sourceDir}`);
|
|
114
111
|
const childProcess = (0, python_1.runWithVirtualEnv)(args, this.sourceDir, envWithAdminPort);
|
|
115
112
|
(_a = childProcess.stdout) === null || _a === void 0 ? void 0 : _a.on("data", (chunk) => {
|
|
116
|
-
|
|
117
|
-
stdout.push(chunkString);
|
|
118
|
-
logger_1.logger.debug(`stdout: ${chunkString}`);
|
|
113
|
+
logger_1.logger.info(chunk.toString("utf8"));
|
|
119
114
|
});
|
|
120
115
|
(_b = childProcess.stderr) === null || _b === void 0 ? void 0 : _b.on("data", (chunk) => {
|
|
121
|
-
|
|
122
|
-
stderr.push(chunkString);
|
|
123
|
-
logger_1.logger.debug(`stderr: ${chunkString}`);
|
|
116
|
+
logger_1.logger.error(chunk.toString("utf8"));
|
|
124
117
|
});
|
|
125
|
-
return Promise.resolve({
|
|
126
|
-
|
|
127
|
-
stdout,
|
|
128
|
-
killProcess: async () => {
|
|
118
|
+
return Promise.resolve(async () => {
|
|
119
|
+
try {
|
|
129
120
|
await (0, node_fetch_1.default)(`http://127.0.0.1:${port}/__/quitquitquit`);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
121
|
+
}
|
|
122
|
+
catch (e) {
|
|
123
|
+
logger_1.logger.debug("Failed to call quitquitquit. This often means the server failed to start", e);
|
|
124
|
+
}
|
|
125
|
+
const quitTimeout = setTimeout(() => {
|
|
126
|
+
if (!childProcess.killed) {
|
|
127
|
+
childProcess.kill("SIGKILL");
|
|
128
|
+
}
|
|
129
|
+
}, 10000);
|
|
130
|
+
clearTimeout(quitTimeout);
|
|
131
|
+
return new Promise((resolve, reject) => {
|
|
132
|
+
childProcess.once("exit", resolve);
|
|
133
|
+
childProcess.once("error", reject);
|
|
134
|
+
});
|
|
137
135
|
});
|
|
138
136
|
}
|
|
139
137
|
async discoverBuild(_configValues, envs) {
|
|
@@ -142,14 +140,10 @@ class Delegate {
|
|
|
142
140
|
const adminPort = await portfinder.getPortPromise({
|
|
143
141
|
port: 8081,
|
|
144
142
|
});
|
|
145
|
-
const
|
|
143
|
+
const killProcess = await this.serveAdmin(adminPort, envs);
|
|
146
144
|
try {
|
|
147
145
|
discovered = await discovery.detectFromPort(adminPort, this.projectId, this.runtime);
|
|
148
146
|
}
|
|
149
|
-
catch (e) {
|
|
150
|
-
(0, utils_1.logLabeledWarning)("functions", `Failed to detect functions from source ${e}.\nstderr:${stderr.join("\n")}`);
|
|
151
|
-
throw e;
|
|
152
|
-
}
|
|
153
147
|
finally {
|
|
154
148
|
await killProcess();
|
|
155
149
|
}
|
|
@@ -5,7 +5,6 @@ const uploader_1 = require("./uploader");
|
|
|
5
5
|
const detectProjectRoot_1 = require("../../detectProjectRoot");
|
|
6
6
|
const listFiles_1 = require("../../listFiles");
|
|
7
7
|
const logger_1 = require("../../logger");
|
|
8
|
-
const track_1 = require("../../track");
|
|
9
8
|
const utils_1 = require("../../utils");
|
|
10
9
|
const colorette_1 = require("colorette");
|
|
11
10
|
const ora = require("ora");
|
|
@@ -67,10 +66,6 @@ async function deploy(context, options) {
|
|
|
67
66
|
try {
|
|
68
67
|
await uploader.start();
|
|
69
68
|
}
|
|
70
|
-
catch (err) {
|
|
71
|
-
void (0, track_1.track)("Hosting Deploy", "failure");
|
|
72
|
-
throw err;
|
|
73
|
-
}
|
|
74
69
|
finally {
|
|
75
70
|
clearInterval(progressInterval);
|
|
76
71
|
updateSpinner(uploader.statusMessage(), debugging);
|
|
@@ -81,7 +76,6 @@ async function deploy(context, options) {
|
|
|
81
76
|
(0, utils_1.logLabeledSuccess)(`hosting[${deploy.config.site}]`, "file upload complete");
|
|
82
77
|
const dt = Date.now() - t0;
|
|
83
78
|
logger_1.logger.debug(`[hosting] deploy completed after ${dt}ms`);
|
|
84
|
-
void (0, track_1.track)("Hosting Deploy", "success", dt);
|
|
85
79
|
return runDeploys(deploys, debugging);
|
|
86
80
|
}
|
|
87
81
|
const debugging = !!(options.debug || options.nonInteractive);
|