Skip to content

Commit

Permalink
ZEA-4514: Implement Medusa support (#416)
Browse files Browse the repository at this point in the history
#### Description (required)

* Refactoring Node.js Start and Build command
* Implement Medusa support with multi-layer Dockerfile

#### Related issues & labels (optional)

- Closes ZEA-4514
- Suggested label: enhancement
  • Loading branch information
yuaanlin authored Jan 21, 2025
2 parents cb5f4eb + 0607ba2 commit 7849312
Show file tree
Hide file tree
Showing 11 changed files with 177 additions and 74 deletions.
26 changes: 15 additions & 11 deletions internal/nodejs/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ type TemplateContext struct {

AppDir string

InstallCmd string
BuildCmd string
StartCmd string
InstallCmd string
BuildCmd string
BuildRuntimeCmd string // currently only used by Medusa
StartCmd string
RuntimeBaseDir string // currently only used by Medusa

Framework string
Serverless bool
Expand Down Expand Up @@ -51,14 +53,16 @@ func (c TemplateContext) Execute() (string, error) {

func getContextBasedOnMeta(meta types.PlanMeta) TemplateContext {
context := TemplateContext{
NodeVersion: meta["nodeVersion"],
AppDir: meta["appDir"],
InstallCmd: meta["installCmd"],
BuildCmd: meta["buildCmd"],
StartCmd: meta["startCmd"],
Framework: meta["framework"],
Serverless: meta["serverless"] == "true",
OutputDir: meta["outputDir"],
NodeVersion: meta["nodeVersion"],
AppDir: meta["appDir"],
InstallCmd: meta["installCmd"],
BuildCmd: meta["buildCmd"],
BuildRuntimeCmd: meta["buildRuntimeCmd"],
StartCmd: meta["startCmd"],
RuntimeBaseDir: meta["runtimeBaseDir"],
Framework: meta["framework"],
Serverless: meta["serverless"] == "true",
OutputDir: meta["outputDir"],

// The flag specific to planner/bun.
Bun: meta["bun"] == "true" || meta["packageManager"] == "bun",
Expand Down
20 changes: 20 additions & 0 deletions internal/nodejs/node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,23 @@ func TestGetContextBasedOnMeta_WithOutputdirAndMPAFramework(t *testing.T) {
OutputDir: "dist",
})
}

func TestGetContextBasedOnMeta_RuntimeEnvironment(t *testing.T) {
meta := getContextBasedOnMeta(types.PlanMeta{
"nodeVersion": "16",
"installCmd": "RUN npm install",
"buildCmd": "npm run build",
"buildRuntimeCmd": "npm run build-runtime",
"startCmd": "npm run start",
"runtimeBaseDir": "/src/.output",
})

assert.Equal(t, meta, TemplateContext{
NodeVersion: "16",
InstallCmd: "RUN npm install",
BuildCmd: "npm run build",
BuildRuntimeCmd: "npm run build-runtime",
StartCmd: "npm run start",
RuntimeBaseDir: "/src/.output",
})
}
170 changes: 111 additions & 59 deletions internal/nodejs/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/goccy/go-yaml"
"github.com/moznion/go-optional"
"github.com/samber/lo"
"github.com/spf13/afero"
"github.com/spf13/cast"
"github.com/zeabur/zbpack/internal/utils"
Expand All @@ -27,6 +28,8 @@ const (

// ConfigNodeFramework is the key for the configuration for specifying
// the Node.js framework explicitly.
//
// This is an undocumented internal configuration and is subjected to change.
ConfigNodeFramework = "node.framework"

// ConfigAppDir indicates the relative path of the app to deploy.
Expand Down Expand Up @@ -288,6 +291,11 @@ func DetermineAppFramework(ctx *nodePlanContext) types.NodeProjectFramework {
return fw.Unwrap()
}

if _, isMedusa := packageJSON.Dependencies["@medusajs/medusa"]; isMedusa {
*fw = optional.Some(types.NodeProjectFrameworkMedusa)
return fw.Unwrap()
}

if _, isVitepress := packageJSON.FindDependency("vitepress"); isVitepress {
*fw = optional.Some(types.NodeProjectFrameworkVitepress)
return fw.Unwrap()
Expand All @@ -313,6 +321,11 @@ func DetermineAppFramework(ctx *nodePlanContext) types.NodeProjectFramework {
return fw.Unwrap()
}

if _, isMedusa := packageJSON.Dependencies["@medusajs/medusa"]; isMedusa {
*fw = optional.Some(types.NodeProjectFrameworkMedusa)
return fw.Unwrap()
}

if _, isVite := packageJSON.DevDependencies["vite"]; isVite {
*fw = optional.Some(types.NodeProjectFrameworkVite)
return fw.Unwrap()
Expand Down Expand Up @@ -450,6 +463,33 @@ func GetStartScript(ctx *nodePlanContext) string {
return ss.Unwrap()
}

// GetPredeployScript gets the predeploy script in package.json's `scripts` of the Node.js app.
func GetPredeployScript(ctx *nodePlanContext) string {
packageJSON := ctx.GetAppPackageJSON()

if _, ok := packageJSON.Scripts["predeploy"]; ok {
return "predeploy"
}

return ""
}

// GetScriptCommand gets the command to run a script in the Node.js or Bun app.
func GetScriptCommand(ctx *nodePlanContext, script string) string {
pkgManager := DeterminePackageManager(ctx)

switch pkgManager {
case types.NodePackageManagerNpm:
return "npm run " + script
case types.NodePackageManagerPnpm:
return "pnpm run " + script
case types.NodePackageManagerBun:
return "bun run " + script
}

return "yarn " + script
}

const (
defaultNodeVersion = "20"
maxNodeVersion uint64 = 22
Expand Down Expand Up @@ -618,20 +658,13 @@ func GetBuildCmd(ctx *nodePlanContext) string {
framework := DetermineAppFramework(ctx)
serverless := getServerless(ctx)

var buildCmd string
switch pkgManager {
case types.NodePackageManagerPnpm:
buildCmd = "pnpm run " + buildScript
case types.NodePackageManagerNpm:
buildCmd = "npm run " + buildScript
case types.NodePackageManagerBun:
buildCmd = "bun run " + buildScript
case types.NodePackageManagerYarn:
fallthrough
default:
buildCmd = "yarn " + buildScript
if buildScript == "" {
*cmd = optional.Some("")
return cmd.Unwrap()
}

buildCmd := GetScriptCommand(ctx, buildScript)

// if this is a Nitro-based framework, we should pass NITRO_PRESET
// to the default build command.
if slices.Contains(types.NitroBasedFrameworks, framework) {
Expand All @@ -644,14 +677,44 @@ func GetBuildCmd(ctx *nodePlanContext) string {
}
}

if buildScript == "" {
buildCmd = ""
}

*cmd = optional.Some(buildCmd)
return cmd.Unwrap()
}

// GetRuntimeBaseDir gets the base directory of the runtime environment of the Node.js app.
func GetRuntimeBaseDir(ctx *nodePlanContext) string {
framework := DetermineAppFramework(ctx)

if framework == types.NodeProjectFrameworkMedusa {
return "/src/.medusa/server"
}

return ""
}

// GetBuildRuntimeCmd gets the build command to build the runtime environment of the Node.js app.
func GetBuildRuntimeCmd(ctx *nodePlanContext) string {
pkgManager := DeterminePackageManager(ctx)
framework := DetermineAppFramework(ctx)

if framework == types.NodeProjectFrameworkMedusa {
switch pkgManager {
case types.NodePackageManagerNpm:
return "npm install"
case types.NodePackageManagerPnpm:
return "pnpm install"
case types.NodePackageManagerYarn:
return "yarn install"
case types.NodePackageManagerBun:
return "bun install"
default:
return "yarn install"
}
}

return ""
}

// GetMonorepoAppRoot gets the app root of the monorepo project in the Node.js project.
func GetMonorepoAppRoot(ctx *nodePlanContext) string {
if appDir, err := ctx.AppDir.Take(); err == nil {
Expand Down Expand Up @@ -777,64 +840,43 @@ func GetStartCmd(ctx *nodePlanContext) string {
return cmd.Unwrap()
}

predeployScript := GetPredeployScript(ctx)

startScript := GetStartScript(ctx)
pkgManager := DeterminePackageManager(ctx)
entry := GetEntry(ctx)
framework := DetermineAppFramework(ctx)

var startCmd string
switch pkgManager {
case types.NodePackageManagerPnpm:
startCmd = "pnpm " + startScript
case types.NodePackageManagerNpm:
startCmd = "npm run " + startScript
case types.NodePackageManagerBun:
startCmd = "bun run " + startScript
case types.NodePackageManagerYarn:
fallthrough
default:
startCmd = "yarn " + startScript
if startScript != "" {
startCmd := GetScriptCommand(ctx, startScript)

if predeployScript != "" {
startCmd = GetScriptCommand(ctx, predeployScript) + " && " + startCmd
}

*cmd = optional.Some(startCmd)
return cmd.Unwrap()
}

var startCmd string
runtime := lo.If(ctx.Bun, "bun").Else("node")

if startScript == "" {
switch {
case entry != "":
if ctx.Bun {
startCmd = "bun " + entry
} else {
startCmd = "node " + entry
}
startCmd = runtime + " " + entry
case framework == types.NodeProjectFrameworkSvelte:
if ctx.Bun {
startCmd = "bun build/index.js"
} else {
startCmd = "node build/index.js"
}
startCmd = runtime + " build/index.js"
case types.IsNitroBasedFramework(string(framework)):
if ctx.Bun {
startCmd = "HOST=0.0.0.0 bun .output/server/index.mjs"
} else {
startCmd = "HOST=0.0.0.0 node .output/server/index.mjs"
}
startCmd = "HOST=0.0.0.0 " + runtime + " .output/server/index.mjs"
default:
if ctx.Bun {
startCmd = "bun index.js"
} else {
startCmd = "node index.js"
}
startCmd = runtime + " index.js"
}
}

// For solid-start projects, when using `solid-start start`
// on solid-start-node, we should use the memory-efficient
// start script instead.
//
// For more information, see the discussion in Discord: Solid.js
// https://ptb.discord.com/channels/722131463138705510/
// 722131463889223772/1140159307648868382
if framework == types.NodeProjectFrameworkSolidStartNode && startScript == "start" {
// solid-start-node specific start script
startCmd = "node dist/server.js"
if predeployScript != "" {
predeployCommand := GetScriptCommand(ctx, predeployScript)

*cmd = optional.Some(predeployCommand + " && " + startCmd)
}

*cmd = optional.Some(startCmd)
Expand Down Expand Up @@ -1052,6 +1094,16 @@ func GetMeta(opt GetMetaOptions) types.PlanMeta {
startCmd := GetStartCmd(ctx)
meta["startCmd"] = startCmd

runtimeBaseDir := GetRuntimeBaseDir(ctx)
if runtimeBaseDir != "" {
meta["runtimeBaseDir"] = runtimeBaseDir
}

buildRuntimeCmd := GetBuildRuntimeCmd(ctx)
if buildRuntimeCmd != "" {
meta["buildRuntimeCmd"] = buildRuntimeCmd
}

// only set outputDir if there is no start command
// (because if there is, it shouldn't be a static project)
if startCmd == "" {
Expand Down
8 changes: 8 additions & 0 deletions internal/nodejs/templates/template.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ COPY --from=build /src/{{ .AppDir }}/{{ .OutputDir }} /
FROM zeabur/caddy-static AS runtime
COPY --from=output / /usr/share/caddy
{{ end }}
{{ else if ne .RuntimeBaseDir "" }}
FROM build AS prod
COPY --from=build {{ .RuntimeBaseDir }} /app/
WORKDIR /app

EXPOSE 8080
RUN {{ .BuildRuntimeCmd }}
CMD {{ .StartCmd }}
{{ else }}
EXPOSE 8080
CMD {{ .StartCmd }}{{ end }}
1 change: 1 addition & 0 deletions pkg/types/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ const (
NodeProjectFrameworkRspress NodeProjectFramework = "rspress"
NodeProjectFrameworkGrammY NodeProjectFramework = "grammy"
NodeProjectFrameworkNitropack NodeProjectFramework = "nitropack"
NodeProjectFrameworkMedusa NodeProjectFramework = "medusa"
)

var NitroBasedFrameworks = []NodeProjectFramework{
Expand Down
5 changes: 5 additions & 0 deletions tests/real_project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,11 @@ var projects = []struct {
owner: "zeabur",
repo: "sveltekit-v2-template",
},
{
name: "nodejs-medusa",
owner: "medusajs",
repo: "medusa-starter-default",
},
{
name: "nodejs-a-lot-of-dependencies",
dir: "nodejs-a-lot-of-dependencies",
Expand Down
2 changes: 1 addition & 1 deletion tests/snapshots/nodejs-expressjs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ Meta:
installCmd: "COPY . .\nRUN pnpm install"
nodeVersion: "20"
packageManager: "pnpm"
startCmd: "pnpm start"
startCmd: "pnpm run start"
2 changes: 1 addition & 1 deletion tests/snapshots/nodejs-foal.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ Meta:
installCmd: "COPY . .\nRUN pnpm install"
nodeVersion: "18"
packageManager: "pnpm"
startCmd: "pnpm start"
startCmd: "pnpm run start"
13 changes: 13 additions & 0 deletions tests/snapshots/nodejs-medusa.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
PlanType: nodejs

Meta:
appDir: ""
buildCmd: "yarn build"
buildRuntimeCmd: "yarn install"
bun: "false"
framework: "medusa"
installCmd: "COPY . .\nRUN yarn install"
nodeVersion: "20"
packageManager: "yarn"
runtimeBaseDir: "/src/.medusa/server"
startCmd: "yarn start"
2 changes: 1 addition & 1 deletion tests/snapshots/nodejs-nestjs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ Meta:
installCmd: "COPY . .\nRUN pnpm install"
nodeVersion: "20"
packageManager: "pnpm"
startCmd: "pnpm start"
startCmd: "pnpm run start"
2 changes: 1 addition & 1 deletion tests/snapshots/nodejs-nuejs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ Meta:
installCmd: "COPY . .\nRUN pnpm install"
nodeVersion: "20"
packageManager: "pnpm"
startCmd: "pnpm start"
startCmd: "pnpm run start"

0 comments on commit 7849312

Please sign in to comment.