@edge-base/cli 0.2.7 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/build-app.d.ts +3 -0
- package/dist/commands/build-app.d.ts.map +1 -0
- package/dist/commands/build-app.js +45 -0
- package/dist/commands/build-app.js.map +1 -0
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +14 -14
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +43 -36
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/docker.d.ts +5 -0
- package/dist/commands/docker.d.ts.map +1 -1
- package/dist/commands/docker.js +83 -5
- package/dist/commands/docker.js.map +1 -1
- package/dist/commands/pack.d.ts +3 -0
- package/dist/commands/pack.d.ts.map +1 -0
- package/dist/commands/pack.js +117 -0
- package/dist/commands/pack.js.map +1 -0
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/app-bundle.d.ts +48 -0
- package/dist/lib/app-bundle.d.ts.map +1 -0
- package/dist/lib/app-bundle.js +240 -0
- package/dist/lib/app-bundle.js.map +1 -0
- package/dist/lib/deploy-shared.d.ts +4 -0
- package/dist/lib/deploy-shared.d.ts.map +1 -1
- package/dist/lib/deploy-shared.js +40 -8
- package/dist/lib/deploy-shared.js.map +1 -1
- package/dist/lib/frontend-config.d.ts +7 -0
- package/dist/lib/frontend-config.d.ts.map +1 -0
- package/dist/lib/frontend-config.js +8 -0
- package/dist/lib/frontend-config.js.map +1 -0
- package/dist/lib/function-registry.d.ts +1 -0
- package/dist/lib/function-registry.d.ts.map +1 -1
- package/dist/lib/function-registry.js +3 -1
- package/dist/lib/function-registry.js.map +1 -1
- package/dist/lib/managed-resource-names.d.ts +2 -0
- package/dist/lib/managed-resource-names.d.ts.map +1 -1
- package/dist/lib/managed-resource-names.js +19 -1
- package/dist/lib/managed-resource-names.js.map +1 -1
- package/dist/lib/node-tools.d.ts +3 -1
- package/dist/lib/node-tools.d.ts.map +1 -1
- package/dist/lib/node-tools.js +2 -2
- package/dist/lib/node-tools.js.map +1 -1
- package/dist/lib/pack.d.ts +102 -0
- package/dist/lib/pack.d.ts.map +1 -0
- package/dist/lib/pack.js +1047 -0
- package/dist/lib/pack.js.map +1 -0
- package/dist/lib/runtime-scaffold.d.ts +43 -3
- package/dist/lib/runtime-scaffold.d.ts.map +1 -1
- package/dist/lib/runtime-scaffold.js +455 -38
- package/dist/lib/runtime-scaffold.js.map +1 -1
- package/package.json +7 -4
package/dist/lib/pack.js
ADDED
|
@@ -0,0 +1,1047 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { chmodSync, copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
4
|
+
import { createAppBundle, findAppProjectRoot, } from './app-bundle.js';
|
|
5
|
+
import { loadConfigSafe } from './load-config.js';
|
|
6
|
+
import { generateTempWranglerToml } from './deploy-shared.js';
|
|
7
|
+
import { resolveLocalDevBindings } from './project-runtime.js';
|
|
8
|
+
const EDGEBASE_CONFIG_FILES = ['edgebase.config.ts', 'edgebase.config.js'];
|
|
9
|
+
export function findPackProjectRoot(startDir) {
|
|
10
|
+
return findAppProjectRoot(startDir);
|
|
11
|
+
}
|
|
12
|
+
function buildPackManifest(outputDir, appManifest) {
|
|
13
|
+
const defaultOpenPath = appManifest.frontend.enabled
|
|
14
|
+
? appManifest.frontend.mountPath ?? '/'
|
|
15
|
+
: '/admin';
|
|
16
|
+
return {
|
|
17
|
+
schemaVersion: 1,
|
|
18
|
+
format: 'dir',
|
|
19
|
+
createdAt: new Date().toISOString(),
|
|
20
|
+
projectName: appManifest.projectName,
|
|
21
|
+
outputDir,
|
|
22
|
+
appManifest: 'edgebase-app.json',
|
|
23
|
+
frontend: appManifest.frontend,
|
|
24
|
+
runtime: appManifest.runtime,
|
|
25
|
+
config: appManifest.config,
|
|
26
|
+
functions: appManifest.functions,
|
|
27
|
+
launcher: {
|
|
28
|
+
entry: 'launcher.mjs',
|
|
29
|
+
unix: 'run.sh',
|
|
30
|
+
windows: 'run.cmd',
|
|
31
|
+
defaultOpenPath,
|
|
32
|
+
defaultPort: deriveLauncherPort(appManifest.projectName),
|
|
33
|
+
defaultHost: '127.0.0.1',
|
|
34
|
+
defaultDataDir: 'os-app-data',
|
|
35
|
+
appDataDirName: buildAppDataDirectoryName(appManifest.projectName),
|
|
36
|
+
stateDir: 'state',
|
|
37
|
+
runtimeDir: 'runtime',
|
|
38
|
+
singleInstance: true,
|
|
39
|
+
portSearchLimit: 20,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function deriveLauncherPort(projectName) {
|
|
44
|
+
const normalized = sanitizeExecutableName(projectName).toLowerCase();
|
|
45
|
+
let hash = 0;
|
|
46
|
+
for (const char of normalized) {
|
|
47
|
+
hash = (hash * 31 + char.charCodeAt(0)) % 2000;
|
|
48
|
+
}
|
|
49
|
+
return 47600 + hash;
|
|
50
|
+
}
|
|
51
|
+
function buildAppDataDirectoryName(projectName) {
|
|
52
|
+
return `edgebase-${sanitizeExecutableName(projectName).toLowerCase()}`;
|
|
53
|
+
}
|
|
54
|
+
function buildPortableManifest(options) {
|
|
55
|
+
return {
|
|
56
|
+
schemaVersion: 1,
|
|
57
|
+
format: 'portable',
|
|
58
|
+
createdAt: new Date().toISOString(),
|
|
59
|
+
projectName: options.projectName,
|
|
60
|
+
platform: process.platform,
|
|
61
|
+
arch: process.arch,
|
|
62
|
+
artifactKind: options.artifactKind,
|
|
63
|
+
outputPath: options.outputPath,
|
|
64
|
+
bundledAppDir: options.bundledAppDir,
|
|
65
|
+
launcherPath: options.launcherPath,
|
|
66
|
+
embeddedNodePath: options.embeddedNodePath,
|
|
67
|
+
appManifest: options.appManifestPath,
|
|
68
|
+
packManifest: options.packManifestPath,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function buildArchiveManifest(options) {
|
|
72
|
+
return {
|
|
73
|
+
schemaVersion: 1,
|
|
74
|
+
format: 'archive',
|
|
75
|
+
createdAt: new Date().toISOString(),
|
|
76
|
+
projectName: options.projectName,
|
|
77
|
+
platform: process.platform,
|
|
78
|
+
arch: process.arch,
|
|
79
|
+
archiveType: options.archiveType,
|
|
80
|
+
outputPath: options.outputPath,
|
|
81
|
+
sourcePortablePath: options.sourcePortablePath,
|
|
82
|
+
launcherPath: options.launcherPath,
|
|
83
|
+
embeddedNodePath: options.embeddedNodePath,
|
|
84
|
+
appManifest: options.appManifestPath,
|
|
85
|
+
packManifest: options.packManifestPath,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function resolvePortableOutputPath(projectDir, projectName, explicitOutput) {
|
|
89
|
+
if (explicitOutput) {
|
|
90
|
+
return resolve(projectDir, explicitOutput);
|
|
91
|
+
}
|
|
92
|
+
if (process.platform === 'darwin') {
|
|
93
|
+
return join(projectDir, 'dist', `${projectName}.app`);
|
|
94
|
+
}
|
|
95
|
+
return join(projectDir, 'dist', `${projectName}-${process.platform}-${process.arch}-portable`);
|
|
96
|
+
}
|
|
97
|
+
function resolveArchiveOutputPath(projectDir, projectName, explicitOutput) {
|
|
98
|
+
if (explicitOutput) {
|
|
99
|
+
return resolve(projectDir, explicitOutput);
|
|
100
|
+
}
|
|
101
|
+
const archiveExtension = process.platform === 'linux' ? '.tar.gz' : '.zip';
|
|
102
|
+
return join(projectDir, 'dist', `${projectName}-${process.platform}-${process.arch}${archiveExtension}`);
|
|
103
|
+
}
|
|
104
|
+
function sanitizeExecutableName(appName) {
|
|
105
|
+
const normalized = appName
|
|
106
|
+
.replace(/\.app$/i, '')
|
|
107
|
+
.replace(/[^A-Za-z0-9._-]+/g, '-')
|
|
108
|
+
.replace(/^-+|-+$/g, '');
|
|
109
|
+
return normalized || 'edgebase-app';
|
|
110
|
+
}
|
|
111
|
+
function commandExists(command) {
|
|
112
|
+
try {
|
|
113
|
+
execFileSync('which', [command], { stdio: 'pipe' });
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function isMacSystemLibraryPath(libraryPath) {
|
|
121
|
+
return libraryPath.startsWith('/System/')
|
|
122
|
+
|| libraryPath.startsWith('/usr/lib/');
|
|
123
|
+
}
|
|
124
|
+
function listMachODependencies(binaryPath) {
|
|
125
|
+
const output = execFileSync('otool', ['-L', binaryPath], {
|
|
126
|
+
encoding: 'utf-8',
|
|
127
|
+
stdio: 'pipe',
|
|
128
|
+
});
|
|
129
|
+
return output
|
|
130
|
+
.split(/\r?\n/)
|
|
131
|
+
.slice(1)
|
|
132
|
+
.map((line) => line.trim())
|
|
133
|
+
.filter(Boolean)
|
|
134
|
+
.map((line) => line.replace(/\s+\(compatibility version.*$/, ''));
|
|
135
|
+
}
|
|
136
|
+
function listMachORpaths(binaryPath) {
|
|
137
|
+
const output = execFileSync('otool', ['-l', binaryPath], {
|
|
138
|
+
encoding: 'utf-8',
|
|
139
|
+
stdio: 'pipe',
|
|
140
|
+
});
|
|
141
|
+
const lines = output.split(/\r?\n/);
|
|
142
|
+
const rpaths = [];
|
|
143
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
144
|
+
if (lines[index]?.trim() !== 'cmd LC_RPATH')
|
|
145
|
+
continue;
|
|
146
|
+
for (let lookahead = index + 1; lookahead < lines.length; lookahead += 1) {
|
|
147
|
+
const trimmed = lines[lookahead]?.trim() ?? '';
|
|
148
|
+
if (trimmed.startsWith('path ')) {
|
|
149
|
+
const match = trimmed.match(/^path\s+(.+?)\s+\(offset \d+\)$/);
|
|
150
|
+
if (match?.[1]) {
|
|
151
|
+
rpaths.push(match[1]);
|
|
152
|
+
}
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
if (trimmed.startsWith('cmd ')) {
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return rpaths;
|
|
161
|
+
}
|
|
162
|
+
function resolveMachOReferencePath(reference, binaryPath, executableDir, visited = new Set()) {
|
|
163
|
+
if (reference.startsWith('/')) {
|
|
164
|
+
return existsSync(reference) ? realpathSync(reference) : null;
|
|
165
|
+
}
|
|
166
|
+
if (reference.startsWith('@loader_path/')) {
|
|
167
|
+
const candidate = resolve(dirname(binaryPath), reference.slice('@loader_path/'.length));
|
|
168
|
+
return existsSync(candidate) ? realpathSync(candidate) : null;
|
|
169
|
+
}
|
|
170
|
+
if (reference.startsWith('@executable_path/')) {
|
|
171
|
+
const candidate = resolve(executableDir, reference.slice('@executable_path/'.length));
|
|
172
|
+
return existsSync(candidate) ? realpathSync(candidate) : null;
|
|
173
|
+
}
|
|
174
|
+
if (reference.startsWith('@rpath/')) {
|
|
175
|
+
const key = `${binaryPath}::${reference}`;
|
|
176
|
+
if (visited.has(key))
|
|
177
|
+
return null;
|
|
178
|
+
visited.add(key);
|
|
179
|
+
const suffix = reference.slice('@rpath/'.length);
|
|
180
|
+
for (const rpath of listMachORpaths(binaryPath)) {
|
|
181
|
+
const resolvedRpath = resolveMachOReferencePath(rpath, binaryPath, executableDir, visited);
|
|
182
|
+
if (!resolvedRpath)
|
|
183
|
+
continue;
|
|
184
|
+
const candidate = join(resolvedRpath, suffix);
|
|
185
|
+
if (existsSync(candidate)) {
|
|
186
|
+
return realpathSync(candidate);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
function collectMacPortableLibraries(entryBinaryPath) {
|
|
193
|
+
const executablePath = realpathSync(entryBinaryPath);
|
|
194
|
+
const executableDir = dirname(executablePath);
|
|
195
|
+
const libraries = new Map();
|
|
196
|
+
const queue = [executablePath];
|
|
197
|
+
while (queue.length > 0) {
|
|
198
|
+
const current = queue.shift();
|
|
199
|
+
if (!current)
|
|
200
|
+
continue;
|
|
201
|
+
for (const dependency of listMachODependencies(current)) {
|
|
202
|
+
const resolvedDependency = resolveMachOReferencePath(dependency, current, executableDir);
|
|
203
|
+
if (!resolvedDependency || isMacSystemLibraryPath(resolvedDependency)) {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (resolvedDependency === executablePath) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const destinationName = basename(resolvedDependency);
|
|
210
|
+
const existingSource = [...libraries.entries()]
|
|
211
|
+
.find(([, existingDestination]) => existingDestination === destinationName)?.[0];
|
|
212
|
+
if (existingSource && existingSource !== resolvedDependency) {
|
|
213
|
+
throw new Error(`Portable macOS packaging found two libraries with the same filename: ${existingSource} and ${resolvedDependency}.`);
|
|
214
|
+
}
|
|
215
|
+
if (libraries.has(resolvedDependency))
|
|
216
|
+
continue;
|
|
217
|
+
libraries.set(resolvedDependency, destinationName);
|
|
218
|
+
queue.push(resolvedDependency);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return libraries;
|
|
222
|
+
}
|
|
223
|
+
function rewriteMacPortableBinary(targetPath, sourcePath, libraries, mode) {
|
|
224
|
+
const executableDir = dirname(realpathSync(process.execPath));
|
|
225
|
+
const args = [];
|
|
226
|
+
if (mode === 'library') {
|
|
227
|
+
args.push('-id', `@loader_path/${basename(targetPath)}`);
|
|
228
|
+
}
|
|
229
|
+
for (const dependency of listMachODependencies(sourcePath)) {
|
|
230
|
+
const resolvedDependency = resolveMachOReferencePath(dependency, sourcePath, executableDir);
|
|
231
|
+
if (!resolvedDependency || isMacSystemLibraryPath(resolvedDependency)) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
const destinationName = libraries.get(resolvedDependency);
|
|
235
|
+
if (!destinationName)
|
|
236
|
+
continue;
|
|
237
|
+
const rewrittenDependency = mode === 'executable'
|
|
238
|
+
? `@loader_path/../Frameworks/${destinationName}`
|
|
239
|
+
: `@loader_path/${destinationName}`;
|
|
240
|
+
if (dependency !== rewrittenDependency) {
|
|
241
|
+
args.push('-change', dependency, rewrittenDependency);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (args.length === 0)
|
|
245
|
+
return;
|
|
246
|
+
execFileSync('install_name_tool', [...args, targetPath], {
|
|
247
|
+
stdio: 'pipe',
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
function signMacPortableBundle(bundlePath) {
|
|
251
|
+
if (!commandExists('codesign'))
|
|
252
|
+
return;
|
|
253
|
+
execFileSync('codesign', ['--force', '--deep', '--sign', '-', bundlePath], {
|
|
254
|
+
stdio: 'pipe',
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
function copyPortableNodeRuntime(embeddedNodePath, options = {}) {
|
|
258
|
+
const sourceNodePath = realpathSync(process.execPath);
|
|
259
|
+
copyFileSync(sourceNodePath, embeddedNodePath);
|
|
260
|
+
chmodSync(embeddedNodePath, 0o755);
|
|
261
|
+
if (process.platform === 'darwin') {
|
|
262
|
+
const frameworksDir = options.macFrameworksDir;
|
|
263
|
+
if (!frameworksDir) {
|
|
264
|
+
throw new Error('macFrameworksDir is required when creating a macOS portable artifact.');
|
|
265
|
+
}
|
|
266
|
+
mkdirSync(frameworksDir, { recursive: true });
|
|
267
|
+
const libraries = collectMacPortableLibraries(sourceNodePath);
|
|
268
|
+
for (const [sourcePath, destinationName] of libraries) {
|
|
269
|
+
const targetPath = join(frameworksDir, destinationName);
|
|
270
|
+
copyFileSync(sourcePath, targetPath);
|
|
271
|
+
chmodSync(targetPath, 0o755);
|
|
272
|
+
}
|
|
273
|
+
rewriteMacPortableBinary(embeddedNodePath, sourceNodePath, libraries, 'executable');
|
|
274
|
+
for (const [sourcePath, destinationName] of libraries) {
|
|
275
|
+
rewriteMacPortableBinary(join(frameworksDir, destinationName), sourcePath, libraries, 'library');
|
|
276
|
+
}
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (process.platform === 'win32') {
|
|
280
|
+
const sourceDir = dirname(sourceNodePath);
|
|
281
|
+
for (const entry of readdirSync(sourceDir)) {
|
|
282
|
+
if (!/\.dll$/i.test(entry))
|
|
283
|
+
continue;
|
|
284
|
+
copyFileSync(join(sourceDir, entry), join(dirname(embeddedNodePath), entry));
|
|
285
|
+
}
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (process.platform === 'linux') {
|
|
289
|
+
const portableLibDir = options.portableLibDir;
|
|
290
|
+
if (!portableLibDir || !commandExists('ldd')) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
mkdirSync(portableLibDir, { recursive: true });
|
|
294
|
+
const output = execFileSync('ldd', [sourceNodePath], {
|
|
295
|
+
encoding: 'utf-8',
|
|
296
|
+
stdio: 'pipe',
|
|
297
|
+
});
|
|
298
|
+
const copiedLibraries = new Set();
|
|
299
|
+
for (const line of output.split(/\r?\n/)) {
|
|
300
|
+
const match = line.match(/=>\s+(\/[^ ]+)/) ?? line.match(/^\s*(\/[^ ]+)/);
|
|
301
|
+
const libraryPath = match?.[1];
|
|
302
|
+
if (!libraryPath || !existsSync(libraryPath))
|
|
303
|
+
continue;
|
|
304
|
+
if (libraryPath.startsWith('/lib/') || libraryPath.startsWith('/usr/lib/')) {
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
const resolvedPath = realpathSync(libraryPath);
|
|
308
|
+
const destinationName = basename(resolvedPath);
|
|
309
|
+
if (copiedLibraries.has(destinationName))
|
|
310
|
+
continue;
|
|
311
|
+
copiedLibraries.add(destinationName);
|
|
312
|
+
copyFileSync(resolvedPath, join(portableLibDir, destinationName));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
export function createArchiveFromPortableArtifact(sourcePath, archivePath) {
|
|
317
|
+
rmSync(archivePath, { force: true, recursive: true });
|
|
318
|
+
mkdirSync(dirname(archivePath), { recursive: true });
|
|
319
|
+
if (process.platform === 'darwin') {
|
|
320
|
+
execFileSync('ditto', ['-c', '-k', '--keepParent', basename(sourcePath), archivePath], {
|
|
321
|
+
cwd: dirname(sourcePath),
|
|
322
|
+
stdio: 'pipe',
|
|
323
|
+
});
|
|
324
|
+
return 'zip';
|
|
325
|
+
}
|
|
326
|
+
if (process.platform === 'win32') {
|
|
327
|
+
execFileSync('powershell', [
|
|
328
|
+
'-NoProfile',
|
|
329
|
+
'-Command',
|
|
330
|
+
`Compress-Archive -Path '${sourcePath.replace(/'/g, "''")}' -DestinationPath '${archivePath.replace(/'/g, "''")}' -Force`,
|
|
331
|
+
], {
|
|
332
|
+
stdio: 'pipe',
|
|
333
|
+
});
|
|
334
|
+
return 'zip';
|
|
335
|
+
}
|
|
336
|
+
execFileSync('tar', ['-czf', archivePath, '-C', dirname(sourcePath), basename(sourcePath)], {
|
|
337
|
+
stdio: 'pipe',
|
|
338
|
+
});
|
|
339
|
+
return 'tar.gz';
|
|
340
|
+
}
|
|
341
|
+
function finalizePackWrangler(projectDir, outputDir) {
|
|
342
|
+
const configFile = EDGEBASE_CONFIG_FILES
|
|
343
|
+
.map((name) => join(projectDir, name))
|
|
344
|
+
.find((path) => existsSync(path));
|
|
345
|
+
if (!configFile) {
|
|
346
|
+
throw new Error(`No EdgeBase config file found in ${projectDir}.`);
|
|
347
|
+
}
|
|
348
|
+
const config = loadConfigSafe(configFile, projectDir, {
|
|
349
|
+
allowRegexFallback: false,
|
|
350
|
+
});
|
|
351
|
+
const wranglerPath = join(outputDir, 'wrangler.toml');
|
|
352
|
+
const generatedPath = generateTempWranglerToml(wranglerPath, {
|
|
353
|
+
bindings: resolveLocalDevBindings(config),
|
|
354
|
+
triggerMode: 'preserve',
|
|
355
|
+
});
|
|
356
|
+
if (!generatedPath)
|
|
357
|
+
return;
|
|
358
|
+
writeFileSync(wranglerPath, readFileSync(generatedPath, 'utf-8'), 'utf-8');
|
|
359
|
+
rmSync(generatedPath, { force: true });
|
|
360
|
+
}
|
|
361
|
+
function buildLauncherSource(manifest) {
|
|
362
|
+
return `#!/usr/bin/env node
|
|
363
|
+
import { createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
364
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
365
|
+
import { createServer } from 'node:net';
|
|
366
|
+
import { homedir } from 'node:os';
|
|
367
|
+
import { dirname, join, resolve } from 'node:path';
|
|
368
|
+
import { fileURLToPath } from 'node:url';
|
|
369
|
+
|
|
370
|
+
const DEFAULT_HOST = ${JSON.stringify(manifest.launcher.defaultHost)};
|
|
371
|
+
const DEFAULT_PORT = ${manifest.launcher.defaultPort};
|
|
372
|
+
const DEFAULT_OPEN_PATH = ${JSON.stringify(manifest.launcher.defaultOpenPath)};
|
|
373
|
+
const APP_DATA_DIR_NAME = ${JSON.stringify(manifest.launcher.appDataDirName)};
|
|
374
|
+
const DEFAULT_STATE_DIR = ${JSON.stringify(manifest.launcher.stateDir)};
|
|
375
|
+
const DEFAULT_RUNTIME_DIR = ${JSON.stringify(manifest.launcher.runtimeDir)};
|
|
376
|
+
const SINGLE_INSTANCE = ${manifest.launcher.singleInstance ? 'true' : 'false'};
|
|
377
|
+
const PORT_SEARCH_LIMIT = ${manifest.launcher.portSearchLimit};
|
|
378
|
+
|
|
379
|
+
function parseArgs(argv) {
|
|
380
|
+
const options = {
|
|
381
|
+
host: process.env.HOST || process.env.EDGEBASE_HOST || DEFAULT_HOST,
|
|
382
|
+
port: process.env.PORT || process.env.EDGEBASE_PORT || '',
|
|
383
|
+
dataDir: process.env.EDGEBASE_DATA_DIR || '',
|
|
384
|
+
persistTo: process.env.PERSIST_DIR || '',
|
|
385
|
+
open: process.env.EDGEBASE_OPEN === '1' || process.env.EDGEBASE_OPEN === 'true',
|
|
386
|
+
dryRun: false,
|
|
387
|
+
json: false,
|
|
388
|
+
envFile: process.env.EDGEBASE_ENV_FILE || '',
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
392
|
+
const arg = argv[index];
|
|
393
|
+
const next = argv[index + 1];
|
|
394
|
+
|
|
395
|
+
if (arg === '--host' && next) {
|
|
396
|
+
options.host = next;
|
|
397
|
+
index += 1;
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
if (arg.startsWith('--host=')) {
|
|
401
|
+
options.host = arg.slice('--host='.length);
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
if (arg === '--port' && next) {
|
|
405
|
+
options.port = next;
|
|
406
|
+
index += 1;
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
if (arg.startsWith('--port=')) {
|
|
410
|
+
options.port = arg.slice('--port='.length);
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
if (arg === '--persist-to' && next) {
|
|
414
|
+
options.persistTo = next;
|
|
415
|
+
index += 1;
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
if (arg.startsWith('--persist-to=')) {
|
|
419
|
+
options.persistTo = arg.slice('--persist-to='.length);
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
if (arg === '--data-dir' && next) {
|
|
423
|
+
options.dataDir = next;
|
|
424
|
+
index += 1;
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
if (arg.startsWith('--data-dir=')) {
|
|
428
|
+
options.dataDir = arg.slice('--data-dir='.length);
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
if (arg === '--env-file' && next) {
|
|
432
|
+
options.envFile = next;
|
|
433
|
+
index += 1;
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
if (arg.startsWith('--env-file=')) {
|
|
437
|
+
options.envFile = arg.slice('--env-file='.length);
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
if (arg === '--open') {
|
|
441
|
+
options.open = true;
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
if (arg === '--dry-run') {
|
|
445
|
+
options.dryRun = true;
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
if (arg === '--json') {
|
|
449
|
+
options.json = true;
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return options;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function readJson(filePath) {
|
|
458
|
+
if (!existsSync(filePath)) return null;
|
|
459
|
+
try {
|
|
460
|
+
return JSON.parse(readFileSync(filePath, 'utf-8'));
|
|
461
|
+
} catch {
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function parseEnvFile(filePath) {
|
|
467
|
+
if (!filePath || !existsSync(filePath)) return {};
|
|
468
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
469
|
+
const values = {};
|
|
470
|
+
|
|
471
|
+
for (const rawLine of content.split(/\\r?\\n/)) {
|
|
472
|
+
const line = rawLine.trim();
|
|
473
|
+
if (!line || line.startsWith('#')) continue;
|
|
474
|
+
const separator = line.indexOf('=');
|
|
475
|
+
if (separator <= 0) continue;
|
|
476
|
+
const key = line.slice(0, separator).trim();
|
|
477
|
+
let value = line.slice(separator + 1).trim();
|
|
478
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
479
|
+
value = value.slice(1, -1);
|
|
480
|
+
}
|
|
481
|
+
values[key] = value;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return values;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function serializeEnvValue(value) {
|
|
488
|
+
return /^[A-Za-z0-9_./:@+-]+$/.test(value) ? value : JSON.stringify(value);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function saveJson(filePath, payload) {
|
|
492
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
493
|
+
writeFileSync(filePath, JSON.stringify(payload, null, 2) + '\\n', 'utf-8');
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function removeFileIfExists(filePath) {
|
|
497
|
+
try {
|
|
498
|
+
rmSync(filePath, { force: true });
|
|
499
|
+
} catch {
|
|
500
|
+
// best-effort cleanup only
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function isProcessAlive(pid) {
|
|
505
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
506
|
+
try {
|
|
507
|
+
process.kill(pid, 0);
|
|
508
|
+
return true;
|
|
509
|
+
} catch {
|
|
510
|
+
return false;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function terminateProcessTree(pid) {
|
|
515
|
+
if (!Number.isInteger(pid) || pid <= 0) return;
|
|
516
|
+
|
|
517
|
+
if (process.platform === 'win32') {
|
|
518
|
+
spawnSync('taskkill', ['/pid', String(pid), '/t', '/f'], { stdio: 'ignore' });
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
try {
|
|
523
|
+
process.kill(pid, 'SIGTERM');
|
|
524
|
+
} catch {
|
|
525
|
+
// best-effort cleanup only
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function resolveDataRoot(dataDir) {
|
|
530
|
+
if (dataDir) {
|
|
531
|
+
return resolve(process.cwd(), dataDir);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (process.platform === 'darwin') {
|
|
535
|
+
return resolve(homedir(), 'Library', 'Application Support', APP_DATA_DIR_NAME);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (process.platform === 'win32') {
|
|
539
|
+
const base = process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local');
|
|
540
|
+
return resolve(base, APP_DATA_DIR_NAME);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const base = process.env.XDG_DATA_HOME || join(homedir(), '.local', 'share');
|
|
544
|
+
return resolve(base, APP_DATA_DIR_NAME);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
async function probePort(port, host) {
|
|
548
|
+
return new Promise((resolvePromise) => {
|
|
549
|
+
const server = createServer();
|
|
550
|
+
server.once('error', () => resolvePromise(false));
|
|
551
|
+
server.once('listening', () => {
|
|
552
|
+
server.close(() => resolvePromise(true));
|
|
553
|
+
});
|
|
554
|
+
server.listen({ port, host, exclusive: true });
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
async function resolveSelectedPort(host, explicitPort, statePath) {
|
|
559
|
+
if (explicitPort) {
|
|
560
|
+
return {
|
|
561
|
+
port: Number.parseInt(String(explicitPort), 10),
|
|
562
|
+
source: 'explicit',
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const savedState = readJson(statePath);
|
|
567
|
+
const preferredPort = typeof savedState?.port === 'number'
|
|
568
|
+
? savedState.port
|
|
569
|
+
: DEFAULT_PORT;
|
|
570
|
+
|
|
571
|
+
for (let offset = 0; offset < PORT_SEARCH_LIMIT; offset += 1) {
|
|
572
|
+
const candidate = preferredPort + offset;
|
|
573
|
+
if (await probePort(candidate, host)) {
|
|
574
|
+
return {
|
|
575
|
+
port: candidate,
|
|
576
|
+
source: candidate === preferredPort
|
|
577
|
+
? (typeof savedState?.port === 'number' ? 'saved' : 'default')
|
|
578
|
+
: 'fallback',
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
throw new Error(
|
|
584
|
+
\`Could not find an available port in the range \${preferredPort}-\${preferredPort + PORT_SEARCH_LIMIT - 1}.\`,
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function readActiveInstance(lockPath, host) {
|
|
589
|
+
if (!SINGLE_INSTANCE) return null;
|
|
590
|
+
|
|
591
|
+
const lock = readJson(lockPath);
|
|
592
|
+
if (!lock || typeof lock.port !== 'number' || typeof lock.pid !== 'number') {
|
|
593
|
+
removeFileIfExists(lockPath);
|
|
594
|
+
return null;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (typeof lock.host !== 'string' || lock.host !== host) {
|
|
598
|
+
return null;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (!isProcessAlive(lock.pid)) {
|
|
602
|
+
removeFileIfExists(lockPath);
|
|
603
|
+
return null;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
return lock;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function findWranglerPackageJson(runtimeNodeModules) {
|
|
610
|
+
const direct = join(runtimeNodeModules, 'wrangler', 'package.json');
|
|
611
|
+
if (existsSync(direct)) return direct;
|
|
612
|
+
|
|
613
|
+
const pnpmDir = join(runtimeNodeModules, '.pnpm');
|
|
614
|
+
if (existsSync(pnpmDir)) {
|
|
615
|
+
const candidate = readdirSync(pnpmDir)
|
|
616
|
+
.filter((entry) => entry.startsWith('wrangler@'))
|
|
617
|
+
.map((entry) => join(pnpmDir, entry, 'node_modules', 'wrangler', 'package.json'))
|
|
618
|
+
.find((entry) => existsSync(entry));
|
|
619
|
+
if (candidate) return candidate;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
throw new Error('Could not find wrangler/package.json inside the packed runtime.');
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function resolveWranglerBin(runtimeNodeModules) {
|
|
626
|
+
const packageJsonPath = findWranglerPackageJson(runtimeNodeModules);
|
|
627
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
628
|
+
const relBin = typeof packageJson.bin === 'string'
|
|
629
|
+
? packageJson.bin
|
|
630
|
+
: packageJson.bin?.wrangler ?? packageJson.bin?.wrangler2;
|
|
631
|
+
if (!relBin) {
|
|
632
|
+
throw new Error('Could not resolve the packed Wrangler binary.');
|
|
633
|
+
}
|
|
634
|
+
return resolve(dirname(packageJsonPath), relBin);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function openBrowser(url) {
|
|
638
|
+
if (process.platform === 'darwin') {
|
|
639
|
+
spawn('open', [url], { stdio: 'ignore', detached: true }).unref();
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
if (process.platform === 'win32') {
|
|
643
|
+
spawn('cmd', ['/c', 'start', '', url], { stdio: 'ignore', detached: true }).unref();
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
spawn('xdg-open', [url], { stdio: 'ignore', detached: true }).unref();
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const artifactRoot = dirname(fileURLToPath(import.meta.url));
|
|
650
|
+
const runtimeRoot = join(artifactRoot, '.edgebase', 'runtime', 'server');
|
|
651
|
+
const runtimeNodeModules = join(runtimeRoot, 'node_modules');
|
|
652
|
+
const options = parseArgs(process.argv.slice(2));
|
|
653
|
+
const dataRoot = resolveDataRoot(options.dataDir);
|
|
654
|
+
const workDir = join(dataRoot, DEFAULT_RUNTIME_DIR);
|
|
655
|
+
const persistDir = options.persistTo
|
|
656
|
+
? resolve(process.cwd(), options.persistTo)
|
|
657
|
+
: join(dataRoot, DEFAULT_STATE_DIR);
|
|
658
|
+
const statePath = join(dataRoot, 'launcher-state.json');
|
|
659
|
+
const lockPath = join(dataRoot, 'launcher-lock.json');
|
|
660
|
+
const logPath = join(dataRoot, 'launcher.log');
|
|
661
|
+
mkdirSync(dataRoot, { recursive: true });
|
|
662
|
+
mkdirSync(workDir, { recursive: true });
|
|
663
|
+
mkdirSync(persistDir, { recursive: true });
|
|
664
|
+
const logStream = createWriteStream(logPath, { flags: 'a' });
|
|
665
|
+
|
|
666
|
+
const envFileCandidates = [
|
|
667
|
+
join(process.cwd(), '.env'),
|
|
668
|
+
join(process.cwd(), '.env.local'),
|
|
669
|
+
join(artifactRoot, '.env'),
|
|
670
|
+
join(artifactRoot, '.env.local'),
|
|
671
|
+
options.envFile ? resolve(process.cwd(), options.envFile) : '',
|
|
672
|
+
].filter(Boolean);
|
|
673
|
+
const mergedEnv = Object.assign(
|
|
674
|
+
{},
|
|
675
|
+
...envFileCandidates.map((filePath) => parseEnvFile(filePath)),
|
|
676
|
+
Object.fromEntries(Object.entries(process.env).filter(([, value]) => typeof value === 'string')),
|
|
677
|
+
);
|
|
678
|
+
|
|
679
|
+
const devVarsPath = join(workDir, '.dev.vars');
|
|
680
|
+
writeFileSync(
|
|
681
|
+
devVarsPath,
|
|
682
|
+
Object.keys(mergedEnv)
|
|
683
|
+
.sort()
|
|
684
|
+
.map((key) => \`\${key}=\${serializeEnvValue(String(mergedEnv[key]))}\`)
|
|
685
|
+
.join('\\n') + '\\n',
|
|
686
|
+
'utf-8',
|
|
687
|
+
);
|
|
688
|
+
|
|
689
|
+
const wranglerBin = resolveWranglerBin(runtimeNodeModules);
|
|
690
|
+
const existingInstance = readActiveInstance(lockPath, options.host);
|
|
691
|
+
const selectedPort = existingInstance && !options.port
|
|
692
|
+
? { port: existingInstance.port, source: 'existing' }
|
|
693
|
+
: await resolveSelectedPort(options.host, options.port, statePath);
|
|
694
|
+
saveJson(statePath, {
|
|
695
|
+
host: options.host,
|
|
696
|
+
port: selectedPort.port,
|
|
697
|
+
updatedAt: new Date().toISOString(),
|
|
698
|
+
});
|
|
699
|
+
const wranglerArgs = [
|
|
700
|
+
wranglerBin,
|
|
701
|
+
'dev',
|
|
702
|
+
'--config',
|
|
703
|
+
join(artifactRoot, 'wrangler.toml'),
|
|
704
|
+
'--port',
|
|
705
|
+
String(selectedPort.port),
|
|
706
|
+
'--ip',
|
|
707
|
+
options.host,
|
|
708
|
+
'--persist-to',
|
|
709
|
+
persistDir,
|
|
710
|
+
];
|
|
711
|
+
const openUrl = \`http://\${options.host}:\${selectedPort.port}\${DEFAULT_OPEN_PATH}\`;
|
|
712
|
+
|
|
713
|
+
if (options.dryRun) {
|
|
714
|
+
const payload = {
|
|
715
|
+
status: 'success',
|
|
716
|
+
artifactRoot,
|
|
717
|
+
runtimeRoot,
|
|
718
|
+
dataRoot,
|
|
719
|
+
workDir,
|
|
720
|
+
wranglerCommand: process.execPath,
|
|
721
|
+
wranglerBin,
|
|
722
|
+
wranglerArgs,
|
|
723
|
+
host: options.host,
|
|
724
|
+
port: selectedPort.port,
|
|
725
|
+
persistDir,
|
|
726
|
+
devVarsPath,
|
|
727
|
+
statePath,
|
|
728
|
+
lockPath,
|
|
729
|
+
existingInstance: Boolean(existingInstance && !options.port),
|
|
730
|
+
openUrl,
|
|
731
|
+
};
|
|
732
|
+
process.stdout.write(options.json ? JSON.stringify(payload, null, 2) + '\\n' : JSON.stringify(payload) + '\\n');
|
|
733
|
+
process.exit(0);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
if (existingInstance && !options.port) {
|
|
737
|
+
openBrowser(openUrl);
|
|
738
|
+
process.exit(0);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
saveJson(lockPath, {
|
|
742
|
+
pid: process.pid,
|
|
743
|
+
host: options.host,
|
|
744
|
+
port: selectedPort.port,
|
|
745
|
+
createdAt: new Date().toISOString(),
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
const child = spawn(process.execPath, wranglerArgs, {
|
|
749
|
+
cwd: workDir,
|
|
750
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
751
|
+
env: {
|
|
752
|
+
...process.env,
|
|
753
|
+
...mergedEnv,
|
|
754
|
+
},
|
|
755
|
+
detached: process.platform !== 'win32',
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
const shouldMirrorStdIo = Boolean(process.stdout?.isTTY || process.stderr?.isTTY);
|
|
759
|
+
|
|
760
|
+
const relayOutput = (stream, writer) => {
|
|
761
|
+
if (!stream) return;
|
|
762
|
+
stream.on('data', (chunk) => {
|
|
763
|
+
if (shouldMirrorStdIo && writer?.writable) {
|
|
764
|
+
writer.write(chunk);
|
|
765
|
+
}
|
|
766
|
+
logStream.write(chunk);
|
|
767
|
+
});
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
relayOutput(child.stdout, process.stdout);
|
|
771
|
+
relayOutput(child.stderr, process.stderr);
|
|
772
|
+
|
|
773
|
+
const cleanupLock = () => {
|
|
774
|
+
const current = readJson(lockPath);
|
|
775
|
+
if (current?.pid === process.pid) {
|
|
776
|
+
removeFileIfExists(lockPath);
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
|
|
780
|
+
process.on('exit', cleanupLock);
|
|
781
|
+
|
|
782
|
+
if (options.open) {
|
|
783
|
+
setTimeout(() => openBrowser(openUrl), 1500);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
const forward = (signal) => {
|
|
787
|
+
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
788
|
+
if (process.platform === 'win32') {
|
|
789
|
+
terminateProcessTree(child.pid);
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
child.kill(signal);
|
|
793
|
+
};
|
|
794
|
+
|
|
795
|
+
process.on('SIGINT', () => forward('SIGINT'));
|
|
796
|
+
process.on('SIGTERM', () => forward('SIGTERM'));
|
|
797
|
+
|
|
798
|
+
child.on('error', (error) => {
|
|
799
|
+
cleanupLock();
|
|
800
|
+
logStream.write(String(error.stack || error) + '\\n');
|
|
801
|
+
logStream.end();
|
|
802
|
+
process.stderr.write(String(error.stack || error) + '\\n');
|
|
803
|
+
process.exit(1);
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
child.on('exit', (code, signal) => {
|
|
807
|
+
cleanupLock();
|
|
808
|
+
logStream.end();
|
|
809
|
+
if (signal) {
|
|
810
|
+
process.exit(1);
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
process.exit(code ?? 0);
|
|
814
|
+
});
|
|
815
|
+
`;
|
|
816
|
+
}
|
|
817
|
+
function writeLauncherFiles(outputDir, manifest) {
|
|
818
|
+
const launcherPath = join(outputDir, manifest.launcher.entry);
|
|
819
|
+
const runShPath = join(outputDir, manifest.launcher.unix);
|
|
820
|
+
const runCmdPath = join(outputDir, manifest.launcher.windows);
|
|
821
|
+
writeFileSync(launcherPath, buildLauncherSource(manifest), 'utf-8');
|
|
822
|
+
writeFileSync(runShPath, `#!/usr/bin/env bash
|
|
823
|
+
set -euo pipefail
|
|
824
|
+
SCRIPT_DIR="$(cd -- "$(dirname -- "$0")" && pwd)"
|
|
825
|
+
exec node "$SCRIPT_DIR/${manifest.launcher.entry}" "$@"
|
|
826
|
+
`, 'utf-8');
|
|
827
|
+
writeFileSync(runCmdPath, `@echo off
|
|
828
|
+
set SCRIPT_DIR=%~dp0
|
|
829
|
+
node "%SCRIPT_DIR%\\${manifest.launcher.entry}" %*
|
|
830
|
+
`, 'utf-8');
|
|
831
|
+
chmodSync(launcherPath, 0o755);
|
|
832
|
+
chmodSync(runShPath, 0o755);
|
|
833
|
+
}
|
|
834
|
+
function createMacPortableArtifact(outputPath, appName, dirArtifact) {
|
|
835
|
+
const executableName = sanitizeExecutableName(appName);
|
|
836
|
+
const contentsDir = join(outputPath, 'Contents');
|
|
837
|
+
const macOsDir = join(contentsDir, 'MacOS');
|
|
838
|
+
const frameworksDir = join(contentsDir, 'Frameworks');
|
|
839
|
+
const resourcesDir = join(contentsDir, 'Resources');
|
|
840
|
+
const bundledAppDir = join(resourcesDir, 'app');
|
|
841
|
+
const embeddedNodePath = join(macOsDir, 'node');
|
|
842
|
+
const launcherPath = join(macOsDir, executableName);
|
|
843
|
+
rmSync(outputPath, { recursive: true, force: true });
|
|
844
|
+
mkdirSync(macOsDir, { recursive: true });
|
|
845
|
+
mkdirSync(frameworksDir, { recursive: true });
|
|
846
|
+
mkdirSync(resourcesDir, { recursive: true });
|
|
847
|
+
cpSync(dirArtifact.outputDir, bundledAppDir, {
|
|
848
|
+
recursive: true,
|
|
849
|
+
force: true,
|
|
850
|
+
dereference: false,
|
|
851
|
+
verbatimSymlinks: true,
|
|
852
|
+
});
|
|
853
|
+
copyPortableNodeRuntime(embeddedNodePath, {
|
|
854
|
+
macFrameworksDir: frameworksDir,
|
|
855
|
+
});
|
|
856
|
+
writeFileSync(launcherPath, `#!/usr/bin/env bash
|
|
857
|
+
set -euo pipefail
|
|
858
|
+
SCRIPT_DIR="$(cd -- "$(dirname -- "$0")" && pwd)"
|
|
859
|
+
APP_DIR="$(cd -- "$SCRIPT_DIR/../Resources/app" && pwd)"
|
|
860
|
+
export DYLD_LIBRARY_PATH="$SCRIPT_DIR/../Frameworks\${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"
|
|
861
|
+
exec "$SCRIPT_DIR/node" "$APP_DIR/launcher.mjs" "$@"
|
|
862
|
+
`, 'utf-8');
|
|
863
|
+
chmodSync(launcherPath, 0o755);
|
|
864
|
+
writeFileSync(join(contentsDir, 'Info.plist'), `<?xml version="1.0" encoding="UTF-8"?>
|
|
865
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
866
|
+
<plist version="1.0">
|
|
867
|
+
<dict>
|
|
868
|
+
<key>CFBundleDevelopmentRegion</key>
|
|
869
|
+
<string>en</string>
|
|
870
|
+
<key>CFBundleDisplayName</key>
|
|
871
|
+
<string>${appName.replace(/\.app$/i, '')}</string>
|
|
872
|
+
<key>CFBundleExecutable</key>
|
|
873
|
+
<string>${executableName}</string>
|
|
874
|
+
<key>CFBundleIdentifier</key>
|
|
875
|
+
<string>fun.edgebase.${sanitizeExecutableName(dirArtifact.manifest.projectName)}</string>
|
|
876
|
+
<key>CFBundleName</key>
|
|
877
|
+
<string>${appName.replace(/\.app$/i, '')}</string>
|
|
878
|
+
<key>CFBundlePackageType</key>
|
|
879
|
+
<string>APPL</string>
|
|
880
|
+
<key>CFBundleShortVersionString</key>
|
|
881
|
+
<string>0.1.0</string>
|
|
882
|
+
<key>LSMinimumSystemVersion</key>
|
|
883
|
+
<string>13.0</string>
|
|
884
|
+
</dict>
|
|
885
|
+
</plist>
|
|
886
|
+
`, 'utf-8');
|
|
887
|
+
const manifest = buildPortableManifest({
|
|
888
|
+
outputPath,
|
|
889
|
+
bundledAppDir,
|
|
890
|
+
launcherPath,
|
|
891
|
+
embeddedNodePath,
|
|
892
|
+
appManifestPath: join(bundledAppDir, 'edgebase-app.json'),
|
|
893
|
+
packManifestPath: join(bundledAppDir, 'edgebase-pack.json'),
|
|
894
|
+
projectName: dirArtifact.manifest.projectName,
|
|
895
|
+
artifactKind: 'macos-app',
|
|
896
|
+
});
|
|
897
|
+
const manifestPath = join(resourcesDir, 'edgebase-portable.json');
|
|
898
|
+
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf-8');
|
|
899
|
+
signMacPortableBundle(outputPath);
|
|
900
|
+
return {
|
|
901
|
+
format: 'portable',
|
|
902
|
+
projectDir: dirArtifact.projectDir,
|
|
903
|
+
outputPath,
|
|
904
|
+
manifestPath,
|
|
905
|
+
manifest,
|
|
906
|
+
launcherPath,
|
|
907
|
+
bundledAppDir,
|
|
908
|
+
appManifestPath: join(bundledAppDir, 'edgebase-app.json'),
|
|
909
|
+
packManifestPath: join(bundledAppDir, 'edgebase-pack.json'),
|
|
910
|
+
appManifest: dirArtifact.appManifest,
|
|
911
|
+
packManifest: dirArtifact.manifest,
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
function createPortableDirectoryArtifact(outputPath, dirArtifact) {
|
|
915
|
+
const bundledAppDir = join(outputPath, 'app');
|
|
916
|
+
const binDir = join(outputPath, 'bin');
|
|
917
|
+
const embeddedNodeName = process.platform === 'win32' ? 'node.exe' : 'node';
|
|
918
|
+
const embeddedNodePath = join(binDir, embeddedNodeName);
|
|
919
|
+
const launcherName = process.platform === 'win32' ? 'run.cmd' : 'run.sh';
|
|
920
|
+
const launcherPath = join(outputPath, launcherName);
|
|
921
|
+
rmSync(outputPath, { recursive: true, force: true });
|
|
922
|
+
mkdirSync(binDir, { recursive: true });
|
|
923
|
+
cpSync(dirArtifact.outputDir, bundledAppDir, {
|
|
924
|
+
recursive: true,
|
|
925
|
+
force: true,
|
|
926
|
+
dereference: false,
|
|
927
|
+
verbatimSymlinks: true,
|
|
928
|
+
});
|
|
929
|
+
copyPortableNodeRuntime(embeddedNodePath, {
|
|
930
|
+
portableLibDir: process.platform === 'linux' ? join(outputPath, 'lib') : undefined,
|
|
931
|
+
});
|
|
932
|
+
if (process.platform === 'win32') {
|
|
933
|
+
writeFileSync(launcherPath, `@echo off
|
|
934
|
+
set SCRIPT_DIR=%~dp0
|
|
935
|
+
"%SCRIPT_DIR%bin\\node.exe" "%SCRIPT_DIR%app\\launcher.mjs" %*
|
|
936
|
+
`, 'utf-8');
|
|
937
|
+
}
|
|
938
|
+
else {
|
|
939
|
+
const libraryEnvExport = process.platform === 'linux'
|
|
940
|
+
? 'export LD_LIBRARY_PATH="$SCRIPT_DIR/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"\n'
|
|
941
|
+
: '';
|
|
942
|
+
writeFileSync(launcherPath, `#!/usr/bin/env bash
|
|
943
|
+
set -euo pipefail
|
|
944
|
+
SCRIPT_DIR="$(cd -- "$(dirname -- "$0")" && pwd)"
|
|
945
|
+
${libraryEnvExport}exec "$SCRIPT_DIR/bin/${embeddedNodeName}" "$SCRIPT_DIR/app/launcher.mjs" "$@"
|
|
946
|
+
`, 'utf-8');
|
|
947
|
+
chmodSync(launcherPath, 0o755);
|
|
948
|
+
}
|
|
949
|
+
const manifest = buildPortableManifest({
|
|
950
|
+
outputPath,
|
|
951
|
+
bundledAppDir,
|
|
952
|
+
launcherPath,
|
|
953
|
+
embeddedNodePath,
|
|
954
|
+
appManifestPath: join(bundledAppDir, 'edgebase-app.json'),
|
|
955
|
+
packManifestPath: join(bundledAppDir, 'edgebase-pack.json'),
|
|
956
|
+
projectName: dirArtifact.manifest.projectName,
|
|
957
|
+
artifactKind: 'portable-dir',
|
|
958
|
+
});
|
|
959
|
+
const manifestPath = join(outputPath, 'edgebase-portable.json');
|
|
960
|
+
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf-8');
|
|
961
|
+
return {
|
|
962
|
+
format: 'portable',
|
|
963
|
+
projectDir: dirArtifact.projectDir,
|
|
964
|
+
outputPath,
|
|
965
|
+
manifestPath,
|
|
966
|
+
manifest,
|
|
967
|
+
launcherPath,
|
|
968
|
+
bundledAppDir,
|
|
969
|
+
appManifestPath: join(bundledAppDir, 'edgebase-app.json'),
|
|
970
|
+
packManifestPath: join(bundledAppDir, 'edgebase-pack.json'),
|
|
971
|
+
appManifest: dirArtifact.appManifest,
|
|
972
|
+
packManifest: dirArtifact.manifest,
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
export function createDirPackArtifact(projectDir, options = {}) {
|
|
976
|
+
const appBundle = createAppBundle(projectDir, {
|
|
977
|
+
...options,
|
|
978
|
+
portableDependencies: true,
|
|
979
|
+
dependencyProfile: 'portable',
|
|
980
|
+
});
|
|
981
|
+
finalizePackWrangler(projectDir, appBundle.outputDir);
|
|
982
|
+
const manifest = buildPackManifest(appBundle.outputDir, appBundle.manifest);
|
|
983
|
+
const manifestPath = join(appBundle.outputDir, 'edgebase-pack.json');
|
|
984
|
+
writePackManifest(manifestPath, manifest);
|
|
985
|
+
writeLauncherFiles(appBundle.outputDir, manifest);
|
|
986
|
+
return {
|
|
987
|
+
format: 'dir',
|
|
988
|
+
projectDir: appBundle.projectDir,
|
|
989
|
+
outputDir: appBundle.outputDir,
|
|
990
|
+
manifestPath,
|
|
991
|
+
manifest,
|
|
992
|
+
appManifestPath: appBundle.manifestPath,
|
|
993
|
+
appManifest: appBundle.manifest,
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
export function createPortablePackArtifact(projectDir, options = {}) {
|
|
997
|
+
const dirArtifact = createDirPackArtifact(projectDir, {
|
|
998
|
+
outputDir: join('.edgebase', 'targets', 'portable-pack-source'),
|
|
999
|
+
overwrite: true,
|
|
1000
|
+
portableDependencies: true,
|
|
1001
|
+
});
|
|
1002
|
+
const outputPath = resolvePortableOutputPath(projectDir, dirArtifact.manifest.projectName, options.outputDir);
|
|
1003
|
+
const appName = options.appName ?? basename(outputPath);
|
|
1004
|
+
if (process.platform === 'darwin') {
|
|
1005
|
+
return createMacPortableArtifact(outputPath, appName, dirArtifact);
|
|
1006
|
+
}
|
|
1007
|
+
return createPortableDirectoryArtifact(outputPath, dirArtifact);
|
|
1008
|
+
}
|
|
1009
|
+
export function createArchivePackArtifact(projectDir, options = {}) {
|
|
1010
|
+
const archiveSourceName = process.platform === 'darwin'
|
|
1011
|
+
? `${sanitizeExecutableName(options.appName ?? basename(projectDir))}.app`
|
|
1012
|
+
: `${sanitizeExecutableName(options.appName ?? basename(projectDir))}-${process.platform}-${process.arch}-portable`;
|
|
1013
|
+
const sourcePortablePath = resolvePortableOutputPath(projectDir, sanitizeExecutableName(options.appName ?? basename(projectDir)), join('.edgebase', 'targets', archiveSourceName));
|
|
1014
|
+
const portableArtifact = createPortablePackArtifact(projectDir, {
|
|
1015
|
+
outputDir: sourcePortablePath,
|
|
1016
|
+
appName: options.appName,
|
|
1017
|
+
});
|
|
1018
|
+
const outputPath = resolveArchiveOutputPath(projectDir, portableArtifact.packManifest.projectName, options.outputDir);
|
|
1019
|
+
const archiveType = createArchiveFromPortableArtifact(portableArtifact.outputPath, outputPath);
|
|
1020
|
+
const manifest = buildArchiveManifest({
|
|
1021
|
+
outputPath,
|
|
1022
|
+
sourcePortablePath: portableArtifact.outputPath,
|
|
1023
|
+
launcherPath: portableArtifact.launcherPath,
|
|
1024
|
+
embeddedNodePath: portableArtifact.manifest.embeddedNodePath,
|
|
1025
|
+
appManifestPath: portableArtifact.appManifestPath,
|
|
1026
|
+
packManifestPath: portableArtifact.packManifestPath,
|
|
1027
|
+
projectName: portableArtifact.packManifest.projectName,
|
|
1028
|
+
archiveType,
|
|
1029
|
+
});
|
|
1030
|
+
return {
|
|
1031
|
+
format: 'archive',
|
|
1032
|
+
projectDir: portableArtifact.projectDir,
|
|
1033
|
+
outputPath,
|
|
1034
|
+
manifest,
|
|
1035
|
+
sourcePortablePath: portableArtifact.outputPath,
|
|
1036
|
+
launcherPath: portableArtifact.launcherPath,
|
|
1037
|
+
bundledAppDir: portableArtifact.bundledAppDir,
|
|
1038
|
+
appManifestPath: portableArtifact.appManifestPath,
|
|
1039
|
+
packManifestPath: portableArtifact.packManifestPath,
|
|
1040
|
+
appManifest: portableArtifact.appManifest,
|
|
1041
|
+
packManifest: portableArtifact.packManifest,
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
function writePackManifest(path, manifest) {
|
|
1045
|
+
writeFileSync(path, `${JSON.stringify(manifest, null, 2)}\n`, 'utf-8');
|
|
1046
|
+
}
|
|
1047
|
+
//# sourceMappingURL=pack.js.map
|