@hagicode/hagiscript 0.1.6 → 0.1.8

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 (50) hide show
  1. package/README.md +144 -201
  2. package/bin/runtime +16 -0
  3. package/dist/cli.js +4 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/pm2-commands.d.ts +2 -0
  6. package/dist/commands/pm2-commands.js +54 -0
  7. package/dist/commands/pm2-commands.js.map +1 -0
  8. package/dist/commands/runtime-commands.d.ts +2 -0
  9. package/dist/commands/runtime-commands.js +136 -0
  10. package/dist/commands/runtime-commands.js.map +1 -0
  11. package/dist/index.d.ts +6 -0
  12. package/dist/index.js +6 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/runtime/command-launch.js +1 -0
  15. package/dist/runtime/command-launch.js.map +1 -1
  16. package/dist/runtime/pm2-manager.d.ts +54 -0
  17. package/dist/runtime/pm2-manager.js +258 -0
  18. package/dist/runtime/pm2-manager.js.map +1 -0
  19. package/dist/runtime/runtime-executor.d.ts +45 -0
  20. package/dist/runtime/runtime-executor.js +153 -0
  21. package/dist/runtime/runtime-executor.js.map +1 -0
  22. package/dist/runtime/runtime-manager.d.ts +79 -0
  23. package/dist/runtime/runtime-manager.js +651 -0
  24. package/dist/runtime/runtime-manager.js.map +1 -0
  25. package/dist/runtime/runtime-manifest.d.ts +77 -0
  26. package/dist/runtime/runtime-manifest.js +277 -0
  27. package/dist/runtime/runtime-manifest.js.map +1 -0
  28. package/dist/runtime/runtime-paths.d.ts +31 -0
  29. package/dist/runtime/runtime-paths.js +77 -0
  30. package/dist/runtime/runtime-paths.js.map +1 -0
  31. package/dist/runtime/runtime-state.d.ts +45 -0
  32. package/dist/runtime/runtime-state.js +82 -0
  33. package/dist/runtime/runtime-state.js.map +1 -0
  34. package/package.json +9 -5
  35. package/runtime/lib/runtime-script-lib.mjs +531 -0
  36. package/runtime/manifest.yaml +136 -0
  37. package/runtime/scripts/configure-code-server.mjs +14 -0
  38. package/runtime/scripts/configure-omniroute.mjs +16 -0
  39. package/runtime/scripts/install-code-server.mjs +53 -0
  40. package/runtime/scripts/install-dotnet.mjs +24 -0
  41. package/runtime/scripts/install-node.mjs +4 -0
  42. package/runtime/scripts/install-npm-packages.mjs +4 -0
  43. package/runtime/scripts/install-omniroute.mjs +60 -0
  44. package/runtime/scripts/remove-npm-packages.mjs +4 -0
  45. package/runtime/scripts/update-npm-packages.mjs +4 -0
  46. package/runtime/scripts/verify-dotnet.mjs +10 -0
  47. package/runtime/scripts/verify-node.mjs +4 -0
  48. package/runtime/templates/code-server-config.yaml +5 -0
  49. package/runtime/templates/omniroute-config.yaml +4 -0
  50. package/README_cn.md +0 -266
