firebase-tools 13.34.0 → 13.35.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.
Files changed (35) hide show
  1. package/lib/apphosting/config.js +8 -6
  2. package/lib/apphosting/yaml.js +21 -48
  3. package/lib/commands/dataconnect-sdk-generate.js +4 -1
  4. package/lib/commands/dataconnect-sql-diff.js +1 -1
  5. package/lib/commands/dataconnect-sql-setup.js +6 -1
  6. package/lib/commands/functions-artifacts-setpolicy.js +123 -0
  7. package/lib/commands/index.js +2 -0
  8. package/lib/commands/open.js +3 -0
  9. package/lib/dataconnect/build.js +3 -1
  10. package/lib/dataconnect/fileUtils.js +8 -4
  11. package/lib/dataconnect/schemaMigration.js +27 -24
  12. package/lib/defaultCredentials.js +12 -1
  13. package/lib/deploy/dataconnect/prepare.js +1 -1
  14. package/lib/deploy/functions/containerCleaner.js +17 -2
  15. package/lib/deploy/functions/runtimes/discovery/index.js +3 -1
  16. package/lib/deploy/index.js +10 -4
  17. package/lib/emulator/ExpressBasedEmulator.js +1 -1
  18. package/lib/emulator/apphosting/index.js +1 -0
  19. package/lib/emulator/apphosting/serve.js +48 -7
  20. package/lib/emulator/controller.js +1 -0
  21. package/lib/emulator/dataconnect/pgliteServer.js +7 -2
  22. package/lib/emulator/dataconnectEmulator.js +15 -4
  23. package/lib/emulator/downloadableEmulators.js +9 -9
  24. package/lib/emulator/env.js +17 -1
  25. package/lib/emulator/functionsEmulator.js +2 -20
  26. package/lib/extensions/extensionsHelper.js +8 -4
  27. package/lib/frameworks/angular/utils.js +60 -42
  28. package/lib/functions/artifacts.js +105 -0
  29. package/lib/gcp/artifactregistry.js +27 -2
  30. package/lib/gcp/cloudsql/connect.js +17 -5
  31. package/lib/gcp/cloudsql/permissions_setup.js +28 -14
  32. package/lib/init/features/dataconnect/index.js +1 -1
  33. package/lib/init/features/dataconnect/sdk.js +23 -3
  34. package/package.json +1 -1
  35. package/templates/init/dataconnect/connector.yaml +1 -1
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getEmulatorEnvs = exports.start = void 0;
4
4
  const net_1 = require("net");
5
+ const clc = require("colorette");
5
6
  const portUtils_1 = require("../portUtils");
6
7
  const developmentServer_1 = require("./developmentServer");
7
8
  const constants_1 = require("../constants");
@@ -12,6 +13,9 @@ const config_1 = require("./config");
12
13
  const projectPath_1 = require("../../projectPath");
13
14
  const registry_1 = require("../registry");
14
15
  const env_1 = require("../env");
