-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
218 lines (157 loc) · 6.9 KB
/
index.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
// Built-In Modules
import { dirname, join, relative, normalize, resolve } from 'node:path'
import { existsSync, unlink, writeFileSync } from 'node:fs'
// Internal Imports
import { globalWorkspacePath, getDefaultMainLocation, templateDir, onCleanup, ensureTargetConsistent, isMobile } from './globals.js'
import { ConfigResolveOptions, ResolvedConfig, ResolvedService, ServiceCreationOptions, TargetType, UserConfig } from './types.js'
import { resolveAll, createAll } from './assets/services/index.js'
import { resolveFile, getJSON } from './utils/files.js'
import merge from './utils/merge.js'
import { bundleConfig } from './utils/assets.js'
import { printFailure, printSubtle } from './utils/formatting.js'
import { lstatSync } from './utils/lstat.js'
import { pathToFileURL } from 'node:url'
// Top-Level Package Exports
export * from './types.js'
export * from './globals.js'
export * from './assets/services/index.js' // Service Helpers
export * as format from './utils/formatting.js'
export { launchApp as launch, launchServices } from './launch.js'
export { buildApp as build, buildServices } from './build.js'
export { app as start, services as startServices } from './start.js'
export { merge } // Other Helpers
// ------------------ Configuration File Handling ------------------
export const resolveConfigPath = (base = '') => resolveFile(join(base, 'commoners.config'), ['.ts', '.js'])
const isDirectory = (root: string) => lstatSync(root).isDirectory()
const isCommonersProject = async (
root: string = process.cwd()
) => {
const rootExists = existsSync(root)
let failMessage = ''
// Root does not exist
if (root && !rootExists) failMessage = `This path does not exist.`
// No index.html file
else if (!existsSync(join(root, 'index.html'))) failMessage = `This directory does not contain an index.html file.`
if (failMessage) {
await printFailure(`Invalid Commoners project`)
await printSubtle(failMessage)
return false
}
return true
}
export async function loadConfigFromFile(
root: string = resolveConfigPath()
) {
const rootExists = existsSync(root)
if (existsSync(root)) {
root = resolve(root) // Resolve to absolute path
if (!isDirectory(root)) root = dirname(root) // Get the parent directory
}
const isValidProject = await isCommonersProject(root)
if (!isValidProject) process.exit(1)
const configPath = resolveConfigPath(
rootExists ?
root : // New root config
'' // Base config
)
const resolvedRoot = configPath ? dirname(configPath) : root || process.cwd()
let config = {} as UserConfig // No user-defined configuration found
if (configPath) {
const configOutputPath = join(resolvedRoot, globalWorkspacePath, `commoners.config.mjs`)
const outputFiles = await bundleConfig(configPath, configOutputPath, { node: true })
const fileURL = pathToFileURL(configOutputPath).href
try {
config = (await import(fileURL)).default as UserConfig
} finally {
onCleanup(() => outputFiles.forEach((file) => unlink(file, () => { })))
}
}
// Set the root of the project
config.root = relative(process.cwd(), resolvedRoot) || resolvedRoot
return config
}
export async function resolveConfig(
o: UserConfig = {},
{
// Service Auto-Configuration
build = false,
// Advanced Service Configuration
services
} : ConfigResolveOptions = {}
) {
if (o.__resolved) return o
// Mobile commands must always run from the root of the specified project
if (isMobile(o.target) && o.root) {
process.chdir(o.root)
delete o.root
}
const root = o.root ?? (o.root = process.cwd()) // Always carry the root of the project
const { services: ogServices, plugins, vite, ...temp } = o
const userPkg = getJSON(join(root, 'package.json'))
// Merge Config and package.json (transformed name)
const copy = merge(structuredClone(temp) , {
...userPkg,
name: userPkg.name ? userPkg.name.split('-').map(str => str[0].toUpperCase() + str.slice(1)).join(' ') : 'Commoners App'
}) as Partial<ResolvedConfig>
if (copy.outDir) copy.outDir = join(copy.root, copy.outDir)
copy.plugins = plugins ?? {} // Transfer the original plugins
copy.services = ogServices as any ?? {} // Transfer original functions on publish
copy.vite = vite ?? {} // Transfer the original Vite config
const target = copy.target = await ensureTargetConsistent(copy.target)
if (!copy.electron) copy.electron = {}
// Set default values for certain properties shared across config and package.json
if (!copy.icon) copy.icon = join(templateDir, 'icon.png')
if (!copy.version) copy.version = '0.0.0'
if (!copy.appId) copy.appId = `com.${copy.name.replace(/\s/g, '').toLowerCase()}.app`
// Always have a build options object
if (!copy.build) copy.build = {}
if (services) {
const selectedServices = typeof services === "string" ? [ services ] : services
const allServices = Object.keys(copy.services)
if (selectedServices) {
if (!selectedServices.every(name => allServices.includes(name))) {
await printFailure(`Invalid service selection`)
await printSubtle(`Available services: ${allServices.join(', ')}`) // Print actual services as a nice list
process.exit(1)
}
}
}
copy.services = await resolveAll(copy.services, { target, build, services, root: copy.root }) // Resolve selected services
// Resolution flag
Object.defineProperty(
copy,
'__resolved',
{
value: true,
writable: false
}
)
return copy as ResolvedConfig
}
const writePackageJSON = (o, root = '') => {
writeFileSync(join(root, 'package.json'), JSON.stringify(o, null, 2)) // Will not update userPkg—but this variable isn't used for the Electron process
}
// Ensure project can handle --desktop command
export const configureForDesktop = async (outDir, root = '', defaults = {}) => {
const userPkg = getJSON(join(root, 'package.json'))
const pkg = {
...defaults,
...userPkg
}
const resolvedOutDir = root ? relative(root, outDir) : outDir
const defaultMainLocation = getDefaultMainLocation(resolvedOutDir)
if (!pkg.main || normalize(pkg.main) !== normalize(defaultMainLocation)) {
// Write back the original package.json on exit
onCleanup(() => writePackageJSON(pkg, root))
writePackageJSON({
...pkg,
main: defaultMainLocation
}, root)
}
}
export const createServices = async (services: ResolvedConfig['services'], opts: ServiceCreationOptions = {}) => await createAll(services, opts) as {
services: {
[name:string]: ResolvedService
}
close: (id?:string) => void
}