firebase-tools 14.20.0 → 14.22.0
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/appUtils.js +2 -1
- package/lib/command.js +5 -9
- package/lib/commands/dataconnect-sdk-generate.js +66 -11
- package/lib/commands/deploy.js +6 -4
- package/lib/commands/firestore-databases-clone.js +99 -0
- package/lib/commands/functions-secrets-set.js +19 -1
- package/lib/commands/hosting-sites-create.js +4 -3
- package/lib/commands/index.js +1 -0
- package/lib/commands/init.js +12 -8
- package/lib/commands/internaltesting-functions-discover.js +1 -3
- package/lib/dataconnect/provisionCloudSql.js +3 -2
- package/lib/deploy/extensions/prepare.js +3 -1
- package/lib/deploy/functions/checkIam.js +1 -1
- package/lib/deploy/functions/functionsDeployHelper.js +8 -7
- package/lib/deploy/functions/params.js +15 -5
- package/lib/deploy/functions/prepare.js +9 -6
- package/lib/detectProjectRoot.js +1 -1
- package/lib/emulator/downloadableEmulatorInfo.json +18 -18
- package/lib/emulator/hubExport.js +5 -0
- package/lib/experiments.js +0 -7
- package/lib/firestore/api.js +15 -0
- package/lib/firestore/util.js +22 -1
- package/lib/frameworks/angular/index.js +1 -1
- package/lib/frameworks/flutter/index.js +1 -1
- package/lib/frameworks/next/index.js +1 -1
- package/lib/frameworks/nuxt/index.js +1 -1
- package/lib/frameworks/vite/index.js +5 -2
- package/lib/functions/projectConfig.js +5 -1
- package/lib/functions/secrets.js +14 -1
- package/lib/hosting/interactive.js +14 -19
- package/lib/init/features/dataconnect/index.js +21 -20
- package/lib/init/features/dataconnect/sdk.js +44 -21
- package/lib/init/features/functions/index.js +1 -0
- package/lib/init/features/hosting/index.js +96 -93
- package/lib/init/features/index.js +3 -2
- package/lib/init/index.js +7 -3
- package/lib/mcp/index.js +46 -18
- package/lib/mcp/prompt.js +4 -1
- package/lib/mcp/prompts/core/consult.js +1 -1
- package/lib/mcp/prompts/core/deploy.js +1 -1
- package/lib/mcp/prompts/core/init.js +1 -1
- package/lib/mcp/prompts/crashlytics/connect.js +1 -1
- package/lib/mcp/prompts/dataconnect/schema.js +1 -1
- package/lib/mcp/prompts/index.js +20 -10
- package/lib/mcp/resources/guides/init_backend.js +3 -26
- package/lib/mcp/resources/guides/init_hosting.js +15 -10
- package/lib/mcp/tool.js +17 -2
- package/lib/mcp/tools/apphosting/fetch_logs.js +1 -1
- package/lib/mcp/tools/apphosting/list_backends.js +1 -1
- package/lib/mcp/tools/auth/get_users.js +1 -1
- package/lib/mcp/tools/auth/set_sms_region_policy.js +1 -1
- package/lib/mcp/tools/auth/update_user.js +1 -1
- package/lib/mcp/tools/core/create_android_sha.js +1 -1
- package/lib/mcp/tools/core/create_app.js +1 -1
- package/lib/mcp/tools/core/create_project.js +1 -1
- package/lib/mcp/tools/core/get_environment.js +1 -1
- package/lib/mcp/tools/core/get_project.js +1 -1
- package/lib/mcp/tools/core/get_sdk_config.js +1 -1
- package/lib/mcp/tools/core/get_security_rules.js +1 -1
- package/lib/mcp/tools/core/init.js +30 -2
- package/lib/mcp/tools/core/list_apps.js +1 -1
- package/lib/mcp/tools/core/list_projects.js +1 -1
- package/lib/mcp/tools/core/login.js +1 -1
- package/lib/mcp/tools/core/logout.js +1 -1
- package/lib/mcp/tools/core/read_resources.js +1 -1
- package/lib/mcp/tools/core/update_environment.js +1 -1
- package/lib/mcp/tools/core/validate_security_rules.js +15 -1
- package/lib/mcp/tools/crashlytics/events.js +3 -3
- package/lib/mcp/tools/crashlytics/issues.js +3 -3
- package/lib/mcp/tools/crashlytics/notes.js +4 -4
- package/lib/mcp/tools/crashlytics/reports.js +6 -6
- package/lib/mcp/tools/dataconnect/compile.js +1 -1
- package/lib/mcp/tools/dataconnect/execute.js +1 -1
- package/lib/mcp/tools/dataconnect/generate_operation.js +1 -1
- package/lib/mcp/tools/dataconnect/generate_schema.js +1 -1
- package/lib/mcp/tools/dataconnect/list_services.js +1 -1
- package/lib/mcp/tools/firestore/delete_document.js +1 -1
- package/lib/mcp/tools/firestore/get_documents.js +1 -1
- package/lib/mcp/tools/firestore/list_collections.js +1 -1
- package/lib/mcp/tools/firestore/query_collection.js +1 -1
- package/lib/mcp/tools/functions/get_logs.js +1 -1
- package/lib/mcp/tools/index.js +14 -4
- package/lib/mcp/tools/messaging/send_message.js +1 -1
- package/lib/mcp/tools/realtime_database/get_data.js +1 -1
- package/lib/mcp/tools/realtime_database/set_data.js +1 -1
- package/lib/mcp/tools/remoteconfig/get_template.js +1 -1
- package/lib/mcp/tools/remoteconfig/update_template.js +1 -1
- package/lib/mcp/tools/storage/get_download_url.js +1 -1
- package/lib/mcp/util/availability.js +22 -0
- package/lib/mcp/util/crashlytics/availability.js +81 -0
- package/lib/mcp/util.js +26 -6
- package/package.json +1 -1
- package/schema/firebase-config.json +3 -0
|
@@ -54,28 +54,28 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dataconnect": {
|
|
56
56
|
"darwin": {
|
|
57
|
-
"version": "2.15.
|
|
58
|
-
"expectedSize":
|
|
59
|
-
"expectedChecksum": "
|
|
60
|
-
"expectedChecksumSHA256": "
|
|
61
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v2.15.
|
|
62
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.15.
|
|
57
|
+
"version": "2.15.1",
|
|
58
|
+
"expectedSize": 29946720,
|
|
59
|
+
"expectedChecksum": "cc8d5dd053cc71adad0f640ac2627018",
|
|
60
|
+
"expectedChecksumSHA256": "a3749396507678bc546987ef047a9d0c25145064503a9adc777229f1bc9b8c6f",
|
|
61
|
+
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v2.15.1",
|
|
62
|
+
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.15.1"
|
|
63
63
|
},
|
|
64
64
|
"win32": {
|
|
65
|
-
"version": "2.15.
|
|
66
|
-
"expectedSize":
|
|
67
|
-
"expectedChecksum": "
|
|
68
|
-
"expectedChecksumSHA256": "
|
|
69
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v2.15.
|
|
70
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.15.
|
|
65
|
+
"version": "2.15.1",
|
|
66
|
+
"expectedSize": 30440960,
|
|
67
|
+
"expectedChecksum": "57390c392101a13952ff2aea74b62787",
|
|
68
|
+
"expectedChecksumSHA256": "905a9ab2200189dc31f23b5454d21803b0c672442f0ee4bb7db8286d159a86e0",
|
|
69
|
+
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v2.15.1",
|
|
70
|
+
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.15.1.exe"
|
|
71
71
|
},
|
|
72
72
|
"linux": {
|
|
73
|
-
"version": "2.15.
|
|
74
|
-
"expectedSize":
|
|
75
|
-
"expectedChecksum": "
|
|
76
|
-
"expectedChecksumSHA256": "
|
|
77
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v2.15.
|
|
78
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.15.
|
|
73
|
+
"version": "2.15.1",
|
|
74
|
+
"expectedSize": 29868216,
|
|
75
|
+
"expectedChecksum": "cc6ef676d8c5c3a3f74c0bf037083cf0",
|
|
76
|
+
"expectedChecksumSHA256": "1dfe085c467637ebf6a71cc6c8e8bc11fac85202056b960633607627bd647a6d",
|
|
77
|
+
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v2.15.1",
|
|
78
|
+
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.15.1"
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
}
|
|
@@ -13,6 +13,7 @@ const hub_1 = require("./hub");
|
|
|
13
13
|
const downloadableEmulators_1 = require("./downloadableEmulators");
|
|
14
14
|
const node_fs_1 = require("node:fs");
|
|
15
15
|
const track_1 = require("../track");
|
|
16
|
+
const api_1 = require("../api");
|
|
16
17
|
class HubExport {
|
|
17
18
|
constructor(projectId, options) {
|
|
18
19
|
this.projectId = projectId;
|
|
@@ -234,5 +235,9 @@ function fetchToFile(options, path) {
|
|
|
234
235
|
});
|
|
235
236
|
}
|
|
236
237
|
function shouldExport(e) {
|
|
238
|
+
if (e === types_1.Emulators.DATACONNECT && (0, api_1.dataConnectLocalConnString)()) {
|
|
239
|
+
logger_1.logger.info("Skipping export for Data Connect because FIREBASE_DATACONNECT_POSTGRESQL_STRING is set.");
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
237
242
|
return types_1.IMPORT_EXPORT_EMULATORS.includes(e) && registry_1.EmulatorRegistry.isRunning(e);
|
|
238
243
|
}
|
package/lib/experiments.js
CHANGED
|
@@ -41,13 +41,6 @@ exports.ALL_EXPERIMENTS = experiments({
|
|
|
41
41
|
"of how that image was created.",
|
|
42
42
|
public: true,
|
|
43
43
|
},
|
|
44
|
-
dangerouslyAllowFunctionsConfig: {
|
|
45
|
-
shortDescription: "Allows the use of deprecated functions.config() API",
|
|
46
|
-
fullDescription: "The functions.config() API is deprecated and will be removed on December 31, 2025. " +
|
|
47
|
-
"This experiment allows continued use of the API during the migration period.",
|
|
48
|
-
default: true,
|
|
49
|
-
public: true,
|
|
50
|
-
},
|
|
51
44
|
runfunctions: {
|
|
52
45
|
shortDescription: "Functions created using the V2 API target Cloud Run Functions (not production ready)",
|
|
53
46
|
public: false,
|
package/lib/firestore/api.js
CHANGED
|
@@ -580,6 +580,21 @@ class FirestoreApi {
|
|
|
580
580
|
}
|
|
581
581
|
return database;
|
|
582
582
|
}
|
|
583
|
+
async cloneDatabase(project, pitrSnapshot, databaseId, encryptionConfig) {
|
|
584
|
+
const url = `/projects/${project}/databases:clone`;
|
|
585
|
+
const payload = {
|
|
586
|
+
databaseId,
|
|
587
|
+
pitrSnapshot,
|
|
588
|
+
encryptionConfig,
|
|
589
|
+
};
|
|
590
|
+
const options = { queryParams: { databaseId: databaseId } };
|
|
591
|
+
const res = await this.apiClient.post(url, payload, options);
|
|
592
|
+
const lro = res.body;
|
|
593
|
+
if (!lro) {
|
|
594
|
+
throw new error_1.FirebaseError("Not found");
|
|
595
|
+
}
|
|
596
|
+
return lro;
|
|
597
|
+
}
|
|
583
598
|
async listOperations(project, databaseId, limit) {
|
|
584
599
|
const url = `/projects/${project}/databases/${databaseId}/operations`;
|
|
585
600
|
const res = await this.apiClient.get(url, {
|
package/lib/firestore/util.js
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.booleanXOR = exports.parseFieldName = exports.parseIndexName = void 0;
|
|
3
|
+
exports.getCurrentMinuteAsIsoString = exports.booleanXOR = exports.parseFieldName = exports.parseIndexName = exports.parseDatabaseName = void 0;
|
|
4
4
|
const error_1 = require("../error");
|
|
5
|
+
const DATABASE_NAME_REGEX = /projects\/([^\/]+?)\/databases\/([^\/]+)/;
|
|
5
6
|
const INDEX_NAME_REGEX = /projects\/([^\/]+?)\/databases\/([^\/]+?)\/collectionGroups\/([^\/]+?)\/indexes\/([^\/]*)/;
|
|
6
7
|
const FIELD_NAME_REGEX = /projects\/([^\/]+?)\/databases\/([^\/]+?)\/collectionGroups\/([^\/]+?)\/fields\/([^\/]*)/;
|
|
8
|
+
function parseDatabaseName(name) {
|
|
9
|
+
if (!name) {
|
|
10
|
+
throw new error_1.FirebaseError(`Cannot parse undefined database name.`);
|
|
11
|
+
}
|
|
12
|
+
const m = name.match(DATABASE_NAME_REGEX);
|
|
13
|
+
if (!m || m.length < 3) {
|
|
14
|
+
throw new error_1.FirebaseError(`Error parsing database name: ${name}`);
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
projectId: m[1],
|
|
18
|
+
databaseId: m[2],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
exports.parseDatabaseName = parseDatabaseName;
|
|
7
22
|
function parseIndexName(name) {
|
|
8
23
|
if (!name) {
|
|
9
24
|
throw new error_1.FirebaseError(`Cannot parse undefined index name.`);
|
|
@@ -37,3 +52,9 @@ function booleanXOR(a, b) {
|
|
|
37
52
|
return !!(Number(a) - Number(b));
|
|
38
53
|
}
|
|
39
54
|
exports.booleanXOR = booleanXOR;
|
|
55
|
+
function getCurrentMinuteAsIsoString() {
|
|
56
|
+
const mostRecentTimestamp = new Date(Date.now());
|
|
57
|
+
mostRecentTimestamp.setSeconds(0, 0);
|
|
58
|
+
return mostRecentTimestamp.toISOString();
|
|
59
|
+
}
|
|
60
|
+
exports.getCurrentMinuteAsIsoString = getCurrentMinuteAsIsoString;
|
|
@@ -26,7 +26,7 @@ async function discover(dir) {
|
|
|
26
26
|
}
|
|
27
27
|
exports.discover = discover;
|
|
28
28
|
function init(setup, config) {
|
|
29
|
-
(0, child_process_1.execSync)(`npx --yes -p @angular/cli@"${exports.supportedRange}" ng new ${setup.projectId} --directory ${setup.hosting.source} --skip-git`, {
|
|
29
|
+
(0, child_process_1.execSync)(`npx --yes -p @angular/cli@"${exports.supportedRange}" ng new ${setup.projectId} --directory ${setup.featureInfo.hosting.source} --skip-git`, {
|
|
30
30
|
stdio: "inherit",
|
|
31
31
|
cwd: config.projectDir,
|
|
32
32
|
});
|
|
@@ -34,7 +34,7 @@ function init(setup, config) {
|
|
|
34
34
|
`--project-name=${projectName}`,
|
|
35
35
|
"--overwrite",
|
|
36
36
|
"--platforms=web",
|
|
37
|
-
setup.hosting.source,
|
|
37
|
+
setup.featureInfo.hosting.source,
|
|
38
38
|
], { stdio: "inherit", cwd: config.projectDir });
|
|
39
39
|
if (result.status !== 0)
|
|
40
40
|
throw new error_1.FirebaseError("We were not able to create your flutter app, create the application yourself https://docs.flutter.dev/get-started/test-drive?tab=terminal before trying again.");
|
|
@@ -214,7 +214,7 @@ async function init(setup, config) {
|
|
|
214
214
|
],
|
|
215
215
|
});
|
|
216
216
|
(0, child_process_1.execSync)(`npx --yes create-next-app@"${exports.supportedRange}" -e hello-world ` +
|
|
217
|
-
`${setup.hosting.source} --use-npm --${language}`, { stdio: "inherit", cwd: config.projectDir });
|
|
217
|
+
`${setup.featureInfo.hosting.source} --use-npm --${language}`, { stdio: "inherit", cwd: config.projectDir });
|
|
218
218
|
}
|
|
219
219
|
exports.init = init;
|
|
220
220
|
async function ɵcodegenPublicDirectory(sourceDir, destDir, _, context) {
|
|
@@ -93,7 +93,7 @@ async function getConfig(cwd) {
|
|
|
93
93
|
}
|
|
94
94
|
exports.getConfig = getConfig;
|
|
95
95
|
function init(setup, config) {
|
|
96
|
-
(0, child_process_1.execSync)(`npx --yes nuxi@"${exports.supportedRange}" init ${setup.hosting.source}`, {
|
|
96
|
+
(0, child_process_1.execSync)(`npx --yes nuxi@"${exports.supportedRange}" init ${setup.featureInfo.hosting.source}`, {
|
|
97
97
|
stdio: "inherit",
|
|
98
98
|
cwd: config.projectDir,
|
|
99
99
|
});
|
|
@@ -25,11 +25,14 @@ async function init(setup, config, baseTemplate = "vanilla") {
|
|
|
25
25
|
{ name: "TypeScript", value: `${baseTemplate}-ts` },
|
|
26
26
|
],
|
|
27
27
|
});
|
|
28
|
-
(0, child_process_1.execSync)(`npm create vite@"${exports.supportedRange}" ${setup.hosting.source} --yes -- --template ${template}`, {
|
|
28
|
+
(0, child_process_1.execSync)(`npm create vite@"${exports.supportedRange}" ${setup.featureInfo.hosting.source} --yes -- --template ${template}`, {
|
|
29
29
|
stdio: "inherit",
|
|
30
30
|
cwd: config.projectDir,
|
|
31
31
|
});
|
|
32
|
-
(0, child_process_1.execSync)(`npm install`, {
|
|
32
|
+
(0, child_process_1.execSync)(`npm install`, {
|
|
33
|
+
stdio: "inherit",
|
|
34
|
+
cwd: (0, path_1.join)(config.projectDir, setup.featureInfo.hosting.source),
|
|
35
|
+
});
|
|
33
36
|
}
|
|
34
37
|
exports.init = init;
|
|
35
38
|
const viteDiscoverWithNpmDependency = (dep) => async (dir) => await discover(dir, undefined, dep);
|
|
@@ -11,7 +11,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
11
11
|
return t;
|
|
12
12
|
};
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
-
exports.resolveConfigDir = exports.requireLocal = exports.isRemoteConfig = exports.isLocalConfig = exports.configForCodebase = exports.normalizeAndValidate = exports.validate = exports.assertUnique = exports.validatePrefix = exports.validateCodebase = exports.normalize = exports.DEFAULT_CODEBASE = void 0;
|
|
14
|
+
exports.shouldUseRuntimeConfig = exports.resolveConfigDir = exports.requireLocal = exports.isRemoteConfig = exports.isLocalConfig = exports.configForCodebase = exports.normalizeAndValidate = exports.validate = exports.assertUnique = exports.validatePrefix = exports.validateCodebase = exports.normalize = exports.DEFAULT_CODEBASE = void 0;
|
|
15
15
|
const error_1 = require("../error");
|
|
16
16
|
exports.DEFAULT_CODEBASE = "default";
|
|
17
17
|
function normalize(config) {
|
|
@@ -149,3 +149,7 @@ function resolveConfigDir(c) {
|
|
|
149
149
|
return c.configDir || c.source;
|
|
150
150
|
}
|
|
151
151
|
exports.resolveConfigDir = resolveConfigDir;
|
|
152
|
+
function shouldUseRuntimeConfig(cfg) {
|
|
153
|
+
return isLocalConfig(cfg) && cfg.disallowLegacyRuntimeConfig !== true;
|
|
154
|
+
}
|
|
155
|
+
exports.shouldUseRuntimeConfig = shouldUseRuntimeConfig;
|
package/lib/functions/secrets.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.describeSecret = exports.updateEndpointSecret = exports.pruneAndDestroySecrets = exports.pruneSecrets = exports.versionInUse = exports.inUse = exports.getSecretVersions = exports.of = exports.ensureSecret = exports.ensureValidKey = void 0;
|
|
3
|
+
exports.describeSecret = exports.updateEndpointSecret = exports.pruneAndDestroySecrets = exports.pruneSecrets = exports.versionInUse = exports.inUse = exports.getSecretVersions = exports.of = exports.ensureSecret = exports.validateJsonSecret = exports.ensureValidKey = void 0;
|
|
4
4
|
const utils = require("../utils");
|
|
5
5
|
const poller = require("../operation-poller");
|
|
6
6
|
const gcfV1 = require("../gcp/cloudfunctions");
|
|
@@ -61,6 +61,19 @@ async function ensureValidKey(key, options) {
|
|
|
61
61
|
return transformedKey;
|
|
62
62
|
}
|
|
63
63
|
exports.ensureValidKey = ensureValidKey;
|
|
64
|
+
function validateJsonSecret(secretName, secretValue) {
|
|
65
|
+
try {
|
|
66
|
+
JSON.parse(secretValue);
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
throw new error_1.FirebaseError(`Provided value for ${secretName} is not valid JSON: ${e.message}\n\n` +
|
|
70
|
+
`For complex JSON values, use:\n` +
|
|
71
|
+
` firebase functions:secrets:set ${secretName} --data-file <file.json>\n` +
|
|
72
|
+
`Or pipe from stdin:\n` +
|
|
73
|
+
` cat <file.json> | firebase functions:secrets:set ${secretName} --format=json`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
exports.validateJsonSecret = validateJsonSecret;
|
|
64
77
|
async function ensureSecret(projectId, name, options) {
|
|
65
78
|
try {
|
|
66
79
|
const secret = await (0, secretManager_1.getSecret)(projectId, name);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.pickHostingSiteName = void 0;
|
|
4
4
|
const error_1 = require("../error");
|
|
5
5
|
const utils_1 = require("../utils");
|
|
6
6
|
const projectUtils_1 = require("../projectUtils");
|
|
@@ -9,11 +9,11 @@ const prompt_1 = require("../prompt");
|
|
|
9
9
|
const nameSuggestion = new RegExp("try something like `(.+)`");
|
|
10
10
|
const prompt = "Please provide an unique, URL-friendly id for your site. Your site's URL will be <site-id>.web.app. " +
|
|
11
11
|
'We recommend using letters, numbers, and hyphens (e.g. "{project-id}-{random-hash}"):';
|
|
12
|
-
async function
|
|
12
|
+
async function pickHostingSiteName(siteId, options) {
|
|
13
13
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
14
14
|
const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
|
|
15
15
|
let id = siteId;
|
|
16
|
-
let
|
|
16
|
+
let nameConfirmed = false;
|
|
17
17
|
let suggestion;
|
|
18
18
|
if (!id) {
|
|
19
19
|
const attempt = await trySiteID(projectNumber, projectId);
|
|
@@ -24,7 +24,7 @@ async function interactiveCreateHostingSite(siteId, appId, options) {
|
|
|
24
24
|
suggestion = attempt.suggestion;
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
|
-
while (!
|
|
27
|
+
while (!nameConfirmed) {
|
|
28
28
|
if (!id || suggestion) {
|
|
29
29
|
id = await (0, prompt_1.input)({
|
|
30
30
|
message: prompt,
|
|
@@ -32,24 +32,16 @@ async function interactiveCreateHostingSite(siteId, appId, options) {
|
|
|
32
32
|
default: suggestion,
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (!(err instanceof error_1.FirebaseError)) {
|
|
40
|
-
throw err;
|
|
41
|
-
}
|
|
42
|
-
if (options.nonInteractive) {
|
|
43
|
-
throw err;
|
|
44
|
-
}
|
|
35
|
+
const attempt = await trySiteID(projectNumber, id, options.nonInteractive);
|
|
36
|
+
nameConfirmed = attempt.available;
|
|
37
|
+
suggestion = attempt.suggestion;
|
|
38
|
+
if (!nameConfirmed)
|
|
45
39
|
id = "";
|
|
46
|
-
suggestion = getSuggestionFromError(err);
|
|
47
|
-
}
|
|
48
40
|
}
|
|
49
|
-
return
|
|
41
|
+
return id;
|
|
50
42
|
}
|
|
51
|
-
exports.
|
|
52
|
-
async function trySiteID(projectNumber, id) {
|
|
43
|
+
exports.pickHostingSiteName = pickHostingSiteName;
|
|
44
|
+
async function trySiteID(projectNumber, id, nonInteractive = false) {
|
|
53
45
|
try {
|
|
54
46
|
await (0, api_1.createSite)(projectNumber, id, "", true);
|
|
55
47
|
return { available: true };
|
|
@@ -58,6 +50,9 @@ async function trySiteID(projectNumber, id) {
|
|
|
58
50
|
if (!(err instanceof error_1.FirebaseError)) {
|
|
59
51
|
throw err;
|
|
60
52
|
}
|
|
53
|
+
if (nonInteractive) {
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
61
56
|
const suggestion = getSuggestionFromError(err);
|
|
62
57
|
return { available: false, suggestion };
|
|
63
58
|
}
|
|
@@ -50,7 +50,7 @@ const templateServiceInfo = {
|
|
|
50
50
|
};
|
|
51
51
|
async function askQuestions(setup) {
|
|
52
52
|
const info = {
|
|
53
|
-
|
|
53
|
+
flow: "",
|
|
54
54
|
appDescription: "",
|
|
55
55
|
serviceId: "",
|
|
56
56
|
locationId: "",
|
|
@@ -97,7 +97,7 @@ async function askQuestions(setup) {
|
|
|
97
97
|
}
|
|
98
98
|
exports.askQuestions = askQuestions;
|
|
99
99
|
async function actuate(setup, config, options) {
|
|
100
|
-
var _a;
|
|
100
|
+
var _a, _b, _c;
|
|
101
101
|
const dir = config.get("dataconnect.source", "dataconnect");
|
|
102
102
|
const dataDir = config.get("emulators.dataconnect.dataDir", `${dir}/.dataconnect/pgliteData`);
|
|
103
103
|
config.set("emulators.dataconnect.dataDir", dataDir);
|
|
@@ -109,37 +109,38 @@ async function actuate(setup, config, options) {
|
|
|
109
109
|
info.cloudSqlInstanceId = info.cloudSqlInstanceId || `${info.serviceId.toLowerCase()}-fdc`;
|
|
110
110
|
info.locationId = info.locationId || exports.FDC_DEFAULT_REGION;
|
|
111
111
|
info.cloudSqlDatabase = info.cloudSqlDatabase || `fdcdb`;
|
|
112
|
+
const startTime = Date.now();
|
|
112
113
|
try {
|
|
113
114
|
await actuateWithInfo(setup, config, info, options);
|
|
114
115
|
await sdk.actuate(setup, config);
|
|
115
116
|
}
|
|
116
117
|
finally {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
?
|
|
118
|
+
const sdkInfo = (_b = setup.featureInfo) === null || _b === void 0 ? void 0 : _b.dataconnectSdk;
|
|
119
|
+
const source = ((_c = setup.featureInfo) === null || _c === void 0 ? void 0 : _c.dataconnectSource) || "init";
|
|
120
|
+
void (0, track_1.trackGA4)("dataconnect_init", Object.assign({ source, flow: info.flow.substring(1), project_status: setup.projectId
|
|
121
|
+
? (await (0, cloudbilling_1.isBillingEnabled)(setup))
|
|
121
122
|
? info.shouldProvisionCSQL
|
|
122
123
|
? "blaze_provisioned_csql"
|
|
123
124
|
: "blaze"
|
|
124
125
|
: "spark"
|
|
125
|
-
: "missing",
|
|
126
|
-
});
|
|
126
|
+
: "missing" }, (sdkInfo ? sdk.initAppCounters(sdkInfo) : {})), Date.now() - startTime);
|
|
127
127
|
}
|
|
128
128
|
if (info.appDescription) {
|
|
129
129
|
setup.instructions.push(`You can visualize the Data Connect Schema in Firebase Console:
|
|
130
130
|
|
|
131
131
|
https://console.firebase.google.com/project/${setup.projectId}/dataconnect/locations/${info.locationId}/services/${info.serviceId}/schema`);
|
|
132
132
|
}
|
|
133
|
-
if (!
|
|
133
|
+
if (!(await (0, cloudbilling_1.isBillingEnabled)(setup))) {
|
|
134
134
|
setup.instructions.push((0, freeTrial_1.upgradeInstructions)(setup.projectId || "your-firebase-project"));
|
|
135
135
|
}
|
|
136
136
|
setup.instructions.push(`Install the Data Connect VS Code Extensions. You can explore Data Connect Query on local pgLite and Cloud SQL Postgres Instance.`);
|
|
137
137
|
}
|
|
138
138
|
exports.actuate = actuate;
|
|
139
139
|
async function actuateWithInfo(setup, config, info, options) {
|
|
140
|
+
var _a;
|
|
140
141
|
const projectId = setup.projectId;
|
|
141
142
|
if (!projectId) {
|
|
142
|
-
info.
|
|
143
|
+
info.flow += "_save_template";
|
|
143
144
|
return await writeFiles(config, info, templateServiceInfo, options);
|
|
144
145
|
}
|
|
145
146
|
await (0, ensureApis_1.ensureApis)(projectId, true);
|
|
@@ -151,7 +152,7 @@ async function actuateWithInfo(setup, config, info, options) {
|
|
|
151
152
|
instanceId: info.cloudSqlInstanceId,
|
|
152
153
|
databaseId: info.cloudSqlDatabase,
|
|
153
154
|
requireGoogleMlIntegration: false,
|
|
154
|
-
source:
|
|
155
|
+
source: ((_a = setup.featureInfo) === null || _a === void 0 ? void 0 : _a.dataconnectSource) || "init",
|
|
155
156
|
});
|
|
156
157
|
}
|
|
157
158
|
const serviceName = `projects/${projectId}/locations/${info.locationId}/services/${info.serviceId}`;
|
|
@@ -160,10 +161,10 @@ async function actuateWithInfo(setup, config, info, options) {
|
|
|
160
161
|
await downloadService(info, serviceName);
|
|
161
162
|
}
|
|
162
163
|
if (info.serviceGql) {
|
|
163
|
-
info.
|
|
164
|
+
info.flow += "_save_downloaded";
|
|
164
165
|
return await writeFiles(config, info, info.serviceGql, options);
|
|
165
166
|
}
|
|
166
|
-
info.
|
|
167
|
+
info.flow += "_save_template";
|
|
167
168
|
return await writeFiles(config, info, templateServiceInfo, options);
|
|
168
169
|
}
|
|
169
170
|
const serviceAlreadyExists = !(await (0, client_1.createService)(projectId, info.locationId, info.serviceId));
|
|
@@ -171,7 +172,7 @@ async function actuateWithInfo(setup, config, info, options) {
|
|
|
171
172
|
const schemaFiles = [{ path: "schema.gql", content: schemaGql }];
|
|
172
173
|
if (serviceAlreadyExists) {
|
|
173
174
|
(0, utils_1.logLabeledError)("dataconnect", `Data Connect Service ${serviceName} already exists. Skip saving them...`);
|
|
174
|
-
info.
|
|
175
|
+
info.flow += "_save_gemini_service_already_exists";
|
|
175
176
|
return await writeFiles(config, info, { schemaGql: schemaFiles, connectors: [] }, options);
|
|
176
177
|
}
|
|
177
178
|
await (0, utils_1.promiseWithSpinner)(async () => {
|
|
@@ -198,12 +199,12 @@ async function actuateWithInfo(setup, config, info, options) {
|
|
|
198
199
|
],
|
|
199
200
|
},
|
|
200
201
|
];
|
|
201
|
-
info.
|
|
202
|
+
info.flow += "_save_gemini";
|
|
202
203
|
await writeFiles(config, info, { schemaGql: schemaFiles, connectors: connectors, seedDataGql: seedDataGql }, options);
|
|
203
204
|
}
|
|
204
205
|
catch (err) {
|
|
205
206
|
(0, utils_1.logLabeledError)("dataconnect", `Operation Generation failed...`);
|
|
206
|
-
info.
|
|
207
|
+
info.flow += "_save_gemini_operation_error";
|
|
207
208
|
await writeFiles(config, info, { schemaGql: schemaFiles, connectors: [] }, options);
|
|
208
209
|
throw err;
|
|
209
210
|
}
|
|
@@ -322,10 +323,10 @@ async function promptForExistingServices(setup, info) {
|
|
|
322
323
|
if (!choice) {
|
|
323
324
|
const existingServiceIds = existingServices.map((s) => s.name.split("/").pop());
|
|
324
325
|
info.serviceId = (0, utils_1.newUniqueId)(defaultServiceId(), existingServiceIds);
|
|
325
|
-
info.
|
|
326
|
+
info.flow += "_pick_new_service";
|
|
326
327
|
return;
|
|
327
328
|
}
|
|
328
|
-
info.
|
|
329
|
+
info.flow += "_pick_existing_service";
|
|
329
330
|
const serviceName = (0, names_1.parseServiceName)(choice.name);
|
|
330
331
|
info.serviceId = serviceName.serviceId;
|
|
331
332
|
info.locationId = serviceName.location;
|
|
@@ -429,11 +430,11 @@ async function promptForCloudSQL(setup, info) {
|
|
|
429
430
|
choices,
|
|
430
431
|
});
|
|
431
432
|
if (info.cloudSqlInstanceId !== "") {
|
|
432
|
-
info.
|
|
433
|
+
info.flow += "_pick_existing_csql";
|
|
433
434
|
info.locationId = choices.find((c) => c.value === info.cloudSqlInstanceId).location;
|
|
434
435
|
}
|
|
435
436
|
else {
|
|
436
|
-
info.
|
|
437
|
+
info.flow += "_pick_new_csql";
|
|
437
438
|
info.cloudSqlInstanceId = await (0, prompt_1.input)({
|
|
438
439
|
message: `What ID would you like to use for your new CloudSQL instance?`,
|
|
439
440
|
default: (0, utils_1.newUniqueId)(`${defaultServiceId().toLowerCase()}-fdc`, instances.map((i) => i.name)),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.addSdkGenerateToConnectorYaml = exports.actuate = exports.chooseApp = exports.askQuestions = exports.FDC_SDK_PLATFORM_ENV = exports.FDC_SDK_FRAMEWORKS_ENV = exports.FDC_APP_FOLDER = void 0;
|
|
3
|
+
exports.addSdkGenerateToConnectorYaml = exports.initAppCounters = exports.actuate = exports.chooseApp = exports.askQuestions = exports.FDC_SDK_PLATFORM_ENV = exports.FDC_SDK_FRAMEWORKS_ENV = exports.FDC_APP_FOLDER = void 0;
|
|
4
4
|
const yaml = require("yaml");
|
|
5
5
|
const clc = require("colorette");
|
|
6
6
|
const path = require("path");
|
|
@@ -16,6 +16,7 @@ const auth_1 = require("../../../auth");
|
|
|
16
16
|
const create_app_1 = require("./create_app");
|
|
17
17
|
const track_1 = require("../../../track");
|
|
18
18
|
const fsutils_1 = require("../../../fsutils");
|
|
19
|
+
const cloudbilling_1 = require("../../../gcp/cloudbilling");
|
|
19
20
|
exports.FDC_APP_FOLDER = "FDC_APP_FOLDER";
|
|
20
21
|
exports.FDC_SDK_FRAMEWORKS_ENV = "FDC_SDK_FRAMEWORKS";
|
|
21
22
|
exports.FDC_SDK_PLATFORM_ENV = "FDC_SDK_PLATFORM";
|
|
@@ -37,7 +38,7 @@ async function askQuestions(setup) {
|
|
|
37
38
|
{ name: `React${npxMissingWarning}`, value: "react" },
|
|
38
39
|
{ name: `Next.JS${npxMissingWarning}`, value: "next" },
|
|
39
40
|
{ name: `Flutter${flutterMissingWarning}`, value: "flutter" },
|
|
40
|
-
{ name: "
|
|
41
|
+
{ name: "skip", value: "skip" },
|
|
41
42
|
],
|
|
42
43
|
});
|
|
43
44
|
try {
|
|
@@ -51,7 +52,7 @@ async function askQuestions(setup) {
|
|
|
51
52
|
case "flutter":
|
|
52
53
|
await (0, create_app_1.createFlutterApp)((0, utils_1.newUniqueId)("flutter_app", (0, fsutils_1.listFiles)(cwd)));
|
|
53
54
|
break;
|
|
54
|
-
case "
|
|
55
|
+
case "skip":
|
|
55
56
|
break;
|
|
56
57
|
}
|
|
57
58
|
}
|
|
@@ -69,7 +70,7 @@ async function chooseApp() {
|
|
|
69
70
|
(0, utils_1.logLabeledSuccess)("dataconnect", `Detected existing apps ${apps.map((a) => (0, appUtils_1.appDescription)(a)).join(", ")}`);
|
|
70
71
|
}
|
|
71
72
|
else {
|
|
72
|
-
(0, utils_1.logLabeledWarning)("dataconnect", "
|
|
73
|
+
(0, utils_1.logLabeledWarning)("dataconnect", "Cannot detect an existing app in the current directory.");
|
|
73
74
|
}
|
|
74
75
|
const envAppFolder = (0, utils_1.envOverride)(exports.FDC_APP_FOLDER, "");
|
|
75
76
|
const envPlatform = (0, utils_1.envOverride)(exports.FDC_SDK_PLATFORM_ENV, "");
|
|
@@ -115,33 +116,55 @@ async function chooseApp() {
|
|
|
115
116
|
}
|
|
116
117
|
exports.chooseApp = chooseApp;
|
|
117
118
|
async function actuate(setup, config) {
|
|
118
|
-
var _a, _b;
|
|
119
|
-
const
|
|
120
|
-
const sdkInfo = (_b = setup.featureInfo) === null || _b === void 0 ? void 0 : _b.dataconnectSdk;
|
|
119
|
+
var _a, _b, _c;
|
|
120
|
+
const sdkInfo = (_a = setup.featureInfo) === null || _a === void 0 ? void 0 : _a.dataconnectSdk;
|
|
121
121
|
if (!sdkInfo) {
|
|
122
122
|
throw new Error("Data Connect SDK feature RequiredInfo is not provided");
|
|
123
123
|
}
|
|
124
|
+
const startTime = Date.now();
|
|
124
125
|
try {
|
|
125
126
|
await actuateWithInfo(setup, config, sdkInfo);
|
|
126
127
|
}
|
|
127
128
|
finally {
|
|
128
|
-
|
|
129
|
-
if (
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
else {
|
|
137
|
-
void (0, track_1.trackGA4)("dataconnect_init", {
|
|
138
|
-
project_status: setup.projectId ? (setup.isBillingEnabled ? "blaze" : "spark") : "missing",
|
|
139
|
-
flow: `cli_sdk_${flow}`,
|
|
140
|
-
});
|
|
129
|
+
const fdcInfo = (_b = setup.featureInfo) === null || _b === void 0 ? void 0 : _b.dataconnect;
|
|
130
|
+
if (!fdcInfo) {
|
|
131
|
+
const source = ((_c = setup.featureInfo) === null || _c === void 0 ? void 0 : _c.dataconnectSource) || "init_sdk";
|
|
132
|
+
void (0, track_1.trackGA4)("dataconnect_init", Object.assign({ source, project_status: setup.projectId
|
|
133
|
+
? (await (0, cloudbilling_1.isBillingEnabled)(setup))
|
|
134
|
+
? "blaze"
|
|
135
|
+
: "spark"
|
|
136
|
+
: "missing" }, initAppCounters(sdkInfo)), Date.now() - startTime);
|
|
141
137
|
}
|
|
142
138
|
}
|
|
143
139
|
}
|
|
144
140
|
exports.actuate = actuate;
|
|
141
|
+
function initAppCounters(info) {
|
|
142
|
+
var _a;
|
|
143
|
+
const counts = {
|
|
144
|
+
num_web_apps: 0,
|
|
145
|
+
num_android_apps: 0,
|
|
146
|
+
num_ios_apps: 0,
|
|
147
|
+
num_flutter_apps: 0,
|
|
148
|
+
};
|
|
149
|
+
for (const app of (_a = info.apps) !== null && _a !== void 0 ? _a : []) {
|
|
150
|
+
switch (app.platform) {
|
|
151
|
+
case appUtils_1.Platform.WEB:
|
|
152
|
+
counts.num_web_apps++;
|
|
153
|
+
break;
|
|
154
|
+
case appUtils_1.Platform.ANDROID:
|
|
155
|
+
counts.num_android_apps++;
|
|
156
|
+
break;
|
|
157
|
+
case appUtils_1.Platform.IOS:
|
|
158
|
+
counts.num_ios_apps++;
|
|
159
|
+
break;
|
|
160
|
+
case appUtils_1.Platform.FLUTTER:
|
|
161
|
+
counts.num_flutter_apps++;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return counts;
|
|
166
|
+
}
|
|
167
|
+
exports.initAppCounters = initAppCounters;
|
|
145
168
|
async function actuateWithInfo(setup, config, info) {
|
|
146
169
|
if (!info.apps.length) {
|
|
147
170
|
info.apps = await (0, appUtils_1.detectApps)(cwd);
|
|
@@ -245,7 +268,7 @@ function addSdkGenerateToConnectorYaml(connectorInfo, connectorYaml, app) {
|
|
|
245
268
|
case appUtils_1.Platform.FLUTTER: {
|
|
246
269
|
const dartSdk = {
|
|
247
270
|
outputDir: path.relative(connectorDir, path.join(appDir, `lib/dataconnect_generated`)),
|
|
248
|
-
package: "dataconnect_generated",
|
|
271
|
+
package: "dataconnect_generated/generated.dart",
|
|
249
272
|
};
|
|
250
273
|
if (!(0, lodash_1.isArray)(generate === null || generate === void 0 ? void 0 : generate.dartSdk)) {
|
|
251
274
|
generate.dartSdk = generate.dartSdk ? [generate.dartSdk] : [];
|