-
Notifications
You must be signed in to change notification settings - Fork 0
/
build.ts
264 lines (185 loc) · 9.53 KB
/
build.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
// Build-In Modules
import path, { dirname, isAbsolute, join, relative, resolve } from "node:path"
// General Internal Imports
import { isDesktop, getBuildConfig, globalTempDir, templateDir, ensureTargetConsistent, isMobile, globalWorkspacePath, handleTemporaryDirectories, chalk, vite, electronVersion } from "./globals.js"
import { BuildHooks, ServiceBuildOptions, UserConfig, WritableElectronBuilderConfig } from "./types.js"
// Internal Utilities
import {
getAppAssets,
getServiceAssets,
buildAssets,
getAssetBuildPath
} from "./utils/assets.js"
import { lstatSync } from './utils/lstat.js'
import { printHeader, printTarget } from "./utils/formatting.js"
import { removeDirectory } from './utils/files.js'
import { getIcon } from "./utils/index.js"
import merge from './utils/merge.js'
// Core Internal Imports
import { configureForDesktop, resolveConfig } from "./index.js"
import * as mobile from './mobile/index.js'
import { resolveViteConfig } from './vite/index.js'
type CliOptions = import('electron-builder').CliOptions
const replaceAllSpecialCharacters = (str: string) => str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')
const convertToBaseRegexString = (str: string) => new RegExp(str).toString().split('/').slice(1, -1).join('/')
export const buildAllAssets = async ( config, dev ) => {
const { outDir, root, target } = config
const appAssets = await getAppAssets(config, dev)
const outputs = await buildAssets(appAssets, {
outDir,
root,
target
})
if (dev || isDesktop(target)) {
const _outputs = await buildServices(config, { dev, outDir }) // Only build when in development, or during desktop builds
outputs.push(..._outputs)
}
return outputs
}
// ------------------------ Main Exports ------------------------
export const buildServices = async (
config: UserConfig = {},
options: ServiceBuildOptions = {}
) => {
const { dev = false, services } = options
let { outDir } = options
// if (!dev) await printHeader(`${name} — ${buildOnlyServices ? 'Building Selected Services' : `${printTarget(target)} Build`}`)
const resolvedConfig = await resolveConfig(config, { services, build: true })
const { root, target } = resolvedConfig
const assets = await getServiceAssets(resolvedConfig, dev)
return await buildAssets(
assets,
{
root,
outDir: outDir ?? resolve(join(root, globalWorkspacePath, 'services')), // Default service output directory
target
}
)
return []
}
export async function buildApp (
config: UserConfig = {},
// Hooks
{
services: devServices,
onBuildAssets,
dev = false // Default to a production build
}: BuildHooks = {},
) {
const _vite = await vite
const _chalk = await chalk
// ---------------- Proper Configuration Resolution ----------------
const resolvedConfig = await resolveConfig(config, { build: true })
const { root, target, build = {} } = resolvedConfig
const { publish, sign } = build
const isElectronBuild = target === 'electron'
const isDesktopBuild = isDesktop(target)
const isMobileBuild = isMobile(target)
// ---------------- Output Directory Resolution ----------------
const defaultOutDir = join(root, globalWorkspacePath, target)
let { outDir = defaultOutDir } = config
const selectedOutDir = outDir // This is used for the actual build output
const customTempDir = isDesktopBuild || isMobileBuild
if (customTempDir) {
outDir = join(root, globalTempDir, isElectronBuild ? 'electron' : 'mobile')
await handleTemporaryDirectories(dirname(outDir)) // Queue removal of temporary directories
}
outDir = resolve(outDir) // Ensure absolute path
const name = resolvedConfig.name
if (!dev) await printHeader(`${name} — ${printTarget(target)} Build`)
if (devServices) resolvedConfig.services = devServices // Ensure local services are resolved with the same information
// ---------------- Clear Previous Builds ----------------
if (isDesktopBuild) await removeDirectory(join(globalWorkspacePath, 'services')) // Clear default service directory
await removeDirectory(outDir)
// ------------------ Set Resolved Configuration ------------------
const configCopy = { ...resolvedConfig, target, outDir } // Replace with internal target representation
// ---------------- Build App Assets ----------------
if (isMobileBuild) await mobile.prebuild(configCopy) // Run mobile prebuild command
// Build the standard output files using Vite. Force recognition as build
await _vite.build(await resolveViteConfig(configCopy, { dev }))
// Log build success
console.log(`${dev ? '' : '\n'}🚀 ${_chalk.bold(_chalk.greenBright('Frontend'))} built successfully\n`)
// ---------------- Create Standard Output Files ----------------
const assets = await buildAllAssets(configCopy, dev)
if (onBuildAssets) {
const result = onBuildAssets(outDir)
if (result === null) return // Skip packaging if requested
}
// ------------------------- Target-Specific Build Steps -------------------------
if (isElectronBuild) {
console.log(`\n👊 Running ${_chalk.bold(_chalk.cyanBright('electron-builder'))}\n`)
const cwdRelativeOutDir = relative(process.cwd(), outDir)
const relativeOutDir = relative(root, cwdRelativeOutDir)
// Configure package.json for proper Electron build
await configureForDesktop(cwdRelativeOutDir, root, {
name: name.toLowerCase().split(' ').join('-'),
version: '0.0.0'
})
const { electron, appId, icon } = configCopy
const buildConfig = merge((electron.build ?? {}), getBuildConfig()) as WritableElectronBuilderConfig
buildConfig.productName = name
buildConfig.appId = appId
const actualOutDir = isAbsolute(selectedOutDir) ? selectedOutDir : join(process.cwd(), selectedOutDir)
buildConfig.directories.output = actualOutDir
const files = buildConfig.files = [
`${relativeOutDir}/**`,
]
// Ensure platform-specific configs exist
if (!buildConfig.mac) buildConfig.mac = {}
if (!buildConfig.win) buildConfig.win = {}
if (!buildConfig.linux) buildConfig.linux = {}
// Ensure proper linux configuration
buildConfig.linux.executableName = buildConfig.productName
Object.assign(buildConfig.linux, {
executableName: buildConfig.productName,
artifactName: "${productName}-${version}.${ext}"
})
// Handle extra resources and code signing
const extraResources = buildConfig.extraResources = []
const signIgnore = buildConfig.mac.signIgnore = []
assets.forEach(({ file, extraResource, sign }) => {
const relPath = relative(cwdRelativeOutDir, file)
const location = join(relativeOutDir, relPath)
const glob = (lstatSync(file).isDirectory()) ? join(location, '**') : location
if (extraResource) {
extraResources.push(glob)
files.push(`!${glob}`)
}
// Ignore Code Signing for Certain Files (NOTE: "Failed to staple your application with code: 65" error)
if (sign === false) signIgnore.push(convertToBaseRegexString(`${replaceAllSpecialCharacters(location)}(/.*)?$`))
})
// TODO: Get platform-specific icon
const rawIconSrc = getIcon(icon)
if (rawIconSrc) {
const defaultIcon = isAbsolute(rawIconSrc) ? rawIconSrc : join(root, rawIconSrc)
const macIcon = defaultIcon ? getAssetBuildPath(defaultIcon, outDir) : defaultIcon // icon && typeof icon === 'object' && 'mac' in icon ? icon.mac : defaultIcon
buildConfig.mac.icon = macIcon
buildConfig.win.icon = macIcon // icon && typeof icon === 'object' && 'win' in icon ? icon.win : defaultIcon
}
// Ensure proper absolute paths are provided for Electron build
const electronTemplateDir = path.join(templateDir, 'electron')
buildConfig.directories.buildResources = path.join(electronTemplateDir, buildConfig.directories.buildResources)
buildConfig.afterSign = typeof buildConfig.afterSign === 'string' ? path.join(electronTemplateDir, buildConfig.afterSign) : buildConfig.afterSign
buildConfig.mac.entitlementsInherit = path.join(electronTemplateDir, buildConfig.mac.entitlementsInherit)
// Disable code signing if publishing or explicitly requested
const toSign = publish || sign
if (!toSign) buildConfig.mac.identity = null
buildConfig.includeSubNodeModules = true // Always grab workspace dependencies
// Correct for different project roots
if (!('electronVersion' in buildConfig)) buildConfig.electronVersion = electronVersion
const electronBuilderOpts: CliOptions = { config: buildConfig as any }
if (root) electronBuilderOpts.projectDir = root
if (publish) electronBuilderOpts.publish = typeof publish === 'string' ? publish : 'always'
else buildConfig.publish = null
// Use electron-builder to package the app
const { build } = await import('electron-builder')
await build(electronBuilderOpts)
}
else if (isMobileBuild) {
const mobileOpts = { target, outDir }
// @ts-ignore
await mobile.init(mobileOpts, resolvedConfig)
// @ts-ignore
await mobile.open(mobileOpts, resolvedConfig)
}
}