Skip to content

Commit a53a130

Browse files
authored
Merge branch 'main' into inlined.vpc-dc
2 parents bf0df0f + d14584d commit a53a130

20 files changed

Lines changed: 516 additions & 153 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
- Add support for VPC direct connect in GCF 2nd gen (#10033)
2+
- Added `--only` flag for `emulators:export` (#4033)

firebase-vscode/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
## NEXT
22

3+
## 2.2.0
4+
5+
- Update internal `firebase-tools` dependency to 15.9.0
6+
37
## 2.1.2
48

59
- Update internal `firebase-tools` dependency to 15.7.0

firebase-vscode/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

firebase-vscode/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"publisher": "GoogleCloudTools",
55
"icon": "./resources/firebase_dataconnect_logo.png",
66
"description": "Firebase Data Connect for VSCode",
7-
"version": "2.1.2",
7+
"version": "2.2.0",
88
"engines": {
99
"vscode": "^1.69.0"
1010
},

npm-shrinkwrap.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "firebase-tools",
3-
"version": "15.9.0",
3+
"version": "15.9.1",
44
"description": "Command-Line Interface for Firebase",
55
"main": "./lib/index.js",
66
"mcpName": "io.github.firebase/firebase-mcp",

scripts/emulator-import-export-tests/tests.ts

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,11 @@ describe("import/export end to end", () => {
220220
const bPath = path.join(dbExportPath, "namespace-b.json");
221221
const bData = JSON.parse(fs.readFileSync(bPath).toString());
222222
expect(bData).to.equal(null);
223+
224+
// Clean up the admin sdk instances to prevent "Firebase app named <name> already exists." errors in later tests
225+
await aApp.delete();
226+
await bApp.delete();
227+
await cApp.delete();
223228
});
224229

225230
it("should be able to import/export auth data", async function (this) {
@@ -326,6 +331,9 @@ describe("import/export end to end", () => {
326331
expect(user2.emailVerified).to.be.true;
327332

328333
await importCLI.stop();
334+
335+
// Clean up the admin sdk instance to prevent "Firebase app named <name> already exists." errors in later tests
336+
await adminApp.delete();
329337
} finally {
330338
delete process.env.FIREBASE_AUTH_EMULATOR_HOST;
331339
}
@@ -489,6 +497,9 @@ describe("import/export end to end", () => {
489497
expect(user4.emailVerified).to.be.true;
490498

491499
await importCLI.stop();
500+
501+
// Clean up the admin sdk instance to prevent "Firebase app named <name> already exists." errors in later tests
502+
await adminApp.delete();
492503
} finally {
493504
delete process.env.FIREBASE_AUTH_EMULATOR_HOST;
494505
}
@@ -576,10 +587,14 @@ describe("import/export end to end", () => {
576587
expect(user.passwordHash).to.match(/:password=testing$/);
577588

578589
await importCLI.stop();
590+
591+
// Clean up the admin sdk instance to prevent "Firebase app named <name> already exists." errors in later tests
592+
await adminApp.delete();
579593
} finally {
580594
delete process.env.FIREBASE_AUTH_EMULATOR_HOST;
581595
}
582596
});
597+
583598
it("should be able to export / import auth data with no users", async function (this) {
584599
this.timeout(2 * TEST_SETUP_TIMEOUT);
585600
await new Promise((resolve) => setTimeout(resolve, 2000));
@@ -738,5 +753,278 @@ describe("import/export end to end", () => {
738753
// expect(buf.toString()).to.eql("a/b hello, world!");
739754

740755
await importCLI.stop();
756+
757+
// Clean up the admin sdk instances to prevent "Firebase app named <name> already exists." errors in later tests
758+
await aApp.delete();
759+
await bApp.delete();
760+
});
761+
762+
it("should export all data when `--only` flag isn't used `emulators:export`", async function (this) {
763+
this.timeout(2 * TEST_SETUP_TIMEOUT);
764+
await new Promise((resolve) => setTimeout(resolve, 2000));
765+
766+
// Start up emulator suite
767+
const emulatorsCLI = new CLIProcess("1", __dirname);
768+
await emulatorsCLI.start(
769+
"emulators:start",
770+
FIREBASE_PROJECT,
771+
["--only", "storage,auth"],
772+
logIncludes(ALL_EMULATORS_STARTED_LOG),
773+
);
774+
775+
const credPath = path.join(__dirname, "service-account-key.json");
776+
const credential = fs.existsSync(credPath)
777+
? admin.credential.cert(credPath)
778+
: admin.credential.applicationDefault();
779+
780+
const config = readConfig();
781+
const storagePort = config.emulators!.storage.port;
782+
process.env.STORAGE_EMULATOR_HOST = `http://${await localhost()}:${storagePort}`;
783+
784+
// Write some data to export
785+
const aApp = admin.initializeApp(
786+
{
787+
projectId: FIREBASE_PROJECT,
788+
storageBucket: "bucket-a",
789+
credential,
790+
},
791+
"storage-export-a",
792+
);
793+
const bApp = admin.initializeApp(
794+
{
795+
projectId: FIREBASE_PROJECT,
796+
storageBucket: "bucket-b",
797+
credential,
798+
},
799+
"storage-export-b",
800+
);
801+
802+
// Write data to two buckets
803+
await aApp.storage().bucket().file("a/b.txt").save("a/b hello, world!");
804+
await aApp.storage().bucket().file("c/d.txt").save("c/d hello, world!");
805+
await bApp.storage().bucket().file("e/f.txt").save("e/f hello, world!");
806+
await bApp.storage().bucket().file("g/h.txt").save("g/h hello, world!");
807+
808+
// Create some accounts to export:
809+
const authPort = config.emulators!.auth.port;
810+
process.env.FIREBASE_AUTH_EMULATOR_HOST = `${await localhost()}:${authPort}`;
811+
const cApp = admin.initializeApp(
812+
{
813+
projectId: FIREBASE_PROJECT,
814+
credential: ADMIN_CREDENTIAL,
815+
},
816+
"auth-export",
817+
);
818+
await cApp.auth().createUser({ uid: "123", email: "foo@example.com", password: "testing" });
819+
await cApp.auth().createUser({ uid: "456", email: "bar@example.com", emailVerified: true });
820+
821+
// Ask for export
822+
const exportCLI = new CLIProcess("2", __dirname);
823+
const exportPath = fs.mkdtempSync(path.join(os.tmpdir(), "emulator-data"));
824+
await exportCLI.start(
825+
"emulators:export",
826+
FIREBASE_PROJECT,
827+
[exportPath],
828+
logIncludes("Export complete"),
829+
);
830+
await exportCLI.stop();
831+
832+
// Check that the right export files are created
833+
const storageExportPath = path.join(exportPath, "storage_export");
834+
const storageExportFiles = fs.readdirSync(storageExportPath).sort();
835+
expect(storageExportFiles).to.eql(["blobs", "buckets.json", "metadata"]);
836+
837+
// Stop the suite
838+
await emulatorsCLI.stop();
839+
840+
// Attempt to import
841+
const importCLI = new CLIProcess("3", __dirname);
842+
await importCLI.start(
843+
"emulators:start",
844+
FIREBASE_PROJECT,
845+
["--only", "storage,auth", "--import", exportPath],
846+
logIncludes(ALL_EMULATORS_STARTED_LOG),
847+
);
848+
849+
// List the files
850+
const [aFiles] = await aApp.storage().bucket().getFiles({
851+
prefix: "a/",
852+
});
853+
const aFileNames = aFiles.map((f) => f.name).sort();
854+
expect(aFileNames).to.eql(["a/b.txt"]);
855+
856+
const [bFiles] = await bApp.storage().bucket().getFiles({
857+
prefix: "e/",
858+
});
859+
const bFileNames = bFiles.map((f) => f.name).sort();
860+
expect(bFileNames).to.eql(["e/f.txt"]);
861+
862+
const user1 = await cApp.auth().getUserByEmail("foo@example.com");
863+
expect(user1.passwordHash).to.match(/:password=testing$/);
864+
const user2 = await cApp.auth().getUserByEmail("bar@example.com");
865+
expect(user2.emailVerified).to.be.true;
866+
867+
await importCLI.stop();
868+
869+
// Clean up the admin sdk instances to prevent "Firebase app named <name> already exists." errors in later tests
870+
await aApp.delete();
871+
await bApp.delete();
872+
await cApp.delete();
873+
});
874+
875+
it("should export only storage data with `emulators:export --only storage`", async function (this) {
876+
this.timeout(2 * TEST_SETUP_TIMEOUT);
877+
await new Promise((resolve) => setTimeout(resolve, 2000));
878+
879+
// Start up emulator suite
880+
const emulatorsCLI = new CLIProcess("1", __dirname);
881+
await emulatorsCLI.start(
882+
"emulators:start",
883+
FIREBASE_PROJECT,
884+
["--only", "storage,auth"],
885+
logIncludes(ALL_EMULATORS_STARTED_LOG),
886+
);
887+
888+
const credPath = path.join(__dirname, "service-account-key.json");
889+
const credential = fs.existsSync(credPath)
890+
? admin.credential.cert(credPath)
891+
: admin.credential.applicationDefault();
892+
893+
const config = readConfig();
894+
const storagePort = config.emulators!.storage.port;
895+
process.env.STORAGE_EMULATOR_HOST = `http://${await localhost()}:${storagePort}`;
896+
897+
// Write some data to export
898+
const aApp = admin.initializeApp(
899+
{
900+
projectId: FIREBASE_PROJECT,
901+
storageBucket: "bucket-a",
902+
credential,
903+
},
904+
"storage-export-a",
905+
);
906+
const bApp = admin.initializeApp(
907+
{
908+
projectId: FIREBASE_PROJECT,
909+
storageBucket: "bucket-b",
910+
credential,
911+
},
912+
"storage-export-b",
913+
);
914+
915+
// Write data to two buckets
916+
await aApp.storage().bucket().file("a/b.txt").save("a/b hello, world!");
917+
await aApp.storage().bucket().file("c/d.txt").save("c/d hello, world!");
918+
await bApp.storage().bucket().file("e/f.txt").save("e/f hello, world!");
919+
await bApp.storage().bucket().file("g/h.txt").save("g/h hello, world!");
920+
921+
// Create some accounts to export:
922+
const authPort = config.emulators!.auth.port;
923+
process.env.FIREBASE_AUTH_EMULATOR_HOST = `${await localhost()}:${authPort}`;
924+
const cApp = admin.initializeApp(
925+
{
926+
projectId: FIREBASE_PROJECT,
927+
credential: ADMIN_CREDENTIAL,
928+
},
929+
"auth-export",
930+
);
931+
await cApp.auth().createUser({ uid: "123", email: "foo@example.com", password: "testing" });
932+
await cApp.auth().createUser({ uid: "456", email: "bar@example.com", emailVerified: true });
933+
934+
// Ask for export
935+
const exportCLI = new CLIProcess("2", __dirname);
936+
const exportPath = fs.mkdtempSync(path.join(os.tmpdir(), "emulator-data"));
937+
await exportCLI.start(
938+
"emulators:export",
939+
FIREBASE_PROJECT,
940+
[exportPath, "--only", "storage"],
941+
logIncludes("Export complete"),
942+
);
943+
await exportCLI.stop();
944+
945+
// Check that the right export files are created
946+
const storageExportPath = path.join(exportPath, "storage_export");
947+
const storageExportFiles = fs.readdirSync(storageExportPath).sort();
948+
expect(storageExportFiles).to.eql(["blobs", "buckets.json", "metadata"]);
949+
950+
// Stop the suite
951+
await emulatorsCLI.stop();
952+
953+
// Attempt to import
954+
const importCLI = new CLIProcess("3", __dirname);
955+
await importCLI.start(
956+
"emulators:start",
957+
FIREBASE_PROJECT,
958+
["--only", "storage,auth", "--import", exportPath],
959+
logIncludes(ALL_EMULATORS_STARTED_LOG),
960+
);
961+
962+
// List the files
963+
const [aFiles] = await aApp.storage().bucket().getFiles({
964+
prefix: "a/",
965+
});
966+
const aFileNames = aFiles.map((f) => f.name).sort();
967+
expect(aFileNames).to.eql(["a/b.txt"]);
968+
969+
const [bFiles] = await bApp.storage().bucket().getFiles({
970+
prefix: "e/",
971+
});
972+
const bFileNames = bFiles.map((f) => f.name).sort();
973+
expect(bFileNames).to.eql(["e/f.txt"]);
974+
975+
await expect(cApp.auth().getUserByEmail("foo@example.com"))
976+
.to.eventually.be.rejectedWith(Error)
977+
.and.have.property("code", "auth/user-not-found");
978+
await expect(cApp.auth().getUserByEmail("bar@example.com"))
979+
.to.eventually.be.rejectedWith(Error)
980+
.and.have.property("code", "auth/user-not-found");
981+
982+
await importCLI.stop();
983+
984+
// Clean up the admin sdk instances to prevent "Firebase app named <name> already exists." errors in later tests
985+
await aApp.delete();
986+
await bApp.delete();
987+
await cApp.delete();
988+
});
989+
990+
it("should be able to export using POST", async function (this) {
991+
this.timeout(2 * TEST_SETUP_TIMEOUT);
992+
await new Promise((resolve) => setTimeout(resolve, 2000));
993+
994+
// Start up emulator suite
995+
const emulatorsCLI = new CLIProcess("1", __dirname);
996+
await emulatorsCLI.start(
997+
"emulators:start",
998+
FIREBASE_PROJECT,
999+
["--only", "firestore"],
1000+
logIncludes(ALL_EMULATORS_STARTED_LOG),
1001+
);
1002+
1003+
const config = readConfig();
1004+
const hubPort = config.emulators!.hub!.port;
1005+
const host = await localhost();
1006+
1007+
// Ask for export using HTTP POST to hub
1008+
const exportPath = fs.mkdtempSync(path.join(os.tmpdir(), "emulator-data"));
1009+
const postData = JSON.stringify({ path: exportPath });
1010+
1011+
const response = await fetch(`http://${host}:${hubPort}/_admin/export`, {
1012+
method: "POST",
1013+
headers: {
1014+
"Content-Type": "application/json",
1015+
},
1016+
body: postData,
1017+
});
1018+
1019+
if (!response.ok) {
1020+
throw new Error(`Failed to export: ${response.status}`);
1021+
}
1022+
1023+
// Check that the export files are created
1024+
const exportFiles = fs.readdirSync(exportPath);
1025+
expect(exportFiles).to.include("firebase-export-metadata.json");
1026+
1027+
// Stop the suite
1028+
await emulatorsCLI.stop();
7411029
});
7421030
});

scripts/publish/firebase-docker-image/Dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
FROM node:lts-alpine AS app-env
22

3+
# Make sure to get the latest version of npm before doing anything else, to avoid vulnerabilities in npm's bundled dependencies.
4+
RUN npm install -g npm@latest
5+
36
# Install Python (for Cloud Functions) and Java (for emulators) and pre-cache emulator dependencies.
47
RUN apk update && \
58
apk add --no-cache python3 openjdk21-jre-headless bash && \

0 commit comments

Comments
 (0)