16
+ const error_1 = require("../../error");
17
+ const secrets = require("../../gcp/secretManager");
18
+ const utils_1 = require("../../utils");
15
19
  async function start(options) {
16
20
  var _a;
17
21
  const hostname = constants_1.DEFAULT_HOST;
@@ -19,19 +23,56 @@ async function start(options) {
19
23
  while (!(await availablePort(hostname, port))) {
20
24
  port += 1;
21
25
  }
22
- serve(port, options === null || options === void 0 ? void 0 : options.startCommand, options === null || options === void 0 ? void 0 : options.rootDirectory);
26
+ await serve(options === null || options === void 0 ? void 0 : options.projectId, port, options === null || options === void 0 ? void 0 : options.startCommand, options === null || options === void 0 ? void 0 : options.rootDirectory);
23
27
  return { hostname, port };
24
28
  }
25
29
  exports.start = start;
26
- async function serve(port, startCommand, backendRelativeDir) {
30
+ const secretResourceRegex = /^projects\/([^/]+)\/secrets\/([^/]+)(?:\/versions\/((?:latest)|\d+))?$/;
31
+ const secretShorthandRegex = /^([^/@]+)(?:@((?:latest)|\d+))?$/;
32
+ async function loadSecret(project, name) {
33
+ var _a, _b, _c, _d;
34
+ let projectId;
35
+ let secretId;
36
+ let version;
37
+ const match = secretResourceRegex.exec(name);
38
+ if (match) {
39
+ projectId = match[1];
40
+ secretId = match[2];
41
+ version = match[3] || "latest";
42
+ }
43
+ else {
44
+ const match = secretShorthandRegex.exec(name);
45
+ if (!match) {
46
+ throw new error_1.FirebaseError(`Invalid secret name: ${name}`);
47
+ }
48
+ if (!project) {
49
+ throw new error_1.FirebaseError(`Cannot load secret ${match[1]} without a project. ` +
50
+ `Please use ${clc.bold("firebase use")} or pass the --project flag.`);
51
+ }
52
+ projectId = project;
53
+ secretId = match[1];
54
+ version = match[2] || "latest";
55
+ }
56
+ try {
57
+ return await secrets.accessSecretVersion(projectId, secretId, version);
58
+ }
59
+ catch (err) {
60
+ if (((_a = err === null || err === void 0 ? void 0 : err.original) === null || _a === void 0 ? void 0 : _a.code) === 403 || ((_d = (_c = (_b = err === null || err === void 0 ? void 0 : err.original) === null || _b === void 0 ? void 0 : _b.context) === null || _c === void 0 ? void 0 : _c.response) === null || _d === void 0 ? void 0 : _d.statusCode) === 403) {
61
+ (0, utils_1.logLabeledError)(types_1.Emulators.APPHOSTING, `Permission denied to access secret ${secretId}. Use ` +
62
+ `${clc.bold("firebase apphosting:secrets:grantaccess")} to get permissions.`);
63
+ }
64
+ throw err;
65
+ }
66
+ }
67
+ async function serve(projectId, port, startCommand, backendRelativeDir) {
27
68
  backendRelativeDir = backendRelativeDir !== null && backendRelativeDir !== void 0 ? backendRelativeDir : "./";
28
69
  const backendRoot = (0, projectPath_1.resolveProjectPath)({}, backendRelativeDir);
29
70
  const apphostingLocalConfig = await (0, config_1.getLocalAppHostingConfiguration)(backendRoot);
30
- const environmentVariablesAsRecord = {};
31
- for (const env of apphostingLocalConfig.environmentVariables) {
32
- environmentVariablesAsRecord[env.variable] = env.value;
33
- }
34
- const environmentVariablesToInject = Object.assign(Object.assign(Object.assign({}, getEmulatorEnvs()), environmentVariablesAsRecord), { PORT: port.toString() });
71
+ const resolveEnv = Object.entries(apphostingLocalConfig.env).map(async ([key, value]) => [
72
+ key,
73
+ value.value ? value.value : await loadSecret(projectId, value.secret),
74
+ ]);
75
+ const environmentVariablesToInject = Object.assign(Object.assign(Object.assign({}, getEmulatorEnvs()), Object.fromEntries(await Promise.all(resolveEnv))), { PORT: port.toString() });
35
76
  if (startCommand) {
36
77
  developmentServer_2.logger.logLabeled("BULLET", types_1.Emulators.APPHOSTING, `running custom start command: '${startCommand}'`);
37
78
  await (0, spawn_1.spawnWithCommandString)(startCommand, backendRoot, environmentVariablesToInject);
@@ -635,6 +635,7 @@ async function startAll(options, showUI = true, runningTestScript = false) {
635
635
  apphostingLogger.logLabeled("WARN", types_1.Emulators.APPHOSTING, "The `firebase.json#emulators.apphosting.startCommandOverride` config is deprecated, please use `firebase.json#emulators.apphosting.startCommand` to set a custom start command instead");
636
636
  }
637
637
  const apphostingEmulator = new apphosting_1.AppHostingEmulator({
638
+ projectId: options.project,
638
639
  host: apphostingAddr.host,
639
640
  port: apphostingAddr.port,
640
641
  startCommand: (apphostingConfig === null || apphostingConfig === void 0 ? void 0 : apphostingConfig.startCommand) || (apphostingConfig === null || apphostingConfig === void 0 ? void 0 : apphostingConfig.startCommandOverride),
@@ -149,6 +149,7 @@ class PGliteExtendedQueryPatch {
149
149
  constructor(connection) {
150
150
  this.connection = connection;
151
151
  this.isExtendedQuery = false;
152
+ this.eqpErrored = false;
152
153
  }
153
154
  filterResponse(message, response) {
154
155
  return __asyncGenerator(this, arguments, function* filterResponse_1() {
@@ -166,6 +167,7 @@ class PGliteExtendedQueryPatch {
166
167
  }
167
168
  if (message[0] === index_1.FrontendMessageCode.Sync) {
168
169
  this.isExtendedQuery = false;
170
+ this.eqpErrored = false;
169
171
  return yield __await(this.connection.createReadyForQuery());
170
172
  }
171
173
  try {
@@ -174,8 +176,11 @@ class PGliteExtendedQueryPatch {
174
176
  _d = false;
175
177
  try {
176
178
  const message = _c;
177
- if (message[0] === index_1.BackendMessageCode.ErrorMessage) {
178
- this.isExtendedQuery = false;
179
+ if (this.eqpErrored) {
180
+ continue;
181
+ }
182
+ if (this.isExtendedQuery && message[0] === index_1.BackendMessageCode.ErrorMessage) {
183
+ this.eqpErrored = true;
179
184
  }
180
185
  if (this.isExtendedQuery && message[0] === index_1.BackendMessageCode.ReadyForQuery) {
181
186
  logger_1.logger.debug("Filtered out a ReadyForQuery.");
@@ -20,6 +20,7 @@ const load_1 = require("../dataconnect/load");
20
20
  const pgliteServer_1 = require("./dataconnect/pgliteServer");
21
21
  const controller_1 = require("./controller");
22
22
  const utils_1 = require("../utils");
23
+ const env_1 = require("./env");
23
24
  exports.dataConnectEmulatorEvents = new events_1.EventEmitter();
24
25
  class DataConnectEmulator {
25
26
  constructor(args) {
@@ -33,7 +34,10 @@ class DataConnectEmulator {
33
34
  let resolvedConfigDir;
34
35
  try {
35
36
  resolvedConfigDir = this.args.config.path(this.args.configDir);
36
- const info = await DataConnectEmulator.build({ configDir: resolvedConfigDir });
37
+ const info = await DataConnectEmulator.build({
38
+ configDir: resolvedConfigDir,
39
+ account: this.args.account,
40
+ });
37
41
  if ((0, types_2.requiresVector)(info.metadata)) {
38
42
  if (constants_1.Constants.isDemoProject(this.args.projectId)) {
39
43
  this.logger.logLabeled("WARN", "dataconnect", "Detected a 'demo-' project, but vector embeddings require a real project. Operations that use vector_embed will fail.");
@@ -46,13 +50,14 @@ class DataConnectEmulator {
46
50
  catch (err) {
47
51
  this.logger.log("DEBUG", `'fdc build' failed with error: ${err.message}`);
48
52
  }
53
+ const env = await DataConnectEmulator.getEnv(this.args.account, this.args.extraEnv);
49
54
  await (0, downloadableEmulators_1.start)(types_1.Emulators.DATACONNECT, {
50
55
  auto_download: this.args.auto_download,
51
56
  listen: (0, portUtils_1.listenSpecsToString)(this.args.listen),
52
57
  config_dir: resolvedConfigDir,
53
58
  enable_output_schema_extensions: this.args.enable_output_schema_extensions,
54
59
  enable_output_generated_sdk: this.args.enable_output_generated_sdk,
55
- }, this.args.extraEnv);
60
+ }, env);
56
61
  this.usingExistingEmulator = false;
57
62
  if (this.args.autoconnectToPostgres) {
58
63
  const info = await (0, load_1.load)(this.args.projectId, this.args.config, this.args.configDir);
@@ -159,7 +164,8 @@ class DataConnectEmulator {
159
164
  if (args.watch) {
160
165
  cmd.push("--watch");
161
166
  }
162
- const res = childProcess.spawnSync(commandInfo.binary, cmd, { encoding: "utf-8" });
167
+ const env = await DataConnectEmulator.getEnv(args.account);
168
+ const res = childProcess.spawnSync(commandInfo.binary, cmd, { encoding: "utf-8", env });
163
169
  if ((0, downloadableEmulators_1.isIncomaptibleArchError)(res.error)) {
164
170
  throw new error_1.FirebaseError(`Unknown system error when running the Data Connect toolkit. ` +
165
171
  `You may be able to fix this by installing Rosetta: ` +
@@ -183,7 +189,8 @@ class DataConnectEmulator {
183
189
  if (args.projectId) {
184
190
  cmd.push(`--project_id=${args.projectId}`);
185
191
  }
186
- const res = childProcess.spawnSync(commandInfo.binary, cmd, { encoding: "utf-8" });
192
+ const env = await DataConnectEmulator.getEnv(args.account);
193
+ const res = childProcess.spawnSync(commandInfo.binary, cmd, { encoding: "utf-8", env });
187
194
  if ((0, downloadableEmulators_1.isIncomaptibleArchError)(res.error)) {
188
195
  throw new error_1.FirebaseError(`Unkown system error when running the Data Connect toolkit. ` +
189
196
  `You may be able to fix this by installing Rosetta: ` +
@@ -236,6 +243,10 @@ class DataConnectEmulator {
236
243
  }
237
244
  return false;
238
245
  }
246
+ static async getEnv(account, extraEnv = {}) {
247
+ const credsEnv = await (0, env_1.getCredentialsEnvironment)(account, emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.DATACONNECT), "dataconnect");
248
+ return Object.assign(Object.assign(Object.assign({}, process.env), extraEnv), credsEnv);
249
+ }
239
250
  }
240
251
  exports.DataConnectEmulator = DataConnectEmulator;
241
252
  class DataConnectEmulatorClient {
@@ -48,20 +48,20 @@ const EMULATOR_UPDATE_DETAILS = {
48
48
  },
49
49
  dataconnect: process.platform === "darwin"
50
50
  ? {
51
- version: "1.8.5",
52
- expectedSize: 25600768,
53
- expectedChecksum: "7e2a935f972ce30e075cca1f36e24663",
51
+ version: "1.9.2",
52
+ expectedSize: 26403584,
53
+ expectedChecksum: "a0a957bb5d564059ed883fee9e9fd67a",
54
54
  }
55
55
  : process.platform === "win32"
56
56
  ? {
57
- version: "1.8.5",
58
- expectedSize: 26031616,
59
- expectedChecksum: "da063f9893b0ff4c99f280653c717977",
57
+ version: "1.9.2",
58
+ expectedSize: 26846208,
59
+ expectedChecksum: "80f49b574aa69ef76fb49e2e96c8a699",
60
60
  }
61
61
  : {
62
- version: "1.8.5",
63
- expectedSize: 25514136,
64
- expectedChecksum: "6564f779f7f5a467e587d7093ed7c3e3",
62
+ version: "1.9.2",
63
+ expectedSize: 26316952,
64
+ expectedChecksum: "cc3c0318e453d9ddf098b582ee0f2b77",
65
65
  },
66
66
  };
67
67
  exports.DownloadDetails = {
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.setEnvVarsForEmulators = void 0;
3
+ exports.getCredentialsEnvironment = exports.setEnvVarsForEmulators = void 0;
4
4
  const constants_1 = require("./constants");
5
5
  const types_1 = require("./types");
6
6
  const functionsEmulatorShared_1 = require("./functionsEmulatorShared");
7
+ const defaultCredentials_1 = require("../defaultCredentials");
7
8
  function setEnvVarsForEmulators(env, emulators) {
8
9
  for (const emu of emulators) {
9
10
  const host = (0, functionsEmulatorShared_1.formatHost)(emu);
@@ -40,3 +41,18 @@ function setEnvVarsForEmulators(env, emulators) {
40
41
  }
41
42
  }
42
43
  exports.setEnvVarsForEmulators = setEnvVarsForEmulators;
44
+ async function getCredentialsEnvironment(account, logger, logLabel) {
45
+ const credentialEnv = {};
46
+ if (await (0, defaultCredentials_1.hasDefaultCredentials)()) {
47
+ logger.logLabeled("WARN", logLabel, `Application Default Credentials detected. Non-emulated services will access production using these credentials. Be careful!`);
48
+ }
49
+ else if (account) {
50
+ const defaultCredPath = await (0, defaultCredentials_1.getCredentialPathAsync)(account);
51
+ if (defaultCredPath) {
52
+ logger.log("DEBUG", `Setting GAC to ${defaultCredPath}`);
53
+ credentialEnv.GOOGLE_APPLICATION_CREDENTIALS = defaultCredPath;
54
+ }
55
+ }
56
+ return credentialEnv;
57
+ }
58
+ exports.getCredentialsEnvironment = getCredentialsEnvironment;
@@ -25,7 +25,6 @@ const functionsRuntimeWorker_1 = require("./functionsRuntimeWorker");
25
25
  const error_1 = require("../error");
26
26
  const workQueue_1 = require("./workQueue");
27
27
  const utils_1 = require("../utils");
28
- const defaultCredentials_1 = require("../defaultCredentials");
29
28
  const adminSdkConfig_1 = require("./adminSdkConfig");
30
29
  const validate_1 = require("../deploy/functions/validate");
31
30
  const secretManager_1 = require("../gcp/secretManager");
@@ -111,7 +110,7 @@ class FunctionsEmulator {
111
110
  this.dynamicBackends =
112
111
  this.args.extensionsEmulator.filterUnemulatedTriggers(unfilteredBackends);
113
112
  const mode = this.debugMode ? types_1.FunctionsExecutionMode.SEQUENTIAL : types_1.FunctionsExecutionMode.AUTO;
114
- const credentialEnv = await this.getCredentialsEnvironment();
113
+ const credentialEnv = await (0, env_1.getCredentialsEnvironment)(this.args.account, this.logger, "functions");
115
114
  for (const backend of this.dynamicBackends) {
116
115
  backend.env = Object.assign(Object.assign({}, credentialEnv), backend.env);
117
116
  if (this.workerPools[backend.codebase]) {
@@ -130,23 +129,6 @@ class FunctionsEmulator {
130
129
  }
131
130
  }
132
131
  }
133
- async getCredentialsEnvironment() {
134
- const credentialEnv = {};
135
- if (process.env.GOOGLE_APPLICATION_CREDENTIALS) {
136
- this.logger.logLabeled("WARN", "functions", `Your GOOGLE_APPLICATION_CREDENTIALS environment variable points to ${process.env.GOOGLE_APPLICATION_CREDENTIALS}. Non-emulated services will access production using these credentials. Be careful!`);
137
- }
138
- else if (this.args.account) {
139
- const defaultCredPath = await (0, defaultCredentials_1.getCredentialPathAsync)(this.args.account);
140
- if (defaultCredPath) {
141
- this.logger.log("DEBUG", `Setting GAC to ${defaultCredPath}`);
142
- credentialEnv.GOOGLE_APPLICATION_CREDENTIALS = defaultCredPath;
143
- }
144
- }
145
- else {
146
- this.logger.logLabeled("WARN", "functions", "You are not signed in to the Firebase CLI. If you have authorized this machine using gcloud application-default credentials those may be discovered and used to access production services.");
147
- }
148
- return credentialEnv;
149
- }
150
132
  createHubServer() {
151
133
  this.workQueue.start();
152
134
  const hub = express();
@@ -255,7 +237,7 @@ class FunctionsEmulator {
255
237
  });
256
238
  }
257
239
  async start() {
258
- const credentialEnv = await this.getCredentialsEnvironment();
240
+ const credentialEnv = await (0, env_1.getCredentialsEnvironment)(this.args.account, this.logger, "functions");
259
241
  for (const e of this.staticBackends) {
260
242
  e.env = Object.assign(Object.assign({}, credentialEnv), e.env);
261
243
  }
@@ -868,10 +868,14 @@ function isUrlPath(extInstallPath) {
868
868
  exports.isUrlPath = isUrlPath;
869
869
  function isLocalPath(extInstallPath) {
870
870
  const trimmedPath = extInstallPath.trim();
871
- return (trimmedPath.startsWith("~/") ||
872
- trimmedPath.startsWith("./") ||
873
- trimmedPath.startsWith("../") ||
874
- trimmedPath.startsWith("/") ||
871
+ return (trimmedPath.startsWith(`~${path.sep}`) ||
872
+ trimmedPath.startsWith(`.${path.sep}`) ||
873
+ trimmedPath.startsWith(`..${path.sep}`) ||
874
+ trimmedPath.startsWith(`${path.sep}`) ||
875
+ trimmedPath.startsWith(`~/`) ||
876
+ trimmedPath.startsWith(`./`) ||
877
+ trimmedPath.startsWith(`../`) ||
878
+ trimmedPath.startsWith(`/`) ||
875
879
  [".", ".."].includes(trimmedPath));
876
880
  }
877
881
  exports.isLocalPath = isLocalPath;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.tryToGetOptionsForTarget = exports.getAngularVersion = exports.getBuildConfig = exports.getServerConfig = exports.getBrowserConfig = exports.getContext = exports.getAllTargets = void 0;
3
+ exports.getBuilderType = exports.tryToGetOptionsForTarget = exports.getAngularVersion = exports.getBuildConfig = exports.getServerConfig = exports.getBrowserConfig = exports.getContext = exports.getAllTargets = exports.BuilderType = void 0;
4
4
  const utils_1 = require("../utils");
5
5
  const error_1 = require("../../error");
6
6
  const path_1 = require("path");
@@ -54,24 +54,30 @@ async function localesForTarget(dir, architectHost, target, workspaceProject) {
54
54
  (0, utils_1.validateLocales)(locales);
55
55
  return { locales, defaultLocale };
56
56
  }
57
- const DEV_SERVER_TARGETS = [
58
- "@angular-devkit/build-angular:dev-server",
59
- "@nguniversal/builders:ssr-dev-server",
60
- "@angular-devkit/build-angular:ssr-dev-server",
61
- ];
62
- function getValidBuilders(purpose) {
57
+ var BuilderType;
58
+ (function (BuilderType) {
59
+ BuilderType["DEPLOY"] = "deploy";
60
+ BuilderType["DEV_SERVER"] = "dev-server";
61
+ BuilderType["SSR_DEV_SERVER"] = "ssr-dev-server";
62
+ BuilderType["SERVER"] = "server";
63
+ BuilderType["BROWSER"] = "browser";
64
+ BuilderType["BROWSER_ESBUILD"] = "browser-esbuild";
65
+ BuilderType["APPLICATION"] = "application";
66
+ BuilderType["PRERENDER"] = "prerender";
67
+ })(BuilderType = exports.BuilderType || (exports.BuilderType = {}));
68
+ const DEV_SERVER_TARGETS = [BuilderType.DEV_SERVER, BuilderType.SSR_DEV_SERVER];
69
+ function getValidBuilderTypes(purpose) {
63
70
  return [
64
- "@angular-devkit/build-angular:application",
65
- "@angular-devkit/build-angular:browser-esbuild",
66
- "@angular/fire:deploy",
67
- "@angular-devkit/build-angular:browser",
68
- "@angular-devkit/build-angular:prerender",
69
- "@nguniversal/builders:prerender",
71
+ BuilderType.APPLICATION,
72
+ BuilderType.BROWSER_ESBUILD,
73
+ BuilderType.DEPLOY,
74
+ BuilderType.BROWSER,
75
+ BuilderType.PRERENDER,
70
76
  ...(purpose === "deploy" ? [] : DEV_SERVER_TARGETS),
71
77
  ];
72
78
  }
73
79
  async function getAllTargets(purpose, dir) {
74
- const validBuilders = getValidBuilders(purpose);
80
+ const validBuilderTypes = getValidBuilderTypes(purpose);
75
81
  const [{ NodeJsAsyncHost }, { workspaces }, { targetStringFromTarget }] = await Promise.all([
76
82
  (0, utils_1.relativeRequire)(dir, "@angular-devkit/core/node"),
77
83
  (0, utils_1.relativeRequire)(dir, "@angular-devkit/core"),
@@ -84,8 +90,10 @@ async function getAllTargets(purpose, dir) {
84
90
  if (projectDefinition.extensions.projectType !== "application")
85
91
  return;
86
92
  projectDefinition.targets.forEach((targetDefinition, target) => {
87
- if (!validBuilders.includes(targetDefinition.builder))
93
+ const builderType = getBuilderType(targetDefinition.builder);
94
+ if (builderType && !validBuilderTypes.includes(builderType)) {
88
95
  return;
96
+ }
89
97
  const configurations = Object.keys(targetDefinition.configurations || {});
90
98
  if (!configurations.includes("production"))
91
99
  configurations.push("production");
@@ -152,34 +160,32 @@ async function getContext(dir, targetOrConfiguration) {
152
160
  throw new error_1.FirebaseError(`No project ${project} found.`);
153
161
  if (overrideTarget) {
154
162
  const target = workspaceProject.targets.get(overrideTarget.target);
155
- const builder = target.builder;
156
- switch (builder) {
157
- case "@angular/fire:deploy":
163
+ const builderType = getBuilderType(target.builder);
164
+ switch (builderType) {
165
+ case BuilderType.DEPLOY:
158
166
  deployTarget = overrideTarget;
159
167
  break;
160
- case "@angular-devkit/build-angular:application":
168
+ case BuilderType.APPLICATION:
161
169
  buildTarget = overrideTarget;
162
170
  break;
163
- case "@angular-devkit/build-angular:browser-esbuild":
164
- case "@angular-devkit/build-angular:browser":
171
+ case BuilderType.BROWSER:
172
+ case BuilderType.BROWSER_ESBUILD:
165
173
  browserTarget = overrideTarget;
166
174
  break;
167
- case "@angular-devkit/build-angular:prerender":
168
- case "@nguniversal/builders:prerender":
175
+ case BuilderType.PRERENDER:
169
176
  prerenderTarget = overrideTarget;
170
177
  break;
171
- case "@angular-devkit/build-angular:dev-server":
172
- case "@nguniversal/builders:ssr-dev-server":
173
- case "@angular-devkit/build-angular:ssr-dev-server":
178
+ case BuilderType.DEV_SERVER:
179
+ case BuilderType.SSR_DEV_SERVER:
174
180
  serveTarget = overrideTarget;
175
181
  break;
176
182
  default:
177
- throw new error_1.FirebaseError(`builder ${builder} not known.`);
183
+ throw new error_1.FirebaseError(`builder type ${builderType} not known.`);
178
184
  }
179
185
  }
180
186
  else if (workspaceProject.targets.has("deploy")) {
181
187
  const { builder, defaultConfiguration = "production" } = workspaceProject.targets.get("deploy");
182
- if (builder === "@angular/fire:deploy") {
188
+ if (getBuilderType(builder) === BuilderType.DEPLOY) {
183
189
  deployTarget = {
184
190
  project,
185
191
  target: "deploy",
@@ -251,13 +257,13 @@ async function getContext(dir, targetOrConfiguration) {
251
257
  }
252
258
  if (!buildTarget && !browserTarget && workspaceProject.targets.has("build")) {
253
259
  const { builder, defaultConfiguration = "production" } = workspaceProject.targets.get("build");
260
+ const builderType = getBuilderType(builder);
254
261
  const target = {
255
262
  project,
256
263
  target: "build",
257
264
  configuration: configuration || defaultConfiguration,
258
265
  };
259
- if (builder === "@angular-devkit/build-angular:browser" ||
260
- builder === "@angular-devkit/build-angular:browser-esbuild") {
266
+ if (builderType === BuilderType.BROWSER || builderType === BuilderType.BROWSER_ESBUILD) {
261
267
  browserTarget = target;
262
268
  }
263
269
  else {
@@ -307,27 +313,30 @@ async function getContext(dir, targetOrConfiguration) {
307
313
  if (!definition)
308
314
  throw new error_1.FirebaseError(`${target} could not be found in your angular.json`);
309
315
  const { builder } = definition;
310
- if (target === deployTarget && builder === "@angular/fire:deploy")
316
+ const builderType = getBuilderType(builder);
317
+ if (target === deployTarget && builderType === BuilderType.DEPLOY)
318
+ continue;
319
+ if (target === buildTarget && builderType === BuilderType.APPLICATION)
311
320
  continue;
312
- if (target === buildTarget && builder === "@angular-devkit/build-angular:application")
321
+ if (target === buildTarget && builderType === BuilderType.BROWSER)
313
322
  continue;
314
- if (target === buildTarget && builder === "@angular-devkit/build-angular:browser")
323
+ if (target === browserTarget && builderType === BuilderType.BROWSER_ESBUILD)
315
324
  continue;
316
- if (target === browserTarget && builder === "@angular-devkit/build-angular:browser-esbuild")
325
+ if (target === browserTarget && builderType === BuilderType.BROWSER)
317
326
  continue;
318
- if (target === browserTarget && builder === "@angular-devkit/build-angular:browser")
327
+ if (target === browserTarget && builderType === BuilderType.APPLICATION)
319
328
  continue;
320
- if (target === prerenderTarget && builder === "@angular-devkit/build-angular:prerender")
329
+ if (target === prerenderTarget && builderType === BuilderType.PRERENDER)
321
330
  continue;
322
- if (target === prerenderTarget && builder === "@nguniversal/builders:prerender")
331
+ if (target === prerenderTarget && builderType === BuilderType.PRERENDER)
323
332
  continue;
324
- if (target === serverTarget && builder === "@angular-devkit/build-angular:server")
333
+ if (target === serverTarget && builderType === BuilderType.SERVER)
325
334
  continue;
326
- if (target === serveTarget && builder === "@nguniversal/builders:ssr-dev-server")
335
+ if (target === serveTarget && builderType === BuilderType.SSR_DEV_SERVER)
327
336
  continue;
328
- if (target === serveTarget && builder === "@angular-devkit/build-angular:ssr-dev-server")
337
+ if (target === serveTarget && builderType === BuilderType.DEV_SERVER)
329
338
  continue;
330
- if (target === serveTarget && builder === "@angular-devkit/build-angular:dev-server")
339
+ if (target === serveTarget && builderType === BuilderType.SERVER)
331
340
  continue;
332
341
  throw new error_1.FirebaseError(`${definition.builder} (${targetString}) is not a recognized builder. Please check your angular.json`);
333
342
  }
@@ -373,7 +382,7 @@ async function getBrowserConfig(sourceDir, configuration) {
373
382
  architectHost.getBuilderNameForTarget(buildOrBrowserTarget),
374
383
  ]);
375
384
  (0, utils_2.assertIsString)(targetOptions === null || targetOptions === void 0 ? void 0 : targetOptions.outputPath);
376
- const outputPath = (0, path_1.join)(targetOptions.outputPath, buildTarget && builderName === "@angular-devkit/build-angular:application" ? "browser" : "");
385
+ const outputPath = (0, path_1.join)(targetOptions.outputPath, buildTarget && getBuilderType(builderName) === BuilderType.APPLICATION ? "browser" : "");
377
386
  return { locales, baseHref, outputPath, defaultLocale };
378
387
  }
379
388
  exports.getBrowserConfig = getBrowserConfig;
@@ -475,3 +484,12 @@ exports.tryToGetOptionsForTarget = tryToGetOptionsForTarget;
475
484
  function throwCannotDetermineTarget(error) {
476
485
  throw new error_1.FirebaseError(`Unable to determine the application to deploy, specify a target via the FIREBASE_FRAMEWORKS_BUILD_TARGET environment variable.`, { original: error });
477
486
  }
487
+ function getBuilderType(builder) {
488
+ const colonIndex = builder.lastIndexOf(":");
489
+ const builderType = colonIndex >= 0 ? builder.slice(colonIndex + 1) : undefined;
490
+ if (!builderType || !Object.values(BuilderType).includes(builderType)) {
491
+ return null;
492
+ }
493
+ return builderType;
494
+ }
495
+ exports.getBuilderType = getBuilderType;
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hasCleanupOptOut = exports.hasSameCleanupPolicy = exports.setCleanupPolicy = exports.optOutRepository = exports.updateRepository = exports.generateCleanupPolicy = exports.parseDaysFromPolicy = exports.daysToSeconds = exports.findExistingPolicy = exports.makeRepoPath = exports.DEFAULT_CLEANUP_DAYS = exports.OPT_OUT_LABEL_KEY = exports.CLEANUP_POLICY_ID = exports.GCF_REPO_ID = void 0;
4
+ const artifactregistry = require("../gcp/artifactregistry");
5
+ const error_1 = require("../error");
6
+ exports.GCF_REPO_ID = "gcf-artifacts";
7
+ exports.CLEANUP_POLICY_ID = "firebase-functions-cleanup";
8
+ exports.OPT_OUT_LABEL_KEY = "firebase-functions-cleanup-opted-out";
9
+ exports.DEFAULT_CLEANUP_DAYS = 1;
10
+ const SECONDS_IN_DAY = 24 * 60 * 60;
11
+ function makeRepoPath(projectId, location, repoName = exports.GCF_REPO_ID) {
12
+ return `projects/${projectId}/locations/${location}/repositories/${repoName}`;
13
+ }
14
+ exports.makeRepoPath = makeRepoPath;
15
+ function findExistingPolicy(repository) {
16
+ var _a;
17
+ return (_a = repository === null || repository === void 0 ? void 0 : repository.cleanupPolicies) === null || _a === void 0 ? void 0 : _a[exports.CLEANUP_POLICY_ID];
18
+ }
19
+ exports.findExistingPolicy = findExistingPolicy;
20
+ function daysToSeconds(days) {
21
+ const seconds = days * SECONDS_IN_DAY;
22
+ return `${seconds}s`;
23
+ }
24
+ exports.daysToSeconds = daysToSeconds;
25
+ function parseDaysFromPolicy(olderThan) {
26
+ const match = olderThan.match(/^(\d+)s$/);
27
+ if (match && match[1]) {
28
+ const seconds = parseInt(match[1], 10);
29
+ return Math.floor(seconds / SECONDS_IN_DAY);
30
+ }
31
+ return;
32
+ }
33
+ exports.parseDaysFromPolicy = parseDaysFromPolicy;
34
+ function generateCleanupPolicy(daysToKeep) {
35
+ return {
36
+ [exports.CLEANUP_POLICY_ID]: {
37
+ id: exports.CLEANUP_POLICY_ID,
38
+ condition: {
39
+ tagState: "ANY",
40
+ olderThan: daysToSeconds(daysToKeep),
41
+ },
42
+ action: "DELETE",
43
+ },
44
+ };
45
+ }
46
+ exports.generateCleanupPolicy = generateCleanupPolicy;
47
+ async function updateRepository(repo) {
48
+ try {
49
+ await artifactregistry.updateRepository(repo);
50
+ }
51
+ catch (err) {
52
+ if (err.status === 403) {
53
+ throw new error_1.FirebaseError(`You don't have permission to update this repository.\n` +
54
+ `To update repository settings, ask your administrator to grant you the ` +
55
+ `Artifact Registry Administrator (roles/artifactregistry.admin) IAM role on the repository project.`, { original: err, exit: 1 });
56
+ }
57
+ else {
58
+ throw new error_1.FirebaseError("Failed to update artifact registry repository", {
59
+ original: err,
60
+ });
61
+ }
62
+ }
63
+ }
64
+ exports.updateRepository = updateRepository;
65
+ async function optOutRepository(repository) {
66
+ const policies = Object.assign({}, repository.cleanupPolicies);
67
+ if (exports.CLEANUP_POLICY_ID in policies) {
68
+ delete policies[exports.CLEANUP_POLICY_ID];
69
+ }
70
+ const update = {
71
+ name: repository.name,
72
+ labels: Object.assign(Object.assign({}, repository.labels), { [exports.OPT_OUT_LABEL_KEY]: "true" }),
73
+ cleanupPolicies: policies,
74
+ };
75
+ await exports.updateRepository(update);
76
+ }
77
+ exports.optOutRepository = optOutRepository;
78
+ async function setCleanupPolicy(repository, daysToKeep) {
79
+ const labels = Object.assign({}, repository.labels);
80
+ delete labels[exports.OPT_OUT_LABEL_KEY];
81
+ const update = {
82
+ name: repository.name,
83
+ cleanupPolicies: Object.assign(Object.assign({}, repository.cleanupPolicies), generateCleanupPolicy(daysToKeep)),
84
+ labels,
85
+ };
86
+ await exports.updateRepository(update);
87
+ }
88
+ exports.setCleanupPolicy = setCleanupPolicy;
89
+ function hasSameCleanupPolicy(repository, daysToKeep) {
90
+ var _a, _b;
91
+ const existingPolicy = findExistingPolicy(repository);
92
+ if (!existingPolicy) {
93
+ return false;
94
+ }
95
+ if (((_a = existingPolicy.condition) === null || _a === void 0 ? void 0 : _a.tagState) !== "ANY" || !((_b = existingPolicy.condition) === null || _b === void 0 ? void 0 : _b.olderThan)) {
96
+ return false;
97
+ }
98
+ const existingSeconds = parseDaysFromPolicy(existingPolicy.condition.olderThan);
99
+ return existingSeconds === daysToKeep;
100
+ }
101
+ exports.hasSameCleanupPolicy = hasSameCleanupPolicy;
102
+ function hasCleanupOptOut(repo) {
103
+ return !!(repo.labels && repo.labels[exports.OPT_OUT_LABEL_KEY] === "true");
104
+ }
105
+ exports.hasCleanupOptOut = hasCleanupOptOut;