@augment-vir/node 30.0.0

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 (74) hide show
  1. package/LICENSE-CC0 +121 -0
  2. package/LICENSE-MIT +21 -0
  3. package/README.md +13 -0
  4. package/dist/augments/console/question.d.ts +14 -0
  5. package/dist/augments/console/question.js +63 -0
  6. package/dist/augments/docker.d.ts +44 -0
  7. package/dist/augments/docker.js +40 -0
  8. package/dist/augments/download.d.ts +4 -0
  9. package/dist/augments/download.js +19 -0
  10. package/dist/augments/fs/json.d.ts +8 -0
  11. package/dist/augments/fs/json.js +22 -0
  12. package/dist/augments/fs/read-dir.d.ts +16 -0
  13. package/dist/augments/fs/read-dir.js +31 -0
  14. package/dist/augments/fs/read-file.d.ts +1 -0
  15. package/dist/augments/fs/read-file.js +10 -0
  16. package/dist/augments/fs/symlink.d.ts +9 -0
  17. package/dist/augments/fs/symlink.js +16 -0
  18. package/dist/augments/fs/write.d.ts +5 -0
  19. package/dist/augments/fs/write.js +10 -0
  20. package/dist/augments/npm/query-workspace.d.ts +18 -0
  21. package/dist/augments/npm/query-workspace.js +14 -0
  22. package/dist/augments/npm/read-package-json.d.ts +2 -0
  23. package/dist/augments/npm/read-package-json.js +14 -0
  24. package/dist/augments/os/operating-system.d.ts +6 -0
  25. package/dist/augments/os/operating-system.js +20 -0
  26. package/dist/augments/path/ancestor.d.ts +5 -0
  27. package/dist/augments/path/ancestor.js +29 -0
  28. package/dist/augments/path/os-path.d.ts +9 -0
  29. package/dist/augments/path/os-path.js +27 -0
  30. package/dist/augments/path/root.d.ts +1 -0
  31. package/dist/augments/path/root.js +5 -0
  32. package/dist/augments/prisma.d.ts +21 -0
  33. package/dist/augments/prisma.js +21 -0
  34. package/dist/augments/shell.d.ts +87 -0
  35. package/dist/augments/shell.js +152 -0
  36. package/dist/docker/containers/container-info.d.ts +28 -0
  37. package/dist/docker/containers/container-info.js +24 -0
  38. package/dist/docker/containers/container-status.d.ts +17 -0
  39. package/dist/docker/containers/container-status.js +53 -0
  40. package/dist/docker/containers/copy-to-container.d.ts +7 -0
  41. package/dist/docker/containers/copy-to-container.js +10 -0
  42. package/dist/docker/containers/docker-command-inputs.d.ts +20 -0
  43. package/dist/docker/containers/docker-command-inputs.js +36 -0
  44. package/dist/docker/containers/kill-container.d.ts +10 -0
  45. package/dist/docker/containers/kill-container.js +12 -0
  46. package/dist/docker/containers/run-command.d.ts +12 -0
  47. package/dist/docker/containers/run-command.js +25 -0
  48. package/dist/docker/containers/run-container.d.ts +15 -0
  49. package/dist/docker/containers/run-container.js +47 -0
  50. package/dist/docker/containers/run-container.mock.d.ts +2 -0
  51. package/dist/docker/containers/run-container.mock.js +14 -0
  52. package/dist/docker/containers/try-or-kill-container.d.ts +6 -0
  53. package/dist/docker/containers/try-or-kill-container.js +14 -0
  54. package/dist/docker/docker-image.d.ts +9 -0
  55. package/dist/docker/docker-image.js +36 -0
  56. package/dist/docker/docker-startup.d.ts +2 -0
  57. package/dist/docker/docker-startup.js +40 -0
  58. package/dist/file-paths.mock.d.ts +17 -0
  59. package/dist/file-paths.mock.js +19 -0
  60. package/dist/index.d.ts +16 -0
  61. package/dist/index.js +16 -0
  62. package/dist/prisma/prisma-client.d.ts +9 -0
  63. package/dist/prisma/prisma-client.js +28 -0
  64. package/dist/prisma/prisma-database.d.ts +3 -0
  65. package/dist/prisma/prisma-database.js +22 -0
  66. package/dist/prisma/prisma-database.mock.d.ts +1 -0
  67. package/dist/prisma/prisma-database.mock.js +7 -0
  68. package/dist/prisma/prisma-errors.d.ts +12 -0
  69. package/dist/prisma/prisma-errors.js +18 -0
  70. package/dist/prisma/prisma-migrations.d.ts +17 -0
  71. package/dist/prisma/prisma-migrations.js +105 -0
  72. package/dist/prisma/run-prisma-command.d.ts +8 -0
  73. package/dist/prisma/run-prisma-command.js +48 -0
  74. package/package.json +58 -0
