From 65e0c5967c0ae3e48bd6997935e76be12ba73f73 Mon Sep 17 00:00:00 2001 From: Joanna Wang Date: Sat, 22 Nov 2025 01:29:41 +0000 Subject: [PATCH] Zip deploy fixes --- npm-shrinkwrap.json | 31 ++++++++++---------- package.json | 3 +- src/apphosting/backend.ts | 1 + src/deploy/apphosting/deploy.ts | 6 ++-- src/deploy/apphosting/release.ts | 11 ++++--- src/deploy/apphosting/util.ts | 50 ++++++++++++++++++++++++++++++++ src/gcp/apphosting.ts | 7 ++++- src/gcp/storage.ts | 9 +++--- 8 files changed, 86 insertions(+), 32 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index d1a14b9692b..62c3891f72b 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -9,8 +9,7 @@ "version": "14.23.0", "license": "MIT", "dependencies": { - "@apphosting/build": "^0.1.6", - "@apphosting/common": "^0.0.8", + "@apphosting/build": "^0.1.7", "@electric-sql/pglite": "^0.3.3", "@electric-sql/pglite-tools": "^0.2.8", "@google-cloud/cloud-sql-connector": "^1.3.3", @@ -303,12 +302,12 @@ } }, "node_modules/@apphosting/build": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@apphosting/build/-/build-0.1.6.tgz", - "integrity": "sha512-nXK1wsR1tehaq9uSRDCGQmN+Dp0xbyGohssYd7g4W8ZbzHfUiab+Pabv34pHVTS03VaSVkjdNcR1g9hezi6s8g==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@apphosting/build/-/build-0.1.7.tgz", + "integrity": "sha512-zNgQGiAWDOj6c+4ylv5ej3nLGXzMAVmzCGMqlbSarHe4bvBmZ2C5GfBRdJksedP7C9pqlwTWpxU5+GSzhJ+nKA==", "license": "Apache-2.0", "dependencies": { - "@apphosting/common": "^0.0.8", + "@apphosting/common": "^0.0.9", "@npmcli/promise-spawn": "^3.0.0", "colorette": "^2.0.20", "commander": "^11.1.0", @@ -329,9 +328,9 @@ } }, "node_modules/@apphosting/common": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@apphosting/common/-/common-0.0.8.tgz", - "integrity": "sha512-RJu5gXs2HYV7+anxpVPpp04oXeuHbV3qn402AdXVlnuYM/uWo7aceqmngpfp6Bi376UzRqGjfpdwFHxuwsEGXQ==", + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@apphosting/common/-/common-0.0.9.tgz", + "integrity": "sha512-ZbPZDcVhEN+8m0sf90PmQN4xWaKmmySnBSKKPaIOD0JvcDsRr509WenFEFlojP++VSxwFZDGG/TYsHs1FMMqpw==", "license": "Apache-2.0" }, "node_modules/@astrojs/compiler": { @@ -22065,11 +22064,11 @@ } }, "@apphosting/build": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@apphosting/build/-/build-0.1.6.tgz", - "integrity": "sha512-nXK1wsR1tehaq9uSRDCGQmN+Dp0xbyGohssYd7g4W8ZbzHfUiab+Pabv34pHVTS03VaSVkjdNcR1g9hezi6s8g==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@apphosting/build/-/build-0.1.7.tgz", + "integrity": "sha512-zNgQGiAWDOj6c+4ylv5ej3nLGXzMAVmzCGMqlbSarHe4bvBmZ2C5GfBRdJksedP7C9pqlwTWpxU5+GSzhJ+nKA==", "requires": { - "@apphosting/common": "^0.0.8", + "@apphosting/common": "^0.0.9", "@npmcli/promise-spawn": "^3.0.0", "colorette": "^2.0.20", "commander": "^11.1.0", @@ -22085,9 +22084,9 @@ } }, "@apphosting/common": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@apphosting/common/-/common-0.0.8.tgz", - "integrity": "sha512-RJu5gXs2HYV7+anxpVPpp04oXeuHbV3qn402AdXVlnuYM/uWo7aceqmngpfp6Bi376UzRqGjfpdwFHxuwsEGXQ==" + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@apphosting/common/-/common-0.0.9.tgz", + "integrity": "sha512-ZbPZDcVhEN+8m0sf90PmQN4xWaKmmySnBSKKPaIOD0JvcDsRr509WenFEFlojP++VSxwFZDGG/TYsHs1FMMqpw==" }, "@astrojs/compiler": { "version": "1.3.1", diff --git a/package.json b/package.json index 5d7755e28b4..bd2abf09055 100644 --- a/package.json +++ b/package.json @@ -103,8 +103,7 @@ ] }, "dependencies": { - "@apphosting/build": "^0.1.6", - "@apphosting/common": "^0.0.8", + "@apphosting/build": "^0.1.7", "@electric-sql/pglite": "^0.3.3", "@electric-sql/pglite-tools": "^0.2.8", "@google-cloud/cloud-sql-connector": "^1.3.3", diff --git a/src/apphosting/backend.ts b/src/apphosting/backend.ts index c3320c9cf12..3e1c1751e14 100644 --- a/src/apphosting/backend.ts +++ b/src/apphosting/backend.ts @@ -356,6 +356,7 @@ export async function createBackend( const defaultServiceAccount = defaultComputeServiceAccountEmail(projectId); const backendReqBody: Omit = { servingLocality: "GLOBAL_ACCESS", + runtime: {value: "nodejs22"}, codebase: repository ? { repository: `${repository.name}`, diff --git a/src/deploy/apphosting/deploy.ts b/src/deploy/apphosting/deploy.ts index fa7eaf2bf7a..4330d1fc4d4 100644 --- a/src/deploy/apphosting/deploy.ts +++ b/src/deploy/apphosting/deploy.ts @@ -7,7 +7,7 @@ import { Options } from "../../options"; import { needProjectId } from "../../projectUtils"; import { logLabeledBullet } from "../../utils"; import { Context } from "./args"; -import { createArchive } from "./util"; +import { createArchive, createTarArchive } from "./util"; /** * Zips and uploads App Hosting source code to Google Cloud Storage in preparation for @@ -71,9 +71,9 @@ export default async function (context: Context, options: Options): Promise context.backendLocalBuilds[id]); if (localBuildBackends.length > 0) { - logLabeledWarning( - "apphosting", - `Skipping backend(s) ${localBuildBackends.join(", ")}. Local Builds are not supported yet.`, - ); - backendIds = backendIds.filter((id) => !localBuildBackends.includes(id)); + console.log(localBuildBackends); + console.log(context.backendStorageUris); + console.log(context.backendLocalBuilds); } if (backendIds.length === 0) { @@ -46,16 +44,17 @@ export default async function (context: Context, options: Options): Promise // TODO(9114): Add run_command // TODO(914): Set the buildConfig. - // TODO(914): Set locallyBuiltSource. orchestrateRollout({ projectId, backendId, location: context.backendLocations[backendId], buildInput: { + config: context.backendLocalBuilds[backendId].buildConfig, source: { archive: { userStorageUri: context.backendStorageUris[backendId], rootDirectory: context.backendConfigs[backendId].rootDir, + locallyBuiltSource: true, // generalize }, }, }, diff --git a/src/deploy/apphosting/util.ts b/src/deploy/apphosting/util.ts index 24d2364961e..bffb35c26ae 100644 --- a/src/deploy/apphosting/util.ts +++ b/src/deploy/apphosting/util.ts @@ -1,11 +1,61 @@ import * as archiver from "archiver"; import * as fs from "fs"; import * as path from "path"; +import * as tar from "tar"; import * as tmp from "tmp"; import { FirebaseError } from "../../error"; import { AppHostingSingle } from "../../firebaseConfig"; import * as fsAsync from "../../fsAsync"; +export async function createTarArchive( + config: AppHostingSingle, + rootDir: string, + targetSubDir?: string, +): Promise { + const tmpFile = tmp.fileSync({ prefix: `${config.backendId}-`, postfix: ".tar.gz" }).name; + + const targetDir = targetSubDir ? path.join(rootDir, targetSubDir) : rootDir; + // We must ignore firebase-debug.log or weird things happen if you're in the public dir when you deploy. + // const ignore = config.ignore || [".git"]; + const ignore = ["firebase-debug.log", "firebase-debug.*.log", ".git"]; + //const gitIgnorePatterns = parseGitIgnorePatterns(targetDir); + //ignore.push(...gitIgnorePatterns); + const rdrFiles = await fsAsync.readdirRecursive({ + path: targetDir, + ignore: ignore, + isGitIgnore: true, + }); + const allFiles: string[] = rdrFiles.map((rdrf) => path.relative(rootDir, rdrf.name)); + console.log(allFiles); + + + // `tar` returns a `TypeError` if `allFiles` is empty. Let's check a feww things. + try { + fs.statSync(rootDir); + } catch (err: any) { + if (err.code === "ENOENT") { + throw new FirebaseError(`Could not read directory "${rootDir}"`); + } + throw err; + } + if (!allFiles.length) { + throw new FirebaseError( + `Cannot create a tar archive with 0 files from directory "${rootDir}"`, + ); + } + + await tar.create( + { + gzip: true, + file: tmpFile, + cwd: rootDir, + portable: true, + }, + allFiles, + ); + return tmpFile +} + /** * Locates the source code for a backend and creates an archive to eventually upload to GCS. * Based heavily on functions upload logic in src/deploy/functions/prepareFunctionsUpload.ts. diff --git a/src/gcp/apphosting.ts b/src/gcp/apphosting.ts index 57c02079b0d..2fabe93cf92 100644 --- a/src/gcp/apphosting.ts +++ b/src/gcp/apphosting.ts @@ -7,7 +7,7 @@ import * as deploymentTool from "../deploymentTool"; import { FirebaseError } from "../error"; import { DeepOmit, RecursiveKeyOf, assertImplements } from "../metaprogramming"; -export const API_VERSION = "v1beta"; +export const API_VERSION = "v1alpha"; export const client = new Client({ urlPrefix: apphostingOrigin(), @@ -22,6 +22,10 @@ interface Codebase { rootDirectory: string; } +interface Runtime { + value: string; +} + /** * Specifies how Backend's data is replicated and served. * GLOBAL_ACCESS: Stores and serves content from multiple points-of-presence (POP) @@ -35,6 +39,7 @@ export interface Backend { name: string; mode?: string; codebase?: Codebase; + runtime?: Runtime; servingLocality: ServingLocality; labels: Record; createTime: string; diff --git a/src/gcp/storage.ts b/src/gcp/storage.ts index 3e7bd825988..5132a85fd46 100644 --- a/src/gcp/storage.ts +++ b/src/gcp/storage.ts @@ -265,16 +265,17 @@ export async function uploadObject( object: string; generation: string | null; }> { - if (path.extname(source.file) !== ".zip") { - throw new FirebaseError(`Expected a file name ending in .zip, got ${source.file}`); - } + // if (path.extname(source.file) !== ".zip") { + //throw new FirebaseError(`Expected a file name ending in .zip, got ${source.file}`); + //} const localAPIClient = new Client({ urlPrefix: storageOrigin() }); const location = `/${bucketName}/${path.basename(source.file)}`; const res = await localAPIClient.request({ method: "PUT", path: location, headers: { - "Content-Type": "application/zip", + //"Content-Type": "application/zip", + "Content-Type": "application/octet-stream", "x-goog-content-length-range": "0,123289600", }, body: source.stream,