@@ -0,0 +1,82 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { dirname } from "node:path";
3
+ export class RuntimeStateError extends Error {
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = "RuntimeStateError";
7
+ }
8
+ }
9
+ export function createInitialRuntimeState(manifest, paths) {
10
+ return {
11
+ schemaVersion: 1,
12
+ runtime: {
13
+ name: manifest.runtime.name,
14
+ version: manifest.runtime.version,
15
+ manifestPath: manifest.manifestPath
16
+ },
17
+ managedRoot: paths.root,
18
+ managedPaths: paths,
19
+ components: {},
20
+ lastOperation: null
21
+ };
22
+ }
23
+ export async function readRuntimeState(statePath) {
24
+ let parsed;
25
+ try {
26
+ parsed = JSON.parse(await readFile(statePath, "utf8"));
27
+ }
28
+ catch (error) {
29
+ if (isMissingFileError(error)) {
30
+ return null;
31
+ }
32
+ const message = error instanceof Error ? error.message : String(error);
33
+ throw new RuntimeStateError(`Failed to read runtime state ${statePath}: ${message}`);
34
+ }
35
+ return validateRuntimeState(parsed, statePath);
36
+ }
37
+ export async function writeRuntimeState(statePath, state) {
38
+ await mkdir(dirname(statePath), { recursive: true });
39
+ await writeFile(statePath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
40
+ }
41
+ export function mergeRuntimeState(manifest, paths, state) {
42
+ const nextState = state ?? createInitialRuntimeState(manifest, paths);
43
+ return {
44
+ ...nextState,
45
+ runtime: {
46
+ name: manifest.runtime.name,
47
+ version: manifest.runtime.version,
48
+ manifestPath: manifest.manifestPath
49
+ },
50
+ managedRoot: paths.root,
51
+ managedPaths: paths,
52
+ components: { ...nextState.components }
53
+ };
54
+ }
55
+ function validateRuntimeState(value, statePath) {
56
+ if (!isRecord(value)) {
57
+ throw new RuntimeStateError(`Runtime state ${statePath} must be a JSON object.`);
58
+ }
59
+ if (value.schemaVersion !== 1) {
60
+ throw new RuntimeStateError(`Runtime state ${statePath} has unsupported schemaVersion ${String(value.schemaVersion)}.`);
61
+ }
62
+ if (!isRecord(value.runtime) || typeof value.runtime.name !== "string" || typeof value.runtime.version !== "string") {
63
+ throw new RuntimeStateError(`Runtime state ${statePath} is missing runtime metadata.`);
64
+ }
65
+ if (!isRecord(value.managedPaths)) {
66
+ throw new RuntimeStateError(`Runtime state ${statePath} is missing managedPaths.`);
67
+ }
68
+ if (!isRecord(value.components)) {
69
+ throw new RuntimeStateError(`Runtime state ${statePath} is missing components.`);
70
+ }
71
+ return value;
72
+ }
73
+ function isRecord(value) {
74
+ return typeof value === "object" && value !== null && !Array.isArray(value);
75
+ }
76
+ function isMissingFileError(error) {
77
+ return (error instanceof Error &&
78
+ "code" in error &&
79
+ typeof error.code === "string" &&
80
+ error.code === "ENOENT");
81
+ }
82
+ //# sourceMappingURL=runtime-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-state.js","sourceRoot":"","sources":["../../src/runtime/runtime-state.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hagicode/hagiscript",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Scoped npm package foundation for Hagiscript language tooling.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/HagiCode-org/hagiscript#readme",
@@ -20,12 +20,14 @@
20
20
  "./package.json": "./package.json"
21
21
  },
22
22
  "bin": {
23
- "hagiscript": "dist/cli.js"
23
+ "hagiscript": "dist/cli.js",
24
+ "hagicode-runtime": "bin/runtime"
24
25
  },
25
26
  "files": [
26
27
  "dist",
27
- "README.md",
28
- "README_cn.md"
28
+ "bin",
29
+ "runtime",
30
+ "README.md"
29
31
  ],
