Fix StartOS 0.4 TypeScript packaging to match SDK API

This commit is contained in:
MacPro
2026-04-09 15:10:44 -05:00
parent d5046a0daf
commit 0b70cbb2bf
3436 changed files with 867051 additions and 92 deletions
@@ -0,0 +1,217 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.CommandController = void 0;
const _1 = require(".");
const types_1 = require("../../../base/lib/types");
const T = __importStar(require("../../../base/lib/types"));
const util_1 = require("../util");
const fs = __importStar(require("node:fs/promises"));
const logErrorOnce_1 = require("../../../base/lib/util/logErrorOnce");
/**
* Low-level controller for a single running process inside a subcontainer (or as a JS function).
*
* Manages the child process lifecycle: spawning, waiting, and signal-based termination.
* Used internally by {@link Daemon} to manage individual command executions.
*
* @typeParam Manifest - The service manifest type
* @typeParam C - The subcontainer type, or `null` for JS-only commands
*/
class CommandController extends util_1.Drop {
constructor(runningAnswer, state, subcontainer, process, sigtermTimeout = _1.DEFAULT_SIGTERM_TIMEOUT) {
super();
this.runningAnswer = runningAnswer;
this.state = state;
this.subcontainer = subcontainer;
this.process = process;
this.sigtermTimeout = sigtermTimeout;
}
/**
* Factory method to create a new CommandController.
*
* Returns a curried async function: `(effects, subcontainer, exec) => CommandController`.
* If the exec spec has an `fn` property, runs the function; otherwise spawns a shell command
* in the subcontainer.
*/
static of() {
return async (effects, subcontainer, exec) => {
try {
if ('fn' in exec) {
const abort = new AbortController();
const cell = {
ctrl: new CommandController(exec.fn(subcontainer, abort.signal).then(async (command) => {
if (subcontainer && command && !abort.signal.aborted) {
const newCtrl = (await CommandController.of()(effects, subcontainer, command)).leak();
Object.assign(cell.ctrl, newCtrl);
return await cell.ctrl.runningAnswer;
}
else {
cell.ctrl.state.exited = true;
}
return null;
}), { exited: false }, subcontainer, abort, exec.sigtermTimeout),
};
return cell.ctrl;
}
let commands;
if (T.isUseEntrypoint(exec.command)) {
const imageMeta = await fs
.readFile(`/media/startos/images/${subcontainer.imageId}.json`, {
encoding: 'utf8',
})
.catch(() => '{}')
.then(JSON.parse);
commands = imageMeta.entrypoint ?? [];
commands = commands.concat(...(exec.command.overridCmd ?? imageMeta.cmd ?? []));
}
else
commands = (0, util_1.splitCommand)(exec.command);
let childProcess;
if (exec.runAsInit) {
childProcess = await subcontainer.launch(commands, {
env: exec.env,
user: exec.user,
cwd: exec.cwd,
});
}
else {
childProcess = await subcontainer.spawn(commands, {
env: exec.env,
user: exec.user,
cwd: exec.cwd,
stdio: exec.onStdout || exec.onStderr ? 'pipe' : 'inherit',
});
}
if (exec.onStdout)
childProcess.stdout?.on('data', exec.onStdout);
if (exec.onStderr)
childProcess.stderr?.on('data', exec.onStderr);
const state = { exited: false };
const answer = new Promise((resolve, reject) => {
childProcess.on('exit', (code) => {
state.exited = true;
if (code === 0 ||
code === 143 ||
(code === null && childProcess.signalCode == 'SIGTERM')) {
return resolve(null);
}
if (code) {
return reject(new Error(`${commands[0]} exited with code ${code}`));
}
else {
return reject(new Error(`${commands[0]} exited with signal ${childProcess.signalCode}`));
}
});
});
return new CommandController(answer, state, subcontainer, childProcess, exec.sigtermTimeout);
}
catch (e) {
await subcontainer?.destroy();
throw e;
}
};
}
/**
* Wait for the command to finish. Optionally terminate after a timeout.
* @param options.timeout - Milliseconds to wait before terminating. Defaults to no timeout.
*/
async wait({ timeout = types_1.NO_TIMEOUT } = {}) {
if (timeout > 0)
setTimeout(() => {
this.term();
}, timeout);
try {
if (timeout > 0 && this.process instanceof AbortController)
await Promise.race([
this.runningAnswer,
new Promise((_, reject) => setTimeout(() => reject(new Error('Timed out waiting for js command to exit')), timeout * 2)),
]);
else
await this.runningAnswer;
}
finally {
if (!this.state.exited) {
if (this.process instanceof AbortController)
this.process.abort();
else
this.process.kill('SIGKILL');
}
await this.subcontainer?.destroy();
}
}
/**
* Terminate the running command by sending a signal.
*
* Sends the specified signal (default: SIGTERM), then escalates to SIGKILL
* after the timeout expires. Destroys the subcontainer after the process exits.
*
* @param options.signal - The signal to send (default: SIGTERM)
* @param options.timeout - Milliseconds before escalating to SIGKILL
*/
async term({ signal = types_1.SIGTERM, timeout = this.sigtermTimeout } = {}) {
try {
if (!this.state.exited) {
if (this.process instanceof AbortController)
return this.process.abort();
if (signal !== 'SIGKILL') {
setTimeout(() => {
if (this.process instanceof AbortController)
this.process.abort();
else
this.process.kill('SIGKILL');
}, timeout);
}
if (!this.process.kill(signal)) {
console.error(`failed to send signal ${signal} to pid ${this.process.pid}`);
}
}
if (this.process instanceof AbortController)
await Promise.race([
this.runningAnswer,
new Promise((_, reject) => setTimeout(() => reject(new Error('Timed out waiting for js command to exit')), timeout * 2)),
]);
else
await this.runningAnswer;
}
finally {
await this.subcontainer?.destroy();
}
}
onDrop() {
this.term().catch(logErrorOnce_1.logErrorOnce);
}
}
exports.CommandController = CommandController;
//# sourceMappingURL=CommandController.js.map