firebase-tools 13.26.0 → 13.27.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/apphosting/config.js +86 -1
- package/lib/apphosting/secrets/index.js +1 -39
- package/lib/apphosting/yaml.js +3 -0
- package/lib/commands/apphosting-config-export.js +4 -26
- package/lib/commands/index.js +2 -4
- package/lib/deploy/functions/release/fabricator.js +3 -3
- package/lib/emulator/apphosting/developmentServer.js +32 -0
- package/lib/emulator/apphosting/index.js +4 -3
- package/lib/emulator/apphosting/serve.js +8 -7
- package/lib/emulator/commandUtils.js +2 -1
- package/lib/emulator/controller.js +27 -18
- package/lib/emulator/dataconnect/pgliteServer.js +51 -18
- package/lib/emulator/dataconnectEmulator.js +26 -2
- package/lib/emulator/hub.js +16 -0
- package/lib/emulator/hubExport.js +23 -0
- package/lib/emulator/initEmulators.js +49 -0
- package/lib/emulator/types.js +2 -2
- package/lib/gcp/cloudfunctionsv2.js +1 -0
- package/lib/init/features/emulators.js +8 -0
- package/lib/utils.js +16 -1
- package/package.json +1 -1
- package/schema/firebase-config.json +3 -0
- package/lib/emulator/apphosting/utils.js +0 -18
package/lib/apphosting/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.loadConfigForEnvironment = exports.maybeAddSecretToYaml = exports.upsertEnv = exports.findEnv = exports.store = exports.load = exports.listAppHostingFilesInPath = exports.discoverBackendRoot = exports.APPHOSTING_YAML_FILE_REGEX = exports.APPHOSTING_LOCAL_YAML_FILE = exports.APPHOSTING_BASE_YAML_FILE = void 0;
|
|
3
|
+
exports.loadConfigToExportSecrets = exports.loadConfigForEnvironment = exports.exportConfig = exports.maybeAddSecretToYaml = exports.upsertEnv = exports.findEnv = exports.store = exports.load = exports.listAppHostingFilesInPath = exports.discoverBackendRoot = exports.APPHOSTING_YAML_FILE_REGEX = exports.APPHOSTING_LOCAL_YAML_FILE = exports.APPHOSTING_BASE_YAML_FILE = void 0;
|
|
4
4
|
const path_1 = require("path");
|
|
5
5
|
const fs_1 = require("fs");
|
|
6
6
|
const yaml = require("yaml");
|
|
@@ -8,9 +8,17 @@ const fs = require("../fsutils");
|
|
|
8
8
|
const prompt = require("../prompt");
|
|
9
9
|
const dialogs = require("./secrets/dialogs");
|
|
10
10
|
const yaml_1 = require("./yaml");
|
|
11
|
+
const error_1 = require("../error");
|
|
12
|
+
const utils_1 = require("./utils");
|
|
13
|
+
const secrets_1 = require("./secrets");
|
|
14
|
+
const logger_1 = require("../logger");
|
|
15
|
+
const utils_2 = require("../utils");
|
|
16
|
+
const projects_1 = require("../management/projects");
|
|
11
17
|
exports.APPHOSTING_BASE_YAML_FILE = "apphosting.yaml";
|
|
12
18
|
exports.APPHOSTING_LOCAL_YAML_FILE = "apphosting.local.yaml";
|
|
13
19
|
exports.APPHOSTING_YAML_FILE_REGEX = /^apphosting(\.[a-z0-9_]+)?\.yaml$/;
|
|
20
|
+
const SECRET_CONFIG = "Secret";
|
|
21
|
+
const EXPORTABLE_CONFIG = [SECRET_CONFIG];
|
|
14
22
|
function discoverBackendRoot(cwd) {
|
|
15
23
|
let dir = cwd;
|
|
16
24
|
while (!fs.fileExistsSync((0, path_1.resolve)(dir, exports.APPHOSTING_BASE_YAML_FILE))) {
|
|
@@ -108,6 +116,49 @@ async function maybeAddSecretToYaml(secretName) {
|
|
|
108
116
|
dynamicDispatch.store(path, projectYaml);
|
|
109
117
|
}
|
|
110
118
|
exports.maybeAddSecretToYaml = maybeAddSecretToYaml;
|
|
119
|
+
async function exportConfig(cwd, projectRoot, backendRoot, projectId, userGivenConfigFile) {
|
|
120
|
+
const choices = await prompt.prompt({}, [
|
|
121
|
+
{
|
|
122
|
+
type: "checkbox",
|
|
123
|
+
name: "configurations",
|
|
124
|
+
message: "What configs would you like to export?",
|
|
125
|
+
choices: EXPORTABLE_CONFIG,
|
|
126
|
+
},
|
|
127
|
+
]);
|
|
128
|
+
if (!choices.configurations.includes(SECRET_CONFIG)) {
|
|
129
|
+
logger_1.logger.info("No configs selected to export");
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (!projectId) {
|
|
133
|
+
const project = await (0, projects_1.getOrPromptProject)({});
|
|
134
|
+
projectId = project.projectId;
|
|
135
|
+
}
|
|
136
|
+
let localAppHostingConfig = yaml_1.AppHostingYamlConfig.empty();
|
|
137
|
+
const localAppHostingConfigPath = (0, path_1.resolve)(backendRoot, exports.APPHOSTING_LOCAL_YAML_FILE);
|
|
138
|
+
if (fs.fileExistsSync(localAppHostingConfigPath)) {
|
|
139
|
+
localAppHostingConfig = await yaml_1.AppHostingYamlConfig.loadFromFile(localAppHostingConfigPath);
|
|
140
|
+
}
|
|
141
|
+
const configToExport = await loadConfigToExportSecrets(cwd, userGivenConfigFile);
|
|
142
|
+
const secretsToExport = configToExport.secrets;
|
|
143
|
+
if (!secretsToExport) {
|
|
144
|
+
logger_1.logger.info("No secrets found to export in the chosen App Hosting config files");
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const secretMaterial = await (0, secrets_1.fetchSecrets)(projectId, secretsToExport);
|
|
148
|
+
for (const [key, value] of secretMaterial) {
|
|
149
|
+
localAppHostingConfig.addEnvironmentVariable({
|
|
150
|
+
variable: key,
|
|
151
|
+
value: value,
|
|
152
|
+
availability: ["RUNTIME"],
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
localAppHostingConfig.clearSecrets();
|
|
156
|
+
localAppHostingConfig.upsertFile(localAppHostingConfigPath);
|
|
157
|
+
logger_1.logger.info(`Wrote secrets as environment variables to ${exports.APPHOSTING_LOCAL_YAML_FILE}.`);
|
|
158
|
+
(0, utils_2.updateOrCreateGitignore)(projectRoot, [exports.APPHOSTING_LOCAL_YAML_FILE]);
|
|
159
|
+
logger_1.logger.info(`${exports.APPHOSTING_LOCAL_YAML_FILE} has been automatically added to your .gitignore.`);
|
|
160
|
+
}
|
|
161
|
+
exports.exportConfig = exportConfig;
|
|
111
162
|
async function loadConfigForEnvironment(envYamlPath, baseYamlPath) {
|
|
112
163
|
const envYamlConfig = await yaml_1.AppHostingYamlConfig.loadFromFile(envYamlPath);
|
|
113
164
|
if (baseYamlPath) {
|
|
@@ -118,3 +169,37 @@ async function loadConfigForEnvironment(envYamlPath, baseYamlPath) {
|
|
|
118
169
|
return envYamlConfig;
|
|
119
170
|
}
|
|
120
171
|
exports.loadConfigForEnvironment = loadConfigForEnvironment;
|
|
172
|
+
async function loadConfigToExportSecrets(cwd, userGivenConfigFile) {
|
|
173
|
+
if (userGivenConfigFile && !exports.APPHOSTING_YAML_FILE_REGEX.test(userGivenConfigFile)) {
|
|
174
|
+
throw new error_1.FirebaseError("Invalid apphosting yaml config file provided. File must be in format: 'apphosting.yaml' or 'apphosting.<environment>.yaml'");
|
|
175
|
+
}
|
|
176
|
+
const allConfigs = getValidConfigs(cwd);
|
|
177
|
+
let userGivenConfigFilePath;
|
|
178
|
+
if (userGivenConfigFile) {
|
|
179
|
+
if (!allConfigs.has(userGivenConfigFile)) {
|
|
180
|
+
throw new error_1.FirebaseError(`The provided app hosting config file "${userGivenConfigFile}" does not exist`);
|
|
181
|
+
}
|
|
182
|
+
userGivenConfigFilePath = allConfigs.get(userGivenConfigFile);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
userGivenConfigFilePath = await (0, utils_1.promptForAppHostingYaml)(allConfigs, "Which environment would you like to export secrets from Secret Manager for?");
|
|
186
|
+
}
|
|
187
|
+
if (userGivenConfigFile === exports.APPHOSTING_BASE_YAML_FILE) {
|
|
188
|
+
return yaml_1.AppHostingYamlConfig.loadFromFile(allConfigs.get(exports.APPHOSTING_BASE_YAML_FILE));
|
|
189
|
+
}
|
|
190
|
+
const baseFilePath = allConfigs.get(exports.APPHOSTING_BASE_YAML_FILE);
|
|
191
|
+
return await loadConfigForEnvironment(userGivenConfigFilePath, baseFilePath);
|
|
192
|
+
}
|
|
193
|
+
exports.loadConfigToExportSecrets = loadConfigToExportSecrets;
|
|
194
|
+
function getValidConfigs(cwd) {
|
|
195
|
+
const appHostingConfigPaths = listAppHostingFilesInPath(cwd).filter((path) => !path.endsWith(exports.APPHOSTING_LOCAL_YAML_FILE));
|
|
196
|
+
if (appHostingConfigPaths.length === 0) {
|
|
197
|
+
throw new error_1.FirebaseError("No apphosting.*.yaml configs found");
|
|
198
|
+
}
|
|
199
|
+
const fileNameToPathMap = new Map();
|
|
200
|
+
for (const path of appHostingConfigPaths) {
|
|
201
|
+
const fileName = (0, path_1.basename)(path);
|
|
202
|
+
fileNameToPathMap.set(fileName, path);
|
|
203
|
+
}
|
|
204
|
+
return fileNameToPathMap;
|
|
205
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getSecretNameParts = exports.
|
|
3
|
+
exports.getSecretNameParts = exports.fetchSecrets = exports.upsertSecret = exports.grantSecretAccess = exports.serviceAccountsForBackend = exports.toMulti = void 0;
|
|
4
4
|
const error_1 = require("../../error");
|
|
5
5
|
const gcsm = require("../../gcp/secretManager");
|
|
6
6
|
const gcb = require("../../gcp/cloudbuild");
|
|
@@ -10,10 +10,6 @@ const secretManager_1 = require("../../gcp/secretManager");
|
|
|
10
10
|
const secretManager_2 = require("../../gcp/secretManager");
|
|
11
11
|
const utils = require("../../utils");
|
|
12
12
|
const prompt = require("../../prompt");
|
|
13
|
-
const path_1 = require("path");
|
|
14
|
-
const config_1 = require("../config");
|
|
15
|
-
const utils_1 = require("../utils");
|
|
16
|
-
const yaml_1 = require("../yaml");
|
|
17
13
|
function toMulti(accounts) {
|
|
18
14
|
const m = {
|
|
19
15
|
buildServiceAccounts: [accounts.buildServiceAccount],
|
|
@@ -125,40 +121,6 @@ async function fetchSecrets(projectId, secrets) {
|
|
|
125
121
|
return secretsKeyValuePairs;
|
|
126
122
|
}
|
|
127
123
|
exports.fetchSecrets = fetchSecrets;
|
|
128
|
-
async function loadConfigToExport(cwd, userGivenConfigFile) {
|
|
129
|
-
if (userGivenConfigFile && !config_1.APPHOSTING_YAML_FILE_REGEX.test(userGivenConfigFile)) {
|
|
130
|
-
throw new error_1.FirebaseError("Invalid apphosting yaml config file provided. File must be in format: 'apphosting.yaml' or 'apphosting.<environment>.yaml'");
|
|
131
|
-
}
|
|
132
|
-
const allConfigs = getValidConfigs(cwd);
|
|
133
|
-
let userGivenConfigFilePath;
|
|
134
|
-
if (userGivenConfigFile) {
|
|
135
|
-
if (!allConfigs.has(userGivenConfigFile)) {
|
|
136
|
-
throw new error_1.FirebaseError(`The provided app hosting config file "${userGivenConfigFile}" does not exist`);
|
|
137
|
-
}
|
|
138
|
-
userGivenConfigFilePath = allConfigs.get(userGivenConfigFile);
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
userGivenConfigFilePath = await (0, utils_1.promptForAppHostingYaml)(allConfigs, "Which environment would you like to export secrets from Secret Manager for?");
|
|
142
|
-
}
|
|
143
|
-
if (userGivenConfigFile === config_1.APPHOSTING_BASE_YAML_FILE) {
|
|
144
|
-
return yaml_1.AppHostingYamlConfig.loadFromFile(allConfigs.get(config_1.APPHOSTING_BASE_YAML_FILE));
|
|
145
|
-
}
|
|
146
|
-
const baseFilePath = allConfigs.get(config_1.APPHOSTING_BASE_YAML_FILE);
|
|
147
|
-
return await (0, config_1.loadConfigForEnvironment)(userGivenConfigFilePath, baseFilePath);
|
|
148
|
-
}
|
|
149
|
-
exports.loadConfigToExport = loadConfigToExport;
|
|
150
|
-
function getValidConfigs(cwd) {
|
|
151
|
-
const appHostingConfigPaths = (0, config_1.listAppHostingFilesInPath)(cwd).filter((path) => !path.endsWith(config_1.APPHOSTING_LOCAL_YAML_FILE));
|
|
152
|
-
if (appHostingConfigPaths.length === 0) {
|
|
153
|
-
throw new error_1.FirebaseError("No apphosting.*.yaml configs found");
|
|
154
|
-
}
|
|
155
|
-
const fileNameToPathMap = new Map();
|
|
156
|
-
for (const path of appHostingConfigPaths) {
|
|
157
|
-
const fileName = (0, path_1.basename)(path);
|
|
158
|
-
fileNameToPathMap.set(fileName, path);
|
|
159
|
-
}
|
|
160
|
-
return fileNameToPathMap;
|
|
161
|
-
}
|
|
162
124
|
function getSecretNameParts(secret) {
|
|
163
125
|
let [name, version] = secret.split("@");
|
|
164
126
|
if (!version) {
|
package/lib/apphosting/yaml.js
CHANGED
|
@@ -43,6 +43,9 @@ class AppHostingYamlConfig {
|
|
|
43
43
|
addSecret(secret) {
|
|
44
44
|
this._secrets.set(secret.variable, secret);
|
|
45
45
|
}
|
|
46
|
+
clearSecrets() {
|
|
47
|
+
this._secrets.clear();
|
|
48
|
+
}
|
|
46
49
|
merge(other) {
|
|
47
50
|
for (const [key, value] of other._environmentVariables) {
|
|
48
51
|
this._environmentVariables.set(key, value);
|
|
@@ -2,17 +2,13 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.command = void 0;
|
|
4
4
|
const command_1 = require("../command");
|
|
5
|
-
const logger_1 = require("../logger");
|
|
6
5
|
const projectUtils_1 = require("../projectUtils");
|
|
7
6
|
const requireAuth_1 = require("../requireAuth");
|
|
8
7
|
const secretManager = require("../gcp/secretManager");
|
|
9
8
|
const requirePermissions_1 = require("../requirePermissions");
|
|
10
9
|
const config_1 = require("../apphosting/config");
|
|
11
|
-
const secrets_1 = require("../apphosting/secrets");
|
|
12
|
-
const path_1 = require("path");
|
|
13
|
-
const fs = require("../fsutils");
|
|
14
|
-
const yaml_1 = require("../apphosting/yaml");
|
|
15
10
|
const error_1 = require("../error");
|
|
11
|
+
const detectProjectRoot_1 = require("../detectProjectRoot");
|
|
16
12
|
exports.command = new command_1.Command("apphosting:config:export")
|
|
17
13
|
.description("Export App Hosting configurations such as secrets into an apphosting.local.yaml file")
|
|
18
14
|
.option("-s, --secrets <apphosting.yaml or apphosting.<environment>.yaml file to export secrets from>", "This command combines the base apphosting.yaml with the specified environment-specific file (e.g., apphosting.staging.yaml). If keys conflict, the environment-specific file takes precedence.")
|
|
@@ -20,32 +16,14 @@ exports.command = new command_1.Command("apphosting:config:export")
|
|
|
20
16
|
.before(secretManager.ensureApi)
|
|
21
17
|
.before(requirePermissions_1.requirePermissions, ["secretmanager.versions.access"])
|
|
22
18
|
.action(async (options) => {
|
|
19
|
+
var _a;
|
|
23
20
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
24
21
|
const environmentConfigFile = options.secrets;
|
|
25
22
|
const cwd = process.cwd();
|
|
26
|
-
let localAppHostingConfig = yaml_1.AppHostingYamlConfig.empty();
|
|
27
23
|
const backendRoot = (0, config_1.discoverBackendRoot)(cwd);
|
|
28
24
|
if (!backendRoot) {
|
|
29
25
|
throw new error_1.FirebaseError("Missing apphosting.yaml: This command requires an apphosting.yaml configuration file. Please run 'firebase init apphosting' and try again.");
|
|
30
26
|
}
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
localAppHostingConfig = await yaml_1.AppHostingYamlConfig.loadFromFile(localAppHostingConfigPath);
|
|
34
|
-
}
|
|
35
|
-
const configToExport = await (0, secrets_1.loadConfigToExport)(cwd, environmentConfigFile);
|
|
36
|
-
const secretsToExport = configToExport.secrets;
|
|
37
|
-
if (!secretsToExport) {
|
|
38
|
-
logger_1.logger.warn("No secrets found to export in the chosen App Hosting config files");
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
const secretMaterial = await (0, secrets_1.fetchSecrets)(projectId, secretsToExport);
|
|
42
|
-
for (const [key, value] of secretMaterial) {
|
|
43
|
-
localAppHostingConfig.addEnvironmentVariable({
|
|
44
|
-
variable: key,
|
|
45
|
-
value: value,
|
|
46
|
-
availability: ["RUNTIME"],
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
localAppHostingConfig.upsertFile(localAppHostingConfigPath);
|
|
50
|
-
logger_1.logger.info(`Wrote secrets as environment variables to ${config_1.APPHOSTING_LOCAL_YAML_FILE}.`);
|
|
27
|
+
const projectRoot = (_a = (0, detectProjectRoot_1.detectProjectRoot)({})) !== null && _a !== void 0 ? _a : backendRoot;
|
|
28
|
+
await (0, config_1.exportConfig)(cwd, projectRoot, backendRoot, projectId, environmentConfigFile);
|
|
51
29
|
});
|
package/lib/commands/index.js
CHANGED
|
@@ -172,6 +172,8 @@ function load(client) {
|
|
|
172
172
|
client.apphosting.secrets.grantaccess = loadCommand("apphosting-secrets-grantaccess");
|
|
173
173
|
client.apphosting.secrets.describe = loadCommand("apphosting-secrets-describe");
|
|
174
174
|
client.apphosting.secrets.access = loadCommand("apphosting-secrets-access");
|
|
175
|
+
client.apphosting.config = {};
|
|
176
|
+
client.apphosting.config.export = loadCommand("apphosting-config-export");
|
|
175
177
|
if (experiments.isEnabled("internaltesting")) {
|
|
176
178
|
client.apphosting.builds = {};
|
|
177
179
|
client.apphosting.builds.get = loadCommand("apphosting-builds-get");
|
|
@@ -182,10 +184,6 @@ function load(client) {
|
|
|
182
184
|
client.apphosting.rollouts.create = loadCommand("apphosting-rollouts-create");
|
|
183
185
|
client.apphosting.rollouts.list = loadCommand("apphosting-rollouts-list");
|
|
184
186
|
}
|
|
185
|
-
if (experiments.isEnabled("emulatorapphosting")) {
|
|
186
|
-
client.apphosting.config = {};
|
|
187
|
-
client.apphosting.config.export = loadCommand("apphosting-config-export");
|
|
188
|
-
}
|
|
189
187
|
}
|
|
190
188
|
client.login = loadCommand("login");
|
|
191
189
|
client.login.add = loadCommand("login-add");
|
|
@@ -224,7 +224,7 @@ class Fabricator {
|
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
226
|
async createV2Function(endpoint, scraper) {
|
|
227
|
-
var _a, _b, _c, _d
|
|
227
|
+
var _a, _b, _c, _d;
|
|
228
228
|
const storageSource = (_a = this.sources[endpoint.codebase]) === null || _a === void 0 ? void 0 : _a.storage;
|
|
229
229
|
if (!storageSource) {
|
|
230
230
|
logger_1.logger.debug("Precondition failed. Cannot create a GCFv2 function without storage");
|
|
@@ -295,8 +295,8 @@ class Fabricator {
|
|
|
295
295
|
}
|
|
296
296
|
});
|
|
297
297
|
}
|
|
298
|
-
endpoint.uri =
|
|
299
|
-
const serviceName = (
|
|
298
|
+
endpoint.uri = resultFunction.url;
|
|
299
|
+
const serviceName = (_d = resultFunction.serviceConfig) === null || _d === void 0 ? void 0 : _d.service;
|
|
300
300
|
endpoint.runServiceId = utils.last(serviceName === null || serviceName === void 0 ? void 0 : serviceName.split("/"));
|
|
301
301
|
if (!serviceName) {
|
|
302
302
|
logger_1.logger.debug("Result function unexpectedly didn't have a service name.");
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.detectStartCommand = exports.detectPackageManager = exports.logger = void 0;
|
|
4
|
+
const fs_extra_1 = require("fs-extra");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const emulatorLogger_1 = require("../emulatorLogger");
|
|
7
|
+
const types_1 = require("../types");
|
|
8
|
+
const error_1 = require("../../error");
|
|
9
|
+
exports.logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.APPHOSTING);
|
|
10
|
+
async function detectPackageManager(rootdir) {
|
|
11
|
+
if (await (0, fs_extra_1.pathExists)((0, path_1.join)(rootdir, "pnpm-lock.yaml"))) {
|
|
12
|
+
return "pnpm";
|
|
13
|
+
}
|
|
14
|
+
if (await (0, fs_extra_1.pathExists)((0, path_1.join)(rootdir, "yarn.lock"))) {
|
|
15
|
+
return "yarn";
|
|
16
|
+
}
|
|
17
|
+
if (await (0, fs_extra_1.pathExists)((0, path_1.join)(rootdir, "package-lock.json"))) {
|
|
18
|
+
return "npm";
|
|
19
|
+
}
|
|
20
|
+
throw new error_1.FirebaseError("Unsupported package manager");
|
|
21
|
+
}
|
|
22
|
+
exports.detectPackageManager = detectPackageManager;
|
|
23
|
+
async function detectStartCommand(rootDir) {
|
|
24
|
+
try {
|
|
25
|
+
const packageManager = await detectPackageManager(rootDir);
|
|
26
|
+
return `${packageManager} run dev`;
|
|
27
|
+
}
|
|
28
|
+
catch (e) {
|
|
29
|
+
throw new error_1.FirebaseError("Failed to auto-detect your project's start command. Consider manually setting the start command by setting `firebase.json#emulators.apphosting.startCommandOverride`");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.detectStartCommand = detectStartCommand;
|
|
@@ -3,13 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.AppHostingEmulator = void 0;
|
|
4
4
|
const types_1 = require("../types");
|
|
5
5
|
const serve_1 = require("./serve");
|
|
6
|
-
const
|
|
6
|
+
const developmentServer_1 = require("./developmentServer");
|
|
7
7
|
class AppHostingEmulator {
|
|
8
8
|
constructor(args) {
|
|
9
9
|
this.args = args;
|
|
10
10
|
}
|
|
11
11
|
async start() {
|
|
12
12
|
const { hostname, port } = await (0, serve_1.start)({
|
|
13
|
+
port: this.args.port,
|
|
13
14
|
startCommand: this.args.startCommandOverride,
|
|
14
15
|
rootDirectory: this.args.rootDirectory,
|
|
15
16
|
});
|
|
@@ -17,11 +18,11 @@ class AppHostingEmulator {
|
|
|
17
18
|
this.args.options.port = port;
|
|
18
19
|
}
|
|
19
20
|
connect() {
|
|
20
|
-
|
|
21
|
+
developmentServer_1.logger.logLabeled("INFO", types_1.Emulators.APPHOSTING, "connecting apphosting emulator");
|
|
21
22
|
return Promise.resolve();
|
|
22
23
|
}
|
|
23
24
|
stop() {
|
|
24
|
-
|
|
25
|
+
developmentServer_1.logger.logLabeled("INFO", types_1.Emulators.APPHOSTING, "stopping apphosting emulator");
|
|
25
26
|
return Promise.resolve();
|
|
26
27
|
}
|
|
27
28
|
getInfo() {
|
|
@@ -3,16 +3,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.start = void 0;
|
|
4
4
|
const net_1 = require("net");
|
|
5
5
|
const portUtils_1 = require("../portUtils");
|
|
6
|
-
const
|
|
6
|
+
const developmentServer_1 = require("./developmentServer");
|
|
7
7
|
const constants_1 = require("../constants");
|
|
8
8
|
const spawn_1 = require("../../init/spawn");
|
|
9
|
-
const
|
|
9
|
+
const developmentServer_2 = require("./developmentServer");
|
|
10
10
|
const types_1 = require("../types");
|
|
11
11
|
const config_1 = require("./config");
|
|
12
12
|
const projectPath_1 = require("../../projectPath");
|
|
13
13
|
async function start(options) {
|
|
14
|
+
var _a;
|
|
14
15
|
const hostname = constants_1.DEFAULT_HOST;
|
|
15
|
-
let port = constants_1.DEFAULT_PORTS.apphosting;
|
|
16
|
+
let port = (_a = options === null || options === void 0 ? void 0 : options.port) !== null && _a !== void 0 ? _a : constants_1.DEFAULT_PORTS.apphosting;
|
|
16
17
|
while (!(await availablePort(hostname, port))) {
|
|
17
18
|
port += 1;
|
|
18
19
|
}
|
|
@@ -30,13 +31,13 @@ async function serve(port, startCommand, backendRelativeDir) {
|
|
|
30
31
|
}
|
|
31
32
|
const environmentVariablesToInject = Object.assign(Object.assign({}, environmentVariablesAsRecord), { PORT: port.toString() });
|
|
32
33
|
if (startCommand) {
|
|
33
|
-
|
|
34
|
+
developmentServer_2.logger.logLabeled("BULLET", types_1.Emulators.APPHOSTING, `running custom start command: '${startCommand}'`);
|
|
34
35
|
await (0, spawn_1.spawnWithCommandString)(startCommand, backendRoot, environmentVariablesToInject);
|
|
35
36
|
return;
|
|
36
37
|
}
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
await (0, spawn_1.
|
|
38
|
+
const detectedStartCommand = await (0, developmentServer_1.detectStartCommand)(backendRoot);
|
|
39
|
+
developmentServer_2.logger.logLabeled("BULLET", types_1.Emulators.APPHOSTING, `starting app with: '${detectedStartCommand}'`);
|
|
40
|
+
await (0, spawn_1.spawnWithCommandString)(detectedStartCommand, backendRoot, environmentVariablesToInject);
|
|
40
41
|
}
|
|
41
42
|
function availablePort(host, port) {
|
|
42
43
|
return (0, portUtils_1.checkListenable)({
|
|
@@ -300,8 +300,9 @@ async function emulatorExec(script, options) {
|
|
|
300
300
|
exitCode = await runScript(script, extraEnv);
|
|
301
301
|
await controller.onExit(options);
|
|
302
302
|
}
|
|
303
|
-
catch (
|
|
303
|
+
catch (err) {
|
|
304
304
|
await (0, webhook_1.sendVSCodeMessage)({ message: webhook_1.VSCODE_MESSAGE.EMULATORS_START_ERRORED });
|
|
305
|
+
throw err;
|
|
305
306
|
}
|
|
306
307
|
finally {
|
|
307
308
|
await controller.cleanShutdown();
|
|
@@ -58,7 +58,7 @@ async function exportOnExit(options) {
|
|
|
58
58
|
await exportEmulatorData(exportOnExitDir, options, "exit");
|
|
59
59
|
}
|
|
60
60
|
catch (e) {
|
|
61
|
-
utils.logWarning(e);
|
|
61
|
+
utils.logWarning(`${e}`);
|
|
62
62
|
utils.logWarning(`Automatic export to "${exportOnExitDir}" failed, going to exit now...`);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
@@ -561,19 +561,30 @@ async function startAll(options, showUI = true, runningTestScript = false) {
|
|
|
561
561
|
else if (config.length > 1) {
|
|
562
562
|
logger_1.logger.warn(`TODO: Add support for multiple services in the Data Connect emulator. Currently emulating first service ${config[0].source}`);
|
|
563
563
|
}
|
|
564
|
-
const
|
|
565
|
-
const dataConnectEmulator = new dataconnectEmulator_1.DataConnectEmulator({
|
|
564
|
+
const args = {
|
|
566
565
|
listen: listenForEmulator.dataconnect,
|
|
567
566
|
projectId,
|
|
568
567
|
auto_download: true,
|
|
569
|
-
configDir,
|
|
568
|
+
configDir: config[0].source,
|
|
570
569
|
rc: options.rc,
|
|
571
570
|
config: options.config,
|
|
572
571
|
autoconnectToPostgres: true,
|
|
573
572
|
postgresListen: listenForEmulator["dataconnect.postgres"],
|
|
574
573
|
enable_output_generated_sdk: true,
|
|
575
574
|
enable_output_schema_extensions: true,
|
|
576
|
-
}
|
|
575
|
+
};
|
|
576
|
+
if (exportMetadata.dataconnect) {
|
|
577
|
+
utils.assertIsString(options.import);
|
|
578
|
+
const importDirAbsPath = path.resolve(options.import);
|
|
579
|
+
const exportMetadataFilePath = path.resolve(importDirAbsPath, exportMetadata.dataconnect.path);
|
|
580
|
+
emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.DATACONNECT).logLabeled("BULLET", "dataconnect", `Importing data from ${exportMetadataFilePath}`);
|
|
581
|
+
args.importPath = exportMetadataFilePath;
|
|
582
|
+
void (0, track_1.trackEmulator)("emulator_import", {
|
|
583
|
+
initiated_by: "start",
|
|
584
|
+
emulator_name: types_1.Emulators.DATACONNECT,
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
const dataConnectEmulator = new dataconnectEmulator_1.DataConnectEmulator(args);
|
|
577
588
|
await startEmulator(dataConnectEmulator);
|
|
578
589
|
}
|
|
579
590
|
if (listenForEmulator.storage) {
|
|
@@ -601,19 +612,17 @@ async function startAll(options, showUI = true, runningTestScript = false) {
|
|
|
601
612
|
});
|
|
602
613
|
await startEmulator(hostingEmulator);
|
|
603
614
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
await startEmulator(apphostingEmulator);
|
|
616
|
-
}
|
|
615
|
+
const apphostingConfig = (_l = options.config.src.emulators) === null || _l === void 0 ? void 0 : _l[types_1.Emulators.APPHOSTING];
|
|
616
|
+
if (listenForEmulator.apphosting) {
|
|
617
|
+
const apphostingAddr = legacyGetFirstAddr(types_1.Emulators.APPHOSTING);
|
|
618
|
+
const apphostingEmulator = new apphosting_1.AppHostingEmulator({
|
|
619
|
+
host: apphostingAddr.host,
|
|
620
|
+
port: apphostingAddr.port,
|
|
621
|
+
startCommandOverride: apphostingConfig === null || apphostingConfig === void 0 ? void 0 : apphostingConfig.startCommandOverride,
|
|
622
|
+
rootDirectory: apphostingConfig === null || apphostingConfig === void 0 ? void 0 : apphostingConfig.rootDirectory,
|
|
623
|
+
options,
|
|
624
|
+
});
|
|
625
|
+
await startEmulator(apphostingEmulator);
|
|
617
626
|
}
|
|
618
627
|
if (listenForEmulator.logging) {
|
|
619
628
|
const loggingAddr = legacyGetFirstAddr(types_1.Emulators.LOGGING);
|
|
@@ -19,17 +19,28 @@ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _ar
|
|
|
19
19
|
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
|
20
20
|
};
|
|
21
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
-
exports.PGliteExtendedQueryPatch = exports.PostgresServer = void 0;
|
|
22
|
+
exports.PGliteExtendedQueryPatch = exports.PostgresServer = exports.TRUNCATE_TABLES_SQL = void 0;
|
|
23
23
|
const pglite_1 = require("@electric-sql/pglite");
|
|
24
24
|
const { dynamicImport } = require(true && "../../dynamicImport");
|
|
25
25
|
const net = require("node:net");
|
|
26
|
+
const fs = require("fs");
|
|
26
27
|
const index_1 = require("./pg-gateway/index");
|
|
27
28
|
const node_1 = require("./pg-gateway/platforms/node");
|
|
28
29
|
const logger_1 = require("../../logger");
|
|
30
|
+
exports.TRUNCATE_TABLES_SQL = `
|
|
31
|
+
DO $do$
|
|
32
|
+
BEGIN
|
|
33
|
+
EXECUTE
|
|
34
|
+
(SELECT 'TRUNCATE TABLE ' || string_agg(oid::regclass::text, ', ') || ' CASCADE'
|
|
35
|
+
FROM pg_class
|
|
36
|
+
WHERE relkind = 'r'
|
|
37
|
+
AND relnamespace = 'public'::regnamespace
|
|
38
|
+
);
|
|
39
|
+
END
|
|
40
|
+
$do$;`;
|
|
29
41
|
class PostgresServer {
|
|
30
42
|
async createPGServer(host = "127.0.0.1", port) {
|
|
31
|
-
const
|
|
32
|
-
await db.waitReady;
|
|
43
|
+
const getDb = this.getDb.bind(this);
|
|
33
44
|
const server = net.createServer(async (socket) => {
|
|
34
45
|
const connection = await (0, node_1.fromNodeSocket)(socket, {
|
|
35
46
|
serverVersion: "16.3 (PGlite 0.2.0)",
|
|
@@ -38,6 +49,7 @@ class PostgresServer {
|
|
|
38
49
|
if (!isAuthenticated) {
|
|
39
50
|
return;
|
|
40
51
|
}
|
|
52
|
+
const db = await getDb();
|
|
41
53
|
const result = await db.execProtocolRaw(data);
|
|
42
54
|
return extendedQueryPatch.filterResponse(data, result);
|
|
43
55
|
},
|
|
@@ -55,29 +67,50 @@ class PostgresServer {
|
|
|
55
67
|
resolve();
|
|
56
68
|
});
|
|
57
69
|
});
|
|
58
|
-
await db.waitReady;
|
|
59
70
|
await listeningPromise;
|
|
60
71
|
return server;
|
|
61
72
|
}
|
|
62
73
|
async getDb() {
|
|
63
|
-
if (this.db) {
|
|
64
|
-
|
|
74
|
+
if (!this.db) {
|
|
75
|
+
const vector = (await dynamicImport("@electric-sql/pglite/vector")).vector;
|
|
76
|
+
const uuidOssp = (await dynamicImport("@electric-sql/pglite/contrib/uuid_ossp")).uuid_ossp;
|
|
77
|
+
const pgliteArgs = {
|
|
78
|
+
username: this.username,
|
|
79
|
+
database: this.database,
|
|
80
|
+
debug: 0,
|
|
81
|
+
extensions: {
|
|
82
|
+
vector,
|
|
83
|
+
uuidOssp,
|
|
84
|
+
},
|
|
85
|
+
dataDir: this.dataDirectory,
|
|
86
|
+
};
|
|
87
|
+
if (this.importPath) {
|
|
88
|
+
logger_1.logger.debug(`Importing from ${this.importPath}`);
|
|
89
|
+
const rf = fs.readFileSync(this.importPath);
|
|
90
|
+
const file = new File([rf], this.importPath);
|
|
91
|
+
pgliteArgs.loadDataDir = file;
|
|
92
|
+
}
|
|
93
|
+
this.db = await pglite_1.PGlite.create(pgliteArgs);
|
|
94
|
+
await this.db.waitReady;
|
|
65
95
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
96
|
+
return this.db;
|
|
97
|
+
}
|
|
98
|
+
async clearDb() {
|
|
99
|
+
const db = await this.getDb();
|
|
100
|
+
await db.query(exports.TRUNCATE_TABLES_SQL);
|
|
101
|
+
}
|
|
102
|
+
async exportData(exportPath) {
|
|
103
|
+
const db = await this.getDb();
|
|
104
|
+
const dump = await db.dumpDataDir();
|
|
105
|
+
const arrayBuff = await dump.arrayBuffer();
|
|
106
|
+
fs.writeFileSync(exportPath, new Uint8Array(arrayBuff));
|
|
77
107
|
}
|
|
78
|
-
constructor(database, username) {
|
|
108
|
+
constructor(database, username, dataDirectory, importPath) {
|
|
109
|
+
this.db = undefined;
|
|
79
110
|
this.username = username;
|
|
80
111
|
this.database = database;
|
|
112
|
+
this.dataDirectory = dataDirectory;
|
|
113
|
+
this.importPath = importPath;
|
|
81
114
|
}
|
|
82
115
|
}
|
|
83
116
|
exports.PostgresServer = PostgresServer;
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DataConnectEmulatorClient = exports.DataConnectEmulator = exports.dataConnectEmulatorEvents = void 0;
|
|
4
4
|
const childProcess = require("child_process");
|
|
5
|
+
const pg = require("pg");
|
|
5
6
|
const events_1 = require("events");
|
|
6
7
|
const clc = require("colorette");
|
|
8
|
+
const path = require("path");
|
|
7
9
|
const api_1 = require("../api");
|
|
8
10
|
const constants_1 = require("./constants");
|
|
9
11
|
const downloadableEmulators_1 = require("./downloadableEmulators");
|
|
@@ -63,8 +65,12 @@ class DataConnectEmulator {
|
|
|
63
65
|
this.logger.logLabeled("INFO", "dataconnect", `FIREBASE_DATACONNECT_POSTGRESQL_STRING is set to ${clc.bold(connStr)} - using that instead of starting a new database`);
|
|
64
66
|
}
|
|
65
67
|
else if (pgHost && pgPort) {
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
+
const dataDirectory = this.args.config.get("emulators.dataconnect.dataDir");
|
|
69
|
+
const postgresDumpPath = this.args.importPath
|
|
70
|
+
? path.join(this.args.importPath, "postgres.tar.gz")
|
|
71
|
+
: undefined;
|
|
72
|
+
this.postgresServer = new pgliteServer_1.PostgresServer(dbId, "postgres", dataDirectory, postgresDumpPath);
|
|
73
|
+
const server = await this.postgresServer.createPGServer(pgHost, pgPort);
|
|
68
74
|
const connectableHost = (0, utils_1.connectableHostname)(pgHost);
|
|
69
75
|
connStr = `postgres://${connectableHost}:${pgPort}/${dbId}?sslmode=disable`;
|
|
70
76
|
server.on("error", (err) => {
|
|
@@ -110,6 +116,24 @@ class DataConnectEmulator {
|
|
|
110
116
|
getName() {
|
|
111
117
|
return types_1.Emulators.DATACONNECT;
|
|
112
118
|
}
|
|
119
|
+
async clearData() {
|
|
120
|
+
if (this.postgresServer) {
|
|
121
|
+
await this.postgresServer.clearDb();
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
const conn = new pg.Client((0, api_1.dataConnectLocalConnString)());
|
|
125
|
+
await conn.query(pgliteServer_1.TRUNCATE_TABLES_SQL);
|
|
126
|
+
await conn.end();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async exportData(exportPath) {
|
|
130
|
+
if (this.postgresServer) {
|
|
131
|
+
await this.postgresServer.exportData(path.join(exportPath, "postgres.tar.gz"));
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
throw new error_1.FirebaseError("The Data Connect emulator is currently connected to a separate Postgres instance. Export is not supported.");
|
|
135
|
+
}
|
|
136
|
+
}
|
|
113
137
|
static async generate(args) {
|
|
114
138
|
const commandInfo = await (0, downloadableEmulators_1.downloadIfNecessary)(types_1.Emulators.DATACONNECT);
|
|
115
139
|
const cmd = [
|
package/lib/emulator/hub.js
CHANGED
|
@@ -105,6 +105,21 @@ class EmulatorHub extends ExpressBasedEmulator_1.ExpressBasedEmulator {
|
|
|
105
105
|
await emu.reloadTriggers();
|
|
106
106
|
res.status(200).json({ enabled: true });
|
|
107
107
|
});
|
|
108
|
+
app.post(EmulatorHub.PATH_CLEAR_DATA_CONNECT, async (req, res) => {
|
|
109
|
+
if (req.headers.origin) {
|
|
110
|
+
res.status(403).json({
|
|
111
|
+
message: `Clear Data Connect cannot be triggered by external callers.`,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
utils.logLabeledBullet("emulators", `Clearing data from Data Connect data sources.`);
|
|
115
|
+
const instance = registry_1.EmulatorRegistry.get(types_1.Emulators.DATACONNECT);
|
|
116
|
+
if (!instance) {
|
|
117
|
+
res.status(400).json({ error: "The Data Connect emulator is not running." });
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
await instance.clearData();
|
|
121
|
+
res.status(200).send("Data cleared");
|
|
122
|
+
});
|
|
108
123
|
return app;
|
|
109
124
|
}
|
|
110
125
|
async stop() {
|
|
@@ -169,3 +184,4 @@ EmulatorHub.PATH_EXPORT = "/_admin/export";
|
|
|
169
184
|
EmulatorHub.PATH_DISABLE_FUNCTIONS = "/functions/disableBackgroundTriggers";
|
|
170
185
|
EmulatorHub.PATH_ENABLE_FUNCTIONS = "/functions/enableBackgroundTriggers";
|
|
171
186
|
EmulatorHub.PATH_EMULATORS = "/emulators";
|
|
187
|
+
EmulatorHub.PATH_CLEAR_DATA_CONNECT = "/dataconnect/clearData";
|
|
@@ -71,6 +71,13 @@ class HubExport {
|
|
|
71
71
|
};
|
|
72
72
|
await this.exportStorage(metadata);
|
|
73
73
|
}
|
|
74
|
+
if (shouldExport(types_1.Emulators.DATACONNECT)) {
|
|
75
|
+
metadata.dataconnect = {
|
|
76
|
+
version: hub_1.EmulatorHub.CLI_VERSION,
|
|
77
|
+
path: "dataconnect_export",
|
|
78
|
+
};
|
|
79
|
+
await this.exportDataConnect(metadata);
|
|
80
|
+
}
|
|
74
81
|
if (!fs.existsSync(this.exportPath)) {
|
|
75
82
|
fs.mkdirSync(this.exportPath);
|
|
76
83
|
}
|
|
@@ -197,6 +204,22 @@ class HubExport {
|
|
|
197
204
|
throw new error_1.FirebaseError(`Failed to export storage: ${await res.response.text()}`);
|
|
198
205
|
}
|
|
199
206
|
}
|
|
207
|
+
async exportDataConnect(metadata) {
|
|
208
|
+
void (0, track_1.trackEmulator)("emulator_export", {
|
|
209
|
+
initiated_by: this.options.initiatedBy,
|
|
210
|
+
emulator_name: types_1.Emulators.DATACONNECT,
|
|
211
|
+
});
|
|
212
|
+
const instance = registry_1.EmulatorRegistry.get(types_1.Emulators.DATACONNECT);
|
|
213
|
+
if (!instance) {
|
|
214
|
+
throw new error_1.FirebaseError("Unable to export Data Connect emulator data: the Data Connect emulator is not running.");
|
|
215
|
+
}
|
|
216
|
+
const dataconnectExportPath = path.join(this.tmpDir, metadata.dataconnect.path);
|
|
217
|
+
if (fs.existsSync(dataconnectExportPath)) {
|
|
218
|
+
fse.removeSync(dataconnectExportPath);
|
|
219
|
+
}
|
|
220
|
+
fs.mkdirSync(dataconnectExportPath);
|
|
221
|
+
await instance.exportData(dataconnectExportPath);
|
|
222
|
+
}
|
|
200
223
|
}
|
|
201
224
|
exports.HubExport = HubExport;
|
|
202
225
|
HubExport.METADATA_FILE_NAME = "firebase-export-metadata.json";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AdditionalInitFns = void 0;
|
|
4
|
+
const path_1 = require("path");
|
|
5
|
+
const prompt_1 = require("../prompt");
|
|
6
|
+
const developmentServer_1 = require("./apphosting/developmentServer");
|
|
7
|
+
const emulatorLogger_1 = require("./emulatorLogger");
|
|
8
|
+
const types_1 = require("./types");
|
|
9
|
+
const config_1 = require("../apphosting/config");
|
|
10
|
+
const detectProjectRoot_1 = require("../detectProjectRoot");
|
|
11
|
+
exports.AdditionalInitFns = {
|
|
12
|
+
[types_1.Emulators.APPHOSTING]: async () => {
|
|
13
|
+
var _a;
|
|
14
|
+
const cwd = process.cwd();
|
|
15
|
+
const additionalConfigs = new Map();
|
|
16
|
+
const logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.APPHOSTING);
|
|
17
|
+
logger.logLabeled("INFO", "Initializing Emulator");
|
|
18
|
+
const backendRelativeDir = await (0, prompt_1.promptOnce)({
|
|
19
|
+
name: "rootDir",
|
|
20
|
+
type: "input",
|
|
21
|
+
default: "./",
|
|
22
|
+
message: "Specify your app's root directory relative to your repository",
|
|
23
|
+
});
|
|
24
|
+
additionalConfigs.set("rootDirectory", backendRelativeDir);
|
|
25
|
+
const backendRoot = (0, path_1.join)(cwd, backendRelativeDir);
|
|
26
|
+
try {
|
|
27
|
+
const startCommand = await (0, developmentServer_1.detectStartCommand)(backendRoot);
|
|
28
|
+
additionalConfigs.set("startCommandOverride", startCommand);
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
logger.log("WARN", "Failed to auto-detect your project's start command. Consider manually setting the start command by setting `firebase.json#emulators.apphosting.startCommandOverride`");
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const projectRoot = (_a = (0, detectProjectRoot_1.detectProjectRoot)({})) !== null && _a !== void 0 ? _a : backendRoot;
|
|
35
|
+
await (0, config_1.exportConfig)(cwd, projectRoot, backendRoot);
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
logger.log("WARN", "failed to export app hosting configs");
|
|
39
|
+
}
|
|
40
|
+
return mapToObject(additionalConfigs);
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
function mapToObject(map) {
|
|
44
|
+
const newObject = {};
|
|
45
|
+
for (const [key, value] of map) {
|
|
46
|
+
newObject[key] = value;
|
|
47
|
+
}
|
|
48
|
+
return newObject;
|
|
49
|
+
}
|
package/lib/emulator/types.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Severity = exports.EmulatorLog = exports.FunctionsExecutionMode = exports.isEmulator = exports.isDownloadableEmulator = exports.ALL_EMULATORS = exports.EMULATORS_SUPPORTED_BY_USE_EMULATOR = exports.EMULATORS_SUPPORTED_BY_UI = exports.EMULATORS_SUPPORTED_BY_FUNCTIONS = exports.ALL_SERVICE_EMULATORS = exports.IMPORT_EXPORT_EMULATORS = exports.DOWNLOADABLE_EMULATORS = exports.Emulators = void 0;
|
|
4
|
-
const experiments = require("../experiments");
|
|
5
4
|
var Emulators;
|
|
6
5
|
(function (Emulators) {
|
|
7
6
|
Emulators["AUTH"] = "auth";
|
|
@@ -33,9 +32,10 @@ exports.IMPORT_EXPORT_EMULATORS = [
|
|
|
33
32
|
Emulators.DATABASE,
|
|
34
33
|
Emulators.AUTH,
|
|
35
34
|
Emulators.STORAGE,
|
|
35
|
+
Emulators.DATACONNECT,
|
|
36
36
|
];
|
|
37
37
|
exports.ALL_SERVICE_EMULATORS = [
|
|
38
|
-
|
|
38
|
+
Emulators.APPHOSTING,
|
|
39
39
|
Emulators.AUTH,
|
|
40
40
|
Emulators.FUNCTIONS,
|
|
41
41
|
Emulators.FIRESTORE,
|
|
@@ -381,6 +381,7 @@ function endpointFromFunction(gcfFunction) {
|
|
|
381
381
|
endpoint.runServiceId = utils.last(serviceName.split("/"));
|
|
382
382
|
}
|
|
383
383
|
}
|
|
384
|
+
proto.renameIfPresent(endpoint, gcfFunction, "uri", "url");
|
|
384
385
|
endpoint.codebase = ((_e = gcfFunction.labels) === null || _e === void 0 ? void 0 : _e[constants_1.CODEBASE_LABEL]) || projectConfig.DEFAULT_CODEBASE;
|
|
385
386
|
if ((_f = gcfFunction.labels) === null || _f === void 0 ? void 0 : _f[constants_1.HASH_LABEL]) {
|
|
386
387
|
endpoint.hash = gcfFunction.labels[constants_1.HASH_LABEL];
|
|
@@ -7,6 +7,7 @@ const prompt_1 = require("../../prompt");
|
|
|
7
7
|
const types_1 = require("../../emulator/types");
|
|
8
8
|
const constants_1 = require("../../emulator/constants");
|
|
9
9
|
const downloadableEmulators_1 = require("../../emulator/downloadableEmulators");
|
|
10
|
+
const initEmulators_1 = require("../../emulator/initEmulators");
|
|
10
11
|
async function doSetup(setup, config) {
|
|
11
12
|
var _a, _b, _c;
|
|
12
13
|
const choices = types_1.ALL_SERVICE_EMULATORS.map((e) => {
|
|
@@ -46,6 +47,13 @@ async function doSetup(setup, config) {
|
|
|
46
47
|
},
|
|
47
48
|
]);
|
|
48
49
|
}
|
|
50
|
+
const additionalInitFn = initEmulators_1.AdditionalInitFns[selected];
|
|
51
|
+
if (additionalInitFn) {
|
|
52
|
+
const additionalOptions = await additionalInitFn();
|
|
53
|
+
if (additionalOptions) {
|
|
54
|
+
setup.config.emulators[selected] = Object.assign(Object.assign({}, setup.config.emulators[selected]), additionalOptions);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
49
57
|
}
|
|
50
58
|
if (selections.emulators.length) {
|
|
51
59
|
const uiDesc = constants_1.Constants.description(types_1.Emulators.UI);
|
package/lib/utils.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.openInBrowser = exports.connectableHostname = exports.randomInt = exports.debounce = exports.last = exports.cloneDeep = exports.groupBy = exports.assertIsStringOrUndefined = exports.assertIsNumber = exports.assertIsString = exports.thirtyDaysFromNow = exports.isRunningInWSL = exports.isCloudEnvironment = exports.datetimeString = exports.createDestroyer = exports.sleep = exports.promiseWithSpinner = exports.setupLoggers = exports.tryParse = exports.tryStringify = exports.promiseProps = exports.withTimeout = exports.promiseWhile = exports.promiseAllSettled = exports.getFunctionsEventProvider = exports.endpoint = exports.makeActiveProject = exports.streamToString = exports.stringToStream = exports.explainStdin = exports.allSettled = exports.reject = exports.logLabeledError = exports.logLabeledWarning = exports.logWarning = exports.logLabeledBullet = exports.logBullet = exports.logLabeledSuccess = exports.logSuccess = exports.addSubdomain = exports.addDatabaseNamespace = exports.getDatabaseViewDataUrl = exports.getDatabaseUrl = exports.envOverride = exports.setVSCodeEnvVars = exports.getInheritedOption = exports.consoleUrl = exports.vscodeEnvVars = exports.envOverrides = exports.IS_WINDOWS = void 0;
|
|
4
|
-
exports.readSecretValue = exports.generateId = exports.wrappedSafeLoad = exports.readFileFromDirectory = exports.getHostnameFromUrl = exports.openInBrowserPopup = void 0;
|
|
4
|
+
exports.updateOrCreateGitignore = exports.readSecretValue = exports.generateId = exports.wrappedSafeLoad = exports.readFileFromDirectory = exports.getHostnameFromUrl = exports.openInBrowserPopup = void 0;
|
|
5
5
|
const fs = require("fs-extra");
|
|
6
6
|
const tty = require("tty");
|
|
7
7
|
const path = require("node:path");
|
|
@@ -597,3 +597,18 @@ function readSecretValue(prompt, dataFile) {
|
|
|
597
597
|
}
|
|
598
598
|
}
|
|
599
599
|
exports.readSecretValue = readSecretValue;
|
|
600
|
+
function updateOrCreateGitignore(dirPath, entries) {
|
|
601
|
+
const gitignorePath = path.join(dirPath, ".gitignore");
|
|
602
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
603
|
+
fs.writeFileSync(gitignorePath, entries.join("\n"));
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
let content = fs.readFileSync(gitignorePath, "utf-8");
|
|
607
|
+
for (const entry of entries) {
|
|
608
|
+
if (!content.includes(entry)) {
|
|
609
|
+
content += `\n${entry}\n`;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
fs.writeFileSync(gitignorePath, content);
|
|
613
|
+
}
|
|
614
|
+
exports.updateOrCreateGitignore = updateOrCreateGitignore;
|
package/package.json
CHANGED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.discoverPackageManager = exports.logger = void 0;
|
|
4
|
-
const fs_extra_1 = require("fs-extra");
|
|
5
|
-
const path_1 = require("path");
|
|
6
|
-
const emulatorLogger_1 = require("../emulatorLogger");
|
|
7
|
-
const types_1 = require("../types");
|
|
8
|
-
exports.logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.APPHOSTING);
|
|
9
|
-
async function discoverPackageManager(rootdir) {
|
|
10
|
-
if (await (0, fs_extra_1.pathExists)((0, path_1.join)(rootdir, "pnpm-lock.yaml"))) {
|
|
11
|
-
return "pnpm";
|
|
12
|
-
}
|
|
13
|
-
if (await (0, fs_extra_1.pathExists)((0, path_1.join)(rootdir, "yarn.lock"))) {
|
|
14
|
-
return "yarn";
|
|
15
|
-
}
|
|
16
|
-
return "npm";
|
|
17
|
-
}
|
|
18
|
-
exports.discoverPackageManager = discoverPackageManager;
|