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.
Files changed (86) hide show
  1. package/README.md +25 -0
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/package.json +38 -0
  4. package/dist/src/index.d.ts +24 -0
  5. package/dist/src/index.js +27 -0
  6. package/dist/src/index.js.map +1 -0
  7. package/dist/src/runtime/command-launch.d.ts +41 -0
  8. package/dist/src/runtime/command-launch.js +100 -0
  9. package/dist/src/runtime/command-launch.js.map +1 -0
  10. package/dist/src/runtime/component-service-manager.d.ts +71 -0
  11. package/dist/src/runtime/component-service-manager.js +367 -0
  12. package/dist/src/runtime/component-service-manager.js.map +1 -0
  13. package/dist/src/runtime/dotnet-installer.d.ts +39 -0
  14. package/dist/src/runtime/dotnet-installer.js +282 -0
  15. package/dist/src/runtime/dotnet-installer.js.map +1 -0
  16. package/dist/src/runtime/download-cache.d.ts +7 -0
  17. package/dist/src/runtime/download-cache.js +110 -0
  18. package/dist/src/runtime/download-cache.js.map +1 -0
  19. package/dist/src/runtime/manifest-manager.d.ts +57 -0
  20. package/dist/src/runtime/manifest-manager.js +188 -0
  21. package/dist/src/runtime/manifest-manager.js.map +1 -0
  22. package/dist/src/runtime/node-download.d.ts +13 -0
  23. package/dist/src/runtime/node-download.js +78 -0
  24. package/dist/src/runtime/node-download.js.map +1 -0
  25. package/dist/src/runtime/node-extract.d.ts +18 -0
  26. package/dist/src/runtime/node-extract.js +170 -0
  27. package/dist/src/runtime/node-extract.js.map +1 -0
  28. package/dist/src/runtime/node-installer.d.ts +43 -0
  29. package/dist/src/runtime/node-installer.js +114 -0
  30. package/dist/src/runtime/node-installer.js.map +1 -0
  31. package/dist/src/runtime/node-platform.d.ts +15 -0
  32. package/dist/src/runtime/node-platform.js +51 -0
  33. package/dist/src/runtime/node-platform.js.map +1 -0
  34. package/dist/src/runtime/node-release.d.ts +28 -0
  35. package/dist/src/runtime/node-release.js +149 -0
  36. package/dist/src/runtime/node-release.js.map +1 -0
  37. package/dist/src/runtime/node-verify.d.ts +30 -0
  38. package/dist/src/runtime/node-verify.js +102 -0
  39. package/dist/src/runtime/node-verify.js.map +1 -0
  40. package/dist/src/runtime/npm-global.d.ts +28 -0
  41. package/dist/src/runtime/npm-global.js +77 -0
  42. package/dist/src/runtime/npm-global.js.map +1 -0
  43. package/dist/src/runtime/npm-sync.d.ts +164 -0
  44. package/dist/src/runtime/npm-sync.js +603 -0
  45. package/dist/src/runtime/npm-sync.js.map +1 -0
  46. package/dist/src/runtime/pm2-manager.d.ts +102 -0
  47. package/dist/src/runtime/pm2-manager.js +715 -0
  48. package/dist/src/runtime/pm2-manager.js.map +1 -0
  49. package/dist/src/runtime/runtime-executor.d.ts +58 -0
  50. package/dist/src/runtime/runtime-executor.js +291 -0
  51. package/dist/src/runtime/runtime-executor.js.map +1 -0
  52. package/dist/src/runtime/runtime-manager.d.ts +92 -0
  53. package/dist/src/runtime/runtime-manager.js +798 -0
  54. package/dist/src/runtime/runtime-manager.js.map +1 -0
  55. package/dist/src/runtime/runtime-manifest.d.ts +106 -0
  56. package/dist/src/runtime/runtime-manifest.js +450 -0
  57. package/dist/src/runtime/runtime-manifest.js.map +1 -0
  58. package/dist/src/runtime/runtime-paths.d.ts +45 -0
  59. package/dist/src/runtime/runtime-paths.js +138 -0
  60. package/dist/src/runtime/runtime-paths.js.map +1 -0
  61. package/dist/src/runtime/runtime-state.d.ts +45 -0
  62. package/dist/src/runtime/runtime-state.js +82 -0
  63. package/dist/src/runtime/runtime-state.js.map +1 -0
  64. package/dist/src/runtime/server-config.d.ts +21 -0
  65. package/dist/src/runtime/server-config.js +169 -0
  66. package/dist/src/runtime/server-config.js.map +1 -0
  67. package/dist/src/runtime/server-manager.d.ts +107 -0
  68. package/dist/src/runtime/server-manager.js +946 -0
  69. package/dist/src/runtime/server-manager.js.map +1 -0
  70. package/dist/src/runtime/server-version-state.d.ts +43 -0
  71. package/dist/src/runtime/server-version-state.js +156 -0
  72. package/dist/src/runtime/server-version-state.js.map +1 -0
  73. package/dist/src/runtime/seven-zip-extract.d.ts +15 -0
  74. package/dist/src/runtime/seven-zip-extract.js +104 -0
  75. package/dist/src/runtime/seven-zip-extract.js.map +1 -0
  76. package/dist/src/runtime/tool-sync-catalog.config.json +59 -0
  77. package/dist/src/runtime/tool-sync-catalog.d.ts +48 -0
  78. package/dist/src/runtime/tool-sync-catalog.js +189 -0
  79. package/dist/src/runtime/tool-sync-catalog.js.map +1 -0
  80. package/dist/src/runtime/zip-extract.d.ts +6 -0
  81. package/dist/src/runtime/zip-extract.js +90 -0
  82. package/dist/src/runtime/zip-extract.js.map +1 -0
  83. package/dist/src/version.d.ts +7 -0
  84. package/dist/src/version.js +23 -0
  85. package/dist/src/version.js.map +1 -0
  86. 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