@hagicode/hagiscript 0.1.14 → 0.1.15-dev.100.1.6114a75

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