firebase-tools 13.24.2 → 13.25.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/auth.js +4 -2
- package/lib/emulator/controller.js +2 -1
- package/lib/emulator/dataconnectEmulator.js +13 -12
- package/lib/emulator/downloadableEmulators.js +9 -9
- package/lib/emulator/emulatorLogger.js +3 -0
- package/lib/emulator/tasksEmulator.js +5 -2
- package/lib/init/features/genkit/index.js +417 -0
- package/package.json +1 -1
- package/templates/genkit/firebase.0.9.0.template +57 -0
- package/lib/init/features/genkit.js +0 -54
package/lib/auth.js
CHANGED
|
@@ -138,11 +138,13 @@ function open(url) {
|
|
|
138
138
|
});
|
|
139
139
|
}
|
|
140
140
|
function invalidCredentialError() {
|
|
141
|
-
|
|
141
|
+
const message = "Authentication Error: Your credentials are no longer valid. Please run " +
|
|
142
142
|
clc.bold("firebase login --reauth") +
|
|
143
143
|
"\n\n" +
|
|
144
144
|
"For CI servers and headless environments, generate a new token with " +
|
|
145
|
-
clc.bold("firebase login:ci")
|
|
145
|
+
clc.bold("firebase login:ci");
|
|
146
|
+
logger_1.logger.error(message);
|
|
147
|
+
return new error_1.FirebaseError(message, { exit: 1 });
|
|
146
148
|
}
|
|
147
149
|
const FIFTEEN_MINUTES_IN_MS = 15 * 60 * 1000;
|
|
148
150
|
const SCOPES = [
|
|
@@ -47,6 +47,7 @@ const fileUtils_1 = require("../dataconnect/fileUtils");
|
|
|
47
47
|
const tasksEmulator_1 = require("./tasksEmulator");
|
|
48
48
|
const apphosting_1 = require("./apphosting");
|
|
49
49
|
const webhook_1 = require("../dataconnect/webhook");
|
|
50
|
+
const api_1 = require("../api");
|
|
50
51
|
const START_LOGGING_EMULATOR = utils.envOverride("START_LOGGING_EMULATOR", "false", (val) => val === "true");
|
|
51
52
|
async function exportOnExit(options) {
|
|
52
53
|
const exportOnExitDir = options.exportOnExit;
|
|
@@ -264,7 +265,7 @@ async function startAll(options, showUI = true, runningTestScript = false) {
|
|
|
264
265
|
portFixed: !!wsPortConfig,
|
|
265
266
|
};
|
|
266
267
|
}
|
|
267
|
-
if (emulator === types_1.Emulators.DATACONNECT) {
|
|
268
|
+
if (emulator === types_1.Emulators.DATACONNECT && !(0, api_1.dataConnectLocalConnString)()) {
|
|
268
269
|
const pglitePortConfig = (_f = (_e = options.config.src.emulators) === null || _e === void 0 ? void 0 : _e.dataconnect) === null || _f === void 0 ? void 0 : _f.postgresPort;
|
|
269
270
|
listenConfig["dataconnect.postgres"] = {
|
|
270
271
|
host: config.host,
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.DataConnectEmulatorClient = exports.DataConnectEmulator = exports.dataConnectEmulatorEvents = void 0;
|
|
4
4
|
const childProcess = require("child_process");
|
|
5
5
|
const events_1 = require("events");
|
|
6
|
+
const clc = require("colorette");
|
|
6
7
|
const api_1 = require("../api");
|
|
7
8
|
const constants_1 = require("./constants");
|
|
8
9
|
const downloadableEmulators_1 = require("./downloadableEmulators");
|
|
@@ -26,26 +27,23 @@ class DataConnectEmulator {
|
|
|
26
27
|
this.emulatorClient = new DataConnectEmulatorClient();
|
|
27
28
|
}
|
|
28
29
|
async start() {
|
|
29
|
-
var _a, _b, _c
|
|
30
|
+
var _a, _b, _c;
|
|
30
31
|
let resolvedConfigDir;
|
|
31
32
|
try {
|
|
32
33
|
resolvedConfigDir = this.args.config.path(this.args.configDir);
|
|
33
34
|
const info = await DataConnectEmulator.build({ configDir: resolvedConfigDir });
|
|
34
35
|
if ((0, types_2.requiresVector)(info.metadata)) {
|
|
35
36
|
if (constants_1.Constants.isDemoProject(this.args.projectId)) {
|
|
36
|
-
this.logger.logLabeled("WARN", "
|
|
37
|
+
this.logger.logLabeled("WARN", "dataconnect", "Detected a 'demo-' project, but vector embeddings require a real project. Operations that use vector_embed will fail.");
|
|
37
38
|
}
|
|
38
39
|
else {
|
|
39
|
-
this.logger.logLabeled("WARN", "
|
|
40
|
+
this.logger.logLabeled("WARN", "dataconnect", "Operations that use vector_embed will make calls to production Vertex AI");
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
catch (err) {
|
|
44
45
|
this.logger.log("DEBUG", `'fdc build' failed with error: ${err.message}`);
|
|
45
46
|
}
|
|
46
|
-
const info = await (0, load_1.load)(this.args.projectId, this.args.config, this.args.configDir);
|
|
47
|
-
const dbId = ((_a = info.dataConnectYaml.schema.datasource.postgresql) === null || _a === void 0 ? void 0 : _a.database) || "postgres";
|
|
48
|
-
const serviceId = info.dataConnectYaml.serviceId;
|
|
49
47
|
await (0, downloadableEmulators_1.start)(types_1.Emulators.DATACONNECT, {
|
|
50
48
|
auto_download: this.args.auto_download,
|
|
51
49
|
listen: (0, portUtils_1.listenSpecsToString)(this.args.listen),
|
|
@@ -55,11 +53,14 @@ class DataConnectEmulator {
|
|
|
55
53
|
});
|
|
56
54
|
this.usingExistingEmulator = false;
|
|
57
55
|
if (this.args.autoconnectToPostgres) {
|
|
56
|
+
const info = await (0, load_1.load)(this.args.projectId, this.args.config, this.args.configDir);
|
|
57
|
+
const dbId = ((_a = info.dataConnectYaml.schema.datasource.postgresql) === null || _a === void 0 ? void 0 : _a.database) || "postgres";
|
|
58
|
+
const serviceId = info.dataConnectYaml.serviceId;
|
|
58
59
|
const pgPort = (_b = this.args.postgresListen) === null || _b === void 0 ? void 0 : _b[0].port;
|
|
59
60
|
const pgHost = (_c = this.args.postgresListen) === null || _c === void 0 ? void 0 : _c[0].address;
|
|
60
61
|
let connStr = (0, api_1.dataConnectLocalConnString)();
|
|
61
|
-
if (
|
|
62
|
-
this.logger.logLabeled("INFO", "
|
|
62
|
+
if (connStr) {
|
|
63
|
+
this.logger.logLabeled("INFO", "dataconnect", `FIREBASE_DATACONNECT_POSTGRESQL_STRING is set to ${clc.bold(connStr)} - using that instead of starting a new database`);
|
|
63
64
|
}
|
|
64
65
|
else if (pgHost && pgPort) {
|
|
65
66
|
const pgServer = new pgliteServer_1.PostgresServer(dbId, "postgres");
|
|
@@ -71,11 +72,11 @@ class DataConnectEmulator {
|
|
|
71
72
|
this.logger.logLabeled("ERROR", "Data Connect", `${err}`);
|
|
72
73
|
}
|
|
73
74
|
else {
|
|
74
|
-
this.logger.logLabeled("ERROR", "
|
|
75
|
+
this.logger.logLabeled("ERROR", "dataconnect", `Postgres threw an unexpected error, shutting down the Data Connect emulator: ${err}`);
|
|
75
76
|
}
|
|
76
77
|
void (0, controller_1.cleanShutdown)();
|
|
77
78
|
});
|
|
78
|
-
this.logger.logLabeled("INFO", "
|
|
79
|
+
this.logger.logLabeled("INFO", "dataconnect", `Started up Postgres server, listening on ${JSON.stringify(server.address())}`);
|
|
79
80
|
}
|
|
80
81
|
await this.connectToPostgres(new URL(connStr), dbId, serviceId);
|
|
81
82
|
}
|
|
@@ -84,14 +85,14 @@ class DataConnectEmulator {
|
|
|
84
85
|
async connect() {
|
|
85
86
|
const emuInfo = await this.emulatorClient.getInfo();
|
|
86
87
|
if (!emuInfo) {
|
|
87
|
-
this.logger.logLabeled("ERROR", "
|
|
88
|
+
this.logger.logLabeled("ERROR", "dataconnect", "Could not connect to Data Connect emulator. Check dataconnect-debug.log for more details.");
|
|
88
89
|
return Promise.reject();
|
|
89
90
|
}
|
|
90
91
|
return Promise.resolve();
|
|
91
92
|
}
|
|
92
93
|
async stop() {
|
|
93
94
|
if (this.usingExistingEmulator) {
|
|
94
|
-
this.logger.logLabeled("INFO", "
|
|
95
|
+
this.logger.logLabeled("INFO", "dataconnect", "Skipping cleanup of Data Connect emulator, as it was not started by this process.");
|
|
95
96
|
return;
|
|
96
97
|
}
|
|
97
98
|
return (0, downloadableEmulators_1.stop)(types_1.Emulators.DATACONNECT);
|
|
@@ -48,20 +48,20 @@ const EMULATOR_UPDATE_DETAILS = {
|
|
|
48
48
|
},
|
|
49
49
|
dataconnect: process.platform === "darwin"
|
|
50
50
|
? {
|
|
51
|
-
version: "1.
|
|
52
|
-
expectedSize:
|
|
53
|
-
expectedChecksum: "
|
|
51
|
+
version: "1.7.0",
|
|
52
|
+
expectedSize: 25350912,
|
|
53
|
+
expectedChecksum: "1479411a52b689a990179c7f674362b6",
|
|
54
54
|
}
|
|
55
55
|
: process.platform === "win32"
|
|
56
56
|
? {
|
|
57
|
-
version: "1.
|
|
58
|
-
expectedSize:
|
|
59
|
-
expectedChecksum: "
|
|
57
|
+
version: "1.7.0",
|
|
58
|
+
expectedSize: 25783808,
|
|
59
|
+
expectedChecksum: "fef62bf25e816aa4ba250e80050f0125",
|
|
60
60
|
}
|
|
61
61
|
: {
|
|
62
|
-
version: "1.
|
|
63
|
-
expectedSize:
|
|
64
|
-
expectedChecksum: "
|
|
62
|
+
version: "1.7.0",
|
|
63
|
+
expectedSize: 25272472,
|
|
64
|
+
expectedChecksum: "24d873457787546ca6f2470fe9ec3edd",
|
|
65
65
|
},
|
|
66
66
|
};
|
|
67
67
|
exports.DownloadDetails = {
|
|
@@ -210,6 +210,9 @@ You can probably fix this by running "npm install ${systemLog.data.name}@latest"
|
|
|
210
210
|
case "BULLET":
|
|
211
211
|
utils.logLabeledBullet(label, text, "info", mergedData);
|
|
212
212
|
break;
|
|
213
|
+
case "INFO":
|
|
214
|
+
utils.logLabeledBullet(label, text, "info", mergedData);
|
|
215
|
+
break;
|
|
213
216
|
case "SUCCESS":
|
|
214
217
|
utils.logLabeledSuccess(label, text, "info", mergedData);
|
|
215
218
|
break;
|
|
@@ -116,7 +116,10 @@ class TasksEmulator {
|
|
|
116
116
|
const locationId = req.params.location_id;
|
|
117
117
|
const queueName = req.params.queue_name;
|
|
118
118
|
if (!this.validateQueueId(queueName)) {
|
|
119
|
-
res.status(400).
|
|
119
|
+
res.status(400).json({
|
|
120
|
+
error: "Queue ID must start with a letter followed by up to 62 letters, numbers, " +
|
|
121
|
+
"hyphens, or underscores and must end with a letter or a number",
|
|
122
|
+
});
|
|
120
123
|
return;
|
|
121
124
|
}
|
|
122
125
|
const key = `queue:${projectId}-${locationId}-${queueName}`;
|
|
@@ -139,7 +142,7 @@ class TasksEmulator {
|
|
|
139
142
|
defaultUri: body.defaultUri,
|
|
140
143
|
};
|
|
141
144
|
if (taskQueueConfig.rateLimits.maxConcurrentDispatches > 5000) {
|
|
142
|
-
res.status(400).
|
|
145
|
+
res.status(400).json({ error: "cannot set maxConcurrentDispatches to a value over 5000" });
|
|
143
146
|
return;
|
|
144
147
|
}
|
|
145
148
|
this.controller.createQueue(key, taskQueueConfig);
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.promptWriteMode = exports.isPackageJson = exports.isTsConfig = exports.genkitSetup = exports.ensureVertexApiEnabled = exports.doSetup = void 0;
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const inquirer = require("inquirer");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const semver = require("semver");
|
|
8
|
+
const clc = require("colorette");
|
|
9
|
+
const child_process_1 = require("child_process");
|
|
10
|
+
const functions_1 = require("../functions");
|
|
11
|
+
const prompt_1 = require("../../../prompt");
|
|
12
|
+
const spawn_1 = require("../../spawn");
|
|
13
|
+
const projectUtils_1 = require("../../../projectUtils");
|
|
14
|
+
const ensureApiEnabled_1 = require("../../../ensureApiEnabled");
|
|
15
|
+
const logger_1 = require("../../../logger");
|
|
16
|
+
const error_1 = require("../../../error");
|
|
17
|
+
const utils_1 = require("../../../utils");
|
|
18
|
+
const types_1 = require("../../../extensions/types");
|
|
19
|
+
async function getGenkitVersion() {
|
|
20
|
+
let genkitVersion;
|
|
21
|
+
let templateVersion = "0.9.0";
|
|
22
|
+
let useInit = false;
|
|
23
|
+
let stopInstall = false;
|
|
24
|
+
if (process.env.GENKIT_DEV_VERSION && typeof process.env.GENKIT_DEV_VERSION === "string") {
|
|
25
|
+
semver.parse(process.env.GENKIT_DEV_VERSION);
|
|
26
|
+
genkitVersion = process.env.GENKIT_DEV_VERSION;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
genkitVersion = (0, child_process_1.execFileSync)("npm", ["view", "genkit", "version"]).toString();
|
|
30
|
+
}
|
|
31
|
+
if (!genkitVersion) {
|
|
32
|
+
throw new error_1.FirebaseError("Unable to determine genkit version to install");
|
|
33
|
+
}
|
|
34
|
+
if (semver.gte(genkitVersion, "1.0.0")) {
|
|
35
|
+
const continueInstall = await (0, prompt_1.confirm)({
|
|
36
|
+
message: clc.yellow(`WARNING: The latest version of Genkit (${genkitVersion}) isn't supported by this\n` +
|
|
37
|
+
"version of firebase-tools. You can proceed, but the provided sample code may\n" +
|
|
38
|
+
"not work with the latest library. You can also try updating firebase-tools with\n" +
|
|
39
|
+
"npm install -g firebase-tools@latest, and then running this command again.\n\n") + "Proceed with installing the latest version of Genkit?",
|
|
40
|
+
default: false,
|
|
41
|
+
});
|
|
42
|
+
if (!continueInstall) {
|
|
43
|
+
stopInstall = true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else if (semver.gte(genkitVersion, "0.6.0")) {
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
templateVersion = "";
|
|
50
|
+
useInit = true;
|
|
51
|
+
}
|
|
52
|
+
return { genkitVersion, templateVersion, useInit, stopInstall };
|
|
53
|
+
}
|
|
54
|
+
async function doSetup(setup, config, options) {
|
|
55
|
+
var _a;
|
|
56
|
+
const genkitInfo = await getGenkitVersion();
|
|
57
|
+
if (genkitInfo.stopInstall) {
|
|
58
|
+
(0, utils_1.logLabeledWarning)("genkit", "Stopped Genkit initialization");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (((_a = setup.functions) === null || _a === void 0 ? void 0 : _a.languageChoice) !== "typescript") {
|
|
62
|
+
const continueFunctions = await (0, prompt_1.confirm)({
|
|
63
|
+
message: "Genkit's Firebase integration uses Cloud Functions for Firebase with TypeScript.\nInitialize Functions to continue?",
|
|
64
|
+
default: true,
|
|
65
|
+
});
|
|
66
|
+
if (!continueFunctions) {
|
|
67
|
+
(0, utils_1.logLabeledWarning)("genkit", "Stopped Genkit initialization");
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
setup.languageOverride = "typescript";
|
|
71
|
+
await (0, functions_1.doSetup)(setup, config, options);
|
|
72
|
+
delete setup.languageOverride;
|
|
73
|
+
logger_1.logger.info();
|
|
74
|
+
}
|
|
75
|
+
if (!setup.functions) {
|
|
76
|
+
throw new error_1.FirebaseError("Failed to initialize Genkit prerequisite: Firebase functions");
|
|
77
|
+
}
|
|
78
|
+
const projectDir = `${config.projectDir}/${setup.functions.source}`;
|
|
79
|
+
const installType = (await inquirer.prompt([
|
|
80
|
+
{
|
|
81
|
+
name: "choice",
|
|
82
|
+
type: "list",
|
|
83
|
+
message: "Install the Genkit CLI globally or locally in this project?",
|
|
84
|
+
choices: [
|
|
85
|
+
{ name: "Globally", value: "globally" },
|
|
86
|
+
{ name: "Just this project", value: "project" },
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
])).choice;
|
|
90
|
+
try {
|
|
91
|
+
(0, utils_1.logLabeledBullet)("genkit", `Installing Genkit CLI version ${genkitInfo.genkitVersion}`);
|
|
92
|
+
if (installType === "globally") {
|
|
93
|
+
if (genkitInfo.useInit) {
|
|
94
|
+
await (0, spawn_1.wrapSpawn)("npm", ["install", "-g", `genkit@${genkitInfo.genkitVersion}`], projectDir);
|
|
95
|
+
await (0, spawn_1.wrapSpawn)("genkit", ["init", "-p", "firebase"], projectDir);
|
|
96
|
+
logger_1.logger.info("Start the Genkit developer experience by running:");
|
|
97
|
+
logger_1.logger.info(` cd ${setup.functions.source} && genkit start`);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
await (0, spawn_1.wrapSpawn)("npm", ["install", "-g", `genkit-cli@${genkitInfo.genkitVersion}`], projectDir);
|
|
101
|
+
await genkitSetup(options, genkitInfo, projectDir);
|
|
102
|
+
logger_1.logger.info("Start the Genkit developer experience by running:");
|
|
103
|
+
logger_1.logger.info(` cd ${setup.functions.source} && npm run genkit:start`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
if (genkitInfo.useInit) {
|
|
108
|
+
await (0, spawn_1.wrapSpawn)("npm", ["install", `genkit@${genkitInfo.genkitVersion}`, "--save-dev"], projectDir);
|
|
109
|
+
await (0, spawn_1.wrapSpawn)("npx", ["genkit", "init", "-p", "firebase"], projectDir);
|
|
110
|
+
logger_1.logger.info("Start the Genkit developer experience by running:");
|
|
111
|
+
logger_1.logger.info(` cd ${setup.functions.source} && npx genkit start`);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
await (0, spawn_1.wrapSpawn)("npm", ["install", `genkit-cli@${genkitInfo.genkitVersion}`, "--save-dev"], projectDir);
|
|
115
|
+
await genkitSetup(options, genkitInfo, projectDir);
|
|
116
|
+
logger_1.logger.info("Start the Genkit developer experience by running:");
|
|
117
|
+
logger_1.logger.info();
|
|
118
|
+
logger_1.logger.info(clc.bold(clc.green(` cd ${setup.functions.source} && npm run genkit:start`)));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
(0, utils_1.logLabeledError)("genkit", `Genkit initialization failed: ${(0, error_1.getErrMsg)(err)}`);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
exports.doSetup = doSetup;
|
|
128
|
+
async function ensureVertexApiEnabled(options) {
|
|
129
|
+
const VERTEX_AI_URL = "https://aiplatform.googleapis.com";
|
|
130
|
+
const projectId = (0, projectUtils_1.getProjectId)(options);
|
|
131
|
+
if (!projectId) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const silently = typeof options.markdown === "boolean" && options.markdown;
|
|
135
|
+
return await (0, ensureApiEnabled_1.ensure)(projectId, VERTEX_AI_URL, "aiplatform", silently);
|
|
136
|
+
}
|
|
137
|
+
exports.ensureVertexApiEnabled = ensureVertexApiEnabled;
|
|
138
|
+
function getModelOptions(genkitVersion) {
|
|
139
|
+
const modelOptions = {
|
|
140
|
+
vertexai: {
|
|
141
|
+
label: "Google Cloud Vertex AI",
|
|
142
|
+
plugin: "@genkit-ai/vertexai",
|
|
143
|
+
package: `@genkit-ai/vertexai@${genkitVersion}`,
|
|
144
|
+
},
|
|
145
|
+
googleai: {
|
|
146
|
+
label: "Google AI",
|
|
147
|
+
plugin: "@genkit-ai/googleai",
|
|
148
|
+
package: `@genkit-ai/googleai@${genkitVersion}`,
|
|
149
|
+
},
|
|
150
|
+
none: { label: "None", plugin: undefined, package: undefined },
|
|
151
|
+
};
|
|
152
|
+
return modelOptions;
|
|
153
|
+
}
|
|
154
|
+
const pluginToInfo = {
|
|
155
|
+
"@genkit-ai/firebase": {
|
|
156
|
+
imports: "firebase",
|
|
157
|
+
init: `
|
|
158
|
+
// Load the Firebase plugin, which provides integrations with several
|
|
159
|
+
// Firebase services.
|
|
160
|
+
firebase()`.trimStart(),
|
|
161
|
+
},
|
|
162
|
+
"@genkit-ai/vertexai": {
|
|
163
|
+
imports: "vertexAI",
|
|
164
|
+
modelImportComment: `
|
|
165
|
+
// Import models from the Vertex AI plugin. The Vertex AI API provides access to
|
|
166
|
+
// several generative models. Here, we import Gemini 1.5 Flash.`.trimStart(),
|
|
167
|
+
init: `
|
|
168
|
+
// Load the Vertex AI plugin. You can optionally specify your project ID
|
|
169
|
+
// by passing in a config object; if you don't, the Vertex AI plugin uses
|
|
170
|
+
// the value from the GCLOUD_PROJECT environment variable.
|
|
171
|
+
vertexAI({location: "us-central1"})`.trimStart(),
|
|
172
|
+
model: "gemini15Flash",
|
|
173
|
+
},
|
|
174
|
+
"@genkit-ai/googleai": {
|
|
175
|
+
imports: "googleAI",
|
|
176
|
+
modelImportComment: `
|
|
177
|
+
// Import models from the Google AI plugin. The Google AI API provides access to
|
|
178
|
+
// several generative models. Here, we import Gemini 1.5 Flash.`.trimStart(),
|
|
179
|
+
init: `
|
|
180
|
+
// Load the Google AI plugin. You can optionally specify your API key
|
|
181
|
+
// by passing in a config object; if you don't, the Google AI plugin uses
|
|
182
|
+
// the value from the GOOGLE_GENAI_API_KEY environment variable, which is
|
|
183
|
+
// the recommended practice.
|
|
184
|
+
googleAI()`.trimStart(),
|
|
185
|
+
model: "gemini15Flash",
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
function getBasePackages(genkitVersion) {
|
|
189
|
+
const basePackages = ["express", `genkit@${genkitVersion}`];
|
|
190
|
+
return basePackages;
|
|
191
|
+
}
|
|
192
|
+
const externalDevPackages = ["typescript", "tsx"];
|
|
193
|
+
async function genkitSetup(options, genkitInfo, projectDir) {
|
|
194
|
+
var _a, _b;
|
|
195
|
+
const modelOptions = getModelOptions(genkitInfo.genkitVersion);
|
|
196
|
+
const supportedModels = Object.keys(modelOptions);
|
|
197
|
+
const answer = await inquirer.prompt([
|
|
198
|
+
{
|
|
199
|
+
type: "list",
|
|
200
|
+
name: "model",
|
|
201
|
+
message: "Select a model provider:",
|
|
202
|
+
choices: supportedModels.map((model) => ({
|
|
203
|
+
name: modelOptions[model].label,
|
|
204
|
+
value: model,
|
|
205
|
+
})),
|
|
206
|
+
},
|
|
207
|
+
]);
|
|
208
|
+
const model = answer.model;
|
|
209
|
+
if (model === "vertexai") {
|
|
210
|
+
await ensureVertexApiEnabled(options);
|
|
211
|
+
}
|
|
212
|
+
const plugins = [];
|
|
213
|
+
const pluginPackages = [];
|
|
214
|
+
pluginPackages.push(`@genkit-ai/firebase@${genkitInfo.genkitVersion}`);
|
|
215
|
+
if ((_a = modelOptions[model]) === null || _a === void 0 ? void 0 : _a.plugin) {
|
|
216
|
+
plugins.push(modelOptions[model].plugin || "");
|
|
217
|
+
}
|
|
218
|
+
if ((_b = modelOptions[model]) === null || _b === void 0 ? void 0 : _b.package) {
|
|
219
|
+
pluginPackages.push(modelOptions[model].package || "");
|
|
220
|
+
}
|
|
221
|
+
const packages = [...getBasePackages(genkitInfo.genkitVersion)];
|
|
222
|
+
packages.push(...pluginPackages);
|
|
223
|
+
await installNpmPackages(projectDir, packages, externalDevPackages);
|
|
224
|
+
if (!fs.existsSync(path.join(projectDir, "src"))) {
|
|
225
|
+
fs.mkdirSync(path.join(projectDir, "src"));
|
|
226
|
+
}
|
|
227
|
+
await updateTsConfig(options.nonInteractive || false, projectDir);
|
|
228
|
+
await updatePackageJson(options.nonInteractive || false, projectDir);
|
|
229
|
+
if (options.nonInteractive ||
|
|
230
|
+
(await (0, prompt_1.confirm)({
|
|
231
|
+
message: "Would you like to generate a sample flow?",
|
|
232
|
+
default: true,
|
|
233
|
+
}))) {
|
|
234
|
+
generateSampleFile(modelOptions[model].plugin, plugins, projectDir, genkitInfo.templateVersion);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
exports.genkitSetup = genkitSetup;
|
|
238
|
+
const isTsConfig = (value) => {
|
|
239
|
+
if (!(0, types_1.isObject)(value) || (value.compilerOptions && !(0, types_1.isObject)(value.compilerOptions))) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
return true;
|
|
243
|
+
};
|
|
244
|
+
exports.isTsConfig = isTsConfig;
|
|
245
|
+
async function updateTsConfig(nonInteractive, projectDir) {
|
|
246
|
+
const tsConfigPath = path.join(projectDir, "tsconfig.json");
|
|
247
|
+
let existingTsConfig = undefined;
|
|
248
|
+
if (fs.existsSync(tsConfigPath)) {
|
|
249
|
+
const parsed = JSON.parse(fs.readFileSync(tsConfigPath, "utf-8"));
|
|
250
|
+
if (!(0, exports.isTsConfig)(parsed)) {
|
|
251
|
+
throw new error_1.FirebaseError("Unable to parse existing tsconfig.json");
|
|
252
|
+
}
|
|
253
|
+
existingTsConfig = parsed;
|
|
254
|
+
}
|
|
255
|
+
let choice = "overwrite";
|
|
256
|
+
if (!nonInteractive && existingTsConfig) {
|
|
257
|
+
choice = await promptWriteMode("Would you like to update your tsconfig.json with suggested settings?");
|
|
258
|
+
}
|
|
259
|
+
const tsConfig = {
|
|
260
|
+
compileOnSave: true,
|
|
261
|
+
include: ["src"],
|
|
262
|
+
compilerOptions: {
|
|
263
|
+
module: "commonjs",
|
|
264
|
+
noImplicitReturns: true,
|
|
265
|
+
outDir: "lib",
|
|
266
|
+
sourceMap: true,
|
|
267
|
+
strict: true,
|
|
268
|
+
target: "es2017",
|
|
269
|
+
skipLibCheck: true,
|
|
270
|
+
esModuleInterop: true,
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
(0, utils_1.logLabeledBullet)("genkit", "Updating tsconfig.json");
|
|
274
|
+
let newTsConfig = {};
|
|
275
|
+
switch (choice) {
|
|
276
|
+
case "overwrite":
|
|
277
|
+
newTsConfig = Object.assign(Object.assign(Object.assign({}, existingTsConfig), tsConfig), { compilerOptions: Object.assign(Object.assign({}, existingTsConfig === null || existingTsConfig === void 0 ? void 0 : existingTsConfig.compilerOptions), tsConfig.compilerOptions) });
|
|
278
|
+
break;
|
|
279
|
+
case "merge":
|
|
280
|
+
newTsConfig = Object.assign(Object.assign(Object.assign({}, tsConfig), existingTsConfig), { compilerOptions: Object.assign(Object.assign({}, tsConfig.compilerOptions), existingTsConfig === null || existingTsConfig === void 0 ? void 0 : existingTsConfig.compilerOptions) });
|
|
281
|
+
break;
|
|
282
|
+
case "keep":
|
|
283
|
+
(0, utils_1.logLabeledWarning)("genkit", "Skipped updating tsconfig.json");
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
try {
|
|
287
|
+
fs.writeFileSync(tsConfigPath, JSON.stringify(newTsConfig, null, 2));
|
|
288
|
+
(0, utils_1.logLabeledSuccess)("genkit", "Successfully updated tsconfig.json");
|
|
289
|
+
}
|
|
290
|
+
catch (err) {
|
|
291
|
+
(0, utils_1.logLabeledError)("genkit", `Failed to update tsconfig.json: ${(0, error_1.getErrMsg)(err)}`);
|
|
292
|
+
process.exit(1);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
async function installNpmPackages(projectDir, packages, devPackages) {
|
|
296
|
+
(0, utils_1.logLabeledBullet)("genkit", "Installing NPM packages for genkit");
|
|
297
|
+
try {
|
|
298
|
+
if (packages.length) {
|
|
299
|
+
await (0, spawn_1.wrapSpawn)("npm", ["install", ...packages, "--save"], projectDir);
|
|
300
|
+
}
|
|
301
|
+
if (devPackages === null || devPackages === void 0 ? void 0 : devPackages.length) {
|
|
302
|
+
await (0, spawn_1.wrapSpawn)("npm", ["install", ...devPackages, "--save-dev"], projectDir);
|
|
303
|
+
}
|
|
304
|
+
(0, utils_1.logLabeledSuccess)("genkit", "Successfully installed NPM packages");
|
|
305
|
+
}
|
|
306
|
+
catch (err) {
|
|
307
|
+
(0, utils_1.logLabeledError)("genkit", `Failed to install NPM packages: ${(0, error_1.getErrMsg)(err)}`);
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function generateSampleFile(modelPlugin, configPlugins, projectDir, templateVersion) {
|
|
312
|
+
let modelImport = "";
|
|
313
|
+
if (modelPlugin && pluginToInfo[modelPlugin].model) {
|
|
314
|
+
const modelInfo = pluginToInfo[modelPlugin].model || "";
|
|
315
|
+
modelImport = "\n" + generateImportStatement(modelInfo, modelPlugin) + "\n";
|
|
316
|
+
}
|
|
317
|
+
let modelImportComment = "";
|
|
318
|
+
if (modelPlugin && pluginToInfo[modelPlugin].modelImportComment) {
|
|
319
|
+
const comment = pluginToInfo[modelPlugin].modelImportComment || "";
|
|
320
|
+
modelImportComment = `\n${comment}`;
|
|
321
|
+
}
|
|
322
|
+
const commentedModelImport = `${modelImportComment}${modelImport}`;
|
|
323
|
+
const templatePath = path.join(__dirname, `../../../../templates/genkit/firebase.${templateVersion}.template`);
|
|
324
|
+
const template = fs.readFileSync(templatePath, "utf8");
|
|
325
|
+
const sample = renderConfig(configPlugins, template
|
|
326
|
+
.replace("$GENKIT_MODEL_IMPORT\n", commentedModelImport)
|
|
327
|
+
.replace("$GENKIT_MODEL", modelPlugin
|
|
328
|
+
? pluginToInfo[modelPlugin].model || pluginToInfo[modelPlugin].modelStr || ""
|
|
329
|
+
: "'' /* TODO: Set a model. */"));
|
|
330
|
+
(0, utils_1.logLabeledBullet)("genkit", "Generating sample file");
|
|
331
|
+
try {
|
|
332
|
+
const samplePath = "src/genkit-sample.ts";
|
|
333
|
+
fs.writeFileSync(path.join(projectDir, samplePath), sample, "utf8");
|
|
334
|
+
(0, utils_1.logLabeledSuccess)("genkit", `Successfully generated sample file (${samplePath})`);
|
|
335
|
+
}
|
|
336
|
+
catch (err) {
|
|
337
|
+
(0, utils_1.logLabeledError)("genkit", `Failed to generate sample file: ${(0, error_1.getErrMsg)(err)}`);
|
|
338
|
+
process.exit(1);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const isPackageJson = (value) => {
|
|
342
|
+
if (!(0, types_1.isObject)(value) || (value.scripts && !(0, types_1.isObject)(value.scripts))) {
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
return true;
|
|
346
|
+
};
|
|
347
|
+
exports.isPackageJson = isPackageJson;
|
|
348
|
+
async function updatePackageJson(nonInteractive, projectDir) {
|
|
349
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
350
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
351
|
+
throw new error_1.FirebaseError("Failed to find package.json.");
|
|
352
|
+
}
|
|
353
|
+
const existingPackageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
354
|
+
if (!(0, exports.isPackageJson)(existingPackageJson)) {
|
|
355
|
+
throw new error_1.FirebaseError("Unable to parse existing package.json file");
|
|
356
|
+
}
|
|
357
|
+
const choice = nonInteractive
|
|
358
|
+
? "overwrite"
|
|
359
|
+
: await promptWriteMode("Would you like to update your package.json with suggested settings?");
|
|
360
|
+
const packageJson = {
|
|
361
|
+
main: "lib/index.js",
|
|
362
|
+
scripts: {
|
|
363
|
+
"genkit:start": "genkit start -- tsx --watch src/genkit-sample.ts",
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
(0, utils_1.logLabeledBullet)("genkit", "Updating package.json");
|
|
367
|
+
let newPackageJson = {};
|
|
368
|
+
switch (choice) {
|
|
369
|
+
case "overwrite":
|
|
370
|
+
newPackageJson = Object.assign(Object.assign(Object.assign({}, existingPackageJson), packageJson), { scripts: Object.assign(Object.assign({}, existingPackageJson.scripts), packageJson.scripts) });
|
|
371
|
+
break;
|
|
372
|
+
case "merge":
|
|
373
|
+
newPackageJson = Object.assign(Object.assign(Object.assign({}, packageJson), existingPackageJson), { main: packageJson.main, scripts: Object.assign(Object.assign({}, packageJson.scripts), existingPackageJson.scripts) });
|
|
374
|
+
break;
|
|
375
|
+
case "keep":
|
|
376
|
+
(0, utils_1.logLabeledWarning)("genkit", "Skipped updating package.json");
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
try {
|
|
380
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(newPackageJson, null, 2));
|
|
381
|
+
(0, utils_1.logLabeledSuccess)("genkit", "Successfully updated package.json");
|
|
382
|
+
}
|
|
383
|
+
catch (err) {
|
|
384
|
+
(0, utils_1.logLabeledError)("genkit", `Failed to update package.json: ${(0, error_1.getErrMsg)(err)}`);
|
|
385
|
+
process.exit(1);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
function renderConfig(pluginNames, template) {
|
|
389
|
+
const imports = pluginNames
|
|
390
|
+
.map((pluginName) => generateImportStatement(pluginToInfo[pluginName].imports, pluginName))
|
|
391
|
+
.join("\n");
|
|
392
|
+
const plugins = pluginNames.map((pluginName) => ` ${pluginToInfo[pluginName].init},`).join("\n") ||
|
|
393
|
+
" /* Add your plugins here. */";
|
|
394
|
+
return template
|
|
395
|
+
.replace("$GENKIT_CONFIG_IMPORTS", imports)
|
|
396
|
+
.replace("$GENKIT_CONFIG_PLUGINS", plugins);
|
|
397
|
+
}
|
|
398
|
+
function generateImportStatement(imports, name) {
|
|
399
|
+
return `import {${imports}} from "${name}";`;
|
|
400
|
+
}
|
|
401
|
+
async function promptWriteMode(message, defaultOption = "merge") {
|
|
402
|
+
const answer = await inquirer.prompt([
|
|
403
|
+
{
|
|
404
|
+
type: "list",
|
|
405
|
+
name: "option",
|
|
406
|
+
message,
|
|
407
|
+
choices: [
|
|
408
|
+
{ name: "Set if unset", value: "merge" },
|
|
409
|
+
{ name: "Overwrite", value: "overwrite" },
|
|
410
|
+
{ name: "Keep unchanged", value: "keep" },
|
|
411
|
+
],
|
|
412
|
+
default: defaultOption,
|
|
413
|
+
},
|
|
414
|
+
]);
|
|
415
|
+
return answer.option;
|
|
416
|
+
}
|
|
417
|
+
exports.promptWriteMode = promptWriteMode;
|
package/package.json
CHANGED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Import the Genkit core libraries and plugins.
|
|
2
|
+
import {genkit, z} from "genkit";
|
|
3
|
+
$GENKIT_CONFIG_IMPORTS
|
|
4
|
+
$GENKIT_MODEL_IMPORT
|
|
5
|
+
|
|
6
|
+
// From the Firebase plugin, import the functions needed to deploy flows using
|
|
7
|
+
// Cloud Functions.
|
|
8
|
+
import {firebaseAuth} from "@genkit-ai/firebase/auth";
|
|
9
|
+
import {onFlow} from "@genkit-ai/firebase/functions";
|
|
10
|
+
|
|
11
|
+
const ai = genkit({
|
|
12
|
+
plugins: [
|
|
13
|
+
$GENKIT_CONFIG_PLUGINS
|
|
14
|
+
],
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// Define a simple flow that prompts an LLM to generate menu suggestions.
|
|
18
|
+
export const menuSuggestionFlow = onFlow(
|
|
19
|
+
ai,
|
|
20
|
+
{
|
|
21
|
+
name: "menuSuggestionFlow",
|
|
22
|
+
inputSchema: z.string(),
|
|
23
|
+
outputSchema: z.string(),
|
|
24
|
+
authPolicy: firebaseAuth((user) => {
|
|
25
|
+
// By default, the firebaseAuth policy requires that all requests have an
|
|
26
|
+
// `Authorization: Bearer` header containing the user's Firebase
|
|
27
|
+
// Authentication ID token. All other requests are rejected with error
|
|
28
|
+
// 403. If your app client uses the Cloud Functions for Firebase callable
|
|
29
|
+
// functions feature, the library automatically attaches this header to
|
|
30
|
+
// requests.
|
|
31
|
+
|
|
32
|
+
// You should also set additional policy requirements as appropriate for
|
|
33
|
+
// your app. For example:
|
|
34
|
+
// if (!user.email_verified) {
|
|
35
|
+
// throw new Error("Verified email required to run flow");
|
|
36
|
+
// }
|
|
37
|
+
}),
|
|
38
|
+
},
|
|
39
|
+
async (subject) => {
|
|
40
|
+
// Construct a request and send it to the model API.
|
|
41
|
+
const prompt =
|
|
42
|
+
`Suggest an item for the menu of a ${subject} themed restaurant`;
|
|
43
|
+
const llmResponse = await ai.generate({
|
|
44
|
+
model: $GENKIT_MODEL,
|
|
45
|
+
prompt: prompt,
|
|
46
|
+
config: {
|
|
47
|
+
temperature: 1,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Handle the response from the model API. In this sample, we just
|
|
52
|
+
// convert it to a string, but more complicated flows might coerce the
|
|
53
|
+
// response into structured output or chain the response into another
|
|
54
|
+
// LLM call, etc.
|
|
55
|
+
return llmResponse.text;
|
|
56
|
+
}
|
|
57
|
+
);
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.doSetup = void 0;
|
|
4
|
-
const logger_1 = require("../../logger");
|
|
5
|
-
const functions_1 = require("./functions");
|
|
6
|
-
const prompt_1 = require("../../prompt");
|
|
7
|
-
const spawn_1 = require("../spawn");
|
|
8
|
-
async function doSetup(setup, config, options) {
|
|
9
|
-
var _a;
|
|
10
|
-
if (((_a = setup.functions) === null || _a === void 0 ? void 0 : _a.languageChoice) !== "typescript") {
|
|
11
|
-
const continueFunctions = await (0, prompt_1.promptOnce)({
|
|
12
|
-
type: "confirm",
|
|
13
|
-
message: "Genkit's Firebase integration uses Cloud Functions for Firebase with TypeScript. Initialize Functions to continue?",
|
|
14
|
-
default: true,
|
|
15
|
-
});
|
|
16
|
-
if (!continueFunctions) {
|
|
17
|
-
logger_1.logger.info("Stopped Genkit initialization");
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
setup.languageOverride = "typescript";
|
|
21
|
-
await (0, functions_1.doSetup)(setup, config, options);
|
|
22
|
-
delete setup.languageOverride;
|
|
23
|
-
logger_1.logger.info();
|
|
24
|
-
}
|
|
25
|
-
const projectDir = `${config.projectDir}/${setup.functions.source}`;
|
|
26
|
-
const installType = await (0, prompt_1.promptOnce)({
|
|
27
|
-
type: "list",
|
|
28
|
-
message: "Install the Genkit CLI globally or locally in this project?",
|
|
29
|
-
choices: [
|
|
30
|
-
{ name: "Globally", value: "globally" },
|
|
31
|
-
{ name: "Just this project", value: "project" },
|
|
32
|
-
],
|
|
33
|
-
});
|
|
34
|
-
try {
|
|
35
|
-
logger_1.logger.info("Installing Genkit CLI");
|
|
36
|
-
if (installType === "globally") {
|
|
37
|
-
await (0, spawn_1.wrapSpawn)("npm", ["install", "-g", "genkit"], projectDir);
|
|
38
|
-
await (0, spawn_1.wrapSpawn)("genkit", ["init", "-p", "firebase"], projectDir);
|
|
39
|
-
logger_1.logger.info("Start the Genkit developer experience by running:");
|
|
40
|
-
logger_1.logger.info(` cd ${setup.functions.source} && genkit start`);
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
43
|
-
await (0, spawn_1.wrapSpawn)("npm", ["install", "genkit", "--save-dev"], projectDir);
|
|
44
|
-
await (0, spawn_1.wrapSpawn)("npx", ["genkit", "init", "-p", "firebase"], projectDir);
|
|
45
|
-
logger_1.logger.info("Start the Genkit developer experience by running:");
|
|
46
|
-
logger_1.logger.info(` cd ${setup.functions.source} && npx genkit start`);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
catch (e) {
|
|
50
|
-
logger_1.logger.error("Genkit initialization failed...");
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
exports.doSetup = doSetup;
|