chore: move build utilities to Compiler class (#10904)
Fixes: FRMW-2866
This commit is contained in:
1 parent
c1930bd656
commit
428fce5313
5 files changed
+389
-273
No files matched your search
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
"@medusajs/framework": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
chore: move build utilities to Compiler class
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
"./feature-flags": "./dist/feature-flags/index.js",
|
"./feature-flags": "./dist/feature-flags/index.js",
|
||||||
"./utils": "./dist/utils/index.js",
|
"./utils": "./dist/utils/index.js",
|
||||||
"./types": "./dist/types/index.js",
|
"./types": "./dist/types/index.js",
|
||||||
|
"./build-tools": "./dist/build-tools/index.js",
|
||||||
"./orchestration": "./dist/orchestration/index.js",
|
"./orchestration": "./dist/orchestration/index.js",
|
||||||
"./workflows-sdk": "./dist/workflows-sdk/index.js",
|
"./workflows-sdk": "./dist/workflows-sdk/index.js",
|
||||||
"./workflows-sdk/composer": "./dist/workflows-sdk/composer.js",
|
"./workflows-sdk/composer": "./dist/workflows-sdk/composer.js",
|
||||||
|
|||||||
@@ -0,0 +1,373 @@
|
|||||||
|
import path from "path"
|
||||||
|
import type tsStatic from "typescript"
|
||||||
|
import { getConfigFile } from "@medusajs/utils"
|
||||||
|
import { access, constants, copyFile, rm } from "fs/promises"
|
||||||
|
import type { AdminOptions, ConfigModule, Logger } from "@medusajs/types"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The compiler exposes the opinionated APIs for compiling Medusa
|
||||||
|
* applications and plugins. You can perform the following
|
||||||
|
* actions.
|
||||||
|
*
|
||||||
|
* - loadTSConfigFile: Load and parse the TypeScript config file. All errors
|
||||||
|
* will be reported using the logger.
|
||||||
|
*
|
||||||
|
* - buildAppBackend: Compile the Medusa application backend source code to the
|
||||||
|
* ".medusa/server" directory. The admin source and integration-tests are
|
||||||
|
* skipped.
|
||||||
|
*
|
||||||
|
* - buildAppFrontend: Compile the admin extensions using the "@medusjs/admin-bundler"
|
||||||
|
* package. Admin can be compiled for self hosting (aka adminOnly), or can be compiled
|
||||||
|
* to be bundled with the backend output.
|
||||||
|
*/
|
||||||
|
export class Compiler {
|
||||||
|
#logger: Logger
|
||||||
|
#projectRoot: string
|
||||||
|
#adminSourceFolder: string
|
||||||
|
#adminOnlyDistFolder: string
|
||||||
|
#tsCompiler?: typeof tsStatic
|
||||||
|
|
||||||
|
constructor(projectRoot: string, logger: Logger) {
|
||||||
|
this.#projectRoot = projectRoot
|
||||||
|
this.#logger = logger
|
||||||
|
this.#adminSourceFolder = path.join(this.#projectRoot, "src/admin")
|
||||||
|
this.#adminOnlyDistFolder = path.join(this.#projectRoot, ".medusa/admin")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Util to track duration using hrtime
|
||||||
|
*/
|
||||||
|
#trackDuration() {
|
||||||
|
const startTime = process.hrtime()
|
||||||
|
return {
|
||||||
|
getSeconds() {
|
||||||
|
const duration = process.hrtime(startTime)
|
||||||
|
return (duration[0] + duration[1] / 1e9).toFixed(2)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the dist folder from the tsconfig.outDir property
|
||||||
|
* or uses the ".medusa/server" folder
|
||||||
|
*/
|
||||||
|
#computeDist(tsConfig: { options: { outDir?: string } }): string {
|
||||||
|
const distFolder = tsConfig.options.outDir ?? ".medusa/server"
|
||||||
|
return path.isAbsolute(distFolder)
|
||||||
|
? distFolder
|
||||||
|
: path.join(this.#projectRoot, distFolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports and stores a reference to the TypeScript compiler.
|
||||||
|
* We dynamically import "typescript", since its is a dev
|
||||||
|
* only dependency
|
||||||
|
*/
|
||||||
|
async #loadTSCompiler() {
|
||||||
|
if (!this.#tsCompiler) {
|
||||||
|
this.#tsCompiler = await import("typescript")
|
||||||
|
}
|
||||||
|
return this.#tsCompiler
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies the file to the destination without throwing any
|
||||||
|
* errors if the source file is missing
|
||||||
|
*/
|
||||||
|
async #copy(source: string, destination: string) {
|
||||||
|
let sourceExists = false
|
||||||
|
try {
|
||||||
|
await access(source, constants.F_OK)
|
||||||
|
sourceExists = true
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code !== "ENOENT") {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sourceExists) {
|
||||||
|
await copyFile(path.join(source), path.join(destination))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies package manager files from the project root
|
||||||
|
* to the specified dist folder
|
||||||
|
*/
|
||||||
|
async #copyPkgManagerFiles(dist: string) {
|
||||||
|
/**
|
||||||
|
* Copying package manager files
|
||||||
|
*/
|
||||||
|
await this.#copy(
|
||||||
|
path.join(this.#projectRoot, "package.json"),
|
||||||
|
path.join(dist, "package.json")
|
||||||
|
)
|
||||||
|
await this.#copy(
|
||||||
|
path.join(this.#projectRoot, "yarn.lock"),
|
||||||
|
path.join(dist, "yarn.lock")
|
||||||
|
)
|
||||||
|
await this.#copy(
|
||||||
|
path.join(this.#projectRoot, "pnpm.lock"),
|
||||||
|
path.join(dist, "pnpm.lock")
|
||||||
|
)
|
||||||
|
await this.#copy(
|
||||||
|
path.join(this.#projectRoot, "package-lock.json"),
|
||||||
|
path.join(dist, "package-lock.json")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the directory and its children recursively and
|
||||||
|
* ignores any errors
|
||||||
|
*/
|
||||||
|
async #clean(path: string) {
|
||||||
|
await rm(path, { recursive: true }).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the medusa config file and prints the error to
|
||||||
|
* the console (in case of any errors). Otherwise, the
|
||||||
|
* file path and the parsed config is returned
|
||||||
|
*/
|
||||||
|
async #loadMedusaConfig() {
|
||||||
|
const { configModule, configFilePath, error } =
|
||||||
|
await getConfigFile<ConfigModule>(this.#projectRoot, "medusa-config")
|
||||||
|
if (error) {
|
||||||
|
this.#logger.error(`Failed to load medusa-config.(js|ts) file`)
|
||||||
|
this.#logger.error(error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return { configFilePath, configModule }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a tsconfig file, this method will write the compiled
|
||||||
|
* output to the specified destination
|
||||||
|
*/
|
||||||
|
async #emitBuildOutput(
|
||||||
|
tsConfig: tsStatic.ParsedCommandLine,
|
||||||
|
chunksToIgnore: string[],
|
||||||
|
dist: string
|
||||||
|
): Promise<{
|
||||||
|
emitResult: tsStatic.EmitResult
|
||||||
|
diagnostics: tsStatic.Diagnostic[]
|
||||||
|
}> {
|
||||||
|
const ts = await this.#loadTSCompiler()
|
||||||
|
const filesToCompile = tsConfig.fileNames.filter((fileName) => {
|
||||||
|
return !chunksToIgnore.some((chunk) => fileName.includes(`${chunk}/`))
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create emit program to compile and emit output
|
||||||
|
*/
|
||||||
|
const program = ts.createProgram(filesToCompile, {
|
||||||
|
...tsConfig.options,
|
||||||
|
...{
|
||||||
|
outDir: dist,
|
||||||
|
inlineSourceMap: !tsConfig.options.sourceMap,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const emitResult = program.emit()
|
||||||
|
const diagnostics = ts
|
||||||
|
.getPreEmitDiagnostics(program)
|
||||||
|
.concat(emitResult.diagnostics)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log errors (if any)
|
||||||
|
*/
|
||||||
|
if (diagnostics.length) {
|
||||||
|
console.error(
|
||||||
|
ts.formatDiagnosticsWithColorAndContext(
|
||||||
|
diagnostics,
|
||||||
|
ts.createCompilerHost({})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { emitResult, diagnostics }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads and parses the TypeScript config file. In case of an error, the errors
|
||||||
|
* will be logged using the logger and undefined it returned
|
||||||
|
*/
|
||||||
|
async loadTSConfigFile(): Promise<tsStatic.ParsedCommandLine | undefined> {
|
||||||
|
const ts = await this.#loadTSCompiler()
|
||||||
|
let tsConfigErrors: tsStatic.Diagnostic[] = []
|
||||||
|
|
||||||
|
const tsConfig = ts.getParsedCommandLineOfConfigFile(
|
||||||
|
path.join(this.#projectRoot, "tsconfig.json"),
|
||||||
|
{
|
||||||
|
inlineSourceMap: true,
|
||||||
|
excludes: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...ts.sys,
|
||||||
|
useCaseSensitiveFileNames: true,
|
||||||
|
getCurrentDirectory: () => this.#projectRoot,
|
||||||
|
onUnRecoverableConfigFileDiagnostic: (error) =>
|
||||||
|
(tsConfigErrors = [error]),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push errors from the tsConfig parsed output to the
|
||||||
|
* tsConfigErrors array.
|
||||||
|
*/
|
||||||
|
if (tsConfig?.errors.length) {
|
||||||
|
tsConfigErrors.push(...tsConfig.errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display all config errors using the diagnostics reporter
|
||||||
|
*/
|
||||||
|
if (tsConfigErrors.length) {
|
||||||
|
const compilerHost = ts.createCompilerHost({})
|
||||||
|
this.#logger.error(
|
||||||
|
ts.formatDiagnosticsWithColorAndContext(tsConfigErrors, compilerHost)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If there are no errors, the `tsConfig` object will always exist.
|
||||||
|
*/
|
||||||
|
return tsConfig!
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the application backend source code using
|
||||||
|
* TypeScript's official compiler. Also performs
|
||||||
|
* type-checking
|
||||||
|
*/
|
||||||
|
async buildAppBackend(
|
||||||
|
tsConfig: tsStatic.ParsedCommandLine
|
||||||
|
): Promise<boolean> {
|
||||||
|
const tracker = this.#trackDuration()
|
||||||
|
const dist = this.#computeDist(tsConfig)
|
||||||
|
this.#logger.info("Compiling backend source...")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step 1: Cleanup existing build output
|
||||||
|
*/
|
||||||
|
this.#logger.info(
|
||||||
|
`Removing existing "${path.relative(this.#projectRoot, dist)}" folder`
|
||||||
|
)
|
||||||
|
await this.#clean(dist)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step 2: Compile TypeScript source code
|
||||||
|
*/
|
||||||
|
const { emitResult, diagnostics } = await this.#emitBuildOutput(
|
||||||
|
tsConfig,
|
||||||
|
["integration-tests", "test", "unit-tests", "src/admin"],
|
||||||
|
dist
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exit early if no output is written to the disk
|
||||||
|
*/
|
||||||
|
if (emitResult.emitSkipped) {
|
||||||
|
this.#logger.warn("Backend build completed without emitting any output")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step 3: Copy package manager files to the output folder
|
||||||
|
*/
|
||||||
|
await this.#copyPkgManagerFiles(dist)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify about the state of build
|
||||||
|
*/
|
||||||
|
if (diagnostics.length) {
|
||||||
|
this.#logger.warn(
|
||||||
|
`Backend build completed with errors (${tracker.getSeconds()}s)`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
this.#logger.info(
|
||||||
|
`Backend build completed successfully (${tracker.getSeconds()}s)`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the frontend source code of a Medusa application
|
||||||
|
* using the "@medusajs/admin-bundler" package.
|
||||||
|
*/
|
||||||
|
async buildAppFrontend(
|
||||||
|
adminOnly: boolean,
|
||||||
|
tsConfig: tsStatic.ParsedCommandLine,
|
||||||
|
adminBundler: {
|
||||||
|
build: (
|
||||||
|
options: AdminOptions & {
|
||||||
|
sources: string[]
|
||||||
|
outDir: string
|
||||||
|
}
|
||||||
|
) => Promise<void>
|
||||||
|
}
|
||||||
|
): Promise<boolean> {
|
||||||
|
const tracker = this.#trackDuration()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step 1: Load the medusa config file to read
|
||||||
|
* admin options
|
||||||
|
*/
|
||||||
|
const configFile = await this.#loadMedusaConfig()
|
||||||
|
if (!configFile) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return early when admin is disabled and we are trying to
|
||||||
|
* create a bundled build for the admin.
|
||||||
|
*/
|
||||||
|
if (configFile.configModule.admin.disable && !adminOnly) {
|
||||||
|
this.#logger.info(
|
||||||
|
"Skipping admin build, since its disabled inside the medusa-config file"
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Warn when we are creating an admin only build, but forgot to disable
|
||||||
|
* the admin inside the config file
|
||||||
|
*/
|
||||||
|
if (!configFile.configModule.admin.disable && adminOnly) {
|
||||||
|
this.#logger.warn(
|
||||||
|
`You are building using the flag --admin-only but the admin is enabled in your medusa-config, If you intend to host the dashboard separately you should disable the admin in your medusa config`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.#logger.info("Compiling frontend source...")
|
||||||
|
await adminBundler.build({
|
||||||
|
disable: false,
|
||||||
|
sources: [this.#adminSourceFolder],
|
||||||
|
...configFile.configModule.admin,
|
||||||
|
outDir: adminOnly
|
||||||
|
? this.#adminOnlyDistFolder
|
||||||
|
: path.join(this.#computeDist(tsConfig), "./public/admin"),
|
||||||
|
})
|
||||||
|
|
||||||
|
this.#logger.info(
|
||||||
|
`Frontend build completed successfully (${tracker.getSeconds()}s)`
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
this.#logger.error("Unable to compile frontend source")
|
||||||
|
this.#logger.error(error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo. To be implemented
|
||||||
|
*/
|
||||||
|
buildPluginBackend() {}
|
||||||
|
developPluginBacked() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./compiler"
|
||||||
@@ -1,273 +1,7 @@
|
|||||||
import path from "path"
|
|
||||||
import { access, constants, copyFile, rm } from "node:fs/promises"
|
|
||||||
import type tsStatic from "typescript"
|
|
||||||
import { logger } from "@medusajs/framework/logger"
|
import { logger } from "@medusajs/framework/logger"
|
||||||
import { ConfigModule } from "@medusajs/framework/types"
|
import { Compiler } from "@medusajs/framework/build-tools"
|
||||||
import { getConfigFile } from "@medusajs/framework/utils"
|
|
||||||
import {
|
|
||||||
ADMIN_ONLY_OUTPUT_DIR,
|
|
||||||
ADMIN_RELATIVE_OUTPUT_DIR,
|
|
||||||
ADMIN_SOURCE_DIR,
|
|
||||||
} from "../utils"
|
|
||||||
|
|
||||||
const INTEGRATION_TESTS_FOLDER = "integration-tests"
|
export default async function build({
|
||||||
|
|
||||||
function computeDist(
|
|
||||||
projectRoot: string,
|
|
||||||
tsConfig: { options: { outDir?: string } }
|
|
||||||
): string {
|
|
||||||
const distFolder = tsConfig.options.outDir ?? ".medusa/server"
|
|
||||||
return path.isAbsolute(distFolder)
|
|
||||||
? distFolder
|
|
||||||
: path.join(projectRoot, distFolder)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadTsConfig(projectRoot: string) {
|
|
||||||
const ts = await import("typescript")
|
|
||||||
const tsConfig = parseTSConfig(projectRoot, ts)
|
|
||||||
if (!tsConfig) {
|
|
||||||
logger.error("Unable to compile backend source")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return tsConfig!
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copies the file to the destination without throwing any
|
|
||||||
* errors if the source file is missing
|
|
||||||
*/
|
|
||||||
async function copy(source: string, destination: string) {
|
|
||||||
let sourceExists = false
|
|
||||||
try {
|
|
||||||
await access(source, constants.F_OK)
|
|
||||||
sourceExists = true
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "ENOENT") {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sourceExists) {
|
|
||||||
await copyFile(path.join(source), path.join(destination))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the directory and its children recursively and
|
|
||||||
* ignores any errors
|
|
||||||
*/
|
|
||||||
async function clean(path: string) {
|
|
||||||
await rm(path, { recursive: true }).catch(() => {})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the medusa config file or exits with an error
|
|
||||||
*/
|
|
||||||
async function loadMedusaConfig(directory: string) {
|
|
||||||
/**
|
|
||||||
* Parsing the medusa config file to ensure it is error
|
|
||||||
* free
|
|
||||||
*/
|
|
||||||
const { configModule, configFilePath, error } =
|
|
||||||
await getConfigFile<ConfigModule>(directory, "medusa-config")
|
|
||||||
if (error) {
|
|
||||||
console.error(`Failed to load medusa-config.js`)
|
|
||||||
console.error(error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return { configFilePath, configModule }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the tsconfig file or exits with an error in case
|
|
||||||
* the file is invalid
|
|
||||||
*/
|
|
||||||
function parseTSConfig(projectRoot: string, ts: typeof tsStatic) {
|
|
||||||
let tsConfigErrors: null | tsStatic.Diagnostic = null
|
|
||||||
|
|
||||||
const tsConfig = ts.getParsedCommandLineOfConfigFile(
|
|
||||||
path.join(projectRoot, "tsconfig.json"),
|
|
||||||
{
|
|
||||||
inlineSourceMap: true,
|
|
||||||
excludes: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
...ts.sys,
|
|
||||||
useCaseSensitiveFileNames: true,
|
|
||||||
getCurrentDirectory: () => projectRoot,
|
|
||||||
onUnRecoverableConfigFileDiagnostic: (error) => (tsConfigErrors = error),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (tsConfigErrors) {
|
|
||||||
const compilerHost = ts.createCompilerHost({})
|
|
||||||
console.error(
|
|
||||||
ts.formatDiagnosticsWithColorAndContext([tsConfigErrors], compilerHost)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tsConfig!.errors.length) {
|
|
||||||
const compilerHost = ts.createCompilerHost({})
|
|
||||||
console.error(
|
|
||||||
ts.formatDiagnosticsWithColorAndContext(tsConfig!.errors, compilerHost)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return tsConfig!
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds the backend project using TSC
|
|
||||||
*/
|
|
||||||
async function buildBackend(
|
|
||||||
projectRoot: string,
|
|
||||||
tsConfig: tsStatic.ParsedCommandLine
|
|
||||||
): Promise<boolean> {
|
|
||||||
const startTime = process.hrtime()
|
|
||||||
logger.info("Compiling backend source...")
|
|
||||||
|
|
||||||
const dist = computeDist(projectRoot, tsConfig)
|
|
||||||
|
|
||||||
logger.info(`Removing existing "${path.relative(projectRoot, dist)}" folder`)
|
|
||||||
await clean(dist)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ignoring admin and integration tests from the compiled
|
|
||||||
* files
|
|
||||||
*/
|
|
||||||
const filesToCompile = tsConfig.fileNames.filter((fileName) => {
|
|
||||||
return (
|
|
||||||
!fileName.includes(`${ADMIN_SOURCE_DIR}/`) &&
|
|
||||||
!fileName.includes(`${INTEGRATION_TESTS_FOLDER}/`)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const ts = await import("typescript")
|
|
||||||
const program = ts.createProgram(filesToCompile, {
|
|
||||||
...tsConfig.options,
|
|
||||||
...{
|
|
||||||
outDir: dist,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disable inline source maps when the user has enabled
|
|
||||||
* source maps within the config file
|
|
||||||
*/
|
|
||||||
inlineSourceMap: !tsConfig.options.sourceMap,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const emitResult = program.emit()
|
|
||||||
const diagnostics = ts
|
|
||||||
.getPreEmitDiagnostics(program)
|
|
||||||
.concat(emitResult.diagnostics)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log errors (if any)
|
|
||||||
*/
|
|
||||||
if (diagnostics.length) {
|
|
||||||
console.error(
|
|
||||||
ts.formatDiagnosticsWithColorAndContext(
|
|
||||||
diagnostics,
|
|
||||||
ts.createCompilerHost({})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exit early if no output is written to the disk
|
|
||||||
*/
|
|
||||||
if (emitResult.emitSkipped) {
|
|
||||||
logger.warn("Backend build completed without emitting any output")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copying package manager files
|
|
||||||
*/
|
|
||||||
await copy(
|
|
||||||
path.join(projectRoot, "package.json"),
|
|
||||||
path.join(dist, "package.json")
|
|
||||||
)
|
|
||||||
await copy(path.join(projectRoot, "yarn.lock"), path.join(dist, "yarn.lock"))
|
|
||||||
await copy(path.join(projectRoot, "pnpm.lock"), path.join(dist, "pnpm.lock"))
|
|
||||||
await copy(
|
|
||||||
path.join(projectRoot, "package-lock.json"),
|
|
||||||
path.join(dist, "package-lock.json")
|
|
||||||
)
|
|
||||||
|
|
||||||
const duration = process.hrtime(startTime)
|
|
||||||
const seconds = (duration[0] + duration[1] / 1e9).toFixed(2)
|
|
||||||
|
|
||||||
if (diagnostics.length) {
|
|
||||||
logger.warn(`Backend build completed with errors (${seconds}s)`)
|
|
||||||
} else {
|
|
||||||
logger.info(`Backend build completed successfully (${seconds}s)`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds the frontend project using the "@medusajs/admin-bundler"
|
|
||||||
*/
|
|
||||||
async function buildFrontend(
|
|
||||||
projectRoot: string,
|
|
||||||
adminOnly: boolean,
|
|
||||||
tsConfig: tsStatic.ParsedCommandLine
|
|
||||||
): Promise<boolean> {
|
|
||||||
const startTime = process.hrtime()
|
|
||||||
const configFile = await loadMedusaConfig(projectRoot)
|
|
||||||
if (!configFile) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const dist = computeDist(projectRoot, tsConfig)
|
|
||||||
|
|
||||||
const adminOutputPath = adminOnly
|
|
||||||
? path.join(projectRoot, ADMIN_ONLY_OUTPUT_DIR)
|
|
||||||
: path.join(dist, ADMIN_RELATIVE_OUTPUT_DIR)
|
|
||||||
|
|
||||||
const adminSource = path.join(projectRoot, ADMIN_SOURCE_DIR)
|
|
||||||
const adminOptions = {
|
|
||||||
disable: false,
|
|
||||||
sources: [adminSource],
|
|
||||||
...configFile.configModule.admin,
|
|
||||||
outDir: adminOutputPath,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (adminOptions.disable && !adminOnly) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!adminOptions.disable && adminOnly) {
|
|
||||||
logger.warn(
|
|
||||||
`You are building using the flag --admin-only but the admin is enabled in your medusa-config, If you intend to host the dashboard separately you should disable the admin in your medusa config`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
logger.info("Compiling frontend source...")
|
|
||||||
const { build: buildProductionBuild } = await import(
|
|
||||||
"@medusajs/admin-bundler"
|
|
||||||
)
|
|
||||||
await buildProductionBuild(adminOptions)
|
|
||||||
const duration = process.hrtime(startTime)
|
|
||||||
const seconds = (duration[0] + duration[1] / 1e9).toFixed(2)
|
|
||||||
|
|
||||||
logger.info(`Frontend build completed successfully (${seconds}s)`)
|
|
||||||
return true
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Unable to compile frontend source")
|
|
||||||
console.error(error)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function ({
|
|
||||||
directory,
|
directory,
|
||||||
adminOnly,
|
adminOnly,
|
||||||
}: {
|
}: {
|
||||||
@@ -275,20 +9,21 @@ export default async function ({
|
|||||||
adminOnly: boolean
|
adminOnly: boolean
|
||||||
}): Promise<boolean> {
|
}): Promise<boolean> {
|
||||||
logger.info("Starting build...")
|
logger.info("Starting build...")
|
||||||
|
const compiler = new Compiler(directory, logger)
|
||||||
|
|
||||||
const tsConfig = await loadTsConfig(directory)
|
const tsConfig = await compiler.loadTSConfigFile()
|
||||||
if (!tsConfig) {
|
if (!tsConfig) {
|
||||||
|
logger.error("Unable to compile application")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const promises: Promise<any>[] = []
|
const promises: Promise<any>[] = []
|
||||||
|
|
||||||
if (!adminOnly) {
|
if (!adminOnly) {
|
||||||
promises.push(buildBackend(directory, tsConfig))
|
promises.push(compiler.buildAppBackend(tsConfig))
|
||||||
}
|
}
|
||||||
|
|
||||||
promises.push(buildFrontend(directory, adminOnly, tsConfig))
|
const bundler = await import("@medusajs/admin-bundler")
|
||||||
|
promises.push(compiler.buildAppFrontend(adminOnly, tsConfig, bundler))
|
||||||
await Promise.all(promises)
|
await Promise.all(promises)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user