-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Integrate FAH Local Builds with Universal Maker #10382
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: next
Are you sure you want to change the base?
Changes from 2 commits
c573c6a
67cc1cc
a7cf61e
a2bce07
80f5aa1
0bb1d5c
0bccdc2
152505d
d2185aa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,14 +1,163 @@ | ||||||||||||||||||||||||||||||||||
| import * as childProcess from "child_process"; | ||||||||||||||||||||||||||||||||||
| import * as fs from "fs"; | ||||||||||||||||||||||||||||||||||
| import * as path from "path"; | ||||||||||||||||||||||||||||||||||
| import { BuildConfig, Env } from "../gcp/apphosting"; | ||||||||||||||||||||||||||||||||||
| import { localBuild as localAppHostingBuild } from "@apphosting/build"; | ||||||||||||||||||||||||||||||||||
| import { EnvMap } from "./yaml"; | ||||||||||||||||||||||||||||||||||
| import { loadSecret } from "./secrets"; | ||||||||||||||||||||||||||||||||||
| import { confirm } from "../prompt"; | ||||||||||||||||||||||||||||||||||
| import { FirebaseError } from "../error"; | ||||||||||||||||||||||||||||||||||
| import * as experiments from "../experiments"; | ||||||||||||||||||||||||||||||||||
| import { logger } from "../logger"; | ||||||||||||||||||||||||||||||||||
|
falahat marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| interface UniversalMakerOutput { | ||||||||||||||||||||||||||||||||||
| command: string; | ||||||||||||||||||||||||||||||||||
| args: string[]; | ||||||||||||||||||||||||||||||||||
| language: string; | ||||||||||||||||||||||||||||||||||
| runtime: string; | ||||||||||||||||||||||||||||||||||
| envVars?: Record<string, string | number | boolean>; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||
| * Runs the Universal Maker binary to build the project. | ||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||
| export function runUniversalMaker(projectRoot: string, framework?: string): AppHostingBuildOutput { | ||||||||||||||||||||||||||||||||||
| if (!process.env.UNIVERSAL_MAKER_BINARY) { | ||||||||||||||||||||||||||||||||||
| throw new FirebaseError( | ||||||||||||||||||||||||||||||||||
| "Please specify the path to your Universal Maker binary by establishing the UNIVERSAL_MAKER_BINARY environment variable.", | ||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const bundleYamlPath = path.join(projectRoot, ".apphosting", "bundle.yaml"); | ||||||||||||||||||||||||||||||||||
| let cachedBundleContent: string | null = null; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if (!fs.existsSync(path.dirname(bundleYamlPath))) { | ||||||||||||||||||||||||||||||||||
| fs.mkdirSync(path.dirname(bundleYamlPath), { recursive: true }); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // Watch for the file being created by the Next.js adapter | ||||||||||||||||||||||||||||||||||
| const watcher = fs.watch(path.dirname(bundleYamlPath), (eventType, filename) => { | ||||||||||||||||||||||||||||||||||
| if (filename === "bundle.yaml") { | ||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||
| if (fs.existsSync(bundleYamlPath)) { | ||||||||||||||||||||||||||||||||||
| const currentText = fs.readFileSync(bundleYamlPath, "utf-8"); | ||||||||||||||||||||||||||||||||||
| if (currentText.trim().length > 0) { | ||||||||||||||||||||||||||||||||||
| cachedBundleContent = currentText; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } catch (e) { | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||
|
falahat marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||
| childProcess.spawnSync( | ||||||||||||||||||||||||||||||||||
| process.env.UNIVERSAL_MAKER_BINARY, | ||||||||||||||||||||||||||||||||||
| ["-application_dir", projectRoot, "-output_dir", projectRoot, "-output_format", "json"], | ||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||
| env: { | ||||||||||||||||||||||||||||||||||
| ...process.env, | ||||||||||||||||||||||||||||||||||
| X_GOOGLE_TARGET_PLATFORM: "fah", | ||||||||||||||||||||||||||||||||||
| FIREBASE_OUTPUT_BUNDLE_DIR: ".apphosting", | ||||||||||||||||||||||||||||||||||
| NPM_CONFIG_REGISTRY: "https://registry.npmjs.org/", | ||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||
| stdio: "inherit", | ||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+35
to
+47
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The exit status of the const result = childProcess.spawnSync(
process.env.UNIVERSAL_MAKER_BINARY,
["-application_dir", projectRoot, "-output_dir", projectRoot, "-output_format", "json"],
{
env: {
...process.env,
X_GOOGLE_TARGET_PLATFORM: "fah",
FIREBASE_OUTPUT_BUNDLE_DIR: "bundle_output",
},
stdio: "inherit",
},
);
if (result.status !== 0) {
throw new FirebaseError(`Universal Maker build failed with status ${result.status}`, {
exit: result.status ?? 1,
});
} |
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // Close the background watcher safely | ||||||||||||||||||||||||||||||||||
| watcher.close(); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // Restore safely if wiped out in the final seconds | ||||||||||||||||||||||||||||||||||
| if (cachedBundleContent && fs.existsSync(bundleYamlPath)) { | ||||||||||||||||||||||||||||||||||
| const lastText = fs.readFileSync(bundleYamlPath, "utf-8"); | ||||||||||||||||||||||||||||||||||
| if (lastText.trim().length === 0) { | ||||||||||||||||||||||||||||||||||
| fs.writeFileSync(bundleYamlPath, cachedBundleContent, "utf-8"); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+53
to
+62
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The if (fs.existsSync(bundleOutput)) {
fs.rmSync(targetAppHosting, { recursive: true, force: true });
fs.mkdirSync(targetAppHosting, { recursive: true });
const files = fs.readdirSync(bundleOutput);
for (const file of files) {
fs.renameSync(path.join(bundleOutput, file), path.join(targetAppHosting, file));
}
fs.rmSync(bundleOutput, { recursive: true, force: true });
} |
||||||||||||||||||||||||||||||||||
| } catch (e) { | ||||||||||||||||||||||||||||||||||
| if (e && typeof e === "object" && "code" in e && e.code === "EACCES") { | ||||||||||||||||||||||||||||||||||
| throw new FirebaseError( | ||||||||||||||||||||||||||||||||||
| "Failed to execute the Universal Maker binary due to permission constraints. Please assure you have set chmod +x on your file.", | ||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| throw e; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const outputFilePath = path.join(projectRoot, "build_output.json"); | ||||||||||||||||||||||||||||||||||
| if (!fs.existsSync(outputFilePath)) { | ||||||||||||||||||||||||||||||||||
| throw new FirebaseError( | ||||||||||||||||||||||||||||||||||
| `Universal Maker did not produce the expected output file at ${outputFilePath}`, | ||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const outputRaw = fs.readFileSync(outputFilePath, "utf-8"); | ||||||||||||||||||||||||||||||||||
| let umOutput: UniversalMakerOutput; | ||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||
| umOutput = JSON.parse(outputRaw) as UniversalMakerOutput; | ||||||||||||||||||||||||||||||||||
| } catch (e) { | ||||||||||||||||||||||||||||||||||
| throw new FirebaseError(`Failed to parse build_output.json: ${(e as Error).message}`); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+79
to
+85
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Suggested change
|
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| let finalRunCommand = `${umOutput.command} ${umOutput.args.join(" ")}`; | ||||||||||||||||||||||||||||||||||
| if (fs.existsSync(bundleYamlPath)) { | ||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||
| const bundleRaw = fs.readFileSync(bundleYamlPath, "utf-8"); | ||||||||||||||||||||||||||||||||||
| // Safely parse the YAML string | ||||||||||||||||||||||||||||||||||
| const bundleData = require("yaml").parse(bundleRaw); | ||||||||||||||||||||||||||||||||||
|
falahat marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||||||
| if (bundleData?.runConfig?.runCommand) { | ||||||||||||||||||||||||||||||||||
| finalRunCommand = bundleData.runConfig.runCommand; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } catch (e) { | ||||||||||||||||||||||||||||||||||
| // Fall back gracefully if parser fails | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||
| metadata: { | ||||||||||||||||||||||||||||||||||
| language: umOutput.language, | ||||||||||||||||||||||||||||||||||
| runtime: umOutput.runtime, | ||||||||||||||||||||||||||||||||||
| framework: framework || "nextjs", | ||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||
| runConfig: { | ||||||||||||||||||||||||||||||||||
| runCommand: finalRunCommand, | ||||||||||||||||||||||||||||||||||
| environmentVariables: Object.entries(umOutput.envVars || {}).map(([k, v]) => ({ | ||||||||||||||||||||||||||||||||||
| variable: k, | ||||||||||||||||||||||||||||||||||
| value: String(v), | ||||||||||||||||||||||||||||||||||
| availability: ["RUNTIME"], | ||||||||||||||||||||||||||||||||||
| })), | ||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||
| outputFiles: { | ||||||||||||||||||||||||||||||||||
| serverApp: { | ||||||||||||||||||||||||||||||||||
| include: [".apphosting"], | ||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| export interface AppHostingBuildOutput { | ||||||||||||||||||||||||||||||||||
| metadata: Record<string, string | number | boolean>; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| runConfig: { | ||||||||||||||||||||||||||||||||||
| runCommand?: string; | ||||||||||||||||||||||||||||||||||
| environmentVariables?: Array<{ | ||||||||||||||||||||||||||||||||||
| variable: string; | ||||||||||||||||||||||||||||||||||
| value: string; | ||||||||||||||||||||||||||||||||||
| availability: string[]; | ||||||||||||||||||||||||||||||||||
| }>; | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
| outputFiles?: { | ||||||||||||||||||||||||||||||||||
| serverApp: { | ||||||||||||||||||||||||||||||||||
| include: string[]; | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||
| * Triggers a local build of your App Hosting codebase. | ||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||
| * This function orchestrates the build process using the App Hosting build adapter. | ||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||
| * It detects the framework (though currently defaults/assumes 'nextjs' in some contexts), | ||||||||||||||||||||||||||||||||||
| * generates the necessary build artifacts, and returns metadata about the build. | ||||||||||||||||||||||||||||||||||
| * @param projectId - The project ID to use for resolving secrets. | ||||||||||||||||||||||||||||||||||
|
|
@@ -62,9 +211,19 @@ export async function localBuild( | |||||||||||||||||||||||||||||||||
| process.env[key] = value; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| let apphostingBuildOutput; | ||||||||||||||||||||||||||||||||||
| let apphostingBuildOutput: AppHostingBuildOutput; | ||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||
| apphostingBuildOutput = await localAppHostingBuild(projectRoot, framework); | ||||||||||||||||||||||||||||||||||
| if (experiments.isEnabled("universalMaker")) { | ||||||||||||||||||||||||||||||||||
| apphostingBuildOutput = runUniversalMaker(projectRoot, framework); | ||||||||||||||||||||||||||||||||||
| logger.debug( | ||||||||||||||||||||||||||||||||||
| `[apphosting] Universal Maker build outputFiles include: ${JSON.stringify(apphostingBuildOutput.outputFiles?.serverApp?.include ?? [])}`, | ||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||
| apphostingBuildOutput = (await localAppHostingBuild( | ||||||||||||||||||||||||||||||||||
| projectRoot, | ||||||||||||||||||||||||||||||||||
| framework, | ||||||||||||||||||||||||||||||||||
| )) as unknown as AppHostingBuildOutput; | ||||||||||||||||||||||||||||||||||
|
falahat marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||||||||||||
| for (const key in process.env) { | ||||||||||||||||||||||||||||||||||
| if (!(key in originalEnv)) { | ||||||||||||||||||||||||||||||||||
|
|
@@ -87,7 +246,7 @@ export async function localBuild( | |||||||||||||||||||||||||||||||||
| value, | ||||||||||||||||||||||||||||||||||
| availability, | ||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||
| ) as unknown as Env[] | undefined; | ||||||||||||||||||||||||||||||||||
|
falahat marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||
| outputFiles: apphostingBuildOutput.outputFiles?.serverApp.include ?? [], | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,47 +27,93 @@ export async function createLocalBuildTarArchive( | |
| ): Promise<string> { | ||
| const tmpFile = tmp.fileSync({ prefix: `${config.backendId}-`, postfix: ".tar.gz" }).name; | ||
|
|
||
| const targetDir = targetSubDir ? path.join(rootDir, targetSubDir) : rootDir; | ||
| const isAppHostingDir = | ||
| targetSubDir === ".apphosting" || | ||
| (!!targetSubDir && path.basename(targetSubDir) === ".apphosting"); | ||
| const targetDir = targetSubDir | ||
| ? path.isAbsolute(targetSubDir) | ||
| ? targetSubDir | ||
| : path.join(rootDir, targetSubDir) | ||
| : rootDir; | ||
| const ignore = ["firebase-debug.log", "firebase-debug.*.log", ".git"]; | ||
| const rdrFiles = await fsAsync.readdirRecursive({ | ||
| path: targetDir, | ||
| ignore: ignore, | ||
| isGitIgnore: true, | ||
| }); | ||
| const allFiles: string[] = rdrFiles.map((rdrf) => path.relative(rootDir, rdrf.name)); | ||
|
|
||
| if (targetSubDir) { | ||
| const defaultFiles = fs.readdirSync(rootDir).filter((file) => { | ||
| return APPHOSTING_YAML_FILE_REGEX.test(file); | ||
| let archiveCwd = rootDir; | ||
| let pathsToPack: string[]; | ||
|
|
||
| if (isAppHostingDir) { | ||
| // create temporary directory to bundle things flattened | ||
| const tempDir = tmp.dirSync({ unsafeCleanup: true }).name; | ||
| fs.cpSync(targetDir, tempDir, { recursive: true }); | ||
|
|
||
| const rootPackageJson = path.join(rootDir, "package.json"); | ||
| if (fs.existsSync(rootPackageJson)) { | ||
| fs.copyFileSync(rootPackageJson, path.join(tempDir, "package.json")); | ||
| } | ||
| const rootFiles = fs.readdirSync(rootDir); | ||
| for (const file of rootFiles) { | ||
| if (APPHOSTING_YAML_FILE_REGEX.test(file)) { | ||
| fs.copyFileSync(path.join(rootDir, file), path.join(tempDir, file)); | ||
| } | ||
| } | ||
| const rootNext = path.join(rootDir, ".next"); | ||
| if (fs.existsSync(rootNext)) { | ||
| fs.cpSync(rootNext, path.join(tempDir, ".next"), { recursive: true }); | ||
| } | ||
| const rootNodeModules = path.join(rootDir, "node_modules"); | ||
| if (fs.existsSync(rootNodeModules)) { | ||
| fs.cpSync(rootNodeModules, path.join(tempDir, "node_modules"), { recursive: true }); | ||
|
falahat marked this conversation as resolved.
Outdated
|
||
| } | ||
|
falahat marked this conversation as resolved.
Outdated
|
||
|
|
||
| const rdrFiles = await fsAsync.readdirRecursive({ | ||
| path: tempDir, | ||
| ignore: ignore, | ||
| isGitIgnore: false, | ||
| }); | ||
| pathsToPack = rdrFiles.map((rdrf) => path.relative(tempDir, rdrf.name)); | ||
| archiveCwd = tempDir; | ||
| } else { | ||
| const rdrFiles = await fsAsync.readdirRecursive({ | ||
| path: targetDir, | ||
| ignore: ignore, | ||
| isGitIgnore: !targetSubDir, // Disable gitignore if we are anchored to a build output subdirectory | ||
| }); | ||
| for (const file of defaultFiles) { | ||
| if (!allFiles.includes(file)) { | ||
| allFiles.push(file); | ||
| pathsToPack = rdrFiles.map((rdrf) => path.relative(rootDir, rdrf.name)); | ||
|
|
||
| if (targetSubDir) { | ||
| const defaultFiles = fs.readdirSync(rootDir).filter((file) => { | ||
| return APPHOSTING_YAML_FILE_REGEX.test(file); | ||
| }); | ||
| for (const file of defaultFiles) { | ||
| const relativePath = path.relative(rootDir, path.join(rootDir, file)); | ||
| if (!pathsToPack.includes(relativePath)) { | ||
| pathsToPack.push(relativePath); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // `tar` returns a `TypeError` if `allFiles` is empty. Let's check a feww things. | ||
| try { | ||
| fs.statSync(rootDir); | ||
| fs.statSync(archiveCwd); | ||
| } catch (err: unknown) { | ||
| if (err instanceof Error && "code" in err && err.code === "ENOENT") { | ||
| throw new FirebaseError(`Could not read directory "${rootDir}"`); | ||
| throw new FirebaseError(`Could not read directory "${archiveCwd}"`); | ||
| } | ||
| throw err; | ||
| } | ||
| if (!allFiles.length) { | ||
| throw new FirebaseError(`Cannot create a tar archive with 0 files from directory "${rootDir}"`); | ||
| if (!pathsToPack.length) { | ||
| throw new FirebaseError( | ||
| `Cannot create a tar archive with 0 files from directory "${archiveCwd}"`, | ||
| ); | ||
| } | ||
|
|
||
| await tar.create( | ||
| { | ||
| gzip: true, | ||
| file: tmpFile, | ||
| cwd: rootDir, | ||
| cwd: archiveCwd, | ||
| portable: true, | ||
| }, | ||
| allFiles, | ||
| pathsToPack, | ||
| ); | ||
| return tmpFile; | ||
| } | ||
|
Comment on lines
27
to
73
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The temporary directory created by export async function createLocalBuildTarArchive(
config: AppHostingSingle,
rootDir: string,
targetSubDir?: string,
): Promise<string> {
const tmpFile = tmp.fileSync({ prefix: `${config.backendId}-`, postfix: ".tar.gz" }).name;
const isAppHostingDir =
targetSubDir === ".apphosting" ||
(!!targetSubDir && path.basename(targetSubDir) === ".apphosting");
const targetDir = targetSubDir
? path.isAbsolute(targetSubDir)
? targetSubDir
: path.join(rootDir, targetSubDir)
: rootDir;
const ignore = ["firebase-debug.log", "firebase-debug.*.log", ".git"];
let archiveCwd = rootDir;
let pathsToPack: string[];
let tempDirObj: tmp.DirResult | undefined;
try {
if (isAppHostingDir) {
// create temporary directory to bundle things flattened
tempDirObj = tmp.dirSync({ unsafeCleanup: true });
const tempDir = tempDirObj.name;
fs.cpSync(targetDir, tempDir, { recursive: true });
const rootPackageJson = path.join(rootDir, "package.json");
if (fs.existsSync(rootPackageJson)) {
fs.copyFileSync(rootPackageJson, path.join(tempDir, "package.json"));
}
const rootFiles = fs.readdirSync(rootDir);
for (const file of rootFiles) {
if (APPHOSTING_YAML_FILE_REGEX.test(file)) {
fs.copyFileSync(path.join(rootDir, file), path.join(tempDir, file));
}
}
const rdrFiles = await fsAsync.readdirRecursive({
path: tempDir,
ignore: ignore,
isGitIgnore: false,
});
pathsToPack = rdrFiles.map((rdrf) => path.relative(tempDir, rdrf.name));
archiveCwd = tempDir;
} else {
const rdrFiles = await fsAsync.readdirRecursive({
path: targetDir,
ignore: ignore,
isGitIgnore: !targetSubDir, // Disable gitignore if we are anchored to a build output subdirectory
});
pathsToPack = rdrFiles.map((rdrf) => path.relative(rootDir, rdrf.name));
if (targetSubDir) {
const defaultFiles = fs.readdirSync(rootDir).filter((file) => {
return APPHOSTING_YAML_FILE_REGEX.test(file);
});
for (const file of defaultFiles) {
const relativePath = path.relative(rootDir, path.join(rootDir, file));
if (!pathsToPack.includes(relativePath)) {
pathsToPack.push(relativePath);
}
}
}
}
try {
fs.statSync(archiveCwd);
} catch (err: unknown) {
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
throw new FirebaseError(`Could not read directory "${archiveCwd}"`);
}
throw err;
}
if (!pathsToPack.length) {
throw new FirebaseError(
`Cannot create a tar archive with 0 files from directory "${archiveCwd}"`,
);
}
await tar.create(
{
gzip: true,
file: tmpFile,
cwd: archiveCwd,
portable: true,
},
pathsToPack,
);
return tmpFile;
} finally {
tempDirObj?.removeCallback();
}
} |
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.