@@ -0,0 +1,27 @@
1
+ import { sep } from 'node:path';
2
+ /** Convert a given path to a windows path if the current system doesn't use `/`. */
3
+ export function replaceWithWindowsPathIfNeeded(input) {
4
+ if (sep === '/') {
5
+ return input;
6
+ /** Can't test on Windows. */
7
+ /* node:coverage ignore next 3 */
8
+ }
9
+ else {
10
+ return input.replace(/\//g, sep);
11
+ }
12
+ }
13
+ /** Convert a Windows path to a posix path. */
14
+ export function toPosixPath(maybeWindowsPath) {
15
+ return maybeWindowsPath
16
+ .replace(/^(.+?):\\+/, (match, captureGroup) => {
17
+ return `/${captureGroup.toLowerCase()}/`;
18
+ })
19
+ .replace(/\\+/g, '/');
20
+ }
21
+ /**
22
+ * Use this to interpolate paths into bash commands. If the given path is not a window path, the
23
+ * path structure will not be modified.
24
+ */
25
+ export function interpolationSafeWindowsPath(input) {
26
+ return input.replace(/\\/g, '\\\\\\\\');
27
+ }
@@ -0,0 +1 @@
1
+ export declare const systemRootPath: string;
@@ -0,0 +1,5 @@
1
+ import { parse } from 'node:path';
2
+ function getSystemRootPath() {
3
+ return parse(process.cwd()).root;
4
+ }
5
+ export const systemRootPath = getSystemRootPath();
@@ -0,0 +1,21 @@
1
+ import { generatePrismaClient, isGeneratedPrismaClientCurrent } from '../prisma/prisma-client.js';
2
+ import { doesPrismaDiffExist, getPrismaDiff, resetDevPrismaDatabase } from '../prisma/prisma-database.js';
3
+ import { applyPrismaMigrationsToDev, applyPrismaMigrationsToProd, createPrismaMigration, getMigrationStatus } from '../prisma/prisma-migrations.js';
4
+ /** Centralized Prisma API from `@augment-vir/node`. */
5
+ export declare const prisma: {
6
+ migration: {
7
+ status: typeof getMigrationStatus;
8
+ create: typeof createPrismaMigration;
9
+ applyProd: typeof applyPrismaMigrationsToProd;
10
+ applyDev: typeof applyPrismaMigrationsToDev;
11
+ };
12
+ database: {
13
+ resetDev: typeof resetDevPrismaDatabase;
14
+ hasDiff: typeof doesPrismaDiffExist;
15
+ diff: typeof getPrismaDiff;
16
+ };
17
+ client: {
18
+ generate: typeof generatePrismaClient;
19
+ isCurrent: typeof isGeneratedPrismaClientCurrent;
20
+ };
21
+ };
@@ -0,0 +1,21 @@
1
+ import { generatePrismaClient, isGeneratedPrismaClientCurrent } from '../prisma/prisma-client.js';
2
+ import { doesPrismaDiffExist, getPrismaDiff, resetDevPrismaDatabase, } from '../prisma/prisma-database.js';
3
+ import { applyPrismaMigrationsToDev, applyPrismaMigrationsToProd, createPrismaMigration, getMigrationStatus, } from '../prisma/prisma-migrations.js';
4
+ /** Centralized Prisma API from `@augment-vir/node`. */
5
+ export const prisma = {
6
+ migration: {
7
+ status: getMigrationStatus,
8
+ create: createPrismaMigration,
9
+ applyProd: applyPrismaMigrationsToProd,
10
+ applyDev: applyPrismaMigrationsToDev,
11
+ },
12
+ database: {
13
+ resetDev: resetDevPrismaDatabase,
14
+ hasDiff: doesPrismaDiffExist,
15
+ diff: getPrismaDiff,
16
+ },
17
+ client: {
18
+ generate: generatePrismaClient,
19
+ isCurrent: isGeneratedPrismaClientCurrent,
20
+ },
21
+ };
@@ -0,0 +1,87 @@
1
+ import { type Logger } from '@augment-vir/common';
2
+ import type { MaybePromise, PartialWithUndefined } from '@augment-vir/core';
3
+ import { ChildProcess } from 'node:child_process';
4
+ import { ListenTarget } from 'typed-event-target';
5
+ export type ShellOutput = {
6
+ error: undefined | Error;
7
+ stderr: string;
8
+ stdout: string;
9
+ exitCode: number | undefined;
10
+ exitSignal: NodeJS.Signals | undefined;
11
+ };
12
+ declare const ShellStdoutEvent_base: (new (eventInitDict: import("typed-event-target").TypedCustomEventInit<string | Buffer>) => import("typed-event-target").TypedCustomEvent<string | Buffer, "shell-stdout">) & Pick<{
13
+ new (type: string, eventInitDict?: EventInit | undefined): Event;
14
+ prototype: Event;
15
+ readonly NONE: 0;
16
+ readonly CAPTURING_PHASE: 1;
17
+ readonly AT_TARGET: 2;
18
+ readonly BUBBLING_PHASE: 3;
19
+ }, "NONE" | "CAPTURING_PHASE" | "AT_TARGET" | "BUBBLING_PHASE" | "prototype"> & Pick<import("typed-event-target").TypedCustomEvent<string | Buffer, "shell-stdout">, "type">;
20
+ export declare class ShellStdoutEvent extends ShellStdoutEvent_base {
21
+ }
22
+ declare const ShellStderrEvent_base: (new (eventInitDict: import("typed-event-target").TypedCustomEventInit<string | Buffer>) => import("typed-event-target").TypedCustomEvent<string | Buffer, "shell-stderr">) & Pick<{
23
+ new (type: string, eventInitDict?: EventInit | undefined): Event;
24
+ prototype: Event;
25
+ readonly NONE: 0;
26
+ readonly CAPTURING_PHASE: 1;
27
+ readonly AT_TARGET: 2;
28
+ readonly BUBBLING_PHASE: 3;
29
+ }, "NONE" | "CAPTURING_PHASE" | "AT_TARGET" | "BUBBLING_PHASE" | "prototype"> & Pick<import("typed-event-target").TypedCustomEvent<string | Buffer, "shell-stderr">, "type">;
30
+ export declare class ShellStderrEvent extends ShellStderrEvent_base {
31
+ }
32
+ declare const ShellDoneEvent_base: (new (eventInitDict: import("typed-event-target").TypedCustomEventInit<{
33
+ exitCode: number | undefined;
34
+ exitSignal: NodeJS.Signals | undefined;
35
+ }>) => import("typed-event-target").TypedCustomEvent<{
36
+ exitCode: number | undefined;
37
+ exitSignal: NodeJS.Signals | undefined;
38
+ }, "shell-done">) & Pick<{
39
+ new (type: string, eventInitDict?: EventInit | undefined): Event;
40
+ prototype: Event;
41
+ readonly NONE: 0;
42
+ readonly CAPTURING_PHASE: 1;
43
+ readonly AT_TARGET: 2;
44
+ readonly BUBBLING_PHASE: 3;
45
+ }, "NONE" | "CAPTURING_PHASE" | "AT_TARGET" | "BUBBLING_PHASE" | "prototype"> & Pick<import("typed-event-target").TypedCustomEvent<{
46
+ exitCode: number | undefined;
47
+ exitSignal: NodeJS.Signals | undefined;
48
+ }, "shell-done">, "type">;
49
+ /**
50
+ * Contains an exit code or an exit signal. Based on the Node.js documentation, either one or the
51
+ * other is defined, never both at the same time.
52
+ */
53
+ export declare class ShellDoneEvent extends ShellDoneEvent_base {
54
+ }
55
+ declare const ShellErrorEvent_base: (new (eventInitDict: import("typed-event-target").TypedCustomEventInit<Error>) => import("typed-event-target").TypedCustomEvent<Error, "shell-error">) & Pick<{
56
+ new (type: string, eventInitDict?: EventInit | undefined): Event;
57
+ prototype: Event;
58
+ readonly NONE: 0;
59
+ readonly CAPTURING_PHASE: 1;
60
+ readonly AT_TARGET: 2;
61
+ readonly BUBBLING_PHASE: 3;
62
+ }, "NONE" | "CAPTURING_PHASE" | "AT_TARGET" | "BUBBLING_PHASE" | "prototype"> & Pick<import("typed-event-target").TypedCustomEvent<Error, "shell-error">, "type">;
63
+ export declare class ShellErrorEvent extends ShellErrorEvent_base {
64
+ }
65
+ export declare class ShellTarget extends ListenTarget<ShellStdoutEvent | ShellStderrEvent | ShellDoneEvent | ShellErrorEvent> {
66
+ readonly childProcess: ChildProcess;
67
+ constructor(childProcess: ChildProcess);
68
+ }
69
+ export declare function streamShellCommand(command: string, cwd?: string, shell?: string, env?: NodeJS.ProcessEnv, hookUpToConsole?: boolean): ShellTarget;
70
+ export type RunShellCommandParams = {
71
+ cwd?: string | undefined;
72
+ env?: NodeJS.ProcessEnv | undefined;
73
+ shell?: string | undefined;
74
+ /** Automatically hook up stdout and stderr printing to the caller's console methods. */
75
+ hookUpToConsole?: boolean | undefined;
76
+ rejectOnError?: boolean | undefined;
77
+ stdoutCallback?: (stdout: string, childProcess: ChildProcess) => MaybePromise<void> | undefined;
78
+ stderrCallback?: (stderr: string, childProcess: ChildProcess) => MaybePromise<void> | undefined;
79
+ };
80
+ export declare function runShellCommand(command: string, options?: RunShellCommandParams): Promise<ShellOutput>;
81
+ export type LogShellOutputOptions = PartialWithUndefined<{
82
+ logger: Logger;
83
+ withLabels: boolean;
84
+ ignoreError: boolean;
85
+ }>;
86
+ export declare function logShellOutput(shellOutput: PartialWithUndefined<Omit<ShellOutput, 'exitSignal'>>, { ignoreError, logger, withLabels, }?: LogShellOutputOptions): void;
87
+ export {};
@@ -0,0 +1,152 @@
1
+ import { combineErrors, log } from '@augment-vir/common';
2
+ import { spawn } from 'node:child_process';
3
+ import { defineTypedCustomEvent, ListenTarget } from 'typed-event-target';
4
+ export class ShellStdoutEvent extends defineTypedCustomEvent()('shell-stdout') {
5
+ }
6
+ export class ShellStderrEvent extends defineTypedCustomEvent()('shell-stderr') {
7
+ }
8
+ /**
9
+ * Contains an exit code or an exit signal. Based on the Node.js documentation, either one or the
10
+ * other is defined, never both at the same time.
11
+ */
12
+ export class ShellDoneEvent extends defineTypedCustomEvent()('shell-done') {
13
+ }
14
+ export class ShellErrorEvent extends defineTypedCustomEvent()('shell-error') {
15
+ }
16
+ export class ShellTarget extends ListenTarget {
17
+ childProcess;
18
+ constructor(childProcess) {
19
+ super();
20
+ this.childProcess = childProcess;
21
+ }
22
+ }
23
+ export function streamShellCommand(command, cwd, shell = 'bash', env = process.env, hookUpToConsole = false) {
24
+ const stdio = hookUpToConsole ? [process.stdin] : undefined;
25
+ const childProcess = spawn(command, { shell, cwd, env, stdio });
26
+ const shellTarget = new ShellTarget(childProcess);
27
+ /** Type guards. */
28
+ /* node:coverage ignore next 5 */
29
+ if (!childProcess.stdout) {
30
+ throw new Error(`stdout emitter was not created by exec for some reason.`);
31
+ }
32
+ else if (!childProcess.stderr) {
33
+ throw new Error(`stderr emitter was not created by exec for some reason.`);
34
+ }
35
+ childProcess.stdout.on('data', (chunk) => {
36
+ shellTarget.dispatch(new ShellStdoutEvent({ detail: chunk }));
37
+ });
38
+ childProcess.stderr.on('data', (chunk) => {
39
+ shellTarget.dispatch(new ShellStderrEvent({ detail: chunk }));
40
+ });
41
+ /** Idk how to trigger the 'error' event. */
42
+ /* node:coverage ignore next 3 */
43
+ childProcess.on('error', (error) => {
44
+ shellTarget.dispatch(new ShellErrorEvent({ detail: error }));
45
+ });
46
+ /**
47
+ * Based on the Node.js documentation, we should listen to "close" instead of "exit" because the
48
+ * io streams will be finished when "close" emits. Also "close" always emits after "exit"
49
+ * anyway.
50
+ */
51
+ childProcess.on('close', (inputExitCode, inputExitSignal) => {
52
+ /** Idk how to control exitCode or exitSignal being null or not-null. */
53
+ /* node:coverage ignore next 2 */
54
+ const exitCode = inputExitCode ?? undefined;
55
+ const exitSignal = inputExitSignal ?? undefined;
56
+ if ((exitCode !== undefined && exitCode !== 0) || exitSignal !== undefined) {
57
+ const execException = new Error(`Command failed: ${command}`);
58
+ execException.code = exitCode;
59
+ execException.signal = exitSignal;
60
+ execException.cmd = command;
61
+ execException.killed = childProcess.killed;
62
+ execException.cwd = cwd;
63
+ shellTarget.dispatch(new ShellErrorEvent({ detail: execException }));
64
+ }
65
+ shellTarget.dispatch(new ShellDoneEvent({
66
+ detail: {
67
+ exitCode,
68
+ exitSignal,
69
+ },
70
+ }));
71
+ });
72
+ return shellTarget;
73
+ }
74
+ function prepareChunkForLogging(chunk, trimEndingLine) {
75
+ const stringified = chunk.toString();
76
+ return trimEndingLine ? stringified.replace(/\n$/, '') : stringified;
77
+ }
78
+ export async function runShellCommand(command, options = {}) {
79
+ return new Promise((resolve, reject) => {
80
+ let stdout = '';
81
+ let stderr = '';
82
+ const errors = [];
83
+ const shellTarget = streamShellCommand(command, options.cwd, options.shell, options.env, options.hookUpToConsole);
84
+ shellTarget.listen(ShellStdoutEvent, ({ detail: chunk }) => {
85
+ if (options.stdoutCallback) {
86
+ void options.stdoutCallback(prepareChunkForLogging(chunk, false), shellTarget.childProcess);
87
+ }
88
+ if (options.hookUpToConsole) {
89
+ process.stdout.write(prepareChunkForLogging(chunk, true) + '\n');
90
+ }
91
+ stdout += String(chunk);
92
+ });
93
+ shellTarget.listen(ShellStderrEvent, ({ detail: chunk }) => {
94
+ if (options.stderrCallback) {
95
+ void options.stderrCallback(prepareChunkForLogging(chunk, false), shellTarget.childProcess);
96
+ }
97
+ if (options.hookUpToConsole) {
98
+ process.stderr.write(prepareChunkForLogging(chunk, true) + '\n');
99
+ }
100
+ stderr += String(chunk);
101
+ });
102
+ shellTarget.listen(ShellErrorEvent, ({ detail: error }) => {
103
+ errors.push(error);
104
+ if (!options.rejectOnError) {
105
+ return;
106
+ }
107
+ /** Covering edge cases. */
108
+ /* node:coverage disable */
109
+ if (shellTarget.childProcess.connected) {
110
+ shellTarget.childProcess.disconnect();
111
+ }
112
+ if (shellTarget.childProcess.exitCode == null &&
113
+ shellTarget.childProcess.signalCode == null &&
114
+ !shellTarget.childProcess.killed) {
115
+ shellTarget.childProcess.kill();
116
+ }
117
+ /* node:coverage enable */
118
+ shellTarget.destroy();
119
+ const rejectionErrorMessage = combineErrors([
120
+ new Error(stderr),
121
+ ...errors,
122
+ ]);
123
+ /** Reject now because the "done" listener won't get fired after killing the process. */
124
+ reject(rejectionErrorMessage);
125
+ });
126
+ shellTarget.listen(ShellDoneEvent, ({ detail: { exitCode, exitSignal } }) => {
127
+ shellTarget.destroy();
128
+ resolve({
129
+ error: combineErrors(errors),
130
+ stdout,
131
+ stderr,
132
+ exitCode,
133
+ exitSignal,
134
+ });
135
+ });
136
+ });
137
+ }
138
+ const defaultLogShellOutputOptions = {
139
+ ignoreError: false,
140
+ logger: log,
141
+ withLabels: false,
142
+ };
143
+ export function logShellOutput(shellOutput, { ignoreError = defaultLogShellOutputOptions.ignoreError, logger = defaultLogShellOutputOptions.logger, withLabels = defaultLogShellOutputOptions.withLabels, } = defaultLogShellOutputOptions) {
144
+ logger.if(withLabels).info('exit code');
145
+ logger.if(shellOutput.exitCode != undefined || withLabels).plain(shellOutput.exitCode || 0);
146
+ logger.if(withLabels).info('stdout');
147
+ logger.if(!!shellOutput.stdout || withLabels).plain(shellOutput.stdout || '');
148
+ logger.if(withLabels).info('stderr');
149
+ logger.if(!!shellOutput.stderr || withLabels).error(shellOutput.stderr || '');
150
+ logger.if(withLabels && !ignoreError).info('error');
151
+ logger.if((!!shellOutput.error || withLabels) && !ignoreError).error(shellOutput.error);
152
+ }
@@ -0,0 +1,28 @@
1
+ /** There may be other possible values for Status. */
2
+ export declare enum DockerContainerStatusEnum {
3
+ exited = "exited",
4
+ running = "running"
5
+ }
6
+ export type DockerContainerInfoState = {
7
+ Status: DockerContainerStatusEnum;
8
+ Running: boolean;
9
+ Paused: boolean;
10
+ Restarting: boolean;
11
+ OOMKilled: boolean;
12
+ Dead: boolean;
13
+ Pid: number;
14
+ ExitCode: number;
15
+ Error: string;
16
+ StartedAt: string;
17
+ FinishedAt: string;
18
+ };
19
+ /** This type signature is incomplete. Add to it as necessary. */
20
+ export type DockerContainerInfo = Readonly<{
21
+ Id: string;
22
+ Created: string;
23
+ Path: string;
24
+ Args: ReadonlyArray<string>;
25
+ State: DockerContainerInfoState;
26
+ Name: string;
27
+ }>;
28
+ export declare function getContainerInfo(containerNameOrId: string): Promise<DockerContainerInfo | undefined>;
@@ -0,0 +1,24 @@
1
+ import { runShellCommand } from '../../augments/shell.js';
2
+ /** There may be other possible values for Status. */
3
+ export var DockerContainerStatusEnum;
4
+ (function (DockerContainerStatusEnum) {
5
+ DockerContainerStatusEnum["exited"] = "exited";
6
+ DockerContainerStatusEnum["running"] = "running";
7
+ })(DockerContainerStatusEnum || (DockerContainerStatusEnum = {}));
8
+ export async function getContainerInfo(containerNameOrId) {
9
+ const command = `docker inspect '${containerNameOrId}'`;
10
+ const output = await runShellCommand(command);
11
+ if (output.stderr.includes('Error: No such object')) {
12
+ return undefined;
13
+ }
14
+ const parsedOutput = JSON.parse(output.stdout);
15
+ /** Edge cases that I don't know how to intentionally trigger. */
16
+ /* node:coverage ignore next 5 */
17
+ if (parsedOutput.length === 0) {
18
+ throw new Error(`Got no output from "${command}"`);
19
+ }
20
+ else if (parsedOutput.length > 1) {
21
+ throw new Error(`Got more than one output from "${command}"`);
22
+ }
23
+ return parsedOutput[0];
24
+ }
@@ -0,0 +1,17 @@
1
+ export declare function getContainerLogs(containerNameOrId: string, latestLineCount?: number): Promise<string>;
2
+ export declare enum DockerContainerStatus {
3
+ Created = "created",
4
+ Running = "running",
5
+ Paused = "paused",
6
+ Restarting = "restarting",
7
+ Exited = "exited",
8
+ Removing = "removing",
9
+ Dead = "dead",
10
+ /** This is not a native Docker status but indicates that the container does not exist. */
11
+ Removed = "removed"
12
+ }
13
+ export declare const exitedDockerContainerStatuses: DockerContainerStatus[];
14
+ export declare function getContainerStatus(containerNameOrId: string): Promise<DockerContainerStatus>;
15
+ export declare function waitUntilContainerRunning(containerNameOrId: string, failureMessage?: string | undefined): Promise<void>;
16
+ export declare function waitUntilContainerRemoved(containerNameOrId: string, failureMessage?: string | undefined): Promise<void>;
17
+ export declare function waitUntilContainerExited(containerNameOrId: string, failureMessage?: string | undefined): Promise<void>;
@@ -0,0 +1,53 @@
1
+ import { assert, waitUntil } from '@augment-vir/assert';
2
+ import { runShellCommand } from '../../augments/shell.js';
3
+ export async function getContainerLogs(containerNameOrId, latestLineCount) {
4
+ const latestLinesArg = latestLineCount == undefined ? '' : `--tail ${latestLineCount}`;
5
+ const logResult = await runShellCommand(`docker logs ${latestLinesArg} '${containerNameOrId}'`, { rejectOnError: true });
6
+ return logResult.stdout;
7
+ }
8
+ export var DockerContainerStatus;
9
+ (function (DockerContainerStatus) {
10
+ DockerContainerStatus["Created"] = "created";
11
+ DockerContainerStatus["Running"] = "running";
12
+ DockerContainerStatus["Paused"] = "paused";
13
+ DockerContainerStatus["Restarting"] = "restarting";
14
+ DockerContainerStatus["Exited"] = "exited";
15
+ DockerContainerStatus["Removing"] = "removing";
16
+ DockerContainerStatus["Dead"] = "dead";
17
+ /** This is not a native Docker status but indicates that the container does not exist. */
18
+ DockerContainerStatus["Removed"] = "removed";
19
+ })(DockerContainerStatus || (DockerContainerStatus = {}));
20
+ export const exitedDockerContainerStatuses = [
21
+ DockerContainerStatus.Dead,
22
+ DockerContainerStatus.Removed,
23
+ DockerContainerStatus.Exited,
24
+ ];
25
+ export async function getContainerStatus(containerNameOrId) {
26
+ const statusResult = await runShellCommand(`docker container inspect -f '{{.State.Status}}' '${containerNameOrId}'`);
27
+ if (statusResult.exitCode !== 0) {
28
+ return DockerContainerStatus.Removed;
29
+ }
30
+ const status = statusResult.stdout.trim();
31
+ assert.isEnumValue(status, DockerContainerStatus, 'Unexpected container status value');
32
+ return status;
33
+ }
34
+ export async function waitUntilContainerRunning(containerNameOrId, failureMessage) {
35
+ await waitUntil.isTrue(async () => {
36
+ /** Check if logs can be accessed yet. */
37
+ await getContainerLogs(containerNameOrId, 1);
38
+ const status = await getContainerStatus(containerNameOrId);
39
+ return status === DockerContainerStatus.Running;
40
+ }, {}, failureMessage);
41
+ }
42
+ export async function waitUntilContainerRemoved(containerNameOrId, failureMessage) {
43
+ await waitUntil.isTrue(async () => {
44
+ const status = await getContainerStatus(containerNameOrId);
45
+ return status === DockerContainerStatus.Removed;
46
+ }, {}, failureMessage);
47
+ }
48
+ export async function waitUntilContainerExited(containerNameOrId, failureMessage) {
49
+ await waitUntil.isTrue(async () => {
50
+ const status = await getContainerStatus(containerNameOrId);
51
+ return exitedDockerContainerStatuses.includes(status);
52
+ }, {}, failureMessage);
53
+ }
@@ -0,0 +1,7 @@
1
+ export type CopyToDockerContainerParams = {
2
+ hostPath: string;
3
+ containerAbsolutePath: string;
4
+ containerNameOrId: string;
5
+ dockerFlags?: ReadonlyArray<string>;
6
+ };
7
+ export declare function copyToContainer({ containerAbsolutePath, hostPath, containerNameOrId, dockerFlags, }: CopyToDockerContainerParams): Promise<void>;
@@ -0,0 +1,10 @@
1
+ import { addSuffix } from '@augment-vir/common';
2
+ import { stat } from 'node:fs/promises';
3
+ import { runShellCommand } from '../../augments/shell.js';
4
+ export async function copyToContainer({ containerAbsolutePath, hostPath, containerNameOrId, dockerFlags = [], }) {
5
+ const isDir = (await stat(hostPath)).isDirectory();
6
+ const suffix = isDir ? '/.' : '';
7
+ const fullHostPath = addSuffix({ value: hostPath, suffix });
8
+ const extraInputs = dockerFlags.join(' ');
9
+ await runShellCommand(`docker cp ${extraInputs} '${fullHostPath}' '${containerNameOrId}:${containerAbsolutePath}'`, { rejectOnError: true });
10
+ }
@@ -0,0 +1,20 @@
1
+ export declare enum DockerVolumeMappingType {
2
+ Cached = "cached",
3
+ Delegated = "delegated"
4
+ }
5
+ export type DockerVolumeMap = {
6
+ hostAbsolutePath: string;
7
+ containerAbsolutePath: string;
8
+ type?: DockerVolumeMappingType | undefined;
9
+ };
10
+ export declare function makeVolumeFlags(volumeMapping?: ReadonlyArray<DockerVolumeMap>): string;
11
+ export type DockerPortMap = {
12
+ hostPort: number;
13
+ containerPort: number;
14
+ };
15
+ export type DockerEnvMap<RequiredKeys extends string = string> = Readonly<Record<RequiredKeys | string, {
16
+ value: string;
17
+ allowInterpolation: boolean;
18
+ }>>;
19
+ export declare function makePortMapFlags(portMapping?: ReadonlyArray<DockerPortMap> | undefined): string;
20
+ export declare function makeEnvFlags(envMapping?: DockerEnvMap | undefined): string;
@@ -0,0 +1,36 @@
1
+ import { wrapString } from '@augment-vir/common';
2
+ export var DockerVolumeMappingType;
3
+ (function (DockerVolumeMappingType) {
4
+ DockerVolumeMappingType["Cached"] = "cached";
5
+ DockerVolumeMappingType["Delegated"] = "delegated";
6
+ })(DockerVolumeMappingType || (DockerVolumeMappingType = {}));
7
+ export function makeVolumeFlags(volumeMapping) {
8
+ if (!volumeMapping) {
9
+ return '';
10
+ }
11
+ const parts = volumeMapping.map((volume) => {
12
+ const mountType = volume.type ? `:${volume.type}` : '';
13
+ return `-v '${volume.hostAbsolutePath}':'${volume.containerAbsolutePath}'${mountType}`;
14
+ });
15
+ return parts.join(' ');
16
+ }
17
+ export function makePortMapFlags(portMapping) {
18
+ if (!portMapping) {
19
+ return '';
20
+ }
21
+ return portMapping
22
+ .map((portMap) => {
23
+ return `-p ${portMap.hostPort}:${portMap.containerPort}`;
24
+ })
25
+ .join(' ');
26
+ }
27
+ export function makeEnvFlags(envMapping) {
28
+ if (!envMapping) {
29
+ return '';
30
+ }
31
+ const flags = Object.entries(envMapping).map(([key, { value, allowInterpolation },]) => {
32
+ const quote = allowInterpolation ? '"' : "'";
33
+ return `-e ${key}=${wrapString({ value, wrapper: quote })}`;
34
+ });
35
+ return flags.join(' ');
36
+ }
@@ -0,0 +1,10 @@
1
+ import type { PartialWithUndefined } from '@augment-vir/core';
2
+ export declare function killContainer(containerNameOrId: string, options?: PartialWithUndefined<{
3
+ /**
4
+ * If set to `true`, the container will be killed but won't be removed. (You'll still see it
5
+ * in your Docker Dashboard but it'll have a status of exited.)
6
+ *
7
+ * @default false
8
+ */
9
+ keepContainer: boolean;
10
+ }>): Promise<void>;
@@ -0,0 +1,12 @@
1
+ import { runShellCommand } from '../../augments/shell.js';
2
+ import { waitUntilContainerExited, waitUntilContainerRemoved } from './container-status.js';
3
+ export async function killContainer(containerNameOrId, options = {}) {
4
+ await runShellCommand(`docker kill '${containerNameOrId}'`);
5
+ if (options.keepContainer) {
6
+ await waitUntilContainerExited(containerNameOrId);
7
+ }
8
+ else {
9
+ await runShellCommand(`docker rm '${containerNameOrId}'`);
10
+ await waitUntilContainerRemoved(containerNameOrId);
11
+ }
12
+ }
@@ -0,0 +1,12 @@
1
+ import { DockerEnvMap } from './docker-command-inputs.js';
2
+ export type RunDockerContainerCommandParams = {
3
+ /** Creates an interactive shell connection. */
4
+ tty?: boolean | undefined;
5
+ containerNameOrId: string;
6
+ command: string;
7
+ envMapping?: DockerEnvMap | undefined;
8
+ executionEnv?: Record<string, string> | undefined;
9
+ dockerFlags?: ReadonlyArray<string> | undefined;
10
+ };
11
+ /** Run a command on a container that is already running. */
12
+ export declare function runContainerCommand({ tty, containerNameOrId, command, envMapping, executionEnv, dockerFlags, }: RunDockerContainerCommandParams): Promise<import("../../augments/shell.js").ShellOutput>;
@@ -0,0 +1,25 @@
1
+ import { check } from '@augment-vir/assert';
2
+ import { runShellCommand } from '../../augments/shell.js';
3
+ import { makeEnvFlags } from './docker-command-inputs.js';
4
+ /** Run a command on a container that is already running. */
5
+ export async function runContainerCommand({ tty, containerNameOrId, command, envMapping, executionEnv, dockerFlags = [], }) {
6
+ const envFlags = makeEnvFlags(envMapping);
7
+ /** Can't test tty in automated tests. */
8
+ /* node:coverage ignore next 1 */
9
+ const ttyFlag = tty ? '-it' : '';
10
+ const fullCommand = [
11
+ 'docker',
12
+ 'exec',
13
+ ttyFlag,
14
+ envFlags,
15
+ ...dockerFlags,
16
+ containerNameOrId,
17
+ command,
18
+ ]
19
+ .filter(check.isTruthy)
20
+ .join(' ');
21
+ return await runShellCommand(fullCommand, {
22
+ env: executionEnv,
23
+ rejectOnError: true,
24
+ });
25
+ }
@@ -0,0 +1,15 @@
1
+ import { DockerEnvMap, DockerPortMap, DockerVolumeMap } from './docker-command-inputs.js';
2
+ export type RunDockerContainerParams = {
3
+ imageName: string;
4
+ detach: boolean;
5
+ command?: string;
6
+ containerName: string;
7
+ volumeMapping?: ReadonlyArray<DockerVolumeMap> | undefined;
8
+ portMapping?: ReadonlyArray<DockerPortMap> | undefined;
9
+ envMapping?: DockerEnvMap | undefined;
10
+ executionEnv?: Record<string, string>;
11
+ removeWhenDone?: boolean;
12
+ dockerFlags?: ReadonlyArray<string>;
13
+ useCurrentUser?: boolean;
14
+ };
15
+ export declare function runContainer({ containerName, imageName, detach, command, portMapping, volumeMapping, envMapping, executionEnv, removeWhenDone, useCurrentUser, dockerFlags, }: RunDockerContainerParams): Promise<void>;
@@ -0,0 +1,47 @@
1
+ import { check } from '@augment-vir/assert';
2
+ import { runShellCommand } from '../../augments/shell.js';
3
+ import { updateImage } from '../docker-image.js';
4
+ import { waitUntilContainerRunning } from './container-status.js';
5
+ import { makeEnvFlags, makePortMapFlags, makeVolumeFlags, } from './docker-command-inputs.js';
6
+ import { killContainer } from './kill-container.js';
7
+ export async function runContainer({ containerName, imageName, detach, command, portMapping, volumeMapping, envMapping, executionEnv, removeWhenDone, useCurrentUser, dockerFlags = [], }) {
8
+ try {
9
+ const portMapFlags = makePortMapFlags(portMapping);
10
+ const envFlags = makeEnvFlags(envMapping);
11
+ const detachFlag = detach ? '-d' : '';
12
+ const volumeMapFlags = makeVolumeFlags(volumeMapping);
13
+ const rmFlag = removeWhenDone ? '--rm' : '';
14
+ const userFlag = useCurrentUser ? '--user "$(id -u)":"$(id -g)"' : '';
15
+ await updateImage(imageName);
16
+ const fullCommand = [
17
+ 'docker',
18
+ 'run',
19
+ portMapFlags,
20
+ userFlag,
21
+ volumeMapFlags,
22
+ envFlags,
23
+ rmFlag,
24
+ detachFlag,
25
+ `--name='${containerName}'`,
26
+ ...dockerFlags,
27
+ imageName,
28
+ command,
29
+ ]
30
+ .filter(check.isTruthy)
31
+ .join(' ');
32
+ await runShellCommand(fullCommand, {
33
+ env: executionEnv,
34
+ rejectOnError: true,
35
+ });
36
+ if (removeWhenDone) {
37
+ await killContainer(containerName);
38
+ }
39
+ else if (detach) {
40
+ await waitUntilContainerRunning(containerName);
41
+ }
42
+ }
43
+ catch (error) {
44
+ await killContainer(containerName);
45
+ throw error;
46
+ }
47
+ }