-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathinit.ts
More file actions
295 lines (264 loc) · 8.57 KB
/
init.ts
File metadata and controls
295 lines (264 loc) · 8.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
import * as clc from "colorette";
import * as os from "os";
import * as path from "path";
import { Command } from "../command";
import { Config } from "../config";
import { getAllAccounts } from "../auth";
import { init, Setup } from "../init";
import { logger } from "../logger";
import { checkbox, confirm } from "../prompt";
import * as fsutils from "../fsutils";
import * as utils from "../utils";
import { Options } from "../options";
import { isEnabled } from "../experiments";
import { readTemplateSync } from "../templates";
import { FirebaseError } from "../error";
import { logBullet } from "../utils";
const homeDir = os.homedir();
const BANNER_TEXT = readTemplateSync("banner.txt");
const GITIGNORE_TEMPLATE = readTemplateSync("_gitignore");
function isOutside(from: string, to: string): boolean {
return !!/^\.\./.exec(path.relative(from, to));
}
let choices: {
value: string;
name: string;
checked: boolean;
hidden?: boolean;
}[] = [
{
value: "dataconnect",
name: "Data Connect: Set up a Firebase Data Connect service",
checked: false,
},
{
value: "firestore",
name: "Firestore: Configure security rules and indexes files for Firestore",
checked: false,
},
{
value: "functions",
name: "Functions: Configure a Cloud Functions directory and its files",
checked: false,
},
{
value: "apphosting",
name: "App Hosting: Set up deployments for full-stack web apps (supports server-side rendering)",
checked: false,
hidden: false,
},
{
value: "hosting",
name: "Hosting: Set up deployments for static web apps",
checked: false,
},
{
value: "storage",
name: "Storage: Configure a security rules file for Cloud Storage",
checked: false,
},
{
value: "emulators",
name: "Emulators: Set up local emulators for Firebase products",
checked: false,
},
{
value: "remoteconfig",
name: "Remote Config: Configure a template file for Remote Config",
checked: false,
},
{
value: "extensions",
name: "Extensions: Set up an empty Extensions manifest",
checked: false,
},
{
value: "database",
name: "Realtime Database: Configure a security rules file for Realtime Database and (optionally) provision default instance",
checked: false,
},
{
value: "hosting:github",
name: "Hosting: Set up GitHub Action deploys",
checked: false,
hidden: true,
},
{
value: "dataconnect:sdk",
name: "Data Connect: Set up a generated SDK for your Firebase Data Connect service",
checked: false,
hidden: true,
},
];
if (isEnabled("fdcwebhooks")) {
choices.push({
value: "dataconnect:resolver",
name: "Data Connect: Set up a custom resolver for your Firebase Data Connect service",
checked: false,
hidden: true,
});
}
if (isEnabled("genkit")) {
choices = [
...choices.slice(0, 2),
{
value: "genkit",
name: "Genkit: Setup a new Genkit project with Firebase",
checked: false,
},
...choices.slice(2),
];
}
if (isEnabled("apptesting")) {
choices.push({
value: "apptesting",
name: "App Testing: create a smoke test, enable Cloud APIs (storage, run, & artifactregistry), and add a service account.",
checked: false,
});
}
choices.push({
value: "ailogic",
name: "AI Logic: Set up Firebase AI Logic with app provisioning",
checked: false,
});
choices.push({
value: "aitools",
name: "AI Tools: Configure AI coding assistants to work with your Firebase project",
checked: false,
hidden: true,
});
const featureNames = choices.map((choice) => choice.value);
const HELP = `Interactively configure the current directory as a Firebase project or initialize new features in an already configured Firebase project directory.
This command will create or update 'firebase.json' and '.firebaserc' configuration files in the current directory.
To initialize a specific Firebase feature, run 'firebase init [feature]'. Valid features are:
${[...featureNames]
.sort()
.map((n) => `\n - ${n}`)
.join("")}`;
export const command = new Command("init [feature]")
.description("interactively configure the current directory as a Firebase project directory")
.help(HELP)
.action(initAction);
/**
* Init command action
* @param feature Feature to init (e.g., hosting, functions)
* @param options Command options
*/
export async function initAction(feature: string, options: Options): Promise<void> {
if (feature && !featureNames.includes(feature)) {
return utils.reject(
clc.bold(feature) +
" is not a supported feature; must be one of " +
featureNames.join(", ") +
".",
);
}
const cwd = options.cwd || process.cwd();
const warnings = [];
let warningText = "";
if (isOutside(homeDir, cwd)) {
warnings.push("You are currently outside your home directory");
}
if (cwd === homeDir) {
warnings.push("You are initializing your home directory as a Firebase project directory");
}
const existingConfig = Config.load(options, true);
if (existingConfig) {
warnings.push("You are initializing within an existing Firebase project directory");
}
const config =
existingConfig !== null ? existingConfig : new Config({}, { projectDir: cwd, cwd: cwd });
if (warnings.length) {
warningText =
"\nBefore we get started, keep in mind:\n\n " +
clc.yellow(clc.bold("* ")) +
warnings.join("\n " + clc.yellow(clc.bold("* "))) +
"\n";
}
logger.info(
clc.yellow(clc.bold(BANNER_TEXT)) +
"\nYou're about to initialize a Firebase project in this directory:\n\n " +
clc.bold(config.projectDir) +
"\n" +
warningText,
);
const setup: Setup = {
config: config.src,
rcfile: config.readProjectFile(".firebaserc", {
json: true,
fallback: {},
}),
instructions: [],
};
// HACK: Windows Node has issues with selectables as the first prompt, so we
// add an extra confirmation prompt that fixes the problem
// TODO: see if this issue still persists in the new prompt library.
if (process.platform === "win32") {
if (!(await confirm("Are you ready to proceed?"))) {
throw new FirebaseError("Aborted by user.", { exit: 1 });
}
}
if (feature) {
setup.featureArg = true;
setup.features = [feature];
} else {
setup.features = await checkbox<string>({
message:
"Which Firebase features do you want to set up for this directory? " +
"Press Space to select features, then Enter to confirm your choices.",
choices: choices.filter((c) => !c.hidden),
validate: (choices) => {
if (choices.length === 0) {
return (
"Must select at least one feature. Use " +
clc.bold(clc.underline("SPACEBAR")) +
" to select features, or specify a feature by running " +
clc.bold("firebase init [feature_name]")
);
}
return true;
},
});
}
if (!setup.features || setup.features?.length === 0) {
throw new FirebaseError(
"Must select at least one feature. Use " +
clc.bold(clc.underline("SPACEBAR")) +
" to select features, or specify a feature by running " +
clc.bold("firebase init [feature_name]"),
);
}
// Always set up project
setup.features.unshift("project");
// If there is more than one account, add an account choice phase
const allAccounts = getAllAccounts();
if (allAccounts.length > 1) {
setup.features.unshift("account");
}
// "hosting:github" is a part of "hosting", so if both are selected, "hosting:github" is ignored.
if (setup.features.includes("hosting") && setup.features.includes("hosting:github")) {
setup.features = setup.features.filter((f) => f !== "hosting:github");
}
// "dataconnect:sdk" is a part of "dataconnect", so if both are selected, "dataconnect:sdk" is ignored.
if (setup.features.includes("dataconnect") && setup.features.includes("dataconnect:sdk")) {
setup.features = setup.features.filter((f) => f !== "dataconnect:sdk");
}
await init(setup, config, options);
await postInitSaves(setup, config);
if (setup.instructions.length) {
logger.info(`\n${clc.bold("To get started:")}\n`);
for (const i of setup.instructions) {
logBullet(i + "\n");
}
}
}
export async function postInitSaves(setup: Setup, config: Config): Promise<void> {
logger.info();
config.writeProjectFile("firebase.json", setup.config);
config.writeProjectFile(".firebaserc", setup.rcfile);
if (!fsutils.fileExistsSync(config.path(".gitignore"))) {
config.writeProjectFile(".gitignore", GITIGNORE_TEMPLATE);
}
logger.info();
utils.logSuccess("Firebase initialization complete!");
}