@hagicode/hagiscript 0.1.15-dev.91.1.7ddec89 → 0.1.15-dev.94.1.64ce9f6

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 (57) hide show
  1. package/README.md +237 -175
  2. package/dist/cli.js +2 -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.js +181 -7
  10. package/dist/commands/server-commands.js.map +1 -1
  11. package/dist/index.d.ts +7 -4
  12. package/dist/index.js +7 -4
  13. package/dist/index.js.map +1 -1
  14. package/dist/runtime/manifest-manager.d.ts +57 -0
  15. package/dist/runtime/manifest-manager.js +213 -0
  16. package/dist/runtime/manifest-manager.js.map +1 -0
  17. package/dist/runtime/npm-sync.d.ts +3 -1
  18. package/dist/runtime/npm-sync.js +31 -3
  19. package/dist/runtime/npm-sync.js.map +1 -1
  20. package/dist/runtime/pm2-manager.d.ts +4 -2
  21. package/dist/runtime/pm2-manager.js +181 -53
  22. package/dist/runtime/pm2-manager.js.map +1 -1
  23. package/dist/runtime/runtime-executor.d.ts +6 -0
  24. package/dist/runtime/runtime-executor.js +55 -0
  25. package/dist/runtime/runtime-executor.js.map +1 -1
  26. package/dist/runtime/runtime-manager.d.ts +9 -0
  27. package/dist/runtime/runtime-manager.js +146 -16
  28. package/dist/runtime/runtime-manager.js.map +1 -1
  29. package/dist/runtime/runtime-manifest.d.ts +5 -1
  30. package/dist/runtime/runtime-manifest.js +23 -8
  31. package/dist/runtime/runtime-manifest.js.map +1 -1
  32. package/dist/runtime/runtime-paths.d.ts +11 -1
  33. package/dist/runtime/runtime-paths.js +41 -5
  34. package/dist/runtime/runtime-paths.js.map +1 -1
  35. package/dist/runtime/server-config.d.ts +21 -0
  36. package/dist/runtime/server-config.js +169 -0
  37. package/dist/runtime/server-config.js.map +1 -0
  38. package/dist/runtime/server-manager.d.ts +48 -4
  39. package/dist/runtime/server-manager.js +499 -66
  40. package/dist/runtime/server-manager.js.map +1 -1
  41. package/dist/runtime/server-version-state.d.ts +43 -0
  42. package/dist/runtime/server-version-state.js +156 -0
  43. package/dist/runtime/server-version-state.js.map +1 -0
  44. package/dist/runtime/tool-sync-catalog.config.json +0 -18
  45. package/dist/runtime/tool-sync-catalog.js +1 -1
  46. package/dist/runtime/tool-sync-catalog.js.map +1 -1
  47. package/package.json +23 -2
  48. package/runtime/lib/runtime-script-lib.mjs +128 -69
  49. package/runtime/manifest.yaml +96 -81
  50. package/runtime/scripts/configure-code-server.mjs +14 -3
  51. package/runtime/scripts/configure-omniroute.mjs +15 -5
  52. package/runtime/scripts/install-code-server.mjs +20 -23
  53. package/runtime/scripts/install-omniroute.mjs +19 -22
  54. package/runtime/scripts/install-pm2.mjs +39 -0
  55. package/runtime/scripts/remove-pm2.mjs +25 -0
  56. package/runtime/templates/code-server-config.yaml +0 -5
  57. package/runtime/templates/omniroute-config.yaml +0 -4
