@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.
- package/README.md +237 -175
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/manifest-commands.d.ts +2 -0
- package/dist/commands/manifest-commands.js +136 -0
- package/dist/commands/manifest-commands.js.map +1 -0
- package/dist/commands/npm-sync-commands.js +63 -6
- package/dist/commands/npm-sync-commands.js.map +1 -1
- package/dist/commands/server-commands.js +181 -7
- package/dist/commands/server-commands.js.map +1 -1
- package/dist/index.d.ts +7 -4
- package/dist/index.js +7 -4
- package/dist/index.js.map +1 -1
- package/dist/runtime/manifest-manager.d.ts +57 -0
- package/dist/runtime/manifest-manager.js +213 -0
- package/dist/runtime/manifest-manager.js.map +1 -0
- package/dist/runtime/npm-sync.d.ts +3 -1
- package/dist/runtime/npm-sync.js +31 -3
- package/dist/runtime/npm-sync.js.map +1 -1
- package/dist/runtime/pm2-manager.d.ts +4 -2
- package/dist/runtime/pm2-manager.js +181 -53
- package/dist/runtime/pm2-manager.js.map +1 -1
- package/dist/runtime/runtime-executor.d.ts +6 -0
- package/dist/runtime/runtime-executor.js +55 -0
- package/dist/runtime/runtime-executor.js.map +1 -1
- package/dist/runtime/runtime-manager.d.ts +9 -0
- package/dist/runtime/runtime-manager.js +146 -16
- package/dist/runtime/runtime-manager.js.map +1 -1
- package/dist/runtime/runtime-manifest.d.ts +5 -1
- package/dist/runtime/runtime-manifest.js +23 -8
- package/dist/runtime/runtime-manifest.js.map +1 -1
- package/dist/runtime/runtime-paths.d.ts +11 -1
- package/dist/runtime/runtime-paths.js +41 -5
- package/dist/runtime/runtime-paths.js.map +1 -1
- package/dist/runtime/server-config.d.ts +21 -0
- package/dist/runtime/server-config.js +169 -0
- package/dist/runtime/server-config.js.map +1 -0
- package/dist/runtime/server-manager.d.ts +48 -4
- package/dist/runtime/server-manager.js +499 -66
- package/dist/runtime/server-manager.js.map +1 -1
- package/dist/runtime/server-version-state.d.ts +43 -0
- package/dist/runtime/server-version-state.js +156 -0
- package/dist/runtime/server-version-state.js.map +1 -0
- package/dist/runtime/tool-sync-catalog.config.json +0 -18
- package/dist/runtime/tool-sync-catalog.js +1 -1
- package/dist/runtime/tool-sync-catalog.js.map +1 -1
- package/package.json +23 -2
- package/runtime/lib/runtime-script-lib.mjs +128 -69
- package/runtime/manifest.yaml +96 -81
- package/runtime/scripts/configure-code-server.mjs +14 -3
- package/runtime/scripts/configure-omniroute.mjs +15 -5
- package/runtime/scripts/install-code-server.mjs +20 -23
- package/runtime/scripts/install-omniroute.mjs +19 -22
- package/runtime/scripts/install-pm2.mjs +39 -0
- package/runtime/scripts/remove-pm2.mjs +25 -0
- package/runtime/templates/code-server-config.yaml +0 -5
- 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 {
|
|
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 {
|
|
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
|
|
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
|
|
30
|
-
const
|
|
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:
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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()
|
|
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()
|
|
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
|
|
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(
|
|
248
|
-
await
|
|
249
|
-
|
|
250
|
-
|
|
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(
|
|
712
|
+
await assertValidManagedServerPayload(targetVersionRoot);
|
|
255
713
|
return {
|
|
256
|
-
stagedPath:
|
|
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
|
|
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);
|