-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Discovery: Added node runtime. #5993
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
Changes from all commits
be779e0
c1db273
497479b
8652c14
9e93673
a919dba
d28caae
0e025c3
7ba91b9
e03ba8c
2edc9c9
f6c92c6
10b27f1
1e9b629
cbd1bd5
f9db923
cb58ef3
f871fbe
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 |
---|---|---|
@@ -0,0 +1,212 @@ | ||
import { readOrNull } from "../filesystem"; | ||
import { FileSystem, FrameworkSpec, Runtime } from "../types"; | ||
import { RuntimeSpec } from "../types"; | ||
import { frameworkMatcher } from "../frameworkMatcher"; | ||
import { LifecycleCommands } from "../types"; | ||
import { Command } from "../types"; | ||
import { FirebaseError } from "../../../../error"; | ||
import { logger } from "../../../../../src/logger"; | ||
import { conjoinOptions } from "../../../utils"; | ||
|
||
export interface PackageJSON { | ||
dependencies?: Record<string, string>; | ||
devDependencies?: Record<string, string>; | ||
scripts?: Record<string, string>; | ||
engines?: Record<string, string>; | ||
} | ||
type PackageManager = "npm" | "yarn"; | ||
|
||
const supportedNodeVersions: string[] = ["18"]; | ||
const NODE_RUNTIME_ID = "nodejs"; | ||
const PACKAGE_JSON = "package.json"; | ||
const YARN_LOCK = "yarn.lock"; | ||
|
||
export class NodejsRuntime implements Runtime { | ||
private readonly runtimeRequiredFiles: string[] = [PACKAGE_JSON]; | ||
private readonly contentCache: Record<string, boolean> = {}; | ||
|
||
// Checks if the codebase is using Node as runtime. | ||
async match(fs: FileSystem): Promise<boolean | null> { | ||
const areAllFilesPresent = await Promise.all( | ||
this.runtimeRequiredFiles.map((file) => fs.exists(file)) | ||
); | ||
|
||
return areAllFilesPresent.every((present) => present); | ||
} | ||
|
||
getRuntimeName(): string { | ||
return NODE_RUNTIME_ID; | ||
} | ||
|
||
getNodeImage(engine: Record<string, string> | undefined): string { | ||
// If no version is mentioned explicitly, assuming application is compatible with latest version. | ||
if (!engine || !engine.node) { | ||
return `node:${supportedNodeVersions[supportedNodeVersions.length - 1]}-slim`; | ||
} | ||
const versionNumber = engine.node; | ||
|
||
if (!supportedNodeVersions.includes(versionNumber)) { | ||
throw new FirebaseError( | ||
`This integration expects Node version ${conjoinOptions( | ||
supportedNodeVersions, | ||
"or" | ||
)}. You're running version ${versionNumber}, which is not compatible.` | ||
); | ||
} | ||
|
||
return `node:${versionNumber}-slim`; | ||
} | ||
|
||
async getPackageManager(fs: FileSystem): Promise<PackageManager> { | ||
try { | ||
if (await fs.exists(YARN_LOCK)) { | ||
return "yarn"; | ||
} | ||
|
||
return "npm"; | ||
} catch (error: any) { | ||
logger.error("Failed to check files to identify package manager"); | ||
throw error; | ||
} | ||
} | ||
|
||
getDependencies(packageJSON: PackageJSON): Record<string, string> { | ||
return { ...packageJSON.dependencies, ...packageJSON.devDependencies }; | ||
} | ||
|
||
packageManagerInstallCommand(packageManager: PackageManager): string | undefined { | ||
const packages: string[] = []; | ||
if (packageManager === "yarn") { | ||
packages.push("yarn"); | ||
} | ||
if (!packages.length) { | ||
return undefined; | ||
} | ||
|
||
return `npm install --global ${packages.join(" ")}`; | ||
} | ||
|
||
installCommand(fs: FileSystem, packageManager: PackageManager): string { | ||
let installCmd = "npm install"; | ||
|
||
if (packageManager === "yarn") { | ||
installCmd = "yarn install"; | ||
} | ||
|
||
return installCmd; | ||
} | ||
|
||
async detectedCommands( | ||
packageManager: PackageManager, | ||
scripts: Record<string, string> | undefined, | ||
matchedFramework: FrameworkSpec | null, | ||
fs: FileSystem | ||
): Promise<LifecycleCommands> { | ||
return { | ||
build: this.getBuildCommand(packageManager, scripts, matchedFramework), | ||
dev: this.getDevCommand(packageManager, scripts, matchedFramework), | ||
run: await this.getRunCommand(packageManager, scripts, matchedFramework, fs), | ||
}; | ||
} | ||
|
||
executeScript(packageManager: string, scriptName: string): string { | ||
return `${packageManager} run ${scriptName}`; | ||
} | ||
|
||
executeFrameworkCommand(packageManager: PackageManager, command: Command): Command { | ||
if (packageManager === "npm" || packageManager === "yarn") { | ||
command.cmd = "npx " + command.cmd; | ||
svnsairam marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
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. what do you think we should do if we don't recognize the package manager? 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. If the application has |
||
|
||
return command; | ||
} | ||
|
||
getBuildCommand( | ||
packageManager: PackageManager, | ||
scripts: Record<string, string> | undefined, | ||
matchedFramework: FrameworkSpec | null | ||
): Command | undefined { | ||
let buildCommand: Command = { cmd: "" }; | ||
svnsairam marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (scripts?.build) { | ||
buildCommand.cmd = this.executeScript(packageManager, "build"); | ||
} else if (matchedFramework && matchedFramework.commands?.build) { | ||
buildCommand = matchedFramework.commands.build; | ||
buildCommand = this.executeFrameworkCommand(packageManager, buildCommand); | ||
} | ||
|
||
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. what do you think we should do if build command is empty? 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. I noticed that |
||
return buildCommand.cmd === "" ? undefined : buildCommand; | ||
} | ||
|
||
getDevCommand( | ||
packageManager: PackageManager, | ||
scripts: Record<string, string> | undefined, | ||
matchedFramework: FrameworkSpec | null | ||
): Command | undefined { | ||
let devCommand: Command = { cmd: "", env: { NODE_ENV: "dev" } }; | ||
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.
|
||
if (scripts?.dev) { | ||
devCommand.cmd = this.executeScript(packageManager, "dev"); | ||
} else if (matchedFramework && matchedFramework.commands?.dev) { | ||
devCommand = matchedFramework.commands.dev; | ||
devCommand = this.executeFrameworkCommand(packageManager, devCommand); | ||
} | ||
|
||
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. what do you think we should do if dev command is empty? 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. So what are we doing here? Nothing? 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. Here too same as |
||
return devCommand.cmd === "" ? undefined : devCommand; | ||
} | ||
|
||
async getRunCommand( | ||
packageManager: PackageManager, | ||
scripts: Record<string, string> | undefined, | ||
matchedFramework: FrameworkSpec | null, | ||
fs: FileSystem | ||
): Promise<Command | undefined> { | ||
let runCommand: Command = { cmd: "", env: { NODE_ENV: "production" } }; | ||
if (scripts?.start) { | ||
runCommand.cmd = this.executeScript(packageManager, "start"); | ||
} else if (matchedFramework && matchedFramework.commands?.run) { | ||
runCommand = matchedFramework.commands.run; | ||
runCommand = this.executeFrameworkCommand(packageManager, runCommand); | ||
} else if (scripts?.main) { | ||
runCommand.cmd = `node ${scripts.main}`; | ||
} else if (await fs.exists("index.js")) { | ||
runCommand.cmd = `node index.js`; | ||
} | ||
|
||
svnsairam marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return runCommand.cmd === "" ? undefined : runCommand; | ||
} | ||
|
||
async analyseCodebase(fs: FileSystem, allFrameworkSpecs: FrameworkSpec[]): Promise<RuntimeSpec> { | ||
try { | ||
const packageJSONRaw = await readOrNull(fs, PACKAGE_JSON); | ||
let packageJSON: PackageJSON = {}; | ||
if (packageJSONRaw) { | ||
packageJSON = JSON.parse(packageJSONRaw) as PackageJSON; | ||
} | ||
const packageManager = await this.getPackageManager(fs); | ||
const nodeImage = this.getNodeImage(packageJSON.engines); | ||
const dependencies = this.getDependencies(packageJSON); | ||
const matchedFramework = await frameworkMatcher( | ||
NODE_RUNTIME_ID, | ||
fs, | ||
allFrameworkSpecs, | ||
dependencies | ||
); | ||
|
||
const runtimeSpec: RuntimeSpec = { | ||
id: NODE_RUNTIME_ID, | ||
baseImage: nodeImage, | ||
packageManagerInstallCommand: this.packageManagerInstallCommand(packageManager), | ||
installCommand: this.installCommand(fs, packageManager), | ||
detectedCommands: await this.detectedCommands( | ||
packageManager, | ||
packageJSON.scripts, | ||
matchedFramework, | ||
fs | ||
), | ||
}; | ||
|
||
return runtimeSpec; | ||
} catch (error: any) { | ||
throw new FirebaseError(`Failed to parse engine: ${error}`); | ||
} | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.