@augment-vir/node 31.0.1 → 31.1.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/scripts/fix-ts-bin.script.d.ts +1 -0
- package/dist/scripts/fix-ts-bin.script.js +48 -0
- package/package.json +4 -4
- package/src/augments/docker.ts +118 -0
- package/src/augments/fs/dir-contents.ts +116 -0
- package/src/augments/fs/download.ts +31 -0
- package/src/augments/fs/json.ts +87 -0
- package/src/augments/fs/read-dir.ts +60 -0
- package/src/augments/fs/read-file.ts +18 -0
- package/src/augments/fs/symlink.ts +37 -0
- package/src/augments/fs/write.ts +18 -0
- package/src/augments/npm/query-workspace.ts +47 -0
- package/src/augments/npm/read-package-json.ts +28 -0
- package/src/augments/os/operating-system.ts +55 -0
- package/src/augments/path/ancestor.ts +78 -0
- package/src/augments/path/os-path.ts +45 -0
- package/src/augments/path/root.ts +14 -0
- package/src/augments/prisma.ts +174 -0
- package/src/augments/terminal/question.ts +142 -0
- package/src/augments/terminal/relevant-args.ts +81 -0
- package/src/augments/terminal/run-cli-script.ts +60 -0
- package/src/augments/terminal/shell.ts +312 -0
- package/src/docker/containers/container-info.ts +83 -0
- package/src/docker/containers/container-status.ts +110 -0
- package/src/docker/containers/copy-to-container.ts +34 -0
- package/src/docker/containers/docker-command-inputs.ts +119 -0
- package/src/docker/containers/kill-container.ts +25 -0
- package/src/docker/containers/run-command.ts +51 -0
- package/src/docker/containers/run-container.mock.ts +17 -0
- package/src/docker/containers/run-container.ts +92 -0
- package/src/docker/containers/try-or-kill-container.ts +18 -0
- package/src/docker/docker-image.ts +56 -0
- package/src/docker/docker-startup.ts +49 -0
- package/src/docker/run-docker-test.mock.ts +26 -0
- package/src/file-paths.mock.ts +29 -0
- package/src/index.ts +19 -0
- package/src/prisma/disable-ci-env.mock.ts +88 -0
- package/src/prisma/model-data.ts +213 -0
- package/src/prisma/prisma-client.ts +43 -0
- package/src/prisma/prisma-database.mock.ts +31 -0
- package/src/prisma/prisma-database.ts +35 -0
- package/src/prisma/prisma-errors.ts +45 -0
- package/src/prisma/prisma-migrations.ts +149 -0
- package/src/prisma/run-prisma-command.ts +59 -0
- package/src/scripts/fix-ts-bin.script.ts +60 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {check} from '@augment-vir/assert';
|
|
2
|
+
import {runShellCommand} from '../../augments/terminal/shell.js';
|
|
3
|
+
import {DockerEnvMap, makeEnvFlags} from './docker-command-inputs.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Parameters for `docker.container.runCommand`.
|
|
7
|
+
*
|
|
8
|
+
* @category Node : Docker : Util
|
|
9
|
+
* @category Package : @augment-vir/node
|
|
10
|
+
* @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
|
|
11
|
+
*/
|
|
12
|
+
export type RunDockerContainerCommandParams = {
|
|
13
|
+
/** Creates an interactive shell connection. */
|
|
14
|
+
tty?: boolean | undefined;
|
|
15
|
+
containerNameOrId: string;
|
|
16
|
+
command: string;
|
|
17
|
+
envMapping?: DockerEnvMap | undefined;
|
|
18
|
+
executionEnv?: Record<string, string> | undefined;
|
|
19
|
+
dockerFlags?: ReadonlyArray<string> | undefined;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export async function runContainerCommand({
|
|
23
|
+
tty,
|
|
24
|
+
containerNameOrId,
|
|
25
|
+
command,
|
|
26
|
+
envMapping,
|
|
27
|
+
executionEnv,
|
|
28
|
+
dockerFlags = [],
|
|
29
|
+
}: RunDockerContainerCommandParams) {
|
|
30
|
+
const envFlags = makeEnvFlags(envMapping);
|
|
31
|
+
/** Can't test tty in automated tests. */
|
|
32
|
+
/* node:coverage ignore next 1 */
|
|
33
|
+
const ttyFlag = tty ? '-it' : '';
|
|
34
|
+
|
|
35
|
+
const fullCommand = [
|
|
36
|
+
'docker',
|
|
37
|
+
'exec',
|
|
38
|
+
ttyFlag,
|
|
39
|
+
envFlags,
|
|
40
|
+
...dockerFlags,
|
|
41
|
+
containerNameOrId,
|
|
42
|
+
command,
|
|
43
|
+
]
|
|
44
|
+
.filter(check.isTruthy)
|
|
45
|
+
.join(' ');
|
|
46
|
+
|
|
47
|
+
return await runShellCommand(fullCommand, {
|
|
48
|
+
env: executionEnv,
|
|
49
|
+
rejectOnError: true,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import {docker} from '../../augments/docker.js';
|
|
2
|
+
import {RunDockerContainerParams} from './run-container.js';
|
|
3
|
+
|
|
4
|
+
export async function runMockLongLivingContainer(
|
|
5
|
+
containerName: string,
|
|
6
|
+
args: Partial<RunDockerContainerParams> = {},
|
|
7
|
+
) {
|
|
8
|
+
await docker.container.run({
|
|
9
|
+
containerName: containerName,
|
|
10
|
+
detach: true,
|
|
11
|
+
imageName: 'alpine:3.20.2',
|
|
12
|
+
dockerFlags: ['-i', '-t'],
|
|
13
|
+
command: 'sh',
|
|
14
|
+
platform: 'amd64',
|
|
15
|
+
...args,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import {check} from '@augment-vir/assert';
|
|
2
|
+
import {runShellCommand} from '../../augments/terminal/shell.js';
|
|
3
|
+
import {updateImage} from '../docker-image.js';
|
|
4
|
+
import {waitUntilContainerRunning} from './container-status.js';
|
|
5
|
+
import {
|
|
6
|
+
DockerEnvMap,
|
|
7
|
+
DockerPortMap,
|
|
8
|
+
DockerVolumeMap,
|
|
9
|
+
makeEnvFlags,
|
|
10
|
+
makePortMapFlags,
|
|
11
|
+
makeVolumeFlags,
|
|
12
|
+
} from './docker-command-inputs.js';
|
|
13
|
+
import {killContainer} from './kill-container.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Parameters for `docker.container.run`.
|
|
17
|
+
*
|
|
18
|
+
* @category Node : Docker : Util
|
|
19
|
+
* @category Package : @augment-vir/node
|
|
20
|
+
* @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
|
|
21
|
+
*/
|
|
22
|
+
export type RunDockerContainerParams = {
|
|
23
|
+
imageName: string;
|
|
24
|
+
detach: boolean;
|
|
25
|
+
command?: string;
|
|
26
|
+
containerName: string;
|
|
27
|
+
volumeMapping?: ReadonlyArray<DockerVolumeMap> | undefined;
|
|
28
|
+
portMapping?: ReadonlyArray<DockerPortMap> | undefined;
|
|
29
|
+
envMapping?: DockerEnvMap | undefined;
|
|
30
|
+
executionEnv?: Record<string, string>;
|
|
31
|
+
removeWhenDone?: boolean;
|
|
32
|
+
dockerFlags?: ReadonlyArray<string>;
|
|
33
|
+
useCurrentUser?: boolean;
|
|
34
|
+
platform?: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export async function runContainer({
|
|
38
|
+
containerName,
|
|
39
|
+
imageName,
|
|
40
|
+
detach,
|
|
41
|
+
command,
|
|
42
|
+
portMapping,
|
|
43
|
+
volumeMapping,
|
|
44
|
+
envMapping,
|
|
45
|
+
executionEnv,
|
|
46
|
+
removeWhenDone,
|
|
47
|
+
useCurrentUser,
|
|
48
|
+
dockerFlags = [],
|
|
49
|
+
platform,
|
|
50
|
+
}: RunDockerContainerParams) {
|
|
51
|
+
try {
|
|
52
|
+
const portMapFlags = makePortMapFlags(portMapping);
|
|
53
|
+
const envFlags = makeEnvFlags(envMapping);
|
|
54
|
+
const detachFlag = detach ? '-d' : '';
|
|
55
|
+
const volumeMapFlags = makeVolumeFlags(volumeMapping);
|
|
56
|
+
const rmFlag = removeWhenDone ? '--rm' : '';
|
|
57
|
+
const userFlag = useCurrentUser ? '--user "$(id -u)":"$(id -g)"' : '';
|
|
58
|
+
|
|
59
|
+
await updateImage(imageName, platform);
|
|
60
|
+
|
|
61
|
+
const fullCommand = [
|
|
62
|
+
'docker',
|
|
63
|
+
'run',
|
|
64
|
+
portMapFlags,
|
|
65
|
+
userFlag,
|
|
66
|
+
volumeMapFlags,
|
|
67
|
+
envFlags,
|
|
68
|
+
rmFlag,
|
|
69
|
+
detachFlag,
|
|
70
|
+
`--name='${containerName}'`,
|
|
71
|
+
...dockerFlags,
|
|
72
|
+
imageName,
|
|
73
|
+
command,
|
|
74
|
+
]
|
|
75
|
+
.filter(check.isTruthy)
|
|
76
|
+
.join(' ');
|
|
77
|
+
|
|
78
|
+
await runShellCommand(fullCommand, {
|
|
79
|
+
env: executionEnv,
|
|
80
|
+
rejectOnError: true,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (removeWhenDone) {
|
|
84
|
+
await killContainer(containerName);
|
|
85
|
+
} else if (detach) {
|
|
86
|
+
await waitUntilContainerRunning(containerName);
|
|
87
|
+
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
await killContainer(containerName);
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {type MaybePromise} from '@augment-vir/core';
|
|
2
|
+
import {killContainer} from './kill-container.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Runs a callback (which presumably runs a command within the given `containerName`) and kills the
|
|
6
|
+
* given `containerName` container if the callback fails.
|
|
7
|
+
*/
|
|
8
|
+
export async function tryOrKillContainer<T>(
|
|
9
|
+
containerNameOrId: string,
|
|
10
|
+
callback: (containerNameOrId: string) => MaybePromise<T>,
|
|
11
|
+
): Promise<Awaited<T>> {
|
|
12
|
+
try {
|
|
13
|
+
return await callback(containerNameOrId);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
await killContainer(containerNameOrId);
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {wrapString} from '@augment-vir/common';
|
|
2
|
+
import {ensureError} from '@augment-vir/core';
|
|
3
|
+
import {runShellCommand} from '../augments/terminal/shell.js';
|
|
4
|
+
|
|
5
|
+
export async function updateImage(
|
|
6
|
+
/** @example 'alpine:3.20.2' */
|
|
7
|
+
imageName: string,
|
|
8
|
+
platform?: string,
|
|
9
|
+
) {
|
|
10
|
+
if (await isImageInLocalRegistry(imageName)) {
|
|
11
|
+
/** If image already exists then we don't need to update it. */
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const command = [
|
|
16
|
+
'docker',
|
|
17
|
+
'pull',
|
|
18
|
+
...(platform ? ['--platform', platform] : []),
|
|
19
|
+
wrapString({value: imageName, wrapper: "'"}),
|
|
20
|
+
].join(' ');
|
|
21
|
+
|
|
22
|
+
await runShellCommand(command, {
|
|
23
|
+
rejectOnError: true,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function isImageInLocalRegistry(
|
|
28
|
+
/** @example 'alpine:3.20.2' */
|
|
29
|
+
imageName: string,
|
|
30
|
+
): Promise<boolean> {
|
|
31
|
+
const output = await runShellCommand(`docker inspect '${imageName}'`);
|
|
32
|
+
|
|
33
|
+
return output.exitCode === 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function removeImageFromLocalRegistry(
|
|
37
|
+
/** @example 'alpine:3.20.2' */
|
|
38
|
+
imageName: string,
|
|
39
|
+
) {
|
|
40
|
+
try {
|
|
41
|
+
await runShellCommand(`docker image rm '${imageName}'`, {
|
|
42
|
+
rejectOnError: true,
|
|
43
|
+
});
|
|
44
|
+
} catch (caught) {
|
|
45
|
+
const error = ensureError(caught);
|
|
46
|
+
|
|
47
|
+
if (error.message.includes('No such image:')) {
|
|
48
|
+
/** Ignore the case where the image has already been deleted. */
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** An edge case that I don't know how to intentionally trigger. */
|
|
53
|
+
/* node:coverage ignore next 2 */
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {waitUntil} from '@augment-vir/assert';
|
|
2
|
+
import {currentOperatingSystem, OperatingSystem} from '../augments/os/operating-system.js';
|
|
3
|
+
import {runShellCommand} from '../augments/terminal/shell.js';
|
|
4
|
+
|
|
5
|
+
export async function isDockerRunning() {
|
|
6
|
+
const output = await runShellCommand('docker info');
|
|
7
|
+
|
|
8
|
+
return output.exitCode === 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const startDockerCommands: Record<OperatingSystem, string> = {
|
|
12
|
+
/**
|
|
13
|
+
* Officially supported for the following distros:
|
|
14
|
+
*
|
|
15
|
+
* - [Ubuntu](https://docs.docker.com/desktop/install/ubuntu/#launch-docker-desktop)
|
|
16
|
+
* - [Debian](https://docs.docker.com/desktop/install/debian/#launch-docker-desktop)
|
|
17
|
+
* - [Fedora](https://docs.docker.com/desktop/install/fedora/#launch-docker-desktop)
|
|
18
|
+
* - [Arch](https://docs.docker.com/desktop/install/archlinux/#launch-docker-desktop)
|
|
19
|
+
*/
|
|
20
|
+
[OperatingSystem.Linux]: 'systemctl --user start docker-desktop',
|
|
21
|
+
[OperatingSystem.Mac]: 'open -a Docker',
|
|
22
|
+
[OperatingSystem.Windows]: String.raw`/c/Program\ Files/Docker/Docker/Docker\ Desktop.exe`,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export async function startDocker() {
|
|
26
|
+
const command = startDockerCommands[currentOperatingSystem];
|
|
27
|
+
|
|
28
|
+
/* node:coverage disable */
|
|
29
|
+
if (await isDockerRunning()) {
|
|
30
|
+
/** Docker is already running. Nothing to do. */
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
await waitUntil.isTrue(
|
|
35
|
+
async () => {
|
|
36
|
+
await runShellCommand(command, {rejectOnError: true});
|
|
37
|
+
return isDockerRunning();
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
interval: {
|
|
41
|
+
seconds: 1,
|
|
42
|
+
},
|
|
43
|
+
timeout: {
|
|
44
|
+
minutes: 1,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
'Failed to start Docker.',
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {type MaybePromise} from '@augment-vir/common';
|
|
2
|
+
import {isOperatingSystem, OperatingSystem} from '../augments/os/operating-system.js';
|
|
3
|
+
|
|
4
|
+
export function dockerTest(callback: () => MaybePromise<void>) {
|
|
5
|
+
if (!isOperatingSystem(OperatingSystem.Linux) && process.env.CI) {
|
|
6
|
+
/**
|
|
7
|
+
* We cannot test Docker on macOS GitHub Actions runners.
|
|
8
|
+
*
|
|
9
|
+
* @see
|
|
10
|
+
* - https://github.com/actions/runner-images/issues/8104
|
|
11
|
+
* - https://github.com/douglascamata/setup-docker-macos-action?tab=readme-ov-file#arm64-processors-m1-m2-m3-series-used-on-macos-14-images-are-unsupported
|
|
12
|
+
* - https://github.com/actions/runner-images/issues/2150
|
|
13
|
+
* - https://github.com/actions/runner/issues/1456
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* We cannot test Docker on Windows GitHub Actions runners because Docker cannot run in
|
|
17
|
+
* Linux container mode on Windows GitHub Actions runners.
|
|
18
|
+
*
|
|
19
|
+
* @see
|
|
20
|
+
* - https://github.com/orgs/community/discussions/25491#discussioncomment-3248089
|
|
21
|
+
*/
|
|
22
|
+
return () => {};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return callback;
|
|
26
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {dirname, join} from 'node:path';
|
|
2
|
+
|
|
3
|
+
export const monoRepoDirPath = dirname(dirname(dirname(import.meta.dirname)));
|
|
4
|
+
export const notCommittedDirPath = join(monoRepoDirPath, '.not-committed');
|
|
5
|
+
|
|
6
|
+
export const nodePackageDir = dirname(import.meta.dirname);
|
|
7
|
+
|
|
8
|
+
export const testFilesDir = join(nodePackageDir, 'test-files');
|
|
9
|
+
const longRunningFileDir = join(testFilesDir, 'long-running-test-file');
|
|
10
|
+
export const dirContentsTestDir = join(testFilesDir, 'dir-contents-test');
|
|
11
|
+
export const longRunningFilePath = join(longRunningFileDir, 'long-running-file.ts');
|
|
12
|
+
export const longRunningFileWithStderr = join(
|
|
13
|
+
longRunningFileDir,
|
|
14
|
+
'long-running-file-with-stderr.ts',
|
|
15
|
+
);
|
|
16
|
+
export const workspaceQueryDir = join(testFilesDir, 'workspace-query');
|
|
17
|
+
export const workspaceQueryPackageJsonPath = join(workspaceQueryDir, 'package.json');
|
|
18
|
+
export const tempWorkspaceQueryFile = join(workspaceQueryDir, 'temp-workspace-query-output.ts');
|
|
19
|
+
|
|
20
|
+
export const recursiveFileReadDir = join(testFilesDir, 'recursive-reading');
|
|
21
|
+
|
|
22
|
+
export const invalidPackageDirPath = join(testFilesDir, 'invalid-package');
|
|
23
|
+
|
|
24
|
+
export const testPrismaSchemaPath = join(testFilesDir, 'schema.prisma');
|
|
25
|
+
export const testPrismaSchema2Path = join(testFilesDir, 'schema2.prisma');
|
|
26
|
+
export const testInvalidPrismaSchemaPath = join(testFilesDir, 'invalid-schema.prisma');
|
|
27
|
+
export const testSqliteDbPath = join(notCommittedDirPath, 'dev.db');
|
|
28
|
+
export const generatedPrismaClientDirPath = join(nodePackageDir, 'node_modules', '.prisma');
|
|
29
|
+
export const testPrismaMigrationsDirPath = join(testFilesDir, 'migrations');
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export * from './augments/docker.js';
|
|
2
|
+
export * from './augments/fs/dir-contents.js';
|
|
3
|
+
export * from './augments/fs/download.js';
|
|
4
|
+
export * from './augments/fs/json.js';
|
|
5
|
+
export * from './augments/fs/read-dir.js';
|
|
6
|
+
export * from './augments/fs/read-file.js';
|
|
7
|
+
export * from './augments/fs/symlink.js';
|
|
8
|
+
export * from './augments/fs/write.js';
|
|
9
|
+
export * from './augments/npm/query-workspace.js';
|
|
10
|
+
export * from './augments/npm/read-package-json.js';
|
|
11
|
+
export * from './augments/os/operating-system.js';
|
|
12
|
+
export * from './augments/path/ancestor.js';
|
|
13
|
+
export * from './augments/path/os-path.js';
|
|
14
|
+
export * from './augments/path/root.js';
|
|
15
|
+
export * from './augments/prisma.js';
|
|
16
|
+
export * from './augments/terminal/question.js';
|
|
17
|
+
export * from './augments/terminal/relevant-args.js';
|
|
18
|
+
export * from './augments/terminal/run-cli-script.js';
|
|
19
|
+
export * from './augments/terminal/shell.js';
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// cspell:disable
|
|
2
|
+
|
|
3
|
+
import {arrayToObject, type MaybePromise} from '@augment-vir/common';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* These are all the env flags that Prisma reads for determining if it's being executed within a CI
|
|
7
|
+
* environment. This list was retrieved from
|
|
8
|
+
* https://github.com/prisma/prisma/blob/075d31287c90b757fd9bd8d9b36032e6349fa671/packages/internals/src/utils/isCi.ts.
|
|
9
|
+
*/
|
|
10
|
+
const prismaCiFlags = [
|
|
11
|
+
'CI',
|
|
12
|
+
'CONTINUOUS_INTEGRATION',
|
|
13
|
+
'BUILD_NUMBER',
|
|
14
|
+
'RUN_ID',
|
|
15
|
+
'AGOLA_GIT_REF',
|
|
16
|
+
'AC_APPCIRCLE',
|
|
17
|
+
'APPVEYOR',
|
|
18
|
+
'CODEBUILD',
|
|
19
|
+
'TF_BUILD',
|
|
20
|
+
'bamboo_planKey',
|
|
21
|
+
'BITBUCKET_COMMIT',
|
|
22
|
+
'BITRISE_IO',
|
|
23
|
+
'BUDDY_WORKSPACE_ID',
|
|
24
|
+
'BUILDKITE',
|
|
25
|
+
'CIRCLECI',
|
|
26
|
+
'CIRRUS_CI',
|
|
27
|
+
'CF_BUILD_ID',
|
|
28
|
+
'CM_BUILD_ID',
|
|
29
|
+
'CI_NAME',
|
|
30
|
+
'DRONE',
|
|
31
|
+
'DSARI',
|
|
32
|
+
'EARTHLY_CI',
|
|
33
|
+
'EAS_BUILD',
|
|
34
|
+
'GERRIT_PROJECT',
|
|
35
|
+
'GITEA_ACTIONS',
|
|
36
|
+
'GITHUB_ACTIONS',
|
|
37
|
+
'GITLAB_CI',
|
|
38
|
+
'GOCD',
|
|
39
|
+
'BUILDER_OUTPUT',
|
|
40
|
+
'HARNESS_BUILD_ID',
|
|
41
|
+
'JENKINS_URL',
|
|
42
|
+
'BUILD_ID',
|
|
43
|
+
'LAYERCI',
|
|
44
|
+
'MAGNUM',
|
|
45
|
+
'NETLIFY',
|
|
46
|
+
'NEVERCODE',
|
|
47
|
+
'PROW_JOB_ID',
|
|
48
|
+
'RELEASE_BUILD_ID',
|
|
49
|
+
'RENDER',
|
|
50
|
+
'SAILCI',
|
|
51
|
+
'HUDSON',
|
|
52
|
+
'JENKINS_URL',
|
|
53
|
+
'BUILD_ID',
|
|
54
|
+
'SCREWDRIVER',
|
|
55
|
+
'SEMAPHORE',
|
|
56
|
+
'SOURCEHUT',
|
|
57
|
+
'STRIDER',
|
|
58
|
+
'TASK_ID',
|
|
59
|
+
'RUN_ID',
|
|
60
|
+
'TEAMCITY_VERSION',
|
|
61
|
+
'TRAVIS',
|
|
62
|
+
'VELA',
|
|
63
|
+
'NOW_BUILDER',
|
|
64
|
+
'APPCENTER_BUILD_ID',
|
|
65
|
+
'CI_XCODE_PROJECT',
|
|
66
|
+
'XCS',
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
export function testWithNonCiEnv(callback: () => MaybePromise<void>) {
|
|
70
|
+
return async () => {
|
|
71
|
+
const usedKeys = prismaCiFlags.filter((ciFlag) => ciFlag in process.env);
|
|
72
|
+
|
|
73
|
+
/** For already non-CI environments. */
|
|
74
|
+
/* node:coverage ignore next 6 */
|
|
75
|
+
const originalEnvValues = arrayToObject(usedKeys, (key) => {
|
|
76
|
+
return {
|
|
77
|
+
key,
|
|
78
|
+
value: process.env[key],
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
usedKeys.forEach((key) => delete process.env[key]);
|
|
83
|
+
|
|
84
|
+
await callback();
|
|
85
|
+
|
|
86
|
+
usedKeys.forEach((key) => (process.env[key] = originalEnvValues[key]));
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import {assert, check} from '@augment-vir/assert';
|
|
2
|
+
import {
|
|
3
|
+
AnyObject,
|
|
4
|
+
arrayToObject,
|
|
5
|
+
awaitedForEach,
|
|
6
|
+
BasePrismaClient,
|
|
7
|
+
ensureErrorAndPrependMessage,
|
|
8
|
+
filterMap,
|
|
9
|
+
getObjectTypedEntries,
|
|
10
|
+
getObjectTypedValues,
|
|
11
|
+
mergeDefinedProperties,
|
|
12
|
+
omitObjectKeys,
|
|
13
|
+
PrismaAllModelsCreate,
|
|
14
|
+
prismaModelCreateExclude,
|
|
15
|
+
prismaModelCreateOmitId,
|
|
16
|
+
type PartialWithUndefined,
|
|
17
|
+
type PrismaAllBasicModels,
|
|
18
|
+
type PrismaModelName,
|
|
19
|
+
} from '@augment-vir/common';
|
|
20
|
+
import type {IsAny} from 'type-fest';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Params for {@link addData}. This is similar to {@link PrismaAllModelsCreate} but allows an array of
|
|
24
|
+
* {@link PrismaAllModelsCreate} for sequential data creation.
|
|
25
|
+
*
|
|
26
|
+
* @category Prisma : Node
|
|
27
|
+
* @category Package : @augment-vir/node
|
|
28
|
+
* @example
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* import {PrismaAddModelData} from '@augment-vir/common';
|
|
32
|
+
* import type {PrismaClient} from '@prisma/client';
|
|
33
|
+
*
|
|
34
|
+
* const mockData: PrismaAddModelData<PrismaClient> = [
|
|
35
|
+
* {
|
|
36
|
+
* user: {
|
|
37
|
+
* mockUser1: {
|
|
38
|
+
* first_name: 'one',
|
|
39
|
+
* id: 123,
|
|
40
|
+
* // etc.
|
|
41
|
+
* },
|
|
42
|
+
* mockUser2: {
|
|
43
|
+
* first_name: 'two',
|
|
44
|
+
* id: 124,
|
|
45
|
+
* authRole: 'user',
|
|
46
|
+
* // etc.
|
|
47
|
+
* },
|
|
48
|
+
* },
|
|
49
|
+
* },
|
|
50
|
+
* {
|
|
51
|
+
* region: [
|
|
52
|
+
* {
|
|
53
|
+
* id: 1,
|
|
54
|
+
* name: 'North America',
|
|
55
|
+
* // etc.
|
|
56
|
+
* },
|
|
57
|
+
* {
|
|
58
|
+
* id: 2,
|
|
59
|
+
* name: 'Europe',
|
|
60
|
+
* // etc.
|
|
61
|
+
* },
|
|
62
|
+
* ],
|
|
63
|
+
* },
|
|
64
|
+
* ];
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
|
|
68
|
+
*/
|
|
69
|
+
export type PrismaAddDataData<PrismaClient extends BasePrismaClient> =
|
|
70
|
+
| Readonly<PrismaAllModelsCreate<PrismaClient>>
|
|
71
|
+
| ReadonlyArray<Readonly<PrismaAllModelsCreate<PrismaClient>>>;
|
|
72
|
+
|
|
73
|
+
export async function addData<const PrismaClient extends BasePrismaClient>(
|
|
74
|
+
prismaClient: Readonly<PrismaClient>,
|
|
75
|
+
data: IsAny<PrismaClient> extends true ? any : PrismaAddDataData<PrismaClient>,
|
|
76
|
+
): Promise<void> {
|
|
77
|
+
const dataArray: Record<string, AnyObject>[] = (check.isArray(data) ? data : [data]) as Record<
|
|
78
|
+
string,
|
|
79
|
+
AnyObject
|
|
80
|
+
>[];
|
|
81
|
+
|
|
82
|
+
await awaitedForEach(dataArray, async (dataEntry) => {
|
|
83
|
+
await addModelDataObject(prismaClient, dataEntry);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function addModelDataObject(
|
|
88
|
+
prismaClient: Readonly<BasePrismaClient>,
|
|
89
|
+
data: Record<string, AnyObject>,
|
|
90
|
+
) {
|
|
91
|
+
/** Add the mock data to the mock prisma client. */
|
|
92
|
+
await awaitedForEach(getObjectTypedEntries(data), async ([modelName, mockData]) => {
|
|
93
|
+
/**
|
|
94
|
+
* This type is dumbed down to just `AnyObject[]` because the union of all possible
|
|
95
|
+
* model data is just way too big (and not helpful as the inputs to this function are
|
|
96
|
+
* already type guarded).
|
|
97
|
+
*/
|
|
98
|
+
const mockModelInstances: AnyObject[] = Array.isArray(mockData)
|
|
99
|
+
? mockData
|
|
100
|
+
: getObjectTypedValues(mockData);
|
|
101
|
+
|
|
102
|
+
const modelApi: AnyObject | undefined = prismaClient[modelName];
|
|
103
|
+
|
|
104
|
+
assert.isDefined(modelApi, `No PrismaClient API found for model '${modelName}'`);
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const allData = filterMap(
|
|
108
|
+
mockModelInstances,
|
|
109
|
+
(entry) => {
|
|
110
|
+
return entry;
|
|
111
|
+
},
|
|
112
|
+
(mapped, modelEntry) => !modelEntry[prismaModelCreateExclude],
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
await awaitedForEach(allData, async (modelEntry) => {
|
|
116
|
+
if (modelEntry[prismaModelCreateOmitId]) {
|
|
117
|
+
modelEntry = omitObjectKeys<AnyObject, PropertyKey>(modelEntry, ['id']);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
await modelApi.create({
|
|
121
|
+
data: modelEntry,
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
} catch (error) {
|
|
125
|
+
throw ensureErrorAndPrependMessage(
|
|
126
|
+
error,
|
|
127
|
+
`Failed to create many '${modelName}' entries.\n\n${JSON.stringify(mockModelInstances, null, 4)}\n\n`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const prismockKeys = ['getData', 'setData'];
|
|
134
|
+
|
|
135
|
+
export function getAllPrismaModelNames<const PrismaClient extends BasePrismaClient>(
|
|
136
|
+
prismaClient: PrismaClient,
|
|
137
|
+
): PrismaModelName<PrismaClient>[] {
|
|
138
|
+
return Object.keys(prismaClient)
|
|
139
|
+
.filter(
|
|
140
|
+
(key) => !key.startsWith('$') && !key.startsWith('_') && !prismockKeys.includes(key),
|
|
141
|
+
)
|
|
142
|
+
.sort() as PrismaModelName<PrismaClient>[];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Options for `prisma.client.dumpData`.
|
|
147
|
+
*
|
|
148
|
+
* @category Prisma : Node
|
|
149
|
+
* @category Package : @augment-vir/node
|
|
150
|
+
* @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
|
|
151
|
+
*/
|
|
152
|
+
export type PrismaDataDumpOptions = {
|
|
153
|
+
/**
|
|
154
|
+
* The max number of entries to load per model. Set to `0` to remove this limit altogether
|
|
155
|
+
* (which could be _very_ expensive for your database).
|
|
156
|
+
*
|
|
157
|
+
* @default 100
|
|
158
|
+
*/
|
|
159
|
+
limit: number;
|
|
160
|
+
/**
|
|
161
|
+
* Strings to omit from the dumped data. For testability, omitting date or UUID id fields is a
|
|
162
|
+
* common practice.
|
|
163
|
+
*/
|
|
164
|
+
omitFields: string[];
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const defaultPrismaDumpDataOptions: PrismaDataDumpOptions = {
|
|
168
|
+
limit: 100,
|
|
169
|
+
omitFields: [],
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export async function dumpData<const PrismaClient extends BasePrismaClient>(
|
|
173
|
+
prismaClient: PrismaClient,
|
|
174
|
+
options: Readonly<PartialWithUndefined<PrismaDataDumpOptions>> = {},
|
|
175
|
+
): Promise<PrismaAllBasicModels<PrismaClient>> {
|
|
176
|
+
const modelNames = getAllPrismaModelNames(prismaClient);
|
|
177
|
+
const finalOptions = mergeDefinedProperties(defaultPrismaDumpDataOptions, options);
|
|
178
|
+
|
|
179
|
+
const data: Partial<Record<PrismaModelName<PrismaClient>, AnyObject[]>> = await arrayToObject(
|
|
180
|
+
modelNames,
|
|
181
|
+
async (modelName) => {
|
|
182
|
+
try {
|
|
183
|
+
const entries: AnyObject[] = await prismaClient[modelName].findMany(
|
|
184
|
+
finalOptions.limit > 0
|
|
185
|
+
? {
|
|
186
|
+
take: finalOptions.limit,
|
|
187
|
+
}
|
|
188
|
+
: {},
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
if (!entries.length) {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const filteredEntries = finalOptions.omitFields.length
|
|
196
|
+
? entries.map((entry) => omitObjectKeys(entry, finalOptions.omitFields))
|
|
197
|
+
: entries;
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
key: modelName,
|
|
201
|
+
value: filteredEntries,
|
|
202
|
+
};
|
|
203
|
+
} catch (error) {
|
|
204
|
+
throw ensureErrorAndPrependMessage(
|
|
205
|
+
error,
|
|
206
|
+
`Failed to read data for model '${modelName}'`,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
return data as PrismaAllBasicModels<PrismaClient>;
|
|
213
|
+
}
|