systemd-ts 0.3.0 → 0.3.1
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 +61 -0
- package/dist/index-DeNBLyGA.d.mts +1612 -0
- package/dist/index.d.mts +2 -1612
- package/dist/index.mjs +1 -1609
- package/dist/main-BXq7TQdg.mjs +1610 -0
- package/dist/test.d.mts +29 -1
- package/dist/test.mjs +93 -16
- package/package.json +2 -2
package/dist/test.d.mts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { j as CommandExecutor, n as Systemd } from "./index-DeNBLyGA.mjs";
|
|
2
|
+
|
|
1
3
|
//#region src/test/host.d.ts
|
|
2
4
|
interface TestHostInfo {
|
|
3
5
|
readonly backend: TestHostBackendName;
|
|
@@ -12,6 +14,32 @@ declare function getTestHostInfo(): TestHostInfo;
|
|
|
12
14
|
declare function ensureTestHost(): Promise<TestHostInfo>;
|
|
13
15
|
declare function closeTestHost(): void;
|
|
14
16
|
declare function runGuestCommand(script: string): Promise<string>;
|
|
17
|
+
declare function runIsolatedGuestCommand(script: string): Promise<string>;
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/test/harness.d.ts
|
|
20
|
+
/**
|
|
21
|
+
* Creates a command executor that runs argv-style commands inside the current
|
|
22
|
+
* test host guest shell.
|
|
23
|
+
*/
|
|
24
|
+
declare function createGuestCommandExecutor(runCommand: (script: string) => Promise<string>): CommandExecutor;
|
|
25
|
+
/**
|
|
26
|
+
* Executor that reuses the warm guest shell for fast sandbox-backed tests.
|
|
27
|
+
*/
|
|
28
|
+
declare const guestCommandExecutor: CommandExecutor;
|
|
29
|
+
/**
|
|
30
|
+
* Executor that runs each guest command in a fresh isolated guest shell.
|
|
31
|
+
*/
|
|
32
|
+
declare const isolatedGuestCommandExecutor: CommandExecutor;
|
|
33
|
+
/**
|
|
34
|
+
* Creates a `Systemd` instance scoped to the active test sandbox and backed by
|
|
35
|
+
* the warm guest-shell executor.
|
|
36
|
+
*/
|
|
37
|
+
declare function sandboxSystemd(): Systemd;
|
|
38
|
+
/**
|
|
39
|
+
* Creates a `Systemd` instance scoped to the active test sandbox and backed by
|
|
40
|
+
* a fresh guest shell per command.
|
|
41
|
+
*/
|
|
42
|
+
declare function isolatedSandboxSystemd(): Systemd;
|
|
15
43
|
//#endregion
|
|
16
44
|
//#region src/test/sandbox.d.ts
|
|
17
45
|
interface TestSandbox {
|
|
@@ -25,4 +53,4 @@ declare function createTestSandbox(testName?: string): Promise<TestSandbox>;
|
|
|
25
53
|
declare function destroyCurrentTestSandbox(): Promise<void>;
|
|
26
54
|
declare function useCurrentTestSandbox(): TestSandbox;
|
|
27
55
|
//#endregion
|
|
28
|
-
export { type TestHostInfo, type TestSandbox, closeTestHost, createTestSandbox, destroyCurrentTestSandbox, ensureTestHost, getTestHostInfo, runGuestCommand, useCurrentTestSandbox };
|
|
56
|
+
export { type TestHostInfo, type TestSandbox, closeTestHost, createGuestCommandExecutor, createTestSandbox, destroyCurrentTestSandbox, ensureTestHost, getTestHostInfo, guestCommandExecutor, isolatedGuestCommandExecutor, isolatedSandboxSystemd, runGuestCommand, runIsolatedGuestCommand, sandboxSystemd, useCurrentTestSandbox };
|
package/dist/test.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { n as Systemd } from "./main-BXq7TQdg.mjs";
|
|
1
2
|
import { fileURLToPath } from "node:url";
|
|
2
3
|
import { execFile, spawn } from "node:child_process";
|
|
3
4
|
import { promisify } from "node:util";
|
|
@@ -35,6 +36,9 @@ function closeTestHost() {
|
|
|
35
36
|
async function runGuestCommand(script) {
|
|
36
37
|
return getGuestShell().run(script);
|
|
37
38
|
}
|
|
39
|
+
async function runIsolatedGuestCommand(script) {
|
|
40
|
+
return backend.runIsolatedCommand(script);
|
|
41
|
+
}
|
|
38
42
|
async function ensureTestHostInner() {
|
|
39
43
|
logTestHost(`Ensuring ${backend.info.backend} test host`);
|
|
40
44
|
await backend.ensureHost();
|
|
@@ -287,16 +291,16 @@ var DockerBackend = class {
|
|
|
287
291
|
`bash`,
|
|
288
292
|
`-lc`,
|
|
289
293
|
[
|
|
290
|
-
`mkdir -p ${shellQuote$
|
|
291
|
-
`chown ${runtimeInfo.userId}:${runtimeInfo.userId} ${shellQuote$
|
|
292
|
-
`loginctl enable-linger ${shellQuote$
|
|
294
|
+
`mkdir -p ${shellQuote$2(dockerStateRoot)}`,
|
|
295
|
+
`chown ${runtimeInfo.userId}:${runtimeInfo.userId} ${shellQuote$2(dockerStateRoot)}`,
|
|
296
|
+
`loginctl enable-linger ${shellQuote$2(dockerUser)}`,
|
|
293
297
|
`systemctl start user@${runtimeInfo.userId}.service`
|
|
294
298
|
].join(`\n`)
|
|
295
299
|
], { timeout: dockerExecTimeoutMs });
|
|
296
300
|
await this.execDockerAsUser(runtimeInfo, [
|
|
297
301
|
`bash`,
|
|
298
302
|
`-lc`,
|
|
299
|
-
`mkdir -p ${shellQuote$
|
|
303
|
+
`mkdir -p ${shellQuote$2(`${dockerStateRoot}/tests`)}`
|
|
300
304
|
]);
|
|
301
305
|
logTestHost(`User session started for ${dockerUser}`);
|
|
302
306
|
}
|
|
@@ -344,7 +348,7 @@ var DockerBackend = class {
|
|
|
344
348
|
dockerContainer,
|
|
345
349
|
`bash`,
|
|
346
350
|
`-lc`,
|
|
347
|
-
`getent passwd ${shellQuote$
|
|
351
|
+
`getent passwd ${shellQuote$2(dockerUser)} | cut -d: -f6`
|
|
348
352
|
])).trim();
|
|
349
353
|
if (home.length === 0) throw new Error(`Could not resolve home directory for Docker test user ${dockerUser}`);
|
|
350
354
|
logTestHost(`Resolved home directory for ${dockerUser}: ${home}`);
|
|
@@ -357,7 +361,7 @@ var DockerBackend = class {
|
|
|
357
361
|
dockerContainer,
|
|
358
362
|
`bash`,
|
|
359
363
|
`-lc`,
|
|
360
|
-
`id -u ${shellQuote$
|
|
364
|
+
`id -u ${shellQuote$2(dockerUser)}`
|
|
361
365
|
]);
|
|
362
366
|
const parsed = Number(output.trim());
|
|
363
367
|
if (!Number.isInteger(parsed)) throw new Error(`Could not resolve uid for Docker test user ${dockerUser}`);
|
|
@@ -456,7 +460,7 @@ printf '\\n${marker}:%s\\n' "$status"
|
|
|
456
460
|
this.drain();
|
|
457
461
|
}
|
|
458
462
|
};
|
|
459
|
-
function shellQuote$
|
|
463
|
+
function shellQuote$2(value) {
|
|
460
464
|
return `'${value.replaceAll(`'`, `'\\''`)}'`;
|
|
461
465
|
}
|
|
462
466
|
const backend = createBackend(selectedBackendName);
|
|
@@ -470,8 +474,8 @@ async function createTestSandbox(testName) {
|
|
|
470
474
|
const linkedUnitDir = `${rootDir}/linked-units`;
|
|
471
475
|
const workDir = `${rootDir}/work`;
|
|
472
476
|
await runGuestCommand(`set -euo pipefail
|
|
473
|
-
rm -rf ${shellQuote(rootDir)}
|
|
474
|
-
mkdir -p ${shellQuote(linkedUnitDir)} ${shellQuote(workDir)}`);
|
|
477
|
+
rm -rf ${shellQuote$1(rootDir)}
|
|
478
|
+
mkdir -p ${shellQuote$1(linkedUnitDir)} ${shellQuote$1(workDir)}`);
|
|
475
479
|
currentSandbox = {
|
|
476
480
|
id,
|
|
477
481
|
linkedUnitDir,
|
|
@@ -485,24 +489,97 @@ async function destroyCurrentTestSandbox() {
|
|
|
485
489
|
if (currentSandbox === void 0) return;
|
|
486
490
|
const sandbox = currentSandbox;
|
|
487
491
|
currentSandbox = void 0;
|
|
488
|
-
const units = (await runGuestCommand(`systemctl --user list-unit-files --all --no-legend ${shellQuote(`${sandbox.namePrefix}*`)} 2>/dev/null | awk '{print $1}' || true`)).split(`\n`).map((unit) => unit.trim()).filter((unit) => unit.length > 0);
|
|
492
|
+
const units = (await runGuestCommand(`systemctl --user list-unit-files --all --no-legend ${shellQuote$1(`${sandbox.namePrefix}*`)} 2>/dev/null | awk '{print $1}' || true`)).split(`\n`).map((unit) => unit.trim()).filter((unit) => unit.length > 0);
|
|
489
493
|
if (units.length > 0) {
|
|
490
|
-
await runGuestCommand(`systemctl --user stop ${units.map(shellQuote).join(` `)} || true`);
|
|
491
|
-
await runGuestCommand(`systemctl --user disable ${units.map(shellQuote).join(` `)} || true`);
|
|
492
|
-
await runGuestCommand(`systemctl --user reset-failed ${units.map(shellQuote).join(` `)} || true`);
|
|
494
|
+
await runGuestCommand(`systemctl --user stop ${units.map(shellQuote$1).join(` `)} || true`);
|
|
495
|
+
await runGuestCommand(`systemctl --user disable ${units.map(shellQuote$1).join(` `)} || true`);
|
|
496
|
+
await runGuestCommand(`systemctl --user reset-failed ${units.map(shellQuote$1).join(` `)} || true`);
|
|
493
497
|
}
|
|
494
498
|
await runGuestCommand(`systemctl --user daemon-reload || true`);
|
|
495
|
-
await runGuestCommand(`rm -rf ${shellQuote(sandbox.rootDir)}`);
|
|
499
|
+
await runGuestCommand(`rm -rf ${shellQuote$1(sandbox.rootDir)}`);
|
|
496
500
|
}
|
|
497
501
|
function useCurrentTestSandbox() {
|
|
498
502
|
if (currentSandbox === void 0) throw new Error(`No active test sandbox is available`);
|
|
499
503
|
return currentSandbox;
|
|
500
504
|
}
|
|
501
|
-
function shellQuote(value) {
|
|
505
|
+
function shellQuote$1(value) {
|
|
502
506
|
return `'${value.replaceAll(`'`, `'\\''`)}'`;
|
|
503
507
|
}
|
|
504
508
|
function slugify(value) {
|
|
505
509
|
return value.toLowerCase().replaceAll(/[^a-z0-9]+/g, `-`).replaceAll(/^-+|-+$/g, ``).slice(0, 32);
|
|
506
510
|
}
|
|
507
511
|
//#endregion
|
|
508
|
-
|
|
512
|
+
//#region src/test/harness.ts
|
|
513
|
+
/**
|
|
514
|
+
* Creates a command executor that runs argv-style commands inside the current
|
|
515
|
+
* test host guest shell.
|
|
516
|
+
*/
|
|
517
|
+
function createGuestCommandExecutor(runCommand) {
|
|
518
|
+
return async (command, args, options = {}) => {
|
|
519
|
+
try {
|
|
520
|
+
return {
|
|
521
|
+
stderr: ``,
|
|
522
|
+
stdout: await runCommand(buildGuestCommand(command, args, options))
|
|
523
|
+
};
|
|
524
|
+
} catch (cause) {
|
|
525
|
+
throw enrichGuestCommandError(cause);
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Executor that reuses the warm guest shell for fast sandbox-backed tests.
|
|
531
|
+
*/
|
|
532
|
+
const guestCommandExecutor = createGuestCommandExecutor(runGuestCommand);
|
|
533
|
+
/**
|
|
534
|
+
* Executor that runs each guest command in a fresh isolated guest shell.
|
|
535
|
+
*/
|
|
536
|
+
const isolatedGuestCommandExecutor = createGuestCommandExecutor(runIsolatedGuestCommand);
|
|
537
|
+
/**
|
|
538
|
+
* Creates a `Systemd` instance scoped to the active test sandbox and backed by
|
|
539
|
+
* the warm guest-shell executor.
|
|
540
|
+
*/
|
|
541
|
+
function sandboxSystemd() {
|
|
542
|
+
return new Systemd({
|
|
543
|
+
executor: guestCommandExecutor,
|
|
544
|
+
linkUnits: true,
|
|
545
|
+
scope: `user`,
|
|
546
|
+
unitDir: useCurrentTestSandbox().linkedUnitDir
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Creates a `Systemd` instance scoped to the active test sandbox and backed by
|
|
551
|
+
* a fresh guest shell per command.
|
|
552
|
+
*/
|
|
553
|
+
function isolatedSandboxSystemd() {
|
|
554
|
+
return new Systemd({
|
|
555
|
+
executor: isolatedGuestCommandExecutor,
|
|
556
|
+
linkUnits: true,
|
|
557
|
+
scope: `user`,
|
|
558
|
+
unitDir: useCurrentTestSandbox().linkedUnitDir
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
function buildGuestCommand(command, args, options) {
|
|
562
|
+
return [
|
|
563
|
+
`env`,
|
|
564
|
+
...options.env === void 0 ? [] : Object.entries(options.env).flatMap(([key, value]) => value === void 0 ? [`-u`, key] : [`${key}=${value}`]),
|
|
565
|
+
command,
|
|
566
|
+
...args
|
|
567
|
+
].map((part) => shellQuote(part)).join(` `);
|
|
568
|
+
}
|
|
569
|
+
function enrichGuestCommandError(cause) {
|
|
570
|
+
if (cause instanceof Error) return Object.assign(cause, {
|
|
571
|
+
code: 1,
|
|
572
|
+
stderr: ``,
|
|
573
|
+
stdout: cause.message
|
|
574
|
+
});
|
|
575
|
+
return Object.assign(/* @__PURE__ */ new Error(`Guest command failed`), {
|
|
576
|
+
code: 1,
|
|
577
|
+
stderr: ``,
|
|
578
|
+
stdout: ``
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
function shellQuote(value) {
|
|
582
|
+
return `'${value.replaceAll(`'`, `'\\''`)}'`;
|
|
583
|
+
}
|
|
584
|
+
//#endregion
|
|
585
|
+
export { closeTestHost, createGuestCommandExecutor, createTestSandbox, destroyCurrentTestSandbox, ensureTestHost, getTestHostInfo, guestCommandExecutor, isolatedGuestCommandExecutor, isolatedSandboxSystemd, runGuestCommand, runIsolatedGuestCommand, sandboxSystemd, useCurrentTestSandbox };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "systemd-ts",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "TypeScript builders and helpers for systemd services and timers.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/node": "25.6.2",
|
|
25
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
25
|
+
"@typescript/native-preview": "7.0.0-dev.20260511.1",
|
|
26
26
|
"takua": "0.1.1",
|
|
27
27
|
"typescript": "6.0.3",
|
|
28
28
|
"vite-plus": "0.1.20"
|