@hagicode/hagiscript 0.1.7 → 0.1.9
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 +134 -259
- package/dist/runtime/dotnet-installer.d.ts +37 -0
- package/dist/runtime/dotnet-installer.js +226 -0
- package/dist/runtime/dotnet-installer.js.map +1 -0
- package/dist/runtime/pm2-manager.js +3 -5
- package/dist/runtime/pm2-manager.js.map +1 -1
- package/dist/runtime/runtime-executor.d.ts +1 -1
- package/dist/runtime/runtime-executor.js +23 -5
- package/dist/runtime/runtime-executor.js.map +1 -1
- package/package.json +2 -3
- package/runtime/lib/runtime-script-lib.mjs +416 -27
- package/runtime/manifest.yaml +1 -0
- package/runtime/scripts/install-code-server.mjs +21 -3
- package/runtime/scripts/install-dotnet.mjs +49 -6
- package/runtime/scripts/install-omniroute.mjs +26 -3
- package/runtime/scripts/remove-dotnet.mjs +13 -0
- package/runtime/scripts/verify-dotnet.mjs +20 -4
- package/README_cn.md +0 -321
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import {
|
|
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"
|
|
2
5
|
import path from "node:path"
|
|
3
6
|
import process from "node:process"
|
|
7
|
+
import { createGunzip } from "node:zlib"
|
|
8
|
+
import { pipeline } from "node:stream/promises"
|
|
4
9
|
|
|
5
10
|
export function readRuntimeScriptContext() {
|
|
6
11
|
return {
|
|
@@ -21,6 +26,10 @@ export function readRuntimeScriptContext() {
|
|
|
21
26
|
componentPm2Home: requiredEnv("HAGISCRIPT_RUNTIME_COMPONENT_PM2_HOME"),
|
|
22
27
|
templateDir: requiredEnv("HAGISCRIPT_RUNTIME_TEMPLATE_DIR"),
|
|
23
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",
|
|
24
33
|
phase: process.env.HAGISCRIPT_RUNTIME_PHASE?.trim() || "install",
|
|
25
34
|
purge: process.env.HAGISCRIPT_RUNTIME_PURGE === "1"
|
|
26
35
|
}
|
|
@@ -74,39 +83,17 @@ export async function writeNodeEntrypoint(filePath, message) {
|
|
|
74
83
|
return filePath
|
|
75
84
|
}
|
|
76
85
|
|
|
77
|
-
export async function writeManagedServiceEntrypoint(filePath, serviceName) {
|
|
78
|
-
await ensureDirectory(path.dirname(filePath))
|
|
79
|
-
await writeFile(
|
|
80
|
-
filePath,
|
|
81
|
-
`#!/usr/bin/env node
|
|
82
|
-
const serviceName = ${JSON.stringify(serviceName)}
|
|
83
|
-
process.stdout.write(serviceName + " ready\\n")
|
|
84
|
-
const timer = setInterval(() => {
|
|
85
|
-
process.stdout.write("")
|
|
86
|
-
}, 60_000)
|
|
87
|
-
const shutdown = (signal) => {
|
|
88
|
-
clearInterval(timer)
|
|
89
|
-
process.stdout.write(serviceName + " shutting down via " + signal + "\\n")
|
|
90
|
-
process.exit(0)
|
|
91
|
-
}
|
|
92
|
-
process.on("SIGINT", () => shutdown("SIGINT"))
|
|
93
|
-
process.on("SIGTERM", () => shutdown("SIGTERM"))
|
|
94
|
-
`,
|
|
95
|
-
"utf8"
|
|
96
|
-
)
|
|
97
|
-
await makeExecutable(filePath)
|
|
98
|
-
return filePath
|
|
99
|
-
}
|
|
100
|
-
|
|
101
86
|
export async function writeCommandWrapper(binDir, commandName, scriptPath) {
|
|
102
87
|
await ensureDirectory(binDir)
|
|
88
|
+
const nodeWrapperPath = path.join(binDir, process.platform === "win32" ? "node.cmd" : "node")
|
|
103
89
|
|
|
104
90
|
if (process.platform === "win32") {
|
|
105
91
|
const wrapperPath = path.join(binDir, `${commandName}.cmd`)
|
|
106
92
|
const relativeTarget = path.relative(path.dirname(wrapperPath), scriptPath).replaceAll("/", "\\")
|
|
93
|
+
const relativeNode = path.relative(path.dirname(wrapperPath), nodeWrapperPath).replaceAll("/", "\\")
|
|
107
94
|
await writeFile(
|
|
108
95
|
wrapperPath,
|
|
109
|
-
`@echo off\r\
|
|
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`,
|
|
110
97
|
"utf8"
|
|
111
98
|
)
|
|
112
99
|
return wrapperPath
|
|
@@ -114,15 +101,166 @@ export async function writeCommandWrapper(binDir, commandName, scriptPath) {
|
|
|
114
101
|
|
|
115
102
|
const wrapperPath = path.join(binDir, commandName)
|
|
116
103
|
const relativeTarget = path.relative(path.dirname(wrapperPath), scriptPath).replaceAll("\\", "/")
|
|
104
|
+
const relativeNode = path.relative(path.dirname(wrapperPath), nodeWrapperPath).replaceAll("\\", "/")
|
|
117
105
|
await writeFile(
|
|
118
106
|
wrapperPath,
|
|
119
|
-
`#!/usr/bin/env sh
|
|
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
|
+
`,
|
|
120
114
|
"utf8"
|
|
121
115
|
)
|
|
122
116
|
await makeExecutable(wrapperPath)
|
|
123
117
|
return wrapperPath
|
|
124
118
|
}
|
|
125
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
|
+
|
|
126
264
|
function requiredEnv(name) {
|
|
127
265
|
const value = process.env[name]?.trim()
|
|
128
266
|
|
|
@@ -140,3 +278,254 @@ async function makeExecutable(filePath) {
|
|
|
140
278
|
|
|
141
279
|
await chmod(filePath, 0o755)
|
|
142
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
|
+
}
|
package/runtime/manifest.yaml
CHANGED
|
@@ -3,11 +3,12 @@ import path from "node:path"
|
|
|
3
3
|
import process from "node:process"
|
|
4
4
|
import {
|
|
5
5
|
ensureDirectory,
|
|
6
|
+
installVendoredPackage,
|
|
6
7
|
materializeTemplate,
|
|
7
8
|
readRuntimeScriptContext,
|
|
8
9
|
writeCommandWrapper,
|
|
9
10
|
writeComponentMarker,
|
|
10
|
-
|
|
11
|
+
writeManagedPackageLauncher
|
|
11
12
|
} from "../lib/runtime-script-lib.mjs"
|
|
12
13
|
|
|
13
14
|
const context = readRuntimeScriptContext()
|
|
@@ -19,14 +20,31 @@ await ensureDirectory(currentRoot)
|
|
|
19
20
|
await materializeTemplate("code-server-config.yaml", configPath, {
|
|
20
21
|
DATA_DIR: context.runtimeDataHome
|
|
21
22
|
})
|
|
22
|
-
await
|
|
23
|
+
const installedPackage = await installVendoredPackage(context, {
|
|
24
|
+
prefixRoot: currentRoot,
|
|
25
|
+
packageName: "code-server",
|
|
26
|
+
entrypointRelativePath: path.join("out", "node", "entry.js")
|
|
27
|
+
})
|
|
28
|
+
await writeManagedPackageLauncher(
|
|
23
29
|
launcherPath,
|
|
24
|
-
|
|
30
|
+
{
|
|
31
|
+
entrypointPath: installedPackage.entrypointPath,
|
|
32
|
+
configPath,
|
|
33
|
+
baseArgs: ["--config", configPath],
|
|
34
|
+
serviceKind: "code-server"
|
|
35
|
+
}
|
|
25
36
|
)
|
|
26
37
|
await writeCommandWrapper(context.binDir, "code-server", launcherPath)
|
|
27
38
|
await writeComponentMarker(context, {
|
|
28
39
|
configPath,
|
|
29
40
|
launcherPath,
|
|
41
|
+
entrypointPath: installedPackage.entrypointPath,
|
|
42
|
+
vendoredReleaseRepository: installedPackage.releaseRepository,
|
|
43
|
+
vendoredReleaseTag: installedPackage.releaseTag,
|
|
44
|
+
vendoredReleaseName: installedPackage.releaseName,
|
|
45
|
+
vendoredReleaseUrl: installedPackage.releaseUrl,
|
|
46
|
+
vendoredAssetName: installedPackage.releaseAssetName,
|
|
47
|
+
vendoredAssetUrl: installedPackage.releaseAssetUrl,
|
|
30
48
|
runtimeHome: context.runtimeHome,
|
|
31
49
|
runtimeDataHome: context.runtimeDataHome,
|
|
32
50
|
pm2Home: context.componentPm2Home,
|
|
@@ -1,24 +1,67 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { chmod, rm, writeFile } from "node:fs/promises"
|
|
2
3
|
import path from "node:path"
|
|
3
4
|
import process from "node:process"
|
|
4
5
|
import {
|
|
5
6
|
ensureDirectory,
|
|
6
7
|
readRuntimeScriptContext,
|
|
7
|
-
writeComponentMarker
|
|
8
|
-
writeJsonFile
|
|
8
|
+
writeComponentMarker
|
|
9
9
|
} from "../lib/runtime-script-lib.mjs"
|
|
10
|
+
import {
|
|
11
|
+
installManagedDotnetRuntime
|
|
12
|
+
} from "../../dist/runtime/dotnet-installer.js"
|
|
10
13
|
|
|
11
14
|
const context = readRuntimeScriptContext()
|
|
12
15
|
const currentRoot = path.join(context.componentRoot, "current")
|
|
16
|
+
const dotnetPath = path.join(currentRoot, process.platform === "win32" ? "dotnet.exe" : "dotnet")
|
|
17
|
+
const wrapperPath = path.join(context.binDir, process.platform === "win32" ? "dotnet.cmd" : "dotnet")
|
|
18
|
+
const dotnetVersion = context.componentVersion ?? "10.0.5"
|
|
13
19
|
|
|
20
|
+
await rm(currentRoot, { recursive: true, force: true })
|
|
14
21
|
await ensureDirectory(currentRoot)
|
|
15
|
-
await
|
|
22
|
+
await installManagedDotnetRuntime({
|
|
23
|
+
targetDirectory: currentRoot,
|
|
24
|
+
version: dotnetVersion,
|
|
25
|
+
scriptBaseUrl: process.env.HAGISCRIPT_DOTNET_INSTALL_SCRIPT_BASE_URL?.trim() || undefined,
|
|
26
|
+
verbose: process.env.HAGISCRIPT_RUNTIME_VERBOSE === "1"
|
|
27
|
+
})
|
|
28
|
+
await writeFile(path.join(currentRoot, "runtime-manifest.json"), `${JSON.stringify({
|
|
16
29
|
component: context.componentName,
|
|
17
30
|
channelVersion: context.componentVersion,
|
|
18
|
-
|
|
19
|
-
|
|
31
|
+
installedVersion: dotnetVersion,
|
|
32
|
+
runtimeRoot: context.runtimeRoot,
|
|
33
|
+
dotnetPath
|
|
34
|
+
}, null, 2)}\n`, "utf8")
|
|
35
|
+
await writeDotnetWrapper(wrapperPath, dotnetPath)
|
|
20
36
|
await writeComponentMarker(context, {
|
|
21
37
|
currentRoot,
|
|
38
|
+
dotnetPath,
|
|
39
|
+
wrapperPath,
|
|
40
|
+
runtimeVersion: dotnetVersion,
|
|
22
41
|
ownership: "hagiscript-managed"
|
|
23
42
|
})
|
|
24
|
-
process.stdout.write(`
|
|
43
|
+
process.stdout.write(`Installed .NET and ASP.NET Core runtime ${dotnetVersion} in ${currentRoot}\n`)
|
|
44
|
+
|
|
45
|
+
async function writeDotnetWrapper(destinationPath, targetPath) {
|
|
46
|
+
await ensureDirectory(path.dirname(destinationPath))
|
|
47
|
+
|
|
48
|
+
if (process.platform === "win32") {
|
|
49
|
+
const relativeTarget = path.relative(path.dirname(destinationPath), targetPath).replaceAll("/", "\\")
|
|
50
|
+
await writeFile(
|
|
51
|
+
destinationPath,
|
|
52
|
+
`@echo off\r\n"%~dp0\\${relativeTarget}" %*\r\n`,
|
|
53
|
+
"utf8"
|
|
54
|
+
)
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const relativeTarget = path.relative(path.dirname(destinationPath), targetPath).replaceAll("\\", "/")
|
|
59
|
+
await writeFile(
|
|
60
|
+
destinationPath,
|
|
61
|
+
`#!/usr/bin/env sh
|
|
62
|
+
exec "$(dirname "$0")/${relativeTarget}" "$@"
|
|
63
|
+
`,
|
|
64
|
+
"utf8"
|
|
65
|
+
)
|
|
66
|
+
await chmod(destinationPath, 0o755)
|
|
67
|
+
}
|
|
@@ -3,11 +3,12 @@ import path from "node:path"
|
|
|
3
3
|
import process from "node:process"
|
|
4
4
|
import {
|
|
5
5
|
ensureDirectory,
|
|
6
|
+
installVendoredPackage,
|
|
6
7
|
materializeTemplate,
|
|
7
8
|
readRuntimeScriptContext,
|
|
8
9
|
writeCommandWrapper,
|
|
9
10
|
writeComponentMarker,
|
|
10
|
-
|
|
11
|
+
writeManagedPackageLauncher
|
|
11
12
|
} from "../lib/runtime-script-lib.mjs"
|
|
12
13
|
|
|
13
14
|
const context = readRuntimeScriptContext()
|
|
@@ -21,14 +22,36 @@ await materializeTemplate("omniroute-config.yaml", configPath, {
|
|
|
21
22
|
DATA_DIR: context.runtimeDataHome,
|
|
22
23
|
LOGS_DIR: context.componentLogsDir
|
|
23
24
|
})
|
|
24
|
-
await
|
|
25
|
+
const installedPackage = await installVendoredPackage(context, {
|
|
26
|
+
prefixRoot: currentRoot,
|
|
27
|
+
packageName: "omniroute",
|
|
28
|
+
entrypointRelativePath: path.join("bin", "omniroute.mjs")
|
|
29
|
+
})
|
|
30
|
+
await writeManagedPackageLauncher(
|
|
25
31
|
launcherPath,
|
|
26
|
-
|
|
32
|
+
{
|
|
33
|
+
entrypointPath: installedPackage.entrypointPath,
|
|
34
|
+
configPath,
|
|
35
|
+
baseArgs: ["--no-open"],
|
|
36
|
+
defaultEnv: {
|
|
37
|
+
DATA_DIR: context.runtimeDataHome,
|
|
38
|
+
LOG_DIR: context.componentLogsDir,
|
|
39
|
+
PORT: "39001"
|
|
40
|
+
},
|
|
41
|
+
serviceKind: "omniroute"
|
|
42
|
+
}
|
|
27
43
|
)
|
|
28
44
|
await writeCommandWrapper(context.binDir, "omniroute", launcherPath)
|
|
29
45
|
await writeComponentMarker(context, {
|
|
30
46
|
configPath,
|
|
31
47
|
launcherPath,
|
|
48
|
+
entrypointPath: installedPackage.entrypointPath,
|
|
49
|
+
vendoredReleaseRepository: installedPackage.releaseRepository,
|
|
50
|
+
vendoredReleaseTag: installedPackage.releaseTag,
|
|
51
|
+
vendoredReleaseName: installedPackage.releaseName,
|
|
52
|
+
vendoredReleaseUrl: installedPackage.releaseUrl,
|
|
53
|
+
vendoredAssetName: installedPackage.releaseAssetName,
|
|
54
|
+
vendoredAssetUrl: installedPackage.releaseAssetUrl,
|
|
32
55
|
runtimeHome: context.runtimeHome,
|
|
33
56
|
runtimeDataHome: context.runtimeDataHome,
|
|
34
57
|
pm2Home: context.componentPm2Home,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { rm } from "node:fs/promises"
|
|
3
|
+
import path from "node:path"
|
|
4
|
+
import process from "node:process"
|
|
5
|
+
import { readRuntimeScriptContext } from "../lib/runtime-script-lib.mjs"
|
|
6
|
+
|
|
7
|
+
const context = readRuntimeScriptContext()
|
|
8
|
+
const wrapperPath = path.join(context.binDir, process.platform === "win32" ? "dotnet.cmd" : "dotnet")
|
|
9
|
+
|
|
10
|
+
await rm(context.componentRoot, { recursive: true, force: true })
|
|
11
|
+
await rm(wrapperPath, { force: true })
|
|
12
|
+
|
|
13
|
+
process.stdout.write(`Removed managed .NET runtime from ${context.componentRoot}\n`)
|