systemd-ts 0.2.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/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$1(dockerStateRoot)}`,
291
- `chown ${runtimeInfo.userId}:${runtimeInfo.userId} ${shellQuote$1(dockerStateRoot)}`,
292
- `loginctl enable-linger ${shellQuote$1(dockerUser)}`,
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$1(`${dockerStateRoot}/tests`)}`
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$1(dockerUser)} | cut -d: -f6`
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$1(dockerUser)}`
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$1(value) {
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
- export { closeTestHost, createTestSandbox, destroyCurrentTestSandbox, ensureTestHost, getTestHostInfo, runGuestCommand, useCurrentTestSandbox };
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.2.0",
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.20260510.1",
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"