@@ -1,16 +1,18 @@
1
- import { cp, mkdtemp, mkdir, readdir, rm, stat, writeFile } from "node:fs/promises";
1
+ import { cp, mkdtemp, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
2
2
  import { tmpdir } from "node:os";
3
- import { basename, join, resolve } from "node:path";
3
+ import { basename, dirname, join, resolve } from "node:path";
4
4
  import { createHash } from "node:crypto";
5
5
  import process from "node:process";
6
6
  import semver from "semver";
7
+ import { parse as parseYaml } from "yaml";
7
8
  import { copyFileFromCache, isDownloadCacheEnabled, resolveDownloadCacheDirectory, storeFileInCache } from "./download-cache.js";
8
9
  import { runCommand } from "./command-launch.js";
9
- import { syncNpmGlobals, validateRegistryMirror } from "./npm-sync.js";
10
- import { resolveManagedPm2Environment, runManagedPm2Command } from "./pm2-manager.js";
10
+ import { resolveManagedPm2Environment, runManagedPm2Command, supportedPm2Services } from "./pm2-manager.js";
11
11
  import { loadRuntimeManifest } from "./runtime-manifest.js";
12
- import { getComponentManagedRoot, resolveRuntimePaths } from "./runtime-paths.js";
13
- import { installRuntime, queryRuntimeState } from "./runtime-manager.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";
14
16
  export class ManagedServerError extends Error {
15
17
  constructor(message, options) {
16
18
  super(message, options);
@@ -19,44 +21,75 @@ export class ManagedServerError extends Error {
19
21
  }
20
22
  const DEFAULT_GITHUB_REPOSITORY = "HagiCode-org/releases";
21
23
  const DEFAULT_GITHUB_TAG = "latest";
22
- const DEFAULT_PM2_INSTANCE = "hagicode";
24
+ const DEFAULT_SERVER_HTTP_INDEX_URL = "https://index.hagicode.com/server/index.json";
25
+ const LEGACY_SERVER_HTTP_INDEX_URLS = new Set([
26
+ "https://server.dl.hagicode.com/index.json"
27
+ ]);
28
+ const DEFAULT_CODE_SERVER_HOST = "127.0.0.1";
29
+ const DEFAULT_CODE_SERVER_PORT = 8080;
30
+ const DEFAULT_CODE_SERVER_AUTH_MODE = "none";
31
+ const DEFAULT_OMNIROUTE_HOST = "127.0.0.1";
32
+ const DEFAULT_OMNIROUTE_PORT = 39001;
33
+ const MANAGED_SERVER_INTEGRATION_DEPENDENCIES = [
34
+ "code-server",
35
+ "omniroute"
36
+ ];
37
+ const VSCODE_SERVER_SOURCE_EXTERNAL = "external";
38
+ const VSCODE_SERVER_SECRET_SOURCE_BOOTSTRAP = "bootstrap";
39
+ const OMNIROUTE_SOURCE_EXTERNAL = "external";
23
40
  export async function installManagedServer(options = {}) {
24
41
  const logger = options.logger ?? (() => undefined);
25
42
  const runner = options.runner ?? runCommand;
26
43
  const manifest = await loadRuntimeManifest({ manifestPath: options.manifestPath });
44
+ const resolvedInstallOptions = applyManifestServerInstallDefaults(manifest, options);
27
45
  const paths = resolveRuntimePaths(manifest, { runtimeRoot: options.runtimeRoot });
28
46
  const serverComponent = assertServerComponent(manifest.componentMap.get("server"));
29
- const resolvedRegistryMirror = validateRegistryMirror(options.registryMirror, "registryMirror");
30
- const archive = await resolveServerArchive(options, logger);
47
+ const archive = await resolveServerArchive(resolvedInstallOptions, logger);
48
+ const installedVersion = resolveManagedServerVersion(archive);
31
49
  try {
32
50
  const staged = await stageServerArchive({
33
51
  archivePath: archive.archivePath,
52
+ version: installedVersion,
34
53
  paths,
35
54
  runner,
36
55
  serverComponent,
56
+ force: options.force ?? false,
37
57
  extractArchive: options.extractArchive
38
58
  });
39
59
  const installRuntimeFn = options.installRuntimeFn ?? installRuntime;
60
+ const runtimeDependencyComponents = [...serverComponent.lifecycleDependencies];
40
61
  const runtimeLifecycle = await installRuntimeFn({
41
62
  manifestPath: options.manifestPath,
42
63
  runtimeRoot: options.runtimeRoot,
43
- components: ["server"],
64
+ components: runtimeDependencyComponents,
44
65
  force: options.force ?? false,
45
66
  downloadCache: options.downloadCache,
46
67
  downloadCacheDir: options.downloadCacheDir,
68
+ npmRegistryMirror: options.registryMirror,
69
+ pm2VersionOverride: options.pm2Version,
47
70
  logger
48
71
  });
49
- let pm2Summary;
50
72
  if (options.ensurePm2 ?? true) {
51
- pm2Summary = await ensureManagedPm2({
52
- paths,
53
- pm2Version: options.pm2Version,
54
- registryMirror: resolvedRegistryMirror,
55
- force: options.force ?? false,
56
- logger,
57
- syncNpmGlobalsFn: options.syncNpmGlobalsFn
73
+ await ensureManagedPm2Package(manifest, paths, {
74
+ npmRegistryMirror: options.registryMirror,
75
+ pm2VersionOverride: options.pm2Version
58
76
  });
59
77
  }
78
+ const versionStateContext = await resolveManagedServerVersionStateContext({
79
+ manifestPath: options.manifestPath,
80
+ runtimeRoot: options.runtimeRoot
81
+ });
82
+ const sharedDataRoot = getServerSharedDataRoot(versionStateContext.paths);
83
+ await registerManagedServerVersion(versionStateContext.statePath, {
84
+ version: installedVersion,
85
+ installPath: staged.stagedPath,
86
+ installedAt: new Date().toISOString(),
87
+ source: {
88
+ kind: archive.kind,
89
+ locator: archive.locator,
90
+ assetName: archive.assetName
91
+ }
92
+ });
60
93
  const queryRuntimeStateFn = options.queryRuntimeStateFn ?? queryRuntimeState;
61
94
  const runtimeState = await queryRuntimeStateFn({
62
95
  manifestPath: options.manifestPath,
@@ -69,14 +102,17 @@ export async function installManagedServer(options = {}) {
69
102
  version: archive.version,
70
103
  assetName: archive.assetName
71
104
  },
105
+ installedVersion,
106
+ activeVersion: installedVersion,
72
107
  stagedPath: staged.stagedPath,
73
108
  stagedDllPath: staged.stagedDllPath,
109
+ statePath: versionStateContext.statePath,
110
+ sharedDataRoot,
74
111
  runtimeLifecycle,
75
112
  runtimeState,
76
113
  pm2: {
77
114
  ensured: options.ensurePm2 ?? true,
78
- versionRange: options.ensurePm2 === false ? null : normalizePm2Version(options.pm2Version),
79
- summary: pm2Summary
115
+ versionRange: options.ensurePm2 === false ? null : normalizePm2Version(options.pm2Version)
80
116
  }
81
117
  };
82
118
  }
@@ -84,9 +120,73 @@ export async function installManagedServer(options = {}) {
84
120
  await archive.cleanup();
85
121
  }
86
122
  }
123
+ function applyManifestServerInstallDefaults(manifest, options) {
124
+ if (options.archivePath ||
125
+ options.packageDirectory ||
126
+ options.url ||
127
+ options.indexUrl ||
128
+ options.indexChannel ||
129
+ options.indexVersion) {
130
+ return options;
131
+ }
132
+ const serverComponent = manifest.componentMap.get("server");
133
+ const activeVersion = serverComponent?.releasedService?.activeVersion?.trim();
134
+ if (!activeVersion) {
135
+ return options;
136
+ }
137
+ return {
138
+ ...options,
139
+ indexVersion: activeVersion
140
+ };
141
+ }
87
142
  export async function startManagedServer(options = {}) {
88
143
  return runManagedServerAction("start", options);
89
144
  }
145
+ export async function listManagedServerVersions(options = {}) {
146
+ const context = await resolveManagedServerVersionStateContext(options);
147
+ const state = await readManagedServerVersionState(context.statePath);
148
+ return {
149
+ activeVersion: state.activeVersion,
150
+ versions: await listManagedServerVersionSummaries(context.statePath),
151
+ statePath: context.statePath,
152
+ sharedDataRoot: getServerSharedDataRoot(context.paths)
153
+ };
154
+ }
155
+ export async function useManagedServerVersion(options) {
156
+ const version = options.version.trim();
157
+ if (!version) {
158
+ throw new ManagedServerError("Managed server version must be a non-empty string.");
159
+ }
160
+ const context = await resolveManagedServerVersionStateContext(options);
161
+ const state = await readManagedServerVersionState(context.statePath);
162
+ const previousActiveVersion = state.activeVersion;
163
+ await setActiveManagedServerVersion(context.statePath, version);
164
+ return {
165
+ previousActiveVersion,
166
+ activeVersion: version,
167
+ statePath: context.statePath
168
+ };
169
+ }
170
+ export async function removeManagedServerInstalledVersion(options) {
171
+ const version = options.version.trim();
172
+ if (!version) {
173
+ throw new ManagedServerError("Managed server version must be a non-empty string.");
174
+ }
175
+ const context = await resolveManagedServerVersionStateContext(options);
176
+ const state = await readManagedServerVersionState(context.statePath);
177
+ const installedVersion = state.versions[version];
178
+ if (!installedVersion) {
179
+ throw new ManagedServerError(`Managed server version ${version} is not installed.`);
180
+ }
181
+ await rm(installedVersion.installPath, { recursive: true, force: true });
182
+ const nextState = await removeInstalledManagedServerVersion(context.statePath, version);
183
+ return {
184
+ activeVersion: nextState.activeVersion,
185
+ removedVersion: version,
186
+ removedPath: installedVersion.installPath,
187
+ statePath: context.statePath
188
+ };
189
+ }
90
190
  export async function restartManagedServer(options = {}) {
91
191
  return runManagedServerAction("restart", options);
92
192
  }
@@ -97,27 +197,160 @@ export async function getManagedServerStatus(options = {}) {
97
197
  return runManagedServerAction("status", options);
98
198
  }
99
199
  export async function resolveManagedServerStartupEnvironment(options = {}) {
200
+ const environmentOverrides = (await resolveManagedServerEnvironment(options)).environment;
100
201
  return resolveManagedPm2Environment({
101
202
  manifestPath: options.manifestPath,
102
203
  runtimeRoot: options.runtimeRoot,
103
204
  service: "server",
104
- nameIdentifierValue: options.instanceName?.trim() || DEFAULT_PM2_INSTANCE
205
+ nameIdentifierValue: options.instanceName?.trim(),
206
+ environmentOverrides
105
207
  });
106
208
  }
107
209
  async function runManagedServerAction(action, options) {
210
+ if (action === "start") {
211
+ await ensureManagedServerDependenciesStarted(options);
212
+ }
213
+ const environmentOverrides = (await resolveManagedServerEnvironment(options)).environment;
108
214
  return runManagedPm2Command({
109
215
  manifestPath: options.manifestPath,
110
216
  runtimeRoot: options.runtimeRoot,
111
217
  service: "server",
112
218
  action,
113
- nameIdentifierValue: options.instanceName?.trim() || DEFAULT_PM2_INSTANCE
219
+ nameIdentifierValue: options.instanceName?.trim(),
220
+ environmentOverrides
221
+ });
222
+ }
223
+ async function ensureManagedServerDependenciesStarted(options) {
224
+ const manifest = await loadRuntimeManifest({ manifestPath: options.manifestPath });
225
+ const managedPm2ServiceNames = new Set(supportedPm2Services);
226
+ const dependencyServices = MANAGED_SERVER_INTEGRATION_DEPENDENCIES.filter((service) => service !== "server" && managedPm2ServiceNames.has(service) && manifest.componentMap.has(service));
227
+ for (const service of dependencyServices) {
228
+ await runManagedPm2Command({
229
+ manifestPath: options.manifestPath,
230
+ runtimeRoot: options.runtimeRoot,
231
+ service,
232
+ action: "start",
233
+ nameIdentifierValue: options.instanceName?.trim()
234
+ });
235
+ }
236
+ }
237
+ export async function resolveManagedServerEnvironment(options) {
238
+ const serverConfig = await getManagedServerConfig({
239
+ manifestPath: options.manifestPath,
240
+ runtimeRoot: options.runtimeRoot
114
241
  });
242
+ const sharedDataRoot = dirname(dirname(serverConfig.configPath));
243
+ const systemDataRoot = join(sharedDataRoot, "data");
244
+ const integrationEnvironment = await resolveManagedServerIntegrationEnvironment(options);
245
+ return {
246
+ host: serverConfig.host,
247
+ port: serverConfig.port,
248
+ aspNetCoreUrls: serverConfig.aspNetCoreUrls,
249
+ configPath: serverConfig.configPath,
250
+ sharedDataRoot,
251
+ environment: {
252
+ ASPNETCORE_URLS: serverConfig.aspNetCoreUrls,
253
+ Urls: serverConfig.aspNetCoreUrls,
254
+ DATADIR: systemDataRoot,
255
+ ...integrationEnvironment
256
+ }
257
+ };
258
+ }
259
+ async function resolveManagedServerIntegrationEnvironment(options) {
260
+ const manifest = await loadRuntimeManifest({ manifestPath: options.manifestPath });
261
+ const paths = resolveRuntimePaths(manifest, { runtimeRoot: options.runtimeRoot });
262
+ return {
263
+ ...(await resolveManagedVsCodeServerEnvironment(manifest.componentMap.get("code-server"), paths)),
264
+ ...(await resolveManagedOmniRouteEnvironment(manifest.componentMap.get("omniroute"), paths))
265
+ };
266
+ }
267
+ async function resolveManagedVsCodeServerEnvironment(component, paths) {
268
+ if (!component) {
269
+ return {};
270
+ }
271
+ const config = await readYamlObject(join(getComponentConfigDirectory(paths, component.name, component.runtimeDataDir), "config.yaml"));
272
+ const address = parseConfiguredAddress(readConfigString(config, "bind-addr"), DEFAULT_CODE_SERVER_HOST, DEFAULT_CODE_SERVER_PORT);
273
+ const authMode = readConfigString(config, "auth") ?? DEFAULT_CODE_SERVER_AUTH_MODE;
274
+ const secret = readConfigString(config, "password");
275
+ return {
276
+ VsCodeServer__Host: address.host,
277
+ VsCodeServer__Port: String(address.port),
278
+ VsCodeServer__AuthMode: authMode,
279
+ VsCodeServer__Source: VSCODE_SERVER_SOURCE_EXTERNAL,
280
+ VsCodeServer__SourceLocked: "true",
281
+ ...(secret
282
+ ? {
283
+ VsCodeServer__Secret: secret,
284
+ VsCodeServer__SecretSource: VSCODE_SERVER_SECRET_SOURCE_BOOTSTRAP
285
+ }
286
+ : {})
287
+ };
288
+ }
289
+ async function resolveManagedOmniRouteEnvironment(component, paths) {
290
+ if (!component) {
291
+ return {};
292
+ }
293
+ const config = await readYamlObject(join(getComponentConfigDirectory(paths, component.name, component.runtimeDataDir), "config.yaml"));
294
+ const address = parseConfiguredAddress(readConfigString(config, "listen"), DEFAULT_OMNIROUTE_HOST, DEFAULT_OMNIROUTE_PORT);
295
+ const baseUrl = buildHttpBaseUrl(address.host, address.port);
296
+ return {
297
+ OmniRoute__Enabled: "true",
298
+ OmniRoute__ApiEndpoint: baseUrl,
299
+ OmniRoute__DefaultBaseUrl: baseUrl,
300
+ OmniRoute__DefaultBaseUrlSource: OMNIROUTE_SOURCE_EXTERNAL,
301
+ OmniRoute__DefaultBaseUrlLocked: "true"
302
+ };
303
+ }
304
+ async function readYamlObject(filePath) {
305
+ try {
306
+ const content = await readFile(filePath, "utf8");
307
+ const parsed = parseYaml(content);
308
+ return isRecord(parsed) ? parsed : undefined;
309
+ }
310
+ catch {
311
+ return undefined;
312
+ }
313
+ }
314
+ function parseConfiguredAddress(value, defaultHost, defaultPort) {
315
+ if (!value) {
316
+ return { host: defaultHost, port: defaultPort };
317
+ }
318
+ try {
319
+ const parsed = new URL(value.includes("://") ? value : `http://${value}`);
320
+ const host = parsed.hostname || defaultHost;
321
+ const port = Number(parsed.port || String(defaultPort));
322
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
323
+ return { host: defaultHost, port: defaultPort };
324
+ }
325
+ return { host, port };
326
+ }
327
+ catch {
328
+ return { host: defaultHost, port: defaultPort };
329
+ }
330
+ }
331
+ function buildHttpBaseUrl(host, port) {
332
+ return new URL(`http://${host}:${port}`).toString();
333
+ }
334
+ function readConfigString(config, key) {
335
+ const value = config?.[key];
336
+ if (typeof value !== "string") {
337
+ return undefined;
338
+ }
339
+ const normalized = value.trim();
340
+ return normalized ? normalized : undefined;
341
+ }
342
+ function isRecord(value) {
343
+ return typeof value === "object" && value !== null && !Array.isArray(value);
115
344
  }
116
345
  async function resolveServerArchive(options, logger) {
346
+ const useHttpIndex = options.indexUrl !== undefined ||
347
+ options.indexChannel !== undefined ||
348
+ options.indexVersion !== undefined;
117
349
  const selectedModes = [
118
350
  options.archivePath ? "archive" : null,
119
351
  options.packageDirectory ? "package-directory" : null,
120
- options.url ? "url" : null
352
+ options.url ? "url" : null,
353
+ useHttpIndex ? "index-url" : null
121
354
  ].filter(Boolean);
122
355
  if (selectedModes.length > 1) {
123
356
  throw new ManagedServerError("Choose only one server package source: --archive, --package-dir, or --url.");
@@ -157,8 +390,56 @@ async function resolveServerArchive(options, logger) {
157
390
  url: options.url
158
391
  }, options);
159
392
  }
393
+ if (useHttpIndex) {
394
+ return resolveHttpIndexArchive(options);
395
+ }
396
+ try {
397
+ return await resolveHttpIndexArchive(options);
398
+ }
399
+ catch (error) {
400
+ logger(`Default HTTP index ${DEFAULT_SERVER_HTTP_INDEX_URL} unavailable. Falling back to GitHub release source.`);
401
+ if (error instanceof ManagedServerError) {
402
+ logger(`HTTP index resolution detail: ${error.message}`);
403
+ }
404
+ }
160
405
  return resolveGitHubReleaseArchive(options);
161
406
  }
407
+ async function resolveHttpIndexArchive(options) {
408
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
409
+ if (typeof fetchImpl !== "function") {
410
+ throw new ManagedServerError("Global fetch is unavailable for HTTP index download.");
411
+ }
412
+ const indexUrl = normalizeOfficialServerHttpIndexUrl(options.indexUrl);
413
+ const response = await fetchImpl(indexUrl, {
414
+ headers: {
415
+ Accept: "application/json",
416
+ "User-Agent": "@hagicode/hagiscript"
417
+ }
418
+ });
419
+ if (!response.ok) {
420
+ throw new ManagedServerError(`Failed to read HTTP index from ${indexUrl}: HTTP ${response.status}`);
421
+ }
422
+ const indexPayload = (await response.json());
423
+ const selection = selectArchiveFromHttpIndex(indexPayload, {
424
+ indexUrl,
425
+ channel: options.indexChannel,
426
+ version: options.indexVersion,
427
+ assetName: options.assetName
428
+ });
429
+ return downloadRemoteArchive({
430
+ kind: "http-index",
431
+ locator: selection.locator,
432
+ assetName: selection.assetName,
433
+ version: selection.version,
434
+ url: selection.downloadUrl
435
+ }, options);
436
+ }
437
+ function normalizeOfficialServerHttpIndexUrl(indexUrl) {
438
+ const normalized = indexUrl?.trim() || DEFAULT_SERVER_HTTP_INDEX_URL;
439
+ return LEGACY_SERVER_HTTP_INDEX_URLS.has(normalized)
440
+ ? DEFAULT_SERVER_HTTP_INDEX_URL
441
+ : normalized;
442
+ }
162
443
  async function resolveGitHubReleaseArchive(options) {
163
444
  const fetchImpl = options.fetchImpl ?? globalThis.fetch;
164
445
  if (typeof fetchImpl !== "function") {
@@ -237,23 +518,200 @@ async function downloadRemoteArchive(source, options) {
237
518
  throw error;
238
519
  }
239
520
  }
521
+ function selectArchiveFromHttpIndex(payload, options) {
522
+ const desiredVersion = options.version?.trim() || null;
523
+ const desiredChannel = options.channel?.trim() || null;
524
+ const desiredAssetName = options.assetName?.trim() || null;
525
+ const defaultSuffix = getDefaultManagedServerAssetSuffix();
526
+ const versionEntries = asArray(payload.versions)
527
+ .map((entry) => normalizeHttpIndexVersionEntry(entry))
528
+ .filter((entry) => entry !== null);
529
+ const indexLevelAssets = asArray(payload.assets)
530
+ .map((entry) => normalizeHttpIndexAssetEntry(entry))
531
+ .filter((entry) => entry !== null);
532
+ const candidateVersions = versionEntries.filter((entry) => {
533
+ if (desiredVersion && entry.version !== desiredVersion) {
534
+ return false;
535
+ }
536
+ if (desiredChannel && !entry.channels.has(desiredChannel)) {
537
+ return false;
538
+ }
539
+ return true;
540
+ });
541
+ const orderedVersions = candidateVersions
542
+ .slice()
543
+ .sort((left, right) => compareVersionValues(left.version, right.version));
544
+ for (const versionEntry of orderedVersions) {
545
+ const match = selectHttpIndexAsset(versionEntry.assets, {
546
+ desiredAssetName,
547
+ defaultSuffix
548
+ });
549
+ if (match) {
550
+ return {
551
+ locator: `${options.indexUrl}@${versionEntry.version ?? "unknown"}`,
552
+ version: versionEntry.version,
553
+ assetName: match.assetName,
554
+ downloadUrl: match.downloadUrl
555
+ };
556
+ }
557
+ }
558
+ if (!desiredVersion && !desiredChannel && indexLevelAssets.length > 0) {
559
+ const match = selectHttpIndexAsset(indexLevelAssets, {
560
+ desiredAssetName,
561
+ defaultSuffix
562
+ });
563
+ if (match) {
564
+ return {
565
+ locator: `${options.indexUrl}@index`,
566
+ version: null,
567
+ assetName: match.assetName,
568
+ downloadUrl: match.downloadUrl
569
+ };
570
+ }
571
+ }
572
+ const scope = [
573
+ desiredVersion ? `version=${desiredVersion}` : null,
574
+ desiredChannel ? `channel=${desiredChannel}` : null,
575
+ desiredAssetName ? `asset=${desiredAssetName}` : null
576
+ ]
577
+ .filter(Boolean)
578
+ .join(", ");
579
+ throw new ManagedServerError(`HTTP index ${options.indexUrl} does not expose a matching server archive${scope ? ` (${scope})` : ""}.`);
580
+ }
581
+ function normalizeHttpIndexVersionEntry(value) {
582
+ if (!value || typeof value !== "object") {
583
+ return null;
584
+ }
585
+ const entry = value;
586
+ const version = normalizeString(entry.version) ?? normalizeString(entry.tag) ?? null;
587
+ const channels = new Set();
588
+ const singleChannel = normalizeString(entry.channel);
589
+ if (singleChannel) {
590
+ channels.add(singleChannel);
591
+ }
592
+ for (const item of asArray(entry.channels)) {
593
+ const normalized = normalizeString(item);
594
+ if (normalized) {
595
+ channels.add(normalized);
596
+ }
597
+ }
598
+ const assets = [...asArray(entry.assets), ...asArray(entry.files)]
599
+ .map((item) => normalizeHttpIndexAssetEntry(item))
600
+ .filter((item) => item !== null);
601
+ return {
602
+ version,
603
+ channels,
604
+ assets
605
+ };
606
+ }
607
+ function normalizeHttpIndexAssetEntry(value) {
608
+ if (!value || typeof value !== "object") {
609
+ return null;
610
+ }
611
+ const entry = value;
612
+ const assetName = normalizeString(entry.name);
613
+ const directUrl = normalizeString(entry.url) ??
614
+ normalizeString(entry.directUrl) ??
615
+ normalizeString(entry.downloadUrl) ??
616
+ normalizeString(entry.browser_download_url) ??
617
+ normalizeString(entry.browserDownloadUrl);
618
+ if (assetName && directUrl) {
619
+ return {
620
+ assetName,
621
+ downloadUrl: directUrl
622
+ };
623
+ }
624
+ const sourceUrl = resolveHttpIndexDownloadSourceUrl(entry.downloadSources ?? entry.sources);
625
+ if (!assetName || !sourceUrl) {
626
+ return null;
627
+ }
628
+ return {
629
+ assetName,
630
+ downloadUrl: sourceUrl
631
+ };
632
+ }
633
+ function resolveHttpIndexDownloadSourceUrl(value) {
634
+ const sources = asArray(value)
635
+ .map((entry) => {
636
+ if (!entry || typeof entry !== "object") {
637
+ return null;
638
+ }
639
+ const source = entry;
640
+ const url = normalizeString(source.url);
641
+ if (!url) {
642
+ return null;
643
+ }
644
+ return {
645
+ url,
646
+ primary: source.primary === true
647
+ };
648
+ })
649
+ .filter((entry) => !!entry);
650
+ if (sources.length === 0) {
651
+ return null;
652
+ }
653
+ return sources.find((entry) => entry.primary)?.url ?? sources[0].url;
654
+ }
655
+ function selectHttpIndexAsset(assets, options) {
656
+ if (options.desiredAssetName) {
657
+ return assets.find((entry) => entry.assetName === options.desiredAssetName) ?? null;
658
+ }
659
+ return (assets.find((entry) => entry.assetName.endsWith(options.defaultSuffix)) ??
660
+ assets[0] ??
661
+ null);
662
+ }
663
+ function asArray(value) {
664
+ return Array.isArray(value) ? value : [];
665
+ }
666
+ function normalizeString(value) {
667
+ if (typeof value !== "string") {
668
+ return undefined;
669
+ }
670
+ const normalized = value.trim();
671
+ return normalized ? normalized : undefined;
672
+ }
673
+ function compareVersionValues(left, right) {
674
+ if (left && right) {
675
+ const normalizedLeft = normalizeSemverLike(left);
676
+ const normalizedRight = normalizeSemverLike(right);
677
+ if (normalizedLeft && normalizedRight) {
678
+ return semver.rcompare(normalizedLeft, normalizedRight);
679
+ }
680
+ }
681
+ if (left === right) {
682
+ return 0;
683
+ }
684
+ if (left === null) {
685
+ return 1;
686
+ }
687
+ if (right === null) {
688
+ return -1;
689
+ }
690
+ return right.localeCompare(left);
691
+ }
692
+ function normalizeSemverLike(value) {
693
+ const normalized = value.startsWith("v") ? value.slice(1) : value;
694
+ return semver.valid(normalized);
695
+ }
240
696
  async function stageServerArchive(options) {
241
697
  const extractRoot = await mkdtemp(join(tmpdir(), "hagiscript-server-extract-"));
242
- const componentRoot = getComponentManagedRoot(options.paths, options.serverComponent.name);
243
- const targetCurrentRoot = join(componentRoot, "current");
698
+ const targetVersionRoot = getServerVersionRoot(options.paths, options.version);
244
699
  try {
245
700
  await (options.extractArchive ?? extractManagedServerArchive)(options.archivePath, extractRoot, options.runner);
246
701
  const payloadRoot = await locateManagedServerPayloadRoot(extractRoot);
247
- const stagedDllPath = join(targetCurrentRoot, "lib", "PCode.Web.dll");
248
- await mkdir(componentRoot, { recursive: true });
249
- await rm(targetCurrentRoot, { recursive: true, force: true });
250
- await cp(payloadRoot, targetCurrentRoot, {
702
+ const stagedDllPath = join(targetVersionRoot, "lib", "PCode.Web.dll");
703
+ if ((await pathExists(targetVersionRoot)) && !options.force) {
704
+ throw new ManagedServerError(`Managed server version ${options.version} is already installed at ${targetVersionRoot}. Use --force to replace it.`);
705
+ }
706
+ await mkdir(targetVersionRoot, { recursive: true });
707
+ await rm(targetVersionRoot, { recursive: true, force: true });
708
+ await cp(payloadRoot, targetVersionRoot, {
251
709
  recursive: true,
252
710
  force: true
253
711
  });
254
- await assertValidManagedServerPayload(targetCurrentRoot);
712
+ await assertValidManagedServerPayload(targetVersionRoot);
255
713
  return {
256
- stagedPath: targetCurrentRoot,
714
+ stagedPath: targetVersionRoot,
257
715
  stagedDllPath
258
716
  };
259
717
  }
@@ -261,40 +719,6 @@ async function stageServerArchive(options) {
261
719
  await rm(extractRoot, { recursive: true, force: true }).catch(() => undefined);
262
720
  }
263
721
  }
264
- async function ensureManagedPm2(options) {
265
- const syncNpmGlobalsFn = options.syncNpmGlobalsFn ?? syncNpmGlobals;
266
- const tempDirectory = await mkdtemp(join(tmpdir(), "hagiscript-pm2-manifest-"));
267
- const manifestPath = join(tempDirectory, "manifest.json");
268
- const versionRange = normalizePm2Version(options.pm2Version);
269
- try {
270
- await writeFile(manifestPath, `${JSON.stringify({
271
- packages: {
272
- pm2: {
273
- version: versionRange,
274
- ...(semver.valid(versionRange) ? { target: versionRange } : {})
275
- }
276
- }
277
- }, null, 2)}\n`);
278
- const summary = await syncNpmGlobalsFn({
279
- runtimePath: options.paths.nodeRuntime,
280
- manifestPath,
281
- registryMirror: options.registryMirror,
282
- force: options.force,
283
- npmOptions: {
284
- prefix: options.paths.npmPrefix
285
- },
286
- onLog: (event) => {
287
- if (event.type === "summary") {
288
- options.logger(`Managed pm2 ready in ${options.paths.npmPrefix} (${event.summary.changedCount} change(s))`);
289
- }
290
- }
291
- });
292
- return summary;
293
- }
294
- finally {
295
- await rm(tempDirectory, { recursive: true, force: true }).catch(() => undefined);
296
- }
297
- }
298
722
  async function selectServerArchiveFromDirectory(directory, assetName) {
299
723
  const entries = await readdir(directory, { withFileTypes: true });
300
724
  const zipFiles = entries
@@ -343,7 +767,7 @@ async function extractManagedServerArchive(archivePath, extractRoot, runner) {
343
767
  try {
344
768
  await runner("unzip", ["-q", archivePath, "-d", extractRoot]);
345
769
  }
346
- catch (error) {
770
+ catch {
347
771
  await runner("bsdtar", ["-xf", archivePath, "-C", extractRoot], {
348
772
  maxBuffer: 10 * 1024 * 1024
349
773
  });
@@ -416,6 +840,15 @@ function extractVersionFromServerAssetName(assetName) {
416
840
  const match = new RegExp(`^hagicode-(.+)-${suffix}$`, "u").exec(assetName);
417
841
  return match?.[1] ?? null;
418
842
  }
843
+ function resolveManagedServerVersion(archive) {
844
+ const detectedVersion = archive.version ?? extractVersionFromServerAssetName(archive.assetName);
845
+ const normalizedSemver = detectedVersion ? normalizeSemverLike(detectedVersion) : null;
846
+ const normalizedVersion = normalizedSemver ?? detectedVersion?.trim() ?? "";
847
+ if (!normalizedVersion) {
848
+ 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()}.`);
849
+ }
850
+ return normalizedVersion;
851
+ }
419
852
  function inferArchiveNameFromUrl(urlValue) {
420
853
  try {
421
854
  const parsed = new URL(urlValue);