Skip to content

Commit d1244ee

Browse files
authored
Adding extensions to firebase init (#5701)
* Adding extensions to firebase init * changelog * Formats
1 parent f5786fd commit d1244ee

File tree

10 files changed

+86
-33
lines changed

10 files changed

+86
-33
lines changed

CHANGELOG.md

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
- Adds new commands for provisioning and managing Firestore databases: (#5616)
2-
- firestore:databases:list
3-
- firestore:databases:create
4-
- firestore:databases:get
5-
- firestore:databases:update
6-
- firestore:databases:delete
7-
- firestore:locations
2+
- firestore:databases:list
3+
- firestore:databases:create
4+
- firestore:databases:get
5+
- firestore:databases:update
6+
- firestore:databases:delete
7+
- firestore:locations
8+
- Adds `extensions` as an option in `firebase init`.
89
- Relaxed repo URI validation in ext:dev:publish (#5698).

src/commands/ext-install.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import * as refs from "../extensions/refs";
1414
import { displayWarningPrompts } from "../extensions/warnings";
1515
import * as paramHelper from "../extensions/paramHelper";
1616
import {
17-
confirm,
1817
createSourceFromLocation,
1918
ensureExtensionsApiEnabled,
2019
logPrefix,
@@ -25,6 +24,7 @@ import {
2524
isLocalPath,
2625
canonicalizeRefInput,
2726
} from "../extensions/extensionsHelper";
27+
import { confirm } from "../prompt";
2828
import { getRandomString } from "../extensions/utils";
2929
import { requirePermissions } from "../requirePermissions";
3030
import * as utils from "../utils";

src/commands/ext-update.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ import {
1111
logPrefix,
1212
getSourceOrigin,
1313
SourceOrigin,
14-
confirm,
1514
diagnoseAndFixProject,
1615
isLocalPath,
1716
} from "../extensions/extensionsHelper";
1817
import * as paramHelper from "../extensions/paramHelper";
1918
import { inferUpdateSource } from "../extensions/updateHelper";
2019
import * as refs from "../extensions/refs";
2120
import { getProjectId } from "../projectUtils";
21+
import { confirm } from "../prompt";
2222
import { requirePermissions } from "../requirePermissions";
2323
import * as utils from "../utils";
2424
import * as experiments from "../experiments";

src/commands/init.ts

+5
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ const choices = [
6565
name: "Remote Config: Configure a template file for Remote Config",
6666
checked: false,
6767
},
68+
{
69+
value: "extensions",
70+
name: "Extensions: Set up an empty Extensions manifest",
71+
checked: false,
72+
},
6873
];
6974
const featureNames = choices.map((choice) => choice.value);
7075

src/extensions/extensionsHelper.ts

+1-23
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import {
3636
import { Extension, ExtensionSource, ExtensionSpec, ExtensionVersion, Param } from "./types";
3737
import * as refs from "./refs";
3838
import { EXTENSIONS_SPEC_FILE, readFile, getLocalExtensionSpec } from "./localHelper";
39-
import { promptOnce } from "../prompt";
39+
import { confirm, promptOnce } from "../prompt";
4040
import { logger } from "../logger";
4141
import { envOverride } from "../utils";
4242
import { getLocalChangelog } from "./change-log";
@@ -1037,28 +1037,6 @@ export function getSourceOrigin(sourceOrVersion: string): SourceOrigin {
10371037
);
10381038
}
10391039

1040-
/**
1041-
* Confirm if the user wants to continue
1042-
*/
1043-
export async function confirm(args: {
1044-
nonInteractive?: boolean;
1045-
force?: boolean;
1046-
default?: boolean;
1047-
}): Promise<boolean> {
1048-
if (!args.nonInteractive && !args.force) {
1049-
const message = `Do you wish to continue?`;
1050-
return await promptOnce({
1051-
type: "confirm",
1052-
message,
1053-
default: args.default,
1054-
});
1055-
} else if (args.nonInteractive && !args.force) {
1056-
throw new FirebaseError("Pass the --force flag to use this command in non-interactive mode");
1057-
} else {
1058-
return true;
1059-
}
1060-
}
1061-
10621040
export async function diagnoseAndFixProject(options: any): Promise<void> {
10631041
const projectId = getProjectId(options);
10641042
if (!projectId) {

src/extensions/manifest.ts

+29-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import * as clc from "colorette";
22
import * as path from "path";
3+
import * as fs from "fs-extra";
4+
35
import * as refs from "./refs";
46
import { Config } from "../config";
57
import { getExtensionSpec, ManifestInstanceSpec } from "../deploy/extensions/planner";
68
import { logger } from "../logger";
7-
import { promptOnce } from "../prompt";
9+
import { confirm, promptOnce } from "../prompt";
810
import { readEnvFile } from "./paramHelper";
911
import { FirebaseError } from "../error";
1012
import * as utils from "../utils";
@@ -60,6 +62,31 @@ export async function writeToManifest(
6062
await writeLocalSecrets(specs, config, options.force);
6163
}
6264

65+
export async function writeEmptyManifest(
66+
config: Config,
67+
options: { nonInteractive: boolean; force: boolean }
68+
): Promise<void> {
69+
if (!fs.existsSync(config.path("extensions"))) {
70+
fs.mkdirSync(config.path("extensions"));
71+
}
72+
if (config.has("extensions") && Object.keys(config.get("extensions")).length) {
73+
const currentExtensions = Object.entries(config.get("extensions"))
74+
.map((i) => `${i[0]}: ${i[1]}`)
75+
.join("\n\t");
76+
if (
77+
!(await confirm({
78+
message: `firebase.json already contains extensions:\n${currentExtensions}\nWould you like to overwrite them?`,
79+
nonInteractive: options.nonInteractive,
80+
force: options.force,
81+
default: false,
82+
}))
83+
) {
84+
return;
85+
}
86+
}
87+
config.set("extensions", {});
88+
}
89+
6390
/**
6491
* Write the secrets in a list of ManifestInstanceSpec into extensions/{instance-id}.secret.local.
6592
*
@@ -173,7 +200,7 @@ export function getInstanceRef(instanceId: string, config: Config): refs.Ref {
173200
return refs.parse(source);
174201
}
175202

176-
function writeExtensionsToFirebaseJson(specs: ManifestInstanceSpec[], config: Config): void {
203+
export function writeExtensionsToFirebaseJson(specs: ManifestInstanceSpec[], config: Config): void {
177204
const extensions = config.get("extensions", {});
178205
for (const s of specs) {
179206
let target;

src/init/features/extensions/index.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { requirePermissions } from "../../../requirePermissions";
2+
import { Options } from "../../../options";
3+
import { ensure } from "../../../ensureApiEnabled";
4+
import { Config } from "../../../config";
5+
import * as manifest from "../../../extensions/manifest";
6+
7+
/**
8+
* Set up a new firebase project for extensions.
9+
*/
10+
export async function doSetup(setup: any, config: Config, options: Options): Promise<any> {
11+
const projectId = setup?.rcfile?.projects?.default;
12+
if (projectId) {
13+
await requirePermissions({ ...options, project: projectId });
14+
await Promise.all([ensure(projectId, "firebaseextensions.googleapis.com", "unused", true)]);
15+
}
16+
return manifest.writeEmptyManifest(config, options);
17+
}

src/init/features/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export { doSetup as functions } from "./functions";
55
export { doSetup as hosting } from "./hosting";
66
export { doSetup as storage } from "./storage";
77
export { doSetup as emulators } from "./emulators";
8+
export { doSetup as extensions } from "./extensions";
89
// always runs, sets up .firebaserc
910
export { doSetup as project } from "./project";
1011
export { doSetup as remoteconfig } from "./remoteconfig";

src/init/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const featureFns = new Map<string, (setup: any, config: any, options?: any) => P
2525
["hosting", features.hosting],
2626
["storage", features.storage],
2727
["emulators", features.emulators],
28+
["extensions", features.extensions],
2829
["project", features.project], // always runs, sets up .firebaserc
2930
["remoteconfig", features.remoteconfig],
3031
["hosting:github", features.hostingGithub],

src/prompt.ts

+23
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,26 @@ export async function promptOnce<A>(question: Question, options: Options = {}):
9797
await prompt(options, [question]);
9898
return options[question.name];
9999
}
100+
101+
/**
102+
* Confirm if the user wants to continue
103+
*/
104+
export async function confirm(args: {
105+
nonInteractive?: boolean;
106+
force?: boolean;
107+
default?: boolean;
108+
message?: string;
109+
}): Promise<boolean> {
110+
if (!args.nonInteractive && !args.force) {
111+
const message = args.message ?? `Do you wish to continue?`;
112+
return await promptOnce({
113+
type: "confirm",
114+
message,
115+
default: args.default,
116+
});
117+
} else if (args.nonInteractive && !args.force) {
118+
throw new FirebaseError("Pass the --force flag to use this command in non-interactive mode");
119+
} else {
120+
return true;
121+
}
122+
}

0 commit comments

Comments
 (0)