Skip to content

Commit 8cc734b

Browse files
maneeshtjoehan
andauthored
First pass at auto generating sdk configs (#7833)
* First pass at auto generating sdk configs * Fixed formatting issues * Removed extra command * Deleted unnecessary files * Fixed more linting' * Removed test assertion * Fixed formatting * Updated erros * Misc * Updated platforms list * Undid last changes * Addressed comments * Fixed client test * Driveby type fixing * missed a spot * Fixed test * Fix issue where if a user passes in an empty 'out' parameter, the CLI crashes * Added intelligent sensing where app should be * Fixed formatting * Fixed lint * Fixed app dir * Misc * Wrote tests * Reverted apps sdkconfig changes * Fixed formatting * Small changes * Revert shrinkwrap changes * Updated test:management script * Fixed apps-sdkconfig boolean check * Fixed more boolean * Fixed formatting * Added changelog * Added new options * Removed unused var * Added experimental flag * Moved apps:init behind a flag * Added apps:init command * Removed unnecessary experiments * Addressed comments --------- Co-authored-by: Joe Hanley <[email protected]>
1 parent c4603eb commit 8cc734b

File tree

12 files changed

+732
-226
lines changed

12 files changed

+732
-226
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
- Switched Data Connect from `v1beta` API to `v1` API.
22
- Added code generation of React hooks for Data Connect
33
- Genkit init improvements around gcloud login and flow input values.
4+
- Added new command `apps:init` under experimental flag (`appsinit`) that automatically detects what SDK to download and places the file in the corresponding place.
45
- Removed dependencies on some packages and methods that caused deprecation warnings on Node 22.
56
- Fixes symbol generation when uploading Unity 6 symbols to Crashlytics. (#7867)
67
- Fixed SSR issues in Angular 19 by adding support for default and reqHandler exports. (#8145)

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"test": "npm run lint:quiet && npm run test:compile && npm run mocha",
2929
"test:client-integration": "bash ./scripts/client-integration-tests/run.sh",
3030
"test:compile": "tsc --project tsconfig.compile.json",
31+
"test:management": "npm run lint:quiet && npm run test:compile && nyc mocha 'src/management/*.spec.{ts,js}'",
3132
"test:dataconnect-deploy": "bash ./scripts/dataconnect-test/run.sh",
3233
"test:dataconnect-emulator": "bash ./scripts/dataconnect-emulator-tests/run.sh",
3334
"test:all-emulators": "npm run test:emulator && npm run test:extensions-emulator && npm run test:import-export && npm run test:storage-emulator-integration",

src/commands/apps-create.ts

Lines changed: 11 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import * as clc from "colorette";
2-
import * as ora from "ora";
32

43
import { Command } from "../command";
54
import { needProjectId } from "../projectUtils";
@@ -8,42 +7,16 @@ import {
87
AndroidAppMetadata,
98
AppMetadata,
109
AppPlatform,
11-
createAndroidApp,
12-
createIosApp,
13-
createWebApp,
1410
getAppPlatform,
1511
IosAppMetadata,
12+
sdkInit,
13+
SdkInitOptions,
1614
WebAppMetadata,
1715
} from "../management/apps";
18-
import { prompt, promptOnce, Question } from "../prompt";
16+
import { promptOnce } from "../prompt";
1917
import { requireAuth } from "../requireAuth";
2018
import { logger } from "../logger";
21-
22-
const DISPLAY_NAME_QUESTION: Question = {
23-
type: "input",
24-
name: "displayName",
25-
default: "",
26-
message: "What would you like to call your app?",
27-
};
28-
29-
interface CreateFirebaseAppOptions {
30-
project: string;
31-
nonInteractive: boolean;
32-
displayName?: string;
33-
}
34-
35-
interface CreateIosAppOptions extends CreateFirebaseAppOptions {
36-
bundleId?: string;
37-
appStoreId?: string;
38-
}
39-
40-
interface CreateAndroidAppOptions extends CreateFirebaseAppOptions {
41-
packageName: string;
42-
}
43-
44-
interface CreateWebAppOptions extends CreateFirebaseAppOptions {
45-
displayName: string;
46-
}
19+
import { Options } from "../options";
4720

4821
function logPostAppCreationInformation(
4922
appMetadata: IosAppMetadata | AndroidAppMetadata | WebAppMetadata,
@@ -72,91 +45,10 @@ function logPostAppCreationInformation(
7245
logger.info(` firebase apps:sdkconfig ${appPlatform} ${appMetadata.appId}`);
7346
}
7447

75-
async function initiateIosAppCreation(options: CreateIosAppOptions): Promise<IosAppMetadata> {
76-
if (!options.nonInteractive) {
77-
await prompt(options, [
78-
DISPLAY_NAME_QUESTION,
79-
{
80-
type: "input",
81-
default: "",
82-
name: "bundleId",
83-
message: "Please specify your iOS app bundle ID:",
84-
},
85-
{
86-
type: "input",
87-
default: "",
88-
name: "appStoreId",
89-
message: "Please specify your iOS app App Store ID:",
90-
},
91-
]);
92-
}
93-
if (!options.bundleId) {
94-
throw new FirebaseError("Bundle ID for iOS app cannot be empty");
95-
}
96-
97-
const spinner = ora("Creating your iOS app").start();
98-
try {
99-
const appData = await createIosApp(options.project, {
100-
displayName: options.displayName,
101-
bundleId: options.bundleId,
102-
appStoreId: options.appStoreId,
103-
});
104-
spinner.succeed();
105-
return appData;
106-
} catch (err: unknown) {
107-
spinner.fail();
108-
throw err;
109-
}
110-
}
111-
112-
async function initiateAndroidAppCreation(
113-
options: CreateAndroidAppOptions,
114-
): Promise<AndroidAppMetadata> {
115-
if (!options.nonInteractive) {
116-
await prompt(options, [
117-
DISPLAY_NAME_QUESTION,
118-
{
119-
type: "input",
120-
default: "",
121-
name: "packageName",
122-
message: "Please specify your Android app package name:",
123-
},
124-
]);
125-
}
126-
if (!options.packageName) {
127-
throw new FirebaseError("Package name for Android app cannot be empty");
128-
}
129-
130-
const spinner = ora("Creating your Android app").start();
131-
try {
132-
const appData = await createAndroidApp(options.project, {
133-
displayName: options.displayName,
134-
packageName: options.packageName,
135-
});
136-
spinner.succeed();
137-
return appData;
138-
} catch (err: unknown) {
139-
spinner.fail();
140-
throw err;
141-
}
142-
}
143-
144-
async function initiateWebAppCreation(options: CreateWebAppOptions): Promise<WebAppMetadata> {
145-
if (!options.nonInteractive) {
146-
await prompt(options, [DISPLAY_NAME_QUESTION]);
147-
}
148-
if (!options.displayName) {
149-
throw new FirebaseError("Display name for Web app cannot be empty");
150-
}
151-
const spinner = ora("Creating your Web app").start();
152-
try {
153-
const appData = await createWebApp(options.project, { displayName: options.displayName });
154-
spinner.succeed();
155-
return appData;
156-
} catch (err: unknown) {
157-
spinner.fail();
158-
throw err;
159-
}
48+
interface AppsCreateOptions extends Options {
49+
packageName: string;
50+
bundleId: string;
51+
appStoreId: string;
16052
}
16153

16254
export const command = new Command("apps:create [platform] [displayName]")
@@ -169,9 +61,9 @@ export const command = new Command("apps:create [platform] [displayName]")
16961
.before(requireAuth)
17062
.action(
17163
async (
172-
platform: string = "",
64+
platform = "",
17365
displayName: string | undefined,
174-
options: any,
66+
options: AppsCreateOptions,
17567
): Promise<AppMetadata> => {
17668
const projectId = needProjectId(options);
17769

@@ -194,21 +86,7 @@ export const command = new Command("apps:create [platform] [displayName]")
19486

19587
logger.info(`Create your ${appPlatform} app in project ${clc.bold(projectId)}:`);
19688
options.displayName = displayName; // add displayName into options to pass into prompt function
197-
let appData;
198-
switch (appPlatform) {
199-
case AppPlatform.IOS:
200-
appData = await initiateIosAppCreation(options);
201-
break;
202-
case AppPlatform.ANDROID:
203-
appData = await initiateAndroidAppCreation(options);
204-
break;
205-
case AppPlatform.WEB:
206-
appData = await initiateWebAppCreation(options);
207-
break;
208-
default:
209-
throw new FirebaseError("Unexpected error. This should not happen");
210-
}
211-
89+
const appData = await sdkInit(appPlatform, options as SdkInitOptions);
21290
logPostAppCreationInformation(appData, appPlatform);
21391
return appData;
21492
},

src/commands/apps-init.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import * as fs from "fs-extra";
2+
import * as path from "path";
3+
4+
import { Command } from "../command";
5+
import {
6+
AppConfig,
7+
AppPlatform,
8+
getAppConfigFile,
9+
getAppPlatform,
10+
getPlatform,
11+
getSdkConfig,
12+
getSdkOutputPath,
13+
sdkInit,
14+
writeConfigToFile,
15+
} from "../management/apps";
16+
import { requireAuth } from "../requireAuth";
17+
import { logger } from "../logger";
18+
import { Options } from "../options";
19+
import { needProjectId } from "../projectUtils";
20+
import { Platform } from "../dataconnect/types";
21+
import { assertEnabled } from "../experiments";
22+
23+
export interface AppsInitOptions extends Options {
24+
out?: string | boolean;
25+
}
26+
27+
function logUse(platform: AppPlatform, filePath: string) {
28+
switch (platform) {
29+
case AppPlatform.WEB:
30+
logger.info(`
31+
How to use your JS SDK Config:
32+
ES Module:
33+
import { initializeApp } from 'firebase/app';
34+
import json from './${filePath || "firebase-sdk-config.json"}';
35+
initializeApp(json); // or copy and paste the config directly from the json file here
36+
// CommonJS Module:
37+
const { initializeApp } = require('firebase/app');
38+
const json = require('./firebase-js-config.json');
39+
initializeApp(json); // or copy and paste the config directly from the json file here`);
40+
break;
41+
case AppPlatform.ANDROID:
42+
logger.info(
43+
`Visit https://firebase.google.com/docs/android/setup#add-config-file
44+
for information on editing your gradle file and adding Firebase SDKs to your app.
45+
46+
If you're using Unity or C++, visit https://firebase.google.com/docs/cpp/setup?platform=android#add-config-file
47+
for information about adding your config file to your project.`,
48+
);
49+
break;
50+
case AppPlatform.IOS:
51+
logger.info(
52+
`Visit https://firebase.google.com/docs/ios/setup#add-config-file
53+
for information on adding the config file to your targets and adding Firebase SDKs to your app.
54+
55+
If you're using Unity or C++, visit https://firebase.google.com/docs/cpp/setup?platform=ios#add-config-file
56+
for information about adding your config file to your project.`,
57+
);
58+
break;
59+
}
60+
}
61+
62+
function toAppPlatform(str: string) {
63+
switch (str.toUpperCase()) {
64+
case Platform.ANDROID:
65+
return Platform.ANDROID as unknown as AppPlatform.ANDROID;
66+
case Platform.IOS:
67+
return Platform.IOS as unknown as AppPlatform.IOS;
68+
case Platform.WEB:
69+
return Platform.WEB as unknown as AppPlatform.WEB;
70+
}
71+
throw new Error(`Platform ${str} is not compatible with apps:configure`);
72+
}
73+
74+
export const command = new Command("apps:init [platform] [appId]")
75+
.description("Automatically download and create config of a Firebase app. ")
76+
.before(requireAuth)
77+
.option("-o, --out [file]", "(optional) write config output to a file")
78+
.action(async (platform = "", appId = "", options: AppsInitOptions): Promise<AppConfig> => {
79+
assertEnabled("appsinit", "autoconfigure an app");
80+
if (typeof options.out === "boolean") {
81+
throw new Error("Please specify a file path to output to.");
82+
}
83+
const config = options.config;
84+
const appDir = process.cwd();
85+
// auto-detect the platform
86+
const detectedPlatform = platform ? toAppPlatform(platform) : await getPlatform(appDir, config);
87+
88+
let sdkConfig: AppConfig | undefined;
89+
while (sdkConfig === undefined) {
90+
try {
91+
sdkConfig = await getSdkConfig(options, getAppPlatform(detectedPlatform), appId);
92+
} catch (e) {
93+
if ((e as Error).message.includes("associated with this Firebase project")) {
94+
const projectId = needProjectId(options);
95+
await sdkInit(platform, { ...options, project: projectId });
96+
} else {
97+
throw e;
98+
}
99+
}
100+
}
101+
102+
let outputPath = options.out;
103+
104+
const fileInfo = getAppConfigFile(sdkConfig, detectedPlatform);
105+
let relativePath = "";
106+
outputPath = outputPath || (await getSdkOutputPath(appDir, detectedPlatform, options));
107+
const outputDir = path.dirname(outputPath);
108+
fs.mkdirpSync(outputDir);
109+
relativePath = path.relative(appDir, outputPath);
110+
const written = await writeConfigToFile(
111+
outputPath,
112+
options.nonInteractive,
113+
fileInfo.fileContents,
114+
);
115+
116+
if (written) {
117+
logger.info(`App configuration is written in ${relativePath}`);
118+
}
119+
logUse(detectedPlatform, relativePath);
120+
121+
return sdkConfig;
122+
});

0 commit comments

Comments
 (0)