hagiscript-sdk 0.2.9-dev.1780142424.1.dedbfb7
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/README.md +25 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/package.json +38 -0
- package/dist/src/index.d.ts +24 -0
- package/dist/src/index.js +27 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/runtime/command-launch.d.ts +41 -0
- package/dist/src/runtime/command-launch.js +100 -0
- package/dist/src/runtime/command-launch.js.map +1 -0
- package/dist/src/runtime/component-service-manager.d.ts +71 -0
- package/dist/src/runtime/component-service-manager.js +367 -0
- package/dist/src/runtime/component-service-manager.js.map +1 -0
- package/dist/src/runtime/dotnet-installer.d.ts +39 -0
- package/dist/src/runtime/dotnet-installer.js +282 -0
- package/dist/src/runtime/dotnet-installer.js.map +1 -0
- package/dist/src/runtime/download-cache.d.ts +7 -0
- package/dist/src/runtime/download-cache.js +110 -0
- package/dist/src/runtime/download-cache.js.map +1 -0
- package/dist/src/runtime/manifest-manager.d.ts +57 -0
- package/dist/src/runtime/manifest-manager.js +188 -0
- package/dist/src/runtime/manifest-manager.js.map +1 -0
- package/dist/src/runtime/node-download.d.ts +13 -0
- package/dist/src/runtime/node-download.js +78 -0
- package/dist/src/runtime/node-download.js.map +1 -0
- package/dist/src/runtime/node-extract.d.ts +18 -0
- package/dist/src/runtime/node-extract.js +170 -0
- package/dist/src/runtime/node-extract.js.map +1 -0
- package/dist/src/runtime/node-installer.d.ts +43 -0
- package/dist/src/runtime/node-installer.js +114 -0
- package/dist/src/runtime/node-installer.js.map +1 -0
- package/dist/src/runtime/node-platform.d.ts +15 -0
- package/dist/src/runtime/node-platform.js +51 -0
- package/dist/src/runtime/node-platform.js.map +1 -0
- package/dist/src/runtime/node-release.d.ts +28 -0
- package/dist/src/runtime/node-release.js +149 -0
- package/dist/src/runtime/node-release.js.map +1 -0
- package/dist/src/runtime/node-verify.d.ts +30 -0
- package/dist/src/runtime/node-verify.js +102 -0
- package/dist/src/runtime/node-verify.js.map +1 -0
- package/dist/src/runtime/npm-global.d.ts +28 -0
- package/dist/src/runtime/npm-global.js +77 -0
- package/dist/src/runtime/npm-global.js.map +1 -0
- package/dist/src/runtime/npm-sync.d.ts +164 -0
- package/dist/src/runtime/npm-sync.js +603 -0
- package/dist/src/runtime/npm-sync.js.map +1 -0
- package/dist/src/runtime/pm2-manager.d.ts +102 -0
- package/dist/src/runtime/pm2-manager.js +715 -0
- package/dist/src/runtime/pm2-manager.js.map +1 -0
- package/dist/src/runtime/runtime-executor.d.ts +58 -0
- package/dist/src/runtime/runtime-executor.js +291 -0
- package/dist/src/runtime/runtime-executor.js.map +1 -0
- package/dist/src/runtime/runtime-manager.d.ts +92 -0
- package/dist/src/runtime/runtime-manager.js +798 -0
- package/dist/src/runtime/runtime-manager.js.map +1 -0
- package/dist/src/runtime/runtime-manifest.d.ts +106 -0
- package/dist/src/runtime/runtime-manifest.js +450 -0
- package/dist/src/runtime/runtime-manifest.js.map +1 -0
- package/dist/src/runtime/runtime-paths.d.ts +45 -0
- package/dist/src/runtime/runtime-paths.js +138 -0
- package/dist/src/runtime/runtime-paths.js.map +1 -0
- package/dist/src/runtime/runtime-state.d.ts +45 -0
- package/dist/src/runtime/runtime-state.js +82 -0
- package/dist/src/runtime/runtime-state.js.map +1 -0
- package/dist/src/runtime/server-config.d.ts +21 -0
- package/dist/src/runtime/server-config.js +169 -0
- package/dist/src/runtime/server-config.js.map +1 -0
- package/dist/src/runtime/server-manager.d.ts +107 -0
- package/dist/src/runtime/server-manager.js +946 -0
- package/dist/src/runtime/server-manager.js.map +1 -0
- package/dist/src/runtime/server-version-state.d.ts +43 -0
- package/dist/src/runtime/server-version-state.js +156 -0
- package/dist/src/runtime/server-version-state.js.map +1 -0
- package/dist/src/runtime/seven-zip-extract.d.ts +15 -0
- package/dist/src/runtime/seven-zip-extract.js +104 -0
- package/dist/src/runtime/seven-zip-extract.js.map +1 -0
- package/dist/src/runtime/tool-sync-catalog.config.json +59 -0
- package/dist/src/runtime/tool-sync-catalog.d.ts +48 -0
- package/dist/src/runtime/tool-sync-catalog.js +189 -0
- package/dist/src/runtime/tool-sync-catalog.js.map +1 -0
- package/dist/src/runtime/zip-extract.d.ts +6 -0
- package/dist/src/runtime/zip-extract.js +90 -0
- package/dist/src/runtime/zip-extract.js.map +1 -0
- package/dist/src/version.d.ts +7 -0
- package/dist/src/version.js +23 -0
- package/dist/src/version.js.map +1 -0
- package/package.json +38 -0
|
@@ -0,0 +1,946 @@
|
|
|
1
|
+
import { cp, mkdtemp, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
import semver from "semver";
|
|
7
|
+
import { parse as parseYaml } from "yaml";
|
|
8
|
+
import { copyFileFromCache, isDownloadCacheEnabled, resolveDownloadCacheDirectory, storeFileInCache } from "./download-cache.js";
|
|
9
|
+
import { runCommand } from "./command-launch.js";
|
|
10
|
+
import { resolveManagedPm2Environment, runManagedPm2Command, supportedPm2Services } from "./pm2-manager.js";
|
|
11
|
+
import { loadRuntimeManifest } from "./runtime-manifest.js";
|
|
12
|
+
import { getComponentConfigDirectory, getServerSharedDataRoot, getServerVersionRoot, resolveRuntimePaths } from "./runtime-paths.js";
|
|
13
|
+
import { ensureManagedPm2Package, installRuntime, queryRuntimeState, } from "./runtime-manager.js";
|
|
14
|
+
import { getManagedServerConfig } from "./server-config.js";
|
|
15
|
+
import { listManagedServerVersions as listManagedServerVersionSummaries, readManagedServerVersionState, registerManagedServerVersion, removeManagedServerVersion as removeInstalledManagedServerVersion, resolveManagedServerVersionStateContext, setActiveManagedServerVersion } from "./server-version-state.js";
|
|
16
|
+
import { extractZipArchive } from "./zip-extract.js";
|
|
17
|
+
export class ManagedServerError extends Error {
|
|
18
|
+
constructor(message, options) {
|
|
19
|
+
super(message, options);
|
|
20
|
+
this.name = "ManagedServerError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const MANAGED_SERVER_REMOVE_RETRY_COUNT = 30;
|
|
24
|
+
const MANAGED_SERVER_REMOVE_RETRY_DELAY_MS = 1_000;
|
|
25
|
+
const DEFAULT_GITHUB_REPOSITORY = "HagiCode-org/releases";
|
|
26
|
+
const DEFAULT_GITHUB_TAG = "latest";
|
|
27
|
+
const DEFAULT_SERVER_HTTP_INDEX_URL = "https://index.hagicode.com/server/index.json";
|
|
28
|
+
const LEGACY_SERVER_HTTP_INDEX_URLS = new Set([
|
|
29
|
+
"https://server.dl.hagicode.com/index.json"
|
|
30
|
+
]);
|
|
31
|
+
const DEFAULT_CODE_SERVER_HOST = "127.0.0.1";
|
|
32
|
+
const DEFAULT_CODE_SERVER_PORT = 8080;
|
|
33
|
+
const DEFAULT_CODE_SERVER_AUTH_MODE = "none";
|
|
34
|
+
const DEFAULT_OMNIROUTE_HOST = "127.0.0.1";
|
|
35
|
+
const DEFAULT_OMNIROUTE_PORT = 39001;
|
|
36
|
+
const MANAGED_SERVER_INTEGRATION_DEPENDENCIES = [
|
|
37
|
+
"code-server",
|
|
38
|
+
"omniroute"
|
|
39
|
+
];
|
|
40
|
+
const VSCODE_SERVER_SOURCE_EXTERNAL = "external";
|
|
41
|
+
const VSCODE_SERVER_SECRET_SOURCE_BOOTSTRAP = "bootstrap";
|
|
42
|
+
const OMNIROUTE_SOURCE_EXTERNAL = "external";
|
|
43
|
+
export async function installManagedServer(options = {}) {
|
|
44
|
+
const logger = options.logger ?? (() => undefined);
|
|
45
|
+
const runner = options.runner ?? runCommand;
|
|
46
|
+
const manifest = await loadRuntimeManifest({ manifestPath: options.manifestPath });
|
|
47
|
+
const resolvedInstallOptions = applyManifestServerInstallDefaults(manifest, options);
|
|
48
|
+
const paths = resolveRuntimePaths(manifest, { runtimeRoot: options.runtimeRoot });
|
|
49
|
+
const serverComponent = assertServerComponent(manifest.componentMap.get("server"));
|
|
50
|
+
const archive = await resolveServerArchive(resolvedInstallOptions, logger);
|
|
51
|
+
const installedVersion = resolveManagedServerVersion(archive);
|
|
52
|
+
try {
|
|
53
|
+
const staged = await stageServerArchive({
|
|
54
|
+
archivePath: archive.archivePath,
|
|
55
|
+
version: installedVersion,
|
|
56
|
+
paths,
|
|
57
|
+
runner,
|
|
58
|
+
serverComponent,
|
|
59
|
+
force: options.force ?? false,
|
|
60
|
+
extractArchive: options.extractArchive
|
|
61
|
+
});
|
|
62
|
+
const installRuntimeFn = options.installRuntimeFn ?? installRuntime;
|
|
63
|
+
const runtimeDependencyComponents = [...serverComponent.lifecycleDependencies];
|
|
64
|
+
const runtimeLifecycle = await installRuntimeFn({
|
|
65
|
+
manifestPath: options.manifestPath,
|
|
66
|
+
runtimeRoot: options.runtimeRoot,
|
|
67
|
+
components: runtimeDependencyComponents,
|
|
68
|
+
force: options.force ?? false,
|
|
69
|
+
downloadCache: options.downloadCache,
|
|
70
|
+
downloadCacheDir: options.downloadCacheDir,
|
|
71
|
+
npmRegistryMirror: options.registryMirror,
|
|
72
|
+
pm2VersionOverride: options.pm2Version,
|
|
73
|
+
logger
|
|
74
|
+
});
|
|
75
|
+
if (options.ensurePm2 ?? true) {
|
|
76
|
+
await ensureManagedPm2Package(manifest, paths, {
|
|
77
|
+
npmRegistryMirror: options.registryMirror,
|
|
78
|
+
pm2VersionOverride: options.pm2Version
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
const versionStateContext = await resolveManagedServerVersionStateContext({
|
|
82
|
+
manifestPath: options.manifestPath,
|
|
83
|
+
runtimeRoot: options.runtimeRoot
|
|
84
|
+
});
|
|
85
|
+
const sharedDataRoot = getServerSharedDataRoot(versionStateContext.paths);
|
|
86
|
+
await registerManagedServerVersion(versionStateContext.statePath, {
|
|
87
|
+
version: installedVersion,
|
|
88
|
+
installPath: staged.stagedPath,
|
|
89
|
+
installedAt: new Date().toISOString(),
|
|
90
|
+
source: {
|
|
91
|
+
kind: archive.kind,
|
|
92
|
+
locator: archive.locator,
|
|
93
|
+
assetName: archive.assetName
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
const queryRuntimeStateFn = options.queryRuntimeStateFn ?? queryRuntimeState;
|
|
97
|
+
const runtimeState = await queryRuntimeStateFn({
|
|
98
|
+
manifestPath: options.manifestPath,
|
|
99
|
+
runtimeRoot: options.runtimeRoot
|
|
100
|
+
});
|
|
101
|
+
return {
|
|
102
|
+
source: {
|
|
103
|
+
kind: archive.kind,
|
|
104
|
+
locator: archive.locator,
|
|
105
|
+
version: archive.version,
|
|
106
|
+
assetName: archive.assetName
|
|
107
|
+
},
|
|
108
|
+
installedVersion,
|
|
109
|
+
activeVersion: installedVersion,
|
|
110
|
+
stagedPath: staged.stagedPath,
|
|
111
|
+
stagedDllPath: staged.stagedDllPath,
|
|
112
|
+
statePath: versionStateContext.statePath,
|
|
113
|
+
sharedDataRoot,
|
|
114
|
+
runtimeLifecycle,
|
|
115
|
+
runtimeState,
|
|
116
|
+
pm2: {
|
|
117
|
+
ensured: options.ensurePm2 ?? true,
|
|
118
|
+
versionRange: options.ensurePm2 === false ? null : normalizePm2Version(options.pm2Version)
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
await archive.cleanup();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function applyManifestServerInstallDefaults(manifest, options) {
|
|
127
|
+
if (options.archivePath ||
|
|
128
|
+
options.packageDirectory ||
|
|
129
|
+
options.url ||
|
|
130
|
+
options.indexUrl ||
|
|
131
|
+
options.indexChannel ||
|
|
132
|
+
options.indexVersion) {
|
|
133
|
+
return options;
|
|
134
|
+
}
|
|
135
|
+
const serverComponent = manifest.componentMap.get("server");
|
|
136
|
+
const activeVersion = serverComponent?.releasedService?.activeVersion?.trim();
|
|
137
|
+
if (!activeVersion) {
|
|
138
|
+
return options;
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
...options,
|
|
142
|
+
indexVersion: activeVersion
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
export async function startManagedServer(options = {}) {
|
|
146
|
+
return runManagedServerAction("start", options);
|
|
147
|
+
}
|
|
148
|
+
export async function listManagedServerVersions(options = {}) {
|
|
149
|
+
const context = await resolveManagedServerVersionStateContext(options);
|
|
150
|
+
const state = await readManagedServerVersionState(context.statePath);
|
|
151
|
+
return {
|
|
152
|
+
activeVersion: state.activeVersion,
|
|
153
|
+
versions: await listManagedServerVersionSummaries(context.statePath),
|
|
154
|
+
statePath: context.statePath,
|
|
155
|
+
sharedDataRoot: getServerSharedDataRoot(context.paths)
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
export async function useManagedServerVersion(options) {
|
|
159
|
+
const version = options.version.trim();
|
|
160
|
+
if (!version) {
|
|
161
|
+
throw new ManagedServerError("Managed server version must be a non-empty string.");
|
|
162
|
+
}
|
|
163
|
+
const context = await resolveManagedServerVersionStateContext(options);
|
|
164
|
+
const state = await readManagedServerVersionState(context.statePath);
|
|
165
|
+
const previousActiveVersion = state.activeVersion;
|
|
166
|
+
await setActiveManagedServerVersion(context.statePath, version);
|
|
167
|
+
return {
|
|
168
|
+
previousActiveVersion,
|
|
169
|
+
activeVersion: version,
|
|
170
|
+
statePath: context.statePath
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
export async function removeManagedServerInstalledVersion(options) {
|
|
174
|
+
const version = options.version.trim();
|
|
175
|
+
if (!version) {
|
|
176
|
+
throw new ManagedServerError("Managed server version must be a non-empty string.");
|
|
177
|
+
}
|
|
178
|
+
const context = await resolveManagedServerVersionStateContext(options);
|
|
179
|
+
const state = await readManagedServerVersionState(context.statePath);
|
|
180
|
+
const installedVersion = state.versions[version];
|
|
181
|
+
if (!installedVersion) {
|
|
182
|
+
throw new ManagedServerError(`Managed server version ${version} is not installed.`);
|
|
183
|
+
}
|
|
184
|
+
if (state.activeVersion === version) {
|
|
185
|
+
throw new ManagedServerError(`Managed server version ${version} is currently active and cannot be removed.`);
|
|
186
|
+
}
|
|
187
|
+
await removeManagedServerInstallPath(installedVersion.installPath, options);
|
|
188
|
+
const nextState = await removeInstalledManagedServerVersion(context.statePath, version);
|
|
189
|
+
return {
|
|
190
|
+
activeVersion: nextState.activeVersion,
|
|
191
|
+
removedVersion: version,
|
|
192
|
+
removedPath: installedVersion.installPath,
|
|
193
|
+
statePath: context.statePath
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
async function removeManagedServerInstallPath(installPath, options) {
|
|
197
|
+
const removeDirectoryFn = options.removeDirectoryFn ?? rm;
|
|
198
|
+
const retryDelayMs = options.retryDelayMs ?? MANAGED_SERVER_REMOVE_RETRY_DELAY_MS;
|
|
199
|
+
for (let attempt = 0;; attempt += 1) {
|
|
200
|
+
try {
|
|
201
|
+
await removeDirectoryFn(installPath, { recursive: true, force: true });
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
if (!isRetryableManagedServerRemoveError(error) ||
|
|
206
|
+
attempt >= MANAGED_SERVER_REMOVE_RETRY_COUNT - 1) {
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
await delay(retryDelayMs);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function isRetryableManagedServerRemoveError(error) {
|
|
214
|
+
const code = error?.code;
|
|
215
|
+
return code === "EBUSY" || code === "EPERM" || code === "ENOTEMPTY";
|
|
216
|
+
}
|
|
217
|
+
function delay(ms) {
|
|
218
|
+
return new Promise((resolve) => globalThis.setTimeout(resolve, ms));
|
|
219
|
+
}
|
|
220
|
+
export async function restartManagedServer(options = {}) {
|
|
221
|
+
return runManagedServerAction("restart", options);
|
|
222
|
+
}
|
|
223
|
+
export async function stopManagedServer(options = {}) {
|
|
224
|
+
return runManagedServerAction("stop", options);
|
|
225
|
+
}
|
|
226
|
+
export async function getManagedServerStatus(options = {}) {
|
|
227
|
+
return runManagedServerAction("status", options);
|
|
228
|
+
}
|
|
229
|
+
export async function resolveManagedServerStartupEnvironment(options = {}) {
|
|
230
|
+
const environmentOverrides = (await resolveManagedServerEnvironment(options)).environment;
|
|
231
|
+
return resolveManagedPm2Environment({
|
|
232
|
+
manifestPath: options.manifestPath,
|
|
233
|
+
runtimeRoot: options.runtimeRoot,
|
|
234
|
+
service: "server",
|
|
235
|
+
nameIdentifierValue: options.instanceName?.trim(),
|
|
236
|
+
environmentOverrides
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
async function runManagedServerAction(action, options) {
|
|
240
|
+
if (action === "start") {
|
|
241
|
+
await ensureManagedServerDependenciesStarted(options);
|
|
242
|
+
}
|
|
243
|
+
const environmentOverrides = (await resolveManagedServerEnvironment(options)).environment;
|
|
244
|
+
return runManagedPm2Command({
|
|
245
|
+
manifestPath: options.manifestPath,
|
|
246
|
+
runtimeRoot: options.runtimeRoot,
|
|
247
|
+
service: "server",
|
|
248
|
+
action,
|
|
249
|
+
nameIdentifierValue: options.instanceName?.trim(),
|
|
250
|
+
environmentOverrides
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
async function ensureManagedServerDependenciesStarted(options) {
|
|
254
|
+
const manifest = await loadRuntimeManifest({ manifestPath: options.manifestPath });
|
|
255
|
+
const runtimeState = await queryRuntimeState({
|
|
256
|
+
manifestPath: options.manifestPath,
|
|
257
|
+
runtimeRoot: options.runtimeRoot
|
|
258
|
+
});
|
|
259
|
+
const managedPm2ServiceNames = new Set(supportedPm2Services);
|
|
260
|
+
const dependencyServices = MANAGED_SERVER_INTEGRATION_DEPENDENCIES.filter((service) => service !== "server" &&
|
|
261
|
+
managedPm2ServiceNames.has(service) &&
|
|
262
|
+
isManagedDependencyInstalled(runtimeState, service) &&
|
|
263
|
+
manifest.componentMap.has(service));
|
|
264
|
+
for (const service of dependencyServices) {
|
|
265
|
+
await runManagedPm2Command({
|
|
266
|
+
manifestPath: options.manifestPath,
|
|
267
|
+
runtimeRoot: options.runtimeRoot,
|
|
268
|
+
service,
|
|
269
|
+
action: "start",
|
|
270
|
+
nameIdentifierValue: options.instanceName?.trim()
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
export async function resolveManagedServerEnvironment(options) {
|
|
275
|
+
const serverConfig = await getManagedServerConfig({
|
|
276
|
+
manifestPath: options.manifestPath,
|
|
277
|
+
runtimeRoot: options.runtimeRoot
|
|
278
|
+
});
|
|
279
|
+
const sharedDataRoot = dirname(dirname(serverConfig.configPath));
|
|
280
|
+
const systemDataRoot = join(sharedDataRoot, "data");
|
|
281
|
+
const integrationEnvironment = await resolveManagedServerIntegrationEnvironment(options);
|
|
282
|
+
return {
|
|
283
|
+
host: serverConfig.host,
|
|
284
|
+
port: serverConfig.port,
|
|
285
|
+
aspNetCoreUrls: serverConfig.aspNetCoreUrls,
|
|
286
|
+
configPath: serverConfig.configPath,
|
|
287
|
+
sharedDataRoot,
|
|
288
|
+
environment: {
|
|
289
|
+
ASPNETCORE_URLS: serverConfig.aspNetCoreUrls,
|
|
290
|
+
Urls: serverConfig.aspNetCoreUrls,
|
|
291
|
+
DATADIR: systemDataRoot,
|
|
292
|
+
...integrationEnvironment
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
async function resolveManagedServerIntegrationEnvironment(options) {
|
|
297
|
+
const manifest = await loadRuntimeManifest({ manifestPath: options.manifestPath });
|
|
298
|
+
const paths = resolveRuntimePaths(manifest, { runtimeRoot: options.runtimeRoot });
|
|
299
|
+
const runtimeState = await queryRuntimeState({
|
|
300
|
+
manifestPath: options.manifestPath,
|
|
301
|
+
runtimeRoot: options.runtimeRoot
|
|
302
|
+
});
|
|
303
|
+
return {
|
|
304
|
+
...(await resolveManagedVsCodeServerEnvironment(isManagedDependencyInstalled(runtimeState, "code-server")
|
|
305
|
+
? manifest.componentMap.get("code-server")
|
|
306
|
+
: undefined, paths, manifest)),
|
|
307
|
+
...(await resolveManagedOmniRouteEnvironment(isManagedDependencyInstalled(runtimeState, "omniroute")
|
|
308
|
+
? manifest.componentMap.get("omniroute")
|
|
309
|
+
: undefined, paths, manifest))
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
async function resolveManagedVsCodeServerEnvironment(component, paths, manifest) {
|
|
313
|
+
if (!component) {
|
|
314
|
+
return {};
|
|
315
|
+
}
|
|
316
|
+
const config = await readYamlObject(join(getComponentConfigDirectory(paths, component.name, component.runtimeDataDir), "config.yaml"));
|
|
317
|
+
const address = parseConfiguredAddress(readConfigString(config, "bind-addr"), DEFAULT_CODE_SERVER_HOST, DEFAULT_CODE_SERVER_PORT);
|
|
318
|
+
const exposedPort = resolveManagedDependencyPublicPort(manifest, "code-server") ?? address.port;
|
|
319
|
+
const authMode = readConfigString(config, "auth") ?? DEFAULT_CODE_SERVER_AUTH_MODE;
|
|
320
|
+
const secret = readConfigString(config, "password");
|
|
321
|
+
return {
|
|
322
|
+
VsCodeServer__Host: address.host,
|
|
323
|
+
VsCodeServer__Port: String(exposedPort),
|
|
324
|
+
VsCodeServer__AuthMode: authMode,
|
|
325
|
+
VsCodeServer__Source: VSCODE_SERVER_SOURCE_EXTERNAL,
|
|
326
|
+
VsCodeServer__SourceLocked: "true",
|
|
327
|
+
...(secret
|
|
328
|
+
? {
|
|
329
|
+
VsCodeServer__Secret: secret,
|
|
330
|
+
VsCodeServer__SecretSource: VSCODE_SERVER_SECRET_SOURCE_BOOTSTRAP
|
|
331
|
+
}
|
|
332
|
+
: {})
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
async function resolveManagedOmniRouteEnvironment(component, paths, manifest) {
|
|
336
|
+
if (!component) {
|
|
337
|
+
return {};
|
|
338
|
+
}
|
|
339
|
+
const config = await readYamlObject(join(getComponentConfigDirectory(paths, component.name, component.runtimeDataDir), "config.yaml"));
|
|
340
|
+
const address = parseConfiguredAddress(readConfigString(config, "listen"), DEFAULT_OMNIROUTE_HOST, DEFAULT_OMNIROUTE_PORT);
|
|
341
|
+
const exposedPort = resolveManagedDependencyPublicPort(manifest, "omniroute") ?? address.port;
|
|
342
|
+
const baseUrl = buildHttpBaseUrl(address.host, exposedPort);
|
|
343
|
+
return {
|
|
344
|
+
OmniRoute__Enabled: "true",
|
|
345
|
+
OmniRoute__ApiEndpoint: baseUrl,
|
|
346
|
+
OmniRoute__DefaultBaseUrl: baseUrl,
|
|
347
|
+
OmniRoute__DefaultBaseUrlSource: OMNIROUTE_SOURCE_EXTERNAL,
|
|
348
|
+
OmniRoute__DefaultBaseUrlLocked: "true"
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
function isManagedDependencyInstalled(runtimeState, componentName) {
|
|
352
|
+
return runtimeState.components.some((component) => component.name === componentName && component.status === "installed");
|
|
353
|
+
}
|
|
354
|
+
function resolveManagedDependencyPublicPort(manifest, service) {
|
|
355
|
+
const publicConfig = manifest.proxy?.caddy?.public;
|
|
356
|
+
if (!publicConfig) {
|
|
357
|
+
return undefined;
|
|
358
|
+
}
|
|
359
|
+
return service === "code-server" ? publicConfig.codeServerPort : publicConfig.omniroutePort;
|
|
360
|
+
}
|
|
361
|
+
async function readYamlObject(filePath) {
|
|
362
|
+
try {
|
|
363
|
+
const content = await readFile(filePath, "utf8");
|
|
364
|
+
const parsed = parseYaml(content);
|
|
365
|
+
return isRecord(parsed) ? parsed : undefined;
|
|
366
|
+
}
|
|
367
|
+
catch {
|
|
368
|
+
return undefined;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
function parseConfiguredAddress(value, defaultHost, defaultPort) {
|
|
372
|
+
if (!value) {
|
|
373
|
+
return { host: defaultHost, port: defaultPort };
|
|
374
|
+
}
|
|
375
|
+
try {
|
|
376
|
+
const parsed = new URL(value.includes("://") ? value : `http://${value}`);
|
|
377
|
+
const host = parsed.hostname || defaultHost;
|
|
378
|
+
const port = Number(parsed.port || String(defaultPort));
|
|
379
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
380
|
+
return { host: defaultHost, port: defaultPort };
|
|
381
|
+
}
|
|
382
|
+
return { host, port };
|
|
383
|
+
}
|
|
384
|
+
catch {
|
|
385
|
+
return { host: defaultHost, port: defaultPort };
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
function buildHttpBaseUrl(host, port) {
|
|
389
|
+
return new URL(`http://${host}:${port}`).toString();
|
|
390
|
+
}
|
|
391
|
+
function readConfigString(config, key) {
|
|
392
|
+
const value = config?.[key];
|
|
393
|
+
if (typeof value !== "string") {
|
|
394
|
+
return undefined;
|
|
395
|
+
}
|
|
396
|
+
const normalized = value.trim();
|
|
397
|
+
return normalized ? normalized : undefined;
|
|
398
|
+
}
|
|
399
|
+
function isRecord(value) {
|
|
400
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
401
|
+
}
|
|
402
|
+
async function resolveServerArchive(options, logger) {
|
|
403
|
+
const useHttpIndex = options.indexUrl !== undefined ||
|
|
404
|
+
options.indexChannel !== undefined ||
|
|
405
|
+
options.indexVersion !== undefined;
|
|
406
|
+
const selectedModes = [
|
|
407
|
+
options.archivePath ? "archive" : null,
|
|
408
|
+
options.packageDirectory ? "package-directory" : null,
|
|
409
|
+
options.url ? "url" : null,
|
|
410
|
+
useHttpIndex ? "index-url" : null
|
|
411
|
+
].filter(Boolean);
|
|
412
|
+
if (selectedModes.length > 1) {
|
|
413
|
+
throw new ManagedServerError("Choose only one server package source: --archive, --package-dir, or --url.");
|
|
414
|
+
}
|
|
415
|
+
if (options.archivePath) {
|
|
416
|
+
const archivePath = resolve(options.archivePath);
|
|
417
|
+
await assertFileExists(archivePath, `Server archive not found: ${archivePath}`);
|
|
418
|
+
logger(`Using local server archive ${archivePath}`);
|
|
419
|
+
return {
|
|
420
|
+
kind: "local-archive",
|
|
421
|
+
locator: archivePath,
|
|
422
|
+
version: null,
|
|
423
|
+
assetName: basename(archivePath),
|
|
424
|
+
archivePath,
|
|
425
|
+
cleanup: async () => undefined
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
if (options.packageDirectory) {
|
|
429
|
+
const directory = resolve(options.packageDirectory);
|
|
430
|
+
const selection = await selectServerArchiveFromDirectory(directory, options.assetName);
|
|
431
|
+
logger(`Selected ${selection.assetName} from ${directory}`);
|
|
432
|
+
return {
|
|
433
|
+
kind: "local-folder",
|
|
434
|
+
locator: directory,
|
|
435
|
+
version: selection.version,
|
|
436
|
+
assetName: selection.assetName,
|
|
437
|
+
archivePath: selection.archivePath,
|
|
438
|
+
cleanup: async () => undefined
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
if (options.url) {
|
|
442
|
+
return downloadRemoteArchive({
|
|
443
|
+
kind: "direct-url",
|
|
444
|
+
locator: options.url,
|
|
445
|
+
assetName: inferArchiveNameFromUrl(options.url),
|
|
446
|
+
version: null,
|
|
447
|
+
url: options.url
|
|
448
|
+
}, options);
|
|
449
|
+
}
|
|
450
|
+
if (useHttpIndex) {
|
|
451
|
+
return resolveHttpIndexArchive(options);
|
|
452
|
+
}
|
|
453
|
+
try {
|
|
454
|
+
return await resolveHttpIndexArchive(options);
|
|
455
|
+
}
|
|
456
|
+
catch (error) {
|
|
457
|
+
logger(`Default HTTP index ${DEFAULT_SERVER_HTTP_INDEX_URL} unavailable. Falling back to GitHub release source.`);
|
|
458
|
+
if (error instanceof ManagedServerError) {
|
|
459
|
+
logger(`HTTP index resolution detail: ${error.message}`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return resolveGitHubReleaseArchive(options);
|
|
463
|
+
}
|
|
464
|
+
async function resolveHttpIndexArchive(options) {
|
|
465
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
466
|
+
if (typeof fetchImpl !== "function") {
|
|
467
|
+
throw new ManagedServerError("Global fetch is unavailable for HTTP index download.");
|
|
468
|
+
}
|
|
469
|
+
const indexUrl = normalizeOfficialServerHttpIndexUrl(options.indexUrl);
|
|
470
|
+
const response = await fetchImpl(indexUrl, {
|
|
471
|
+
headers: {
|
|
472
|
+
Accept: "application/json",
|
|
473
|
+
"User-Agent": "@hagicode/hagiscript"
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
if (!response.ok) {
|
|
477
|
+
throw new ManagedServerError(`Failed to read HTTP index from ${indexUrl}: HTTP ${response.status}`);
|
|
478
|
+
}
|
|
479
|
+
const indexPayload = (await response.json());
|
|
480
|
+
const selection = selectArchiveFromHttpIndex(indexPayload, {
|
|
481
|
+
indexUrl,
|
|
482
|
+
channel: options.indexChannel,
|
|
483
|
+
version: options.indexVersion,
|
|
484
|
+
assetName: options.assetName
|
|
485
|
+
});
|
|
486
|
+
return downloadRemoteArchive({
|
|
487
|
+
kind: "http-index",
|
|
488
|
+
locator: selection.locator,
|
|
489
|
+
assetName: selection.assetName,
|
|
490
|
+
version: selection.version,
|
|
491
|
+
url: selection.downloadUrl
|
|
492
|
+
}, options);
|
|
493
|
+
}
|
|
494
|
+
function normalizeOfficialServerHttpIndexUrl(indexUrl) {
|
|
495
|
+
const normalized = indexUrl?.trim() || DEFAULT_SERVER_HTTP_INDEX_URL;
|
|
496
|
+
return LEGACY_SERVER_HTTP_INDEX_URLS.has(normalized)
|
|
497
|
+
? DEFAULT_SERVER_HTTP_INDEX_URL
|
|
498
|
+
: normalized;
|
|
499
|
+
}
|
|
500
|
+
async function resolveGitHubReleaseArchive(options) {
|
|
501
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
502
|
+
if (typeof fetchImpl !== "function") {
|
|
503
|
+
throw new ManagedServerError("Global fetch is unavailable for GitHub release download.");
|
|
504
|
+
}
|
|
505
|
+
const repository = options.githubRepository?.trim() || DEFAULT_GITHUB_REPOSITORY;
|
|
506
|
+
const tag = options.githubTag?.trim() || DEFAULT_GITHUB_TAG;
|
|
507
|
+
const releaseEndpoint = tag === "latest"
|
|
508
|
+
? `https://api.github.com/repos/${repository}/releases/latest`
|
|
509
|
+
: `https://api.github.com/repos/${repository}/releases/tags/${encodeURIComponent(tag)}`;
|
|
510
|
+
const response = await fetchImpl(releaseEndpoint, {
|
|
511
|
+
headers: buildGitHubRequestHeaders(options.githubToken, "application/vnd.github+json")
|
|
512
|
+
});
|
|
513
|
+
if (!response.ok) {
|
|
514
|
+
throw new ManagedServerError(`Failed to read GitHub release metadata from ${repository}: HTTP ${response.status}`);
|
|
515
|
+
}
|
|
516
|
+
const release = (await response.json());
|
|
517
|
+
const desiredAssetName = options.assetName?.trim() || null;
|
|
518
|
+
const defaultSuffix = getDefaultManagedServerAssetSuffix();
|
|
519
|
+
const asset = Array.isArray(release.assets)
|
|
520
|
+
? release.assets.find((entry) => desiredAssetName
|
|
521
|
+
? entry?.name === desiredAssetName
|
|
522
|
+
: typeof entry?.name === "string" && entry.name.endsWith(defaultSuffix))
|
|
523
|
+
: undefined;
|
|
524
|
+
if (!asset?.name || !asset.browser_download_url) {
|
|
525
|
+
throw new ManagedServerError(desiredAssetName
|
|
526
|
+
? `GitHub release ${tag} in ${repository} does not expose asset ${desiredAssetName}.`
|
|
527
|
+
: `GitHub release ${tag} in ${repository} does not expose an asset ending with ${defaultSuffix}.`);
|
|
528
|
+
}
|
|
529
|
+
return downloadRemoteArchive({
|
|
530
|
+
kind: "github-release",
|
|
531
|
+
locator: `${repository}@${release.tag_name ?? tag}`,
|
|
532
|
+
assetName: asset.name,
|
|
533
|
+
version: release.tag_name ?? null,
|
|
534
|
+
url: asset.browser_download_url
|
|
535
|
+
}, options);
|
|
536
|
+
}
|
|
537
|
+
async function downloadRemoteArchive(source, options) {
|
|
538
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
539
|
+
if (typeof fetchImpl !== "function") {
|
|
540
|
+
throw new ManagedServerError("Global fetch is unavailable for server download.");
|
|
541
|
+
}
|
|
542
|
+
const tempDirectory = await mkdtemp(join(tmpdir(), "hagiscript-server-download-"));
|
|
543
|
+
const archivePath = join(tempDirectory, source.assetName);
|
|
544
|
+
const cachePath = isDownloadCacheEnabled(options.downloadCache)
|
|
545
|
+
? join(resolveDownloadCacheDirectory(options.downloadCacheDir), "server", createHash("sha256").update(source.url).digest("hex"), source.assetName)
|
|
546
|
+
: undefined;
|
|
547
|
+
try {
|
|
548
|
+
const restoredSize = cachePath ? await copyFileFromCache(cachePath, archivePath) : undefined;
|
|
549
|
+
if (restoredSize === undefined) {
|
|
550
|
+
const response = await fetchImpl(source.url, {
|
|
551
|
+
headers: buildGitHubRequestHeaders(options.githubToken, "application/octet-stream")
|
|
552
|
+
});
|
|
553
|
+
if (!response.ok) {
|
|
554
|
+
throw new ManagedServerError(`Failed to download ${source.url}: HTTP ${response.status}`);
|
|
555
|
+
}
|
|
556
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
557
|
+
await writeFile(archivePath, buffer);
|
|
558
|
+
if (cachePath) {
|
|
559
|
+
await storeFileInCache(archivePath, cachePath);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return {
|
|
563
|
+
kind: source.kind,
|
|
564
|
+
locator: source.locator,
|
|
565
|
+
version: source.version,
|
|
566
|
+
assetName: source.assetName,
|
|
567
|
+
archivePath,
|
|
568
|
+
cleanup: async () => {
|
|
569
|
+
await rm(tempDirectory, { recursive: true, force: true });
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
catch (error) {
|
|
574
|
+
await rm(tempDirectory, { recursive: true, force: true }).catch(() => undefined);
|
|
575
|
+
throw error;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
function selectArchiveFromHttpIndex(payload, options) {
|
|
579
|
+
const desiredVersion = options.version?.trim() || null;
|
|
580
|
+
const desiredChannel = options.channel?.trim() || null;
|
|
581
|
+
const desiredAssetName = options.assetName?.trim() || null;
|
|
582
|
+
const defaultSuffix = getDefaultManagedServerAssetSuffix();
|
|
583
|
+
const versionEntries = asArray(payload.versions)
|
|
584
|
+
.map((entry) => normalizeHttpIndexVersionEntry(entry))
|
|
585
|
+
.filter((entry) => entry !== null);
|
|
586
|
+
const indexLevelAssets = asArray(payload.assets)
|
|
587
|
+
.map((entry) => normalizeHttpIndexAssetEntry(entry))
|
|
588
|
+
.filter((entry) => entry !== null);
|
|
589
|
+
const candidateVersions = versionEntries.filter((entry) => {
|
|
590
|
+
if (desiredVersion && entry.version !== desiredVersion) {
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
if (desiredChannel && !entry.channels.has(desiredChannel)) {
|
|
594
|
+
return false;
|
|
595
|
+
}
|
|
596
|
+
return true;
|
|
597
|
+
});
|
|
598
|
+
const orderedVersions = candidateVersions
|
|
599
|
+
.slice()
|
|
600
|
+
.sort((left, right) => compareVersionValues(left.version, right.version));
|
|
601
|
+
for (const versionEntry of orderedVersions) {
|
|
602
|
+
const match = selectHttpIndexAsset(versionEntry.assets, {
|
|
603
|
+
desiredAssetName,
|
|
604
|
+
defaultSuffix
|
|
605
|
+
});
|
|
606
|
+
if (match) {
|
|
607
|
+
return {
|
|
608
|
+
locator: `${options.indexUrl}@${versionEntry.version ?? "unknown"}`,
|
|
609
|
+
version: versionEntry.version,
|
|
610
|
+
assetName: match.assetName,
|
|
611
|
+
downloadUrl: match.downloadUrl
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
if (!desiredVersion && !desiredChannel && indexLevelAssets.length > 0) {
|
|
616
|
+
const match = selectHttpIndexAsset(indexLevelAssets, {
|
|
617
|
+
desiredAssetName,
|
|
618
|
+
defaultSuffix
|
|
619
|
+
});
|
|
620
|
+
if (match) {
|
|
621
|
+
return {
|
|
622
|
+
locator: `${options.indexUrl}@index`,
|
|
623
|
+
version: null,
|
|
624
|
+
assetName: match.assetName,
|
|
625
|
+
downloadUrl: match.downloadUrl
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
const scope = [
|
|
630
|
+
desiredVersion ? `version=${desiredVersion}` : null,
|
|
631
|
+
desiredChannel ? `channel=${desiredChannel}` : null,
|
|
632
|
+
desiredAssetName ? `asset=${desiredAssetName}` : null
|
|
633
|
+
]
|
|
634
|
+
.filter(Boolean)
|
|
635
|
+
.join(", ");
|
|
636
|
+
throw new ManagedServerError(`HTTP index ${options.indexUrl} does not expose a matching server archive${scope ? ` (${scope})` : ""}.`);
|
|
637
|
+
}
|
|
638
|
+
function normalizeHttpIndexVersionEntry(value) {
|
|
639
|
+
if (!value || typeof value !== "object") {
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
const entry = value;
|
|
643
|
+
const version = normalizeString(entry.version) ?? normalizeString(entry.tag) ?? null;
|
|
644
|
+
const channels = new Set();
|
|
645
|
+
const singleChannel = normalizeString(entry.channel);
|
|
646
|
+
if (singleChannel) {
|
|
647
|
+
channels.add(singleChannel);
|
|
648
|
+
}
|
|
649
|
+
for (const item of asArray(entry.channels)) {
|
|
650
|
+
const normalized = normalizeString(item);
|
|
651
|
+
if (normalized) {
|
|
652
|
+
channels.add(normalized);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
const assets = [...asArray(entry.assets), ...asArray(entry.files)]
|
|
656
|
+
.map((item) => normalizeHttpIndexAssetEntry(item))
|
|
657
|
+
.filter((item) => item !== null);
|
|
658
|
+
return {
|
|
659
|
+
version,
|
|
660
|
+
channels,
|
|
661
|
+
assets
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
function normalizeHttpIndexAssetEntry(value) {
|
|
665
|
+
if (!value || typeof value !== "object") {
|
|
666
|
+
return null;
|
|
667
|
+
}
|
|
668
|
+
const entry = value;
|
|
669
|
+
const assetName = normalizeString(entry.name);
|
|
670
|
+
const directUrl = normalizeString(entry.url) ??
|
|
671
|
+
normalizeString(entry.directUrl) ??
|
|
672
|
+
normalizeString(entry.downloadUrl) ??
|
|
673
|
+
normalizeString(entry.browser_download_url) ??
|
|
674
|
+
normalizeString(entry.browserDownloadUrl);
|
|
675
|
+
if (assetName && directUrl) {
|
|
676
|
+
return {
|
|
677
|
+
assetName,
|
|
678
|
+
downloadUrl: directUrl
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
const sourceUrl = resolveHttpIndexDownloadSourceUrl(entry.downloadSources ?? entry.sources);
|
|
682
|
+
if (!assetName || !sourceUrl) {
|
|
683
|
+
return null;
|
|
684
|
+
}
|
|
685
|
+
return {
|
|
686
|
+
assetName,
|
|
687
|
+
downloadUrl: sourceUrl
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
function resolveHttpIndexDownloadSourceUrl(value) {
|
|
691
|
+
const sources = asArray(value)
|
|
692
|
+
.map((entry) => {
|
|
693
|
+
if (!entry || typeof entry !== "object") {
|
|
694
|
+
return null;
|
|
695
|
+
}
|
|
696
|
+
const source = entry;
|
|
697
|
+
const url = normalizeString(source.url);
|
|
698
|
+
if (!url) {
|
|
699
|
+
return null;
|
|
700
|
+
}
|
|
701
|
+
return {
|
|
702
|
+
url,
|
|
703
|
+
primary: source.primary === true
|
|
704
|
+
};
|
|
705
|
+
})
|
|
706
|
+
.filter((entry) => !!entry);
|
|
707
|
+
if (sources.length === 0) {
|
|
708
|
+
return null;
|
|
709
|
+
}
|
|
710
|
+
return sources.find((entry) => entry.primary)?.url ?? sources[0].url;
|
|
711
|
+
}
|
|
712
|
+
function selectHttpIndexAsset(assets, options) {
|
|
713
|
+
if (options.desiredAssetName) {
|
|
714
|
+
return assets.find((entry) => entry.assetName === options.desiredAssetName) ?? null;
|
|
715
|
+
}
|
|
716
|
+
return (assets.find((entry) => entry.assetName.endsWith(options.defaultSuffix)) ??
|
|
717
|
+
assets[0] ??
|
|
718
|
+
null);
|
|
719
|
+
}
|
|
720
|
+
function asArray(value) {
|
|
721
|
+
return Array.isArray(value) ? value : [];
|
|
722
|
+
}
|
|
723
|
+
function normalizeString(value) {
|
|
724
|
+
if (typeof value !== "string") {
|
|
725
|
+
return undefined;
|
|
726
|
+
}
|
|
727
|
+
const normalized = value.trim();
|
|
728
|
+
return normalized ? normalized : undefined;
|
|
729
|
+
}
|
|
730
|
+
function compareVersionValues(left, right) {
|
|
731
|
+
if (left && right) {
|
|
732
|
+
const normalizedLeft = normalizeSemverLike(left);
|
|
733
|
+
const normalizedRight = normalizeSemverLike(right);
|
|
734
|
+
if (normalizedLeft && normalizedRight) {
|
|
735
|
+
return semver.rcompare(normalizedLeft, normalizedRight);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
if (left === right) {
|
|
739
|
+
return 0;
|
|
740
|
+
}
|
|
741
|
+
if (left === null) {
|
|
742
|
+
return 1;
|
|
743
|
+
}
|
|
744
|
+
if (right === null) {
|
|
745
|
+
return -1;
|
|
746
|
+
}
|
|
747
|
+
return right.localeCompare(left);
|
|
748
|
+
}
|
|
749
|
+
function normalizeSemverLike(value) {
|
|
750
|
+
const normalized = value.startsWith("v") ? value.slice(1) : value;
|
|
751
|
+
return semver.valid(normalized);
|
|
752
|
+
}
|
|
753
|
+
async function stageServerArchive(options) {
|
|
754
|
+
const extractRoot = await mkdtemp(join(tmpdir(), "hagiscript-server-extract-"));
|
|
755
|
+
const targetVersionRoot = getServerVersionRoot(options.paths, options.version);
|
|
756
|
+
try {
|
|
757
|
+
await (options.extractArchive ?? extractManagedServerArchive)(options.archivePath, extractRoot, options.runner);
|
|
758
|
+
const payloadRoot = await locateManagedServerPayloadRoot(extractRoot);
|
|
759
|
+
const stagedDllPath = join(targetVersionRoot, "lib", "PCode.Web.dll");
|
|
760
|
+
if ((await pathExists(targetVersionRoot)) && !options.force) {
|
|
761
|
+
throw new ManagedServerError(`Managed server version ${options.version} is already installed at ${targetVersionRoot}. Use --force to replace it.`);
|
|
762
|
+
}
|
|
763
|
+
await mkdir(targetVersionRoot, { recursive: true });
|
|
764
|
+
await rm(targetVersionRoot, { recursive: true, force: true });
|
|
765
|
+
await cp(payloadRoot, targetVersionRoot, {
|
|
766
|
+
recursive: true,
|
|
767
|
+
force: true
|
|
768
|
+
});
|
|
769
|
+
await assertValidManagedServerPayload(targetVersionRoot);
|
|
770
|
+
return {
|
|
771
|
+
stagedPath: targetVersionRoot,
|
|
772
|
+
stagedDllPath
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
finally {
|
|
776
|
+
await rm(extractRoot, { recursive: true, force: true }).catch(() => undefined);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
async function selectServerArchiveFromDirectory(directory, assetName) {
|
|
780
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
781
|
+
const zipFiles = entries
|
|
782
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".zip"))
|
|
783
|
+
.map((entry) => entry.name);
|
|
784
|
+
if (zipFiles.length === 0) {
|
|
785
|
+
throw new ManagedServerError(`No .zip archives found in ${directory}.`);
|
|
786
|
+
}
|
|
787
|
+
const desiredAssetName = assetName?.trim() || null;
|
|
788
|
+
if (desiredAssetName) {
|
|
789
|
+
const matched = zipFiles.find((entry) => entry === desiredAssetName);
|
|
790
|
+
if (!matched) {
|
|
791
|
+
throw new ManagedServerError(`Archive ${desiredAssetName} was not found in ${directory}.`);
|
|
792
|
+
}
|
|
793
|
+
return {
|
|
794
|
+
archivePath: join(directory, matched),
|
|
795
|
+
assetName: matched,
|
|
796
|
+
version: extractVersionFromServerAssetName(matched)
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
const suffix = getDefaultManagedServerAssetSuffix();
|
|
800
|
+
const candidates = zipFiles
|
|
801
|
+
.filter((entry) => entry.endsWith(suffix))
|
|
802
|
+
.map((entry) => ({
|
|
803
|
+
assetName: entry,
|
|
804
|
+
archivePath: join(directory, entry),
|
|
805
|
+
version: extractVersionFromServerAssetName(entry)
|
|
806
|
+
}))
|
|
807
|
+
.filter((entry) => entry.version && semver.valid(entry.version))
|
|
808
|
+
.sort((left, right) => semver.rcompare(String(left.version), String(right.version)));
|
|
809
|
+
if (candidates.length === 0) {
|
|
810
|
+
throw new ManagedServerError(`No server archive ending with ${suffix} was found in ${directory}.`);
|
|
811
|
+
}
|
|
812
|
+
return candidates[0];
|
|
813
|
+
}
|
|
814
|
+
async function extractManagedServerArchive(archivePath, extractRoot, _runner) {
|
|
815
|
+
try {
|
|
816
|
+
await extractZipArchive(archivePath, extractRoot);
|
|
817
|
+
}
|
|
818
|
+
catch (error) {
|
|
819
|
+
const archiveError = error instanceof Error ? error : new Error(String(error));
|
|
820
|
+
throw new ManagedServerError(`Failed to extract managed server archive ${archivePath}: ${archiveError.message}`, { cause: archiveError });
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
async function locateManagedServerPayloadRoot(extractRoot) {
|
|
824
|
+
const searchQueue = [extractRoot];
|
|
825
|
+
while (searchQueue.length > 0) {
|
|
826
|
+
const current = searchQueue.shift();
|
|
827
|
+
if (!current) {
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
const dllPath = join(current, "lib", "PCode.Web.dll");
|
|
831
|
+
if (await pathExists(dllPath)) {
|
|
832
|
+
return current;
|
|
833
|
+
}
|
|
834
|
+
const entries = await readdir(current, { withFileTypes: true });
|
|
835
|
+
for (const entry of entries) {
|
|
836
|
+
if (entry.isDirectory()) {
|
|
837
|
+
searchQueue.push(join(current, entry.name));
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
throw new ManagedServerError(`Extracted server archive does not contain lib/PCode.Web.dll under ${extractRoot}.`);
|
|
842
|
+
}
|
|
843
|
+
async function assertValidManagedServerPayload(payloadRoot) {
|
|
844
|
+
for (const relativePath of [
|
|
845
|
+
"lib/PCode.Web.dll",
|
|
846
|
+
"lib/PCode.Web.deps.json",
|
|
847
|
+
"lib/PCode.Web.runtimeconfig.json"
|
|
848
|
+
]) {
|
|
849
|
+
await assertFileExists(join(payloadRoot, relativePath), `Managed server payload is missing required file ${relativePath}.`);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
function assertServerComponent(component) {
|
|
853
|
+
if (!component) {
|
|
854
|
+
throw new ManagedServerError("Runtime manifest does not define a server component.");
|
|
855
|
+
}
|
|
856
|
+
if (component.type !== "released-service") {
|
|
857
|
+
throw new ManagedServerError("Runtime manifest server component must be a released-service.");
|
|
858
|
+
}
|
|
859
|
+
return component;
|
|
860
|
+
}
|
|
861
|
+
async function assertFileExists(pathValue, message) {
|
|
862
|
+
const target = resolve(pathValue);
|
|
863
|
+
try {
|
|
864
|
+
const result = await stat(target);
|
|
865
|
+
if (!result.isFile()) {
|
|
866
|
+
throw new ManagedServerError(message);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
catch (error) {
|
|
870
|
+
if (error instanceof ManagedServerError) {
|
|
871
|
+
throw error;
|
|
872
|
+
}
|
|
873
|
+
throw new ManagedServerError(message, error instanceof Error ? { cause: error } : undefined);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
async function pathExists(pathValue) {
|
|
877
|
+
try {
|
|
878
|
+
await stat(pathValue);
|
|
879
|
+
return true;
|
|
880
|
+
}
|
|
881
|
+
catch {
|
|
882
|
+
return false;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
function extractVersionFromServerAssetName(assetName) {
|
|
886
|
+
const suffix = getDefaultManagedServerAssetSuffix().replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
887
|
+
const match = new RegExp(`^hagicode-(.+)-${suffix}$`, "u").exec(assetName);
|
|
888
|
+
return match?.[1] ?? null;
|
|
889
|
+
}
|
|
890
|
+
function resolveManagedServerVersion(archive) {
|
|
891
|
+
const detectedVersion = archive.version ?? extractVersionFromServerAssetName(archive.assetName);
|
|
892
|
+
const normalizedSemver = detectedVersion ? normalizeSemverLike(detectedVersion) : null;
|
|
893
|
+
const normalizedVersion = normalizedSemver ?? detectedVersion?.trim() ?? "";
|
|
894
|
+
if (!normalizedVersion) {
|
|
895
|
+
throw new ManagedServerError(`Unable to determine a concrete server version from ${archive.assetName}. Use a versioned archive name such as hagicode-1.2.3-${getDefaultManagedServerAssetSuffix()}.`);
|
|
896
|
+
}
|
|
897
|
+
return normalizedVersion;
|
|
898
|
+
}
|
|
899
|
+
function inferArchiveNameFromUrl(urlValue) {
|
|
900
|
+
try {
|
|
901
|
+
const parsed = new URL(urlValue);
|
|
902
|
+
const candidate = basename(parsed.pathname);
|
|
903
|
+
return candidate || "hagicode-server.zip";
|
|
904
|
+
}
|
|
905
|
+
catch {
|
|
906
|
+
return "hagicode-server.zip";
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
function normalizePm2Version(pm2Version) {
|
|
910
|
+
const normalized = pm2Version?.trim();
|
|
911
|
+
return normalized && normalized.length > 0 ? normalized : "*";
|
|
912
|
+
}
|
|
913
|
+
function getDefaultManagedServerAssetSuffix() {
|
|
914
|
+
return `${getManagedServerPlatform()}-${getManagedServerArchitecture()}-nort.zip`;
|
|
915
|
+
}
|
|
916
|
+
function getManagedServerPlatform(platform = process.platform) {
|
|
917
|
+
switch (platform) {
|
|
918
|
+
case "linux":
|
|
919
|
+
return "linux";
|
|
920
|
+
case "win32":
|
|
921
|
+
return "win";
|
|
922
|
+
case "darwin":
|
|
923
|
+
return "osx";
|
|
924
|
+
default:
|
|
925
|
+
throw new ManagedServerError(`Unsupported server package platform: ${platform}`);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
function getManagedServerArchitecture(arch = process.arch) {
|
|
929
|
+
switch (arch) {
|
|
930
|
+
case "x64":
|
|
931
|
+
return "x64";
|
|
932
|
+
case "arm64":
|
|
933
|
+
return "arm64";
|
|
934
|
+
default:
|
|
935
|
+
throw new ManagedServerError(`Unsupported server package architecture: ${arch}`);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
function buildGitHubRequestHeaders(githubToken, accept = "application/json") {
|
|
939
|
+
const token = githubToken?.trim() || process.env.GITHUB_TOKEN?.trim() || process.env.GH_TOKEN?.trim();
|
|
940
|
+
return {
|
|
941
|
+
Accept: accept,
|
|
942
|
+
"User-Agent": "@hagicode/hagiscript",
|
|
943
|
+
...(token ? { Authorization: `Bearer ${token}` } : {})
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
//# sourceMappingURL=server-manager.js.map
|