Skip to content

Commit

Permalink
fix: enable usage of config files when package.json type=module (#8455
Browse files Browse the repository at this point in the history
)
  • Loading branch information
mmaietta authored Sep 8, 2024
1 parent 054fad8 commit 5c8373d
Show file tree
Hide file tree
Showing 19 changed files with 248 additions and 90 deletions.
6 changes: 6 additions & 0 deletions .changeset/smart-impalas-explain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"app-builder-lib": patch
"electron-builder": patch
---

fix: allow usage of "module" typ config files
8 changes: 6 additions & 2 deletions packages/app-builder-lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@
"builder-util": "workspace:*",
"builder-util-runtime": "workspace:*",
"chromium-pickle-js": "^0.2.0",
"config-file-ts": "0.2.8-rc1",
"debug": "^4.3.4",
"dotenv": "^16.4.5",
"dotenv-expand": "^11.0.6",
"ejs": "^3.1.8",
"electron-publish": "workspace:*",
"form-data": "^4.0.0",
Expand All @@ -67,9 +70,9 @@
"is-ci": "^3.0.0",
"isbinaryfile": "^5.0.0",
"js-yaml": "^4.1.0",
"json5": "^2.2.3",
"lazy-val": "^1.0.5",
"minimatch": "^10.0.0",
"read-config-file": "6.4.0",
"resedit": "^1.7.0",
"sanitize-filename": "^1.6.3",
"semver": "^7.3.8",
Expand Down Expand Up @@ -105,7 +108,8 @@
"@types/semver": "7.3.8",
"@types/tar": "^6.1.3",
"dmg-builder": "workspace:*",
"electron-builder-squirrel-windows": "workspace:*"
"electron-builder-squirrel-windows": "workspace:*",
"toml": "^3.0.0"
},
"peerDependencies": {
"dmg-builder": "workspace:*",
Expand Down
2 changes: 1 addition & 1 deletion packages/app-builder-lib/src/codeSign/windowsCodeSign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { computeToolEnv, ToolInfo } from "../util/bundledTool"
import { rename } from "fs-extra"
import * as os from "os"
import * as path from "path"
import { resolveFunction } from "../platformPackager"
import { resolveFunction } from "../util/resolve"
import { isUseSystemSigncode } from "../util/flags"
import { VmManager } from "../vm/vm"
import { WinPackager } from "../winPackager"
Expand Down
4 changes: 2 additions & 2 deletions packages/app-builder-lib/src/electron/electronVersion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { httpExecutor } from "builder-util"
import { readJson } from "fs-extra"
import { Lazy } from "lazy-val"
import * as path from "path"
import { orNullIfFileNotExist } from "read-config-file"
import { orNullIfFileNotExist } from "../util/config/load"
import * as semver from "semver"
import { Configuration } from "../configuration"
import { getConfig } from "../util/config"
import { getConfig } from "../util/config/config"

export type MetadataValue = Lazy<{ [key: string]: any } | null>

Expand Down
2 changes: 1 addition & 1 deletion packages/app-builder-lib/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { log, InvalidConfigurationError, executeFinally } from "builder-util"
import { asArray } from "builder-util-runtime"
import { Packager } from "./packager"
import { PackagerOptions } from "./packagerApi"
import { resolveFunction } from "./platformPackager"
import { resolveFunction } from "./util/resolve"
import { PublishManager } from "./publish/PublishManager"

export { Packager, BuildResult } from "./packager"
Expand Down
3 changes: 2 additions & 1 deletion packages/app-builder-lib/src/macPackager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { DIR_TARGET, Platform, Target } from "./core"
import { AfterPackContext, ElectronPlatformName } from "./index"
import { MacConfiguration, MasConfiguration, NotarizeNotaryOptions } from "./options/macOptions"
import { Packager } from "./packager"
import { chooseNotNull, resolveFunction, PlatformPackager } from "./platformPackager"
import { chooseNotNull, PlatformPackager } from "./platformPackager"
import { ArchiveTarget } from "./targets/ArchiveTarget"
import { PkgTarget, prepareProductBuildArgs } from "./targets/pkg"
import { createCommonTarget, NoOpTarget } from "./targets/targetFactory"
Expand All @@ -20,6 +20,7 @@ import * as fs from "fs/promises"
import { notarize } from "@electron/notarize"
import { NotarizeOptionsNotaryTool, NotaryToolKeychainCredentials } from "@electron/notarize/lib/types"
import { MemoLazy } from "builder-util-runtime"
import { resolveFunction } from "./util/resolve"

export type CustomMacSignOptions = SignOptions
export type CustomMacSign = (configuration: CustomMacSignOptions, packager: MacPackager) => Promise<void>
Expand Down
5 changes: 3 additions & 2 deletions packages/app-builder-lib/src/packager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,18 @@ import { Framework } from "./Framework"
import { LibUiFramework } from "./frameworks/LibUiFramework"
import { Metadata } from "./options/metadata"
import { ArtifactBuildStarted, ArtifactCreated, PackagerOptions } from "./packagerApi"
import { PlatformPackager, resolveFunction } from "./platformPackager"
import { PlatformPackager } from "./platformPackager"
import { ProtonFramework } from "./ProtonFramework"
import { computeArchToTargetNamesMap, createTargets, NoOpTarget } from "./targets/targetFactory"
import { computeDefaultAppDirectory, getConfig, validateConfiguration } from "./util/config"
import { computeDefaultAppDirectory, getConfig, validateConfiguration } from "./util/config/config"
import { expandMacro } from "./util/macroExpander"
import { createLazyProductionDeps, NodeModuleDirInfo, NodeModuleInfo } from "./util/packageDependencies"
import { checkMetadata, readPackageJson } from "./util/packageMetadata"
import { getRepositoryInfo } from "./util/repositoryInfo"
import { installOrRebuild, nodeGypRebuild } from "./util/yarn"
import { PACKAGE_VERSION } from "./version"
import { release as getOsRelease } from "os"
import { resolveFunction } from "./util/resolve"

function addHandler(emitter: EventEmitter, event: string, handler: (...args: Array<any>) => void) {
emitter.on(event, handler)
Expand Down
49 changes: 2 additions & 47 deletions packages/app-builder-lib/src/platformPackager.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import BluebirdPromise from "bluebird-lst"
import { Arch, asArray, AsyncTaskManager, debug, DebugLogger, deepAssign, getArchSuffix, InvalidConfigurationError, isEmptyOrSpaces, log } from "builder-util"
import { Arch, asArray, AsyncTaskManager, DebugLogger, deepAssign, getArchSuffix, InvalidConfigurationError, isEmptyOrSpaces, log } from "builder-util"
import { defaultArchFromString, getArtifactArchName, FileTransformer, statOrNull, orIfFileNotExist } from "builder-util"
import { readdir } from "fs/promises"
import { Lazy } from "lazy-val"
import { Minimatch } from "minimatch"
import * as path from "path"
import { pathToFileURL } from "url"
import { AppInfo } from "./appInfo"
import { checkFileInArchive } from "./asar/asarFileChecker"
import { AsarPackager } from "./asar/asarUtil"
Expand All @@ -30,6 +29,7 @@ import {
import { executeAppBuilderAsJson } from "./util/appBuilder"
import { computeFileSets, computeNodeModuleFileSets, copyAppFiles, ELECTRON_COMPILE_SHIM_FILENAME, transformFiles } from "./util/appFileCopier"
import { expandMacro as doExpandMacro } from "./util/macroExpander"
import { resolveFunction } from "./util/resolve"

export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions> {
get packagerOptions(): PackagerOptions {
Expand Down Expand Up @@ -770,51 +770,6 @@ export function normalizeExt(ext: string) {
return ext.startsWith(".") ? ext.substring(1) : ext
}

async function resolveModule<T>(type: string | undefined, name: string): Promise<T> {
const extension = path.extname(name).toLowerCase()
const isModuleType = type === "module"
try {
if (extension === ".mjs" || (extension === ".js" && isModuleType)) {
const fileUrl = pathToFileURL(name).href
return await eval("import('" + fileUrl + "')")
}
} catch (error: any) {
log.debug({ moduleName: name, message: error.message ?? error.stack }, "Unable to dynamically import hook, falling back to `require`")
}
try {
return require(name)
} catch (error: any) {
log.error({ moduleName: name, message: error.message ?? error.stack }, "Unable to `require` hook")
throw new Error(error.message ?? error.stack)
}
}

export async function resolveFunction<T>(type: string | undefined, executor: T | string, name: string): Promise<T> {
if (executor == null || typeof executor !== "string") {
return executor
}

let p = executor as string
if (p.startsWith(".")) {
p = path.resolve(p)
}

try {
p = require.resolve(p)
} catch (e: any) {
debug(e)
p = path.resolve(p)
}

const m: any = await resolveModule(type, p)
const namedExport = m[name]
if (namedExport == null) {
return m.default || m
} else {
return namedExport
}
}

export function chooseNotNull(v1: string | null | undefined, v2: string | null | undefined): string | null | undefined {
return v1 == null ? v2 : v1
}
Expand Down
2 changes: 1 addition & 1 deletion packages/app-builder-lib/src/util/NodeModuleCopyHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { lstat, readdir, lstatSync } from "fs-extra"
import * as path from "path"
import { excludedNames, FileMatcher } from "../fileMatcher"
import { Packager } from "../packager"
import { resolveFunction } from "../platformPackager"
import { resolveFunction } from "./resolve"
import { FileCopyHelper } from "./AppFileWalker"
import { NodeModuleInfo } from "./packageDependencies"
import { realpathSync } from "fs"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { DebugLogger, deepAssign, InvalidConfigurationError, log, safeStringifyJ
import { readJson } from "fs-extra"
import { Lazy } from "lazy-val"
import * as path from "path"
import { getConfig as _getConfig, loadParentConfig, orNullIfFileNotExist, ReadConfigRequest } from "read-config-file"
import { Configuration } from "../configuration"
import { FileSet } from "../options/PlatformSpecificBuildOptions"
import { reactCra } from "../presets/rectCra"
import { PACKAGE_VERSION } from "../version"
import { getConfig as _getConfig, loadParentConfig, orNullIfFileNotExist, ReadConfigRequest } from "./load"
import { Configuration } from "../../configuration"
import { FileSet } from "../../options/PlatformSpecificBuildOptions"
import { reactCra } from "../../presets/rectCra"
import { PACKAGE_VERSION } from "../../version"
// eslint-disable-next-line @typescript-eslint/no-var-requires
const validateSchema = require("@develar/schema-utils")

Expand Down Expand Up @@ -214,7 +214,7 @@ function getDefaultConfig(): Configuration {
}
}

const schemeDataPromise = new Lazy(() => readJson(path.join(__dirname, "..", "..", "scheme.json")))
const schemeDataPromise = new Lazy(() => readJson(path.join(__dirname, "..", "..", "..", "scheme.json")))

export async function validateConfiguration(config: Configuration, debugLogger: DebugLogger) {
const extraMetadata = config.extraMetadata
Expand Down
142 changes: 142 additions & 0 deletions packages/app-builder-lib/src/util/config/load.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { promises as fs } from "fs"
import { load } from "js-yaml"
import * as path from "path"
import { Lazy } from "lazy-val"
import { parse as parseEnv } from "dotenv"
import { loadTsConfig } from "config-file-ts"
import { DotenvParseInput, expand } from "dotenv-expand"
import { resolveModule } from "../resolve"
import { log } from "builder-util"

export interface ReadConfigResult<T> {
readonly result: T
readonly configFile: string | null
}

async function readConfig<T>(configFile: string, request: ReadConfigRequest): Promise<ReadConfigResult<T>> {
const data = await fs.readFile(configFile, "utf8")
let result: any
if (configFile.endsWith(".json5") || configFile.endsWith(".json")) {
result = require("json5").parse(data)
} else if (configFile.endsWith(".js") || configFile.endsWith(".cjs" || configFile.endsWith(".mjs"))) {
const json = await orNullIfFileNotExist(fs.readFile(path.join(process.cwd(), "package.json"), "utf8"))
const moduleType = json === null ? null : JSON.parse(json).type
result = await resolveModule(moduleType, configFile)
if (result.default != null) {
result = result.default
}
if (typeof result === "function") {
result = result(request)
}
result = await Promise.resolve(result)
} else if (configFile.endsWith(".ts")) {
result = loadTsConfig(configFile)
if (typeof result === "function") {
result = result(request)
}
result = await Promise.resolve(result)
} else if (configFile.endsWith(".toml")) {
result = require("toml").parse(data)
} else {
result = load(data)
}
return { result, configFile }
}

export async function findAndReadConfig<T>(request: ReadConfigRequest): Promise<ReadConfigResult<T> | null> {
const prefix = request.configFilename
for (const configFile of [`${prefix}.yml`, `${prefix}.yaml`, `${prefix}.json`, `${prefix}.json5`, `${prefix}.toml`, `${prefix}.js`, `${prefix}.cjs`, `${prefix}.ts`]) {
const data = await orNullIfFileNotExist(readConfig<T>(path.join(request.projectDir, configFile), request))
if (data != null) {
return data
}
}

return null
}

export function orNullIfFileNotExist<T>(promise: Promise<T>): Promise<T | null> {
return orIfFileNotExist(promise, null)
}

export function orIfFileNotExist<T>(promise: Promise<T>, fallbackValue: T): Promise<T> {
return promise.catch(e => {
if (e.code === "ENOENT" || e.code === "ENOTDIR") {
return fallbackValue
}
throw e
})
}

export interface ReadConfigRequest {
packageKey: string
configFilename: string

projectDir: string
packageMetadata: Lazy<{ [key: string]: any } | null> | null
}

export async function loadConfig<T>(request: ReadConfigRequest): Promise<ReadConfigResult<T> | null> {
let packageMetadata = request.packageMetadata == null ? null : await request.packageMetadata.value
if (packageMetadata == null) {
const json = await orNullIfFileNotExist(fs.readFile(path.join(request.projectDir, "package.json"), "utf8"))
packageMetadata = json == null ? null : JSON.parse(json)
}

const data: T = packageMetadata == null ? null : packageMetadata[request.packageKey]
return data == null ? findAndReadConfig<T>(request) : { result: data, configFile: null }
}

export function getConfig<T>(request: ReadConfigRequest, configPath?: string | null): Promise<ReadConfigResult<T> | null> {
if (configPath == null) {
return loadConfig<T>(request)
} else {
return readConfig<T>(path.resolve(request.projectDir, configPath), request)
}
}

export async function loadParentConfig<T>(request: ReadConfigRequest, spec: string): Promise<ReadConfigResult<T>> {
let isFileSpec: boolean | undefined
if (spec.startsWith("file:")) {
spec = spec.substring("file:".length)
isFileSpec = true
}

let parentConfig = await orNullIfFileNotExist(readConfig<T>(path.resolve(request.projectDir, spec), request))
if (parentConfig == null && isFileSpec !== true) {
let resolved: string | null = null
try {
resolved = require.resolve(spec)
} catch (e) {
// ignore
}

if (resolved != null) {
parentConfig = await readConfig<T>(resolved, request)
}
}

if (parentConfig == null) {
throw new Error(`Cannot find parent config file: ${spec}`)
}

return parentConfig
}

export async function loadEnv(envFile: string) {
const data = await orNullIfFileNotExist(fs.readFile(envFile, "utf8"))
if (data == null) {
return null
}

const parsed = parseEnv<DotenvParseInput>(data)

log.info({ envFile }, "injecting environment")
Object.entries(parsed).forEach(([key, value]) => {
if (!Object.prototype.hasOwnProperty.call(process.env, key)) {
process.env[key] = value
}
})
expand({ parsed })
return parsed
}
Loading

0 comments on commit 5c8373d

Please sign in to comment.