30
32
  "scripts": {
31
33
  "build": "tsc -p tsconfig.json",
@@ -35,6 +37,7 @@
35
37
  "format:check": "prettier --check .",
36
38
  "lint": "eslint .",
37
39
  "integration:installed-runtime": "node scripts/integration-installed-runtime.mjs",
40
+ "integration:runtime-management": "node scripts/integration-runtime-management.mjs",
38
41
  "pack:check": "node scripts/verify-package.mjs",
39
42
  "publish:check-prereqs": "node scripts/verify-npm-publish-prereqs.mjs",
40
43
  "publish:prepare-dev-version": "node scripts/prepare-dev-version.mjs",
@@ -59,7 +62,8 @@
59
62
  "dependencies": {
60
63
  "commander": "^14.0.1",
61
64
  "execa": "^9.6.1",
62
- "semver": "^7.7.4"
65
+ "semver": "^7.7.4",
66
+ "yaml": "^2.8.4"
63
67
  },
64
68
  "devDependencies": {
65
69
  "@eslint/js": "^9.38.0",
@@ -0,0 +1,531 @@
1
+ import { spawn } from "node:child_process"
2
+ import { createReadStream, createWriteStream } from "node:fs"
3
+ import { access, chmod, mkdir, mkdtemp, readFile, readdir, rename, rm, writeFile } from "node:fs/promises"
4
+ import { tmpdir } from "node:os"
5
+ import path from "node:path"
6
+ import process from "node:process"
7
+ import { createGunzip } from "node:zlib"
8
+ import { pipeline } from "node:stream/promises"
9
+
10
+ export function readRuntimeScriptContext() {
11
+ return {
12
+ runtimeRoot: requiredEnv("HAGISCRIPT_RUNTIME_ROOT"),
13
+ runtimeHome: requiredEnv("HAGICODE_RUNTIME_HOME"),
14
+ runtimeDataHome: requiredEnv("HAGICODE_RUNTIME_DATA_HOME"),
15
+ binDir: requiredEnv("HAGISCRIPT_RUNTIME_BIN_DIR"),
16
+ configDir: requiredEnv("HAGISCRIPT_RUNTIME_CONFIG_DIR"),
17
+ logsDir: requiredEnv("HAGISCRIPT_RUNTIME_LOGS_DIR"),
18
+ dataDir: requiredEnv("HAGISCRIPT_RUNTIME_DATA_DIR"),
19
+ statePath: requiredEnv("HAGISCRIPT_RUNTIME_STATE_PATH"),
20
+ componentName: requiredEnv("HAGISCRIPT_RUNTIME_COMPONENT_NAME"),
21
+ componentType: requiredEnv("HAGISCRIPT_RUNTIME_COMPONENT_TYPE"),
22
+ componentRoot: requiredEnv("HAGISCRIPT_RUNTIME_COMPONENT_ROOT"),
23
+ componentConfigDir: requiredEnv("HAGISCRIPT_RUNTIME_COMPONENT_CONFIG_DIR"),
24
+ componentDataDir: requiredEnv("HAGISCRIPT_RUNTIME_COMPONENT_DATA_DIR"),
25
+ componentLogsDir: requiredEnv("HAGISCRIPT_RUNTIME_COMPONENT_LOGS_DIR"),
26
+ componentPm2Home: requiredEnv("HAGISCRIPT_RUNTIME_COMPONENT_PM2_HOME"),
27
+ templateDir: requiredEnv("HAGISCRIPT_RUNTIME_TEMPLATE_DIR"),
28
+ componentVersion: process.env.HAGISCRIPT_RUNTIME_COMPONENT_VERSION?.trim() || null,
29
+ vendoredRepository: process.env.HAGISCRIPT_RUNTIME_VENDORED_REPOSITORY?.trim() || "HagiCode-org/vendered",
30
+ vendoredTag:
31
+ process.env.HAGISCRIPT_RUNTIME_VENDORED_TAG?.trim() || "v2026.0506.0029",
32
+ vendoredBaseUrl: process.env.HAGISCRIPT_RUNTIME_VENDORED_BASE_URL?.trim() || "https://github.com",
33
+ phase: process.env.HAGISCRIPT_RUNTIME_PHASE?.trim() || "install",
34
+ purge: process.env.HAGISCRIPT_RUNTIME_PURGE === "1"
35
+ }
36
+ }
37
+
38
+ export async function ensureDirectory(directory) {
39
+ await mkdir(directory, { recursive: true })
40
+ }
41
+
42
+ export async function writeJsonFile(filePath, value) {
43
+ await ensureDirectory(path.dirname(filePath))
44
+ await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8")
45
+ }
46
+
47
+ export async function writeComponentMarker(context, extra = {}) {
48
+ const markerPath = path.join(context.componentRoot, ".hagicode-runtime.json")
49
+ await writeJsonFile(markerPath, {
50
+ component: context.componentName,
51
+ type: context.componentType,
52
+ version: context.componentVersion,
53
+ phase: context.phase,
54
+ runtimeRoot: context.runtimeRoot,
55
+ generatedAt: new Date().toISOString(),
56
+ ...extra
57
+ })
58
+ return markerPath
59
+ }
60
+
61
+ export async function materializeTemplate(templateName, destinationPath, variables) {
62
+ const templatePath = path.join(readRuntimeScriptContext().templateDir, templateName)
63
+ const template = await readFile(templatePath, "utf8")
64
+ let rendered = template
65
+
66
+ for (const [key, value] of Object.entries(variables)) {
67
+ rendered = rendered.replaceAll(`{{${key}}}`, String(value))
68
+ }
69
+
70
+ await ensureDirectory(path.dirname(destinationPath))
71
+ await writeFile(destinationPath, rendered, "utf8")
72
+ return destinationPath
73
+ }
74
+
75
+ export async function writeNodeEntrypoint(filePath, message) {
76
+ await ensureDirectory(path.dirname(filePath))
77
+ await writeFile(
78
+ filePath,
79
+ `#!/usr/bin/env node\nprocess.stdout.write(${JSON.stringify(message)} + "\\n")\n`,
80
+ "utf8"
81
+ )
82
+ await makeExecutable(filePath)
83
+ return filePath
84
+ }
85
+
86
+ export async function writeCommandWrapper(binDir, commandName, scriptPath) {
87
+ await ensureDirectory(binDir)
88
+ const nodeWrapperPath = path.join(binDir, process.platform === "win32" ? "node.cmd" : "node")
89
+
90
+ if (process.platform === "win32") {
91
+ const wrapperPath = path.join(binDir, `${commandName}.cmd`)
92
+ const relativeTarget = path.relative(path.dirname(wrapperPath), scriptPath).replaceAll("/", "\\")
93
+ const relativeNode = path.relative(path.dirname(wrapperPath), nodeWrapperPath).replaceAll("/", "\\")
94
+ await writeFile(
95
+ wrapperPath,
96
+ `@echo off\r\nset "HAGISCRIPT_NODE=%~dp0\\${relativeNode}"\r\nif exist "%HAGISCRIPT_NODE%" (\r\n "%HAGISCRIPT_NODE%" "%~dp0\\${relativeTarget}" %*\r\n) else (\r\n node "%~dp0\\${relativeTarget}" %*\r\n)\r\n`,
97
+ "utf8"
98
+ )
99
+ return wrapperPath
100
+ }
101
+
102
+ const wrapperPath = path.join(binDir, commandName)
103
+ const relativeTarget = path.relative(path.dirname(wrapperPath), scriptPath).replaceAll("\\", "/")
104
+ const relativeNode = path.relative(path.dirname(wrapperPath), nodeWrapperPath).replaceAll("\\", "/")
105
+ await writeFile(
106
+ wrapperPath,
107
+ `#!/usr/bin/env sh
108
+ node_cmd="$(dirname "$0")/${relativeNode}"
109
+ if [ -x "$node_cmd" ]; then
110
+ exec "$node_cmd" "$(dirname "$0")/${relativeTarget}" "$@"
111
+ fi
112
+ exec node "$(dirname "$0")/${relativeTarget}" "$@"
113
+ `,
114
+ "utf8"
115
+ )
116
+ await makeExecutable(wrapperPath)
117
+ return wrapperPath
118
+ }
119
+
120
+ export async function installVendoredPackage(context, options) {
121
+ const { repository, baseUrl } = parseGitHubRepositoryConfig(
122
+ context.vendoredRepository,
123
+ context.vendoredBaseUrl
124
+ )
125
+ const releaseTag = context.vendoredTag
126
+ const platform = normalizeVendoredPlatform(process.platform)
127
+ const arch = normalizeVendoredArchitecture(process.arch)
128
+ const assetName = buildVendoredAssetName({
129
+ packageName: options.packageName,
130
+ releaseTag,
131
+ platform,
132
+ arch
133
+ })
134
+ const assetUrl = buildVendoredAssetUrl(baseUrl, repository, releaseTag, assetName)
135
+
136
+ const stagingRoot = await mkdtemp(path.join(tmpdir(), `hagiscript-vendored-${options.packageName}-`))
137
+
138
+ try {
139
+ const archivePath = path.join(stagingRoot, assetName)
140
+ const extractRoot = path.join(stagingRoot, "extract")
141
+ await downloadVendoredAsset(assetUrl, archivePath)
142
+ const extractedRoot = await extractVendoredArchive(
143
+ archivePath,
144
+ extractRoot,
145
+ path.extname(assetName).toLowerCase() === ".zip" ? "zip" : "tar.gz"
146
+ )
147
+ await replaceDirectory(extractedRoot, options.prefixRoot)
148
+
149
+ const entrypointPath = path.join(options.prefixRoot, options.entrypointRelativePath)
150
+ await access(entrypointPath)
151
+
152
+ return {
153
+ entrypointPath,
154
+ releaseRepository: repository,
155
+ releaseTag,
156
+ releaseName: releaseTag.replace(/^v/u, ""),
157
+ releaseUrl: `${baseUrl}/${repository}/releases/tag/${encodeURIComponent(releaseTag)}`,
158
+ releaseAssetName: assetName,
159
+ releaseAssetUrl: assetUrl
160
+ }
161
+ } finally {
162
+ await rm(stagingRoot, { recursive: true, force: true })
163
+ }
164
+ }
165
+
166
+ export async function writeManagedPackageLauncher(filePath, options) {
167
+ await ensureDirectory(path.dirname(filePath))
168
+ const configLoader =
169
+ options.serviceKind === "omniroute"
170
+ ? `
171
+ const runtimeConfig = await loadOmnirouteConfig()
172
+ const runtimeEnv = {
173
+ ...(runtimeConfig.dataDir ? { DATA_DIR: runtimeConfig.dataDir } : {}),
174
+ ...(runtimeConfig.logDir ? { LOG_DIR: runtimeConfig.logDir } : {}),
175
+ ...(runtimeConfig.port ? { PORT: runtimeConfig.port } : {})
176
+ }
177
+ `
178
+ : ""
179
+ const configHelpers =
180
+ options.serviceKind === "omniroute"
181
+ ? `
182
+ async function loadOmnirouteConfig() {
183
+ try {
184
+ const [{ readFile }, { parse }] = await Promise.all([
185
+ import("node:fs/promises"),
186
+ import("yaml")
187
+ ])
188
+ const loaded = parse(await readFile(configPath, "utf8"))
189
+ const listen = typeof loaded?.listen === "string" ? loaded.listen : ""
190
+ const portMatch = listen.match(/:(\\d+)$/u)
191
+ return {
192
+ dataDir: typeof loaded?.dataDir === "string" ? loaded.dataDir : ${JSON.stringify(
193
+ options.defaultEnv?.DATA_DIR ?? ""
194
+ )},
195
+ logDir: typeof loaded?.logDir === "string" ? loaded.logDir : ${JSON.stringify(
196
+ options.defaultEnv?.LOG_DIR ?? ""
197
+ )},
198
+ port: portMatch?.[1] ?? ${JSON.stringify(options.defaultEnv?.PORT ?? "")}
199
+ }
200
+ } catch {
201
+ return {
202
+ dataDir: ${JSON.stringify(options.defaultEnv?.DATA_DIR ?? "")},
203
+ logDir: ${JSON.stringify(options.defaultEnv?.LOG_DIR ?? "")},
204
+ port: ${JSON.stringify(options.defaultEnv?.PORT ?? "")}
205
+ }
206
+ }
207
+ }
208
+ `
209
+ : ""
210
+
211
+ await writeFile(
212
+ filePath,
213
+ `#!/usr/bin/env node
214
+ import { spawn } from "node:child_process"
215
+
216
+ const entrypointPath = ${JSON.stringify(options.entrypointPath)}
217
+ const configPath = ${JSON.stringify(options.configPath)}
218
+ const baseArgs = ${JSON.stringify(options.baseArgs ?? [])}
219
+ const baseEnv = ${JSON.stringify(options.defaultEnv ?? {})}
220
+ ${configLoader}
221
+ const child = spawn(
222
+ process.execPath,
223
+ [entrypointPath, ...baseArgs, ...process.argv.slice(2)],
224
+ {
225
+ stdio: "inherit",
226
+ env: {
227
+ ...process.env,
228
+ ...baseEnv,
229
+ ${options.serviceKind === "omniroute" ? "...runtimeEnv" : ""}
230
+ }
231
+ }
232
+ )
233
+
234
+ const forwardSignal = (signal) => {
235
+ if (!child.killed) {
236
+ child.kill(signal)
237
+ }
238
+ }
239
+
240
+ process.on("SIGINT", () => forwardSignal("SIGINT"))
241
+ process.on("SIGTERM", () => forwardSignal("SIGTERM"))
242
+
243
+ child.on("exit", (code, signal) => {
244
+ if (signal) {
245
+ process.kill(process.pid, signal)
246
+ return
247
+ }
248
+
249
+ process.exit(code ?? 0)
250
+ })
251
+
252
+ child.on("error", (error) => {
253
+ process.stderr.write(String(error?.stack ?? error) + "\\n")
254
+ process.exit(1)
255
+ })
256
+ ${configHelpers}
257
+ `,
258
+ "utf8"
259
+ )
260
+ await makeExecutable(filePath)
261
+ return filePath
262
+ }
263
+
264
+ function requiredEnv(name) {
265
+ const value = process.env[name]?.trim()
266
+
267
+ if (!value) {
268
+ throw new Error(`Missing runtime script environment variable: ${name}`)
269
+ }
270
+
271
+ return value
272
+ }
273
+
274
+ async function makeExecutable(filePath) {
275
+ if (process.platform === "win32") {
276
+ return
277
+ }
278
+
279
+ await chmod(filePath, 0o755)
280
+ }
281
+
282
+ function runManagedCommand(command, args, options = {}) {
283
+ return new Promise((resolve, reject) => {
284
+ const child = spawn(command, args, {
285
+ env: options.env,
286
+ cwd: options.cwd,
287
+ stdio: ["ignore", "pipe", "pipe"]
288
+ })
289
+ let stdout = ""
290
+ let stderr = ""
291
+
292
+ child.stdout.on("data", (chunk) => {
293
+ stdout += chunk.toString()
294
+ })
295
+ child.stderr.on("data", (chunk) => {
296
+ stderr += chunk.toString()
297
+ })
298
+ child.on("error", reject)
299
+ child.on("exit", (code, signal) => {
300
+ if (code === 0) {
301
+ resolve({ stdout, stderr })
302
+ return
303
+ }
304
+
305
+ reject(
306
+ new Error(
307
+ `Managed command failed: ${command} ${args.join(" ")} (code=${code ?? "null"}, signal=${signal ?? "null"})\n${stderr || stdout}`.trim()
308
+ )
309
+ )
310
+ })
311
+ })
312
+ }
313
+
314
+ function parseGitHubRepositoryConfig(repositoryValue, baseUrlValue) {
315
+ const trimmedRepository = String(repositoryValue || "").trim()
316
+ const repository =
317
+ trimmedRepository.startsWith("https://github.com/")
318
+ ? trimmedRepository
319
+ .replace(/^https:\/\/github\.com\//u, "")
320
+ .replace(/\.git$/u, "")
321
+ .replace(/\/+$/u, "")
322
+ : trimmedRepository
323
+
324
+ if (!/^[^/]+\/[^/]+$/u.test(repository)) {
325
+ throw new Error(`Invalid vendored GitHub repository: ${trimmedRepository}`)
326
+ }
327
+
328
+ return {
329
+ repository,
330
+ baseUrl: String(baseUrlValue || "https://github.com").replace(/\/+$/u, "")
331
+ }
332
+ }
333
+
334
+ function buildVendoredAssetName(options) {
335
+ const releaseVersion = normalizeVendoredReleaseVersion(options.releaseTag)
336
+ return `${options.packageName}-${releaseVersion}-${options.platform}-${options.arch}${getVendoredArchiveExtension(options.platform)}`
337
+ }
338
+
339
+ function buildVendoredAssetUrl(baseUrl, repository, releaseTag, assetName) {
340
+ return `${baseUrl}/${repository}/releases/download/${encodeURIComponent(releaseTag)}/${encodeURIComponent(assetName)}`
341
+ }
342
+
343
+ function normalizeVendoredReleaseVersion(releaseTag) {
344
+ const normalized = String(releaseTag || "").trim().replace(/^v/u, "")
345
+ if (!normalized) {
346
+ throw new Error(`Invalid vendored release tag: ${String(releaseTag)}`)
347
+ }
348
+
349
+ return normalized
350
+ }
351
+
352
+ function normalizeVendoredPlatform(platform) {
353
+ switch (platform) {
354
+ case "darwin":
355
+ return "macos"
356
+ case "win32":
357
+ return "windows"
358
+ default:
359
+ return "linux"
360
+ }
361
+ }
362
+
363
+ function normalizeVendoredArchitecture(arch) {
364
+ switch (String(arch).toLowerCase()) {
365
+ case "x64":
366
+ return "amd64"
367
+ case "arm64":
368
+ case "aarch64":
369
+ return "arm64"
370
+ default:
371
+ throw new Error(`Unsupported vendored runtime architecture: ${arch}`)
372
+ }
373
+ }
374
+
375
+ function getVendoredArchiveExtension(platform) {
376
+ return platform === "windows" ? ".zip" : ".tar.gz"
377
+ }
378
+
379
+ async function downloadVendoredAsset(url, destinationPath) {
380
+ const response = await globalThis.fetch(url)
381
+
382
+ if (!response.ok) {
383
+ throw new Error(`Failed to download vendored asset ${url}: HTTP ${response.status}`)
384
+ }
385
+
386
+ if (!response.body) {
387
+ throw new Error(`Failed to download vendored asset ${url}: empty response body.`)
388
+ }
389
+
390
+ await ensureDirectory(path.dirname(destinationPath))
391
+ const file = createWriteStream(destinationPath)
392
+
393
+ const reader = response.body.getReader()
394
+
395
+ try {
396
+ while (true) {
397
+ const { done, value } = await reader.read()
398
+ if (done) {
399
+ break
400
+ }
401
+
402
+ if (!file.write(value)) {
403
+ await new Promise((resolve) => file.once("drain", resolve))
404
+ }
405
+ }
406
+
407
+ await new Promise((resolve, reject) =>
408
+ file.end((error) => (error ? reject(error) : resolve()))
409
+ )
410
+ } finally {
411
+ reader.releaseLock()
412
+ }
413
+ }
414
+
415
+ async function extractVendoredArchive(archivePath, stagingDirectory, archiveKind) {
416
+ await rm(stagingDirectory, { recursive: true, force: true })
417
+ await mkdir(stagingDirectory, { recursive: true })
418
+
419
+ if (archiveKind === "zip") {
420
+ await extractZipArchive(archivePath, stagingDirectory)
421
+ } else {
422
+ await extractTarGzArchive(archivePath, stagingDirectory)
423
+ }
424
+
425
+ const entries = await readdir(stagingDirectory, { withFileTypes: true })
426
+ const directories = entries.filter((entry) => entry.isDirectory())
427
+
428
+ if (directories.length !== 1) {
429
+ throw new Error(
430
+ `Expected exactly one extracted vendored root in ${stagingDirectory}, found ${directories.length}.`
431
+ )
432
+ }
433
+
434
+ return path.join(stagingDirectory, directories[0].name)
435
+ }
436
+
437
+ async function extractZipArchive(archivePath, destination) {
438
+ if (process.platform === "win32") {
439
+ await runManagedCommand("powershell.exe", [
440
+ "-NoLogo",
441
+ "-NoProfile",
442
+ "-Command",
443
+ `Expand-Archive -Path '${escapePowerShell(archivePath.replaceAll("/", "\\"))}' -DestinationPath '${escapePowerShell(destination.replaceAll("/", "\\"))}' -Force`
444
+ ])
445
+ return
446
+ }
447
+
448
+ try {
449
+ await runManagedCommand("unzip", ["-q", archivePath, "-d", destination])
450
+ } catch {
451
+ await runManagedCommand("bsdtar", ["-xf", archivePath, "-C", destination])
452
+ }
453
+ }
454
+
455
+ async function extractTarGzArchive(archivePath, destination) {
456
+ try {
457
+ await runManagedCommand("tar", ["-xzf", archivePath, "-C", destination])
458
+ } catch {
459
+ await extractTarGzWithNode(archivePath, destination)
460
+ }
461
+ }
462
+
463
+ async function extractTarGzWithNode(archivePath, destination) {
464
+ const tarPath = `${archivePath}.tar`
465
+
466
+ try {
467
+ await pipeline(
468
+ createReadStream(archivePath),
469
+ createGunzip(),
470
+ createWriteStream(tarPath)
471
+ )
472
+ await extractTarBuffer(await readFile(tarPath), destination)
473
+ } finally {
474
+ await rm(tarPath, { force: true }).catch(() => undefined)
475
+ }
476
+ }
477
+
478
+ async function extractTarBuffer(buffer, destination) {
479
+ let offset = 0
480
+
481
+ while (offset + 512 <= buffer.length) {
482
+ const header = buffer.subarray(offset, offset + 512)
483
+ if (header.every((byte) => byte === 0)) {
484
+ break
485
+ }
486
+
487
+ const name = header.toString("utf8", 0, 100).replace(/\0.*$/u, "")
488
+ const sizeText = header
489
+ .toString("utf8", 124, 136)
490
+ .replace(/\0.*$/u, "")
491
+ .trim()
492
+ const typeFlag = header.toString("utf8", 156, 157)
493
+ const size = parseInt(sizeText || "0", 8)
494
+ const outputPath = safeArchiveJoin(destination, name)
495
+
496
+ if (typeFlag === "5") {
497
+ await mkdir(outputPath, { recursive: true })
498
+ } else if (typeFlag === "0" || typeFlag === "") {
499
+ await mkdir(path.dirname(outputPath), { recursive: true })
500
+ await writeFile(outputPath, buffer.subarray(offset + 512, offset + 512 + size))
501
+ }
502
+
503
+ offset += 512 + Math.ceil(size / 512) * 512
504
+ }
505
+ }
506
+
507
+ function safeArchiveJoin(root, entryName) {
508
+ const normalized = path.join(root, entryName)
509
+ const relativePath = path.relative(root, normalized)
510
+
511
+ if (
512
+ !entryName ||
513
+ relativePath.startsWith("..") ||
514
+ relativePath.includes(`${path.sep}..${path.sep}`) ||
515
+ path.isAbsolute(entryName)
516
+ ) {
517
+ throw new Error(`Vendored archive entry escapes the extraction root: ${entryName}`)
518
+ }
519
+
520
+ return normalized
521
+ }
522
+
523
+ async function replaceDirectory(sourceDirectory, targetDirectory) {
524
+ await rm(targetDirectory, { recursive: true, force: true })
525
+ await ensureDirectory(path.dirname(targetDirectory))
526
+ await rename(sourceDirectory, targetDirectory)
527
+ }
528
+
529
+ function escapePowerShell(value) {
530
+ return value.replaceAll("'", "''")
531
+ }