@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.
- package/LICENSE-CC0 +121 -0
- package/LICENSE-MIT +21 -0
- package/README.md +13 -0
- package/dist/augments/console/question.d.ts +14 -0
- package/dist/augments/console/question.js +63 -0
- package/dist/augments/docker.d.ts +44 -0
- package/dist/augments/docker.js +40 -0
- package/dist/augments/download.d.ts +4 -0
- package/dist/augments/download.js +19 -0
- package/dist/augments/fs/json.d.ts +8 -0
- package/dist/augments/fs/json.js +22 -0
- package/dist/augments/fs/read-dir.d.ts +16 -0
- package/dist/augments/fs/read-dir.js +31 -0
- package/dist/augments/fs/read-file.d.ts +1 -0
- package/dist/augments/fs/read-file.js +10 -0
- package/dist/augments/fs/symlink.d.ts +9 -0
- package/dist/augments/fs/symlink.js +16 -0
- package/dist/augments/fs/write.d.ts +5 -0
- package/dist/augments/fs/write.js +10 -0
- package/dist/augments/npm/query-workspace.d.ts +18 -0
- package/dist/augments/npm/query-workspace.js +14 -0
- package/dist/augments/npm/read-package-json.d.ts +2 -0
- package/dist/augments/npm/read-package-json.js +14 -0
- package/dist/augments/os/operating-system.d.ts +6 -0
- package/dist/augments/os/operating-system.js +20 -0
- package/dist/augments/path/ancestor.d.ts +5 -0
- package/dist/augments/path/ancestor.js +29 -0
- package/dist/augments/path/os-path.d.ts +9 -0
- package/dist/augments/path/os-path.js +27 -0
- package/dist/augments/path/root.d.ts +1 -0
- package/dist/augments/path/root.js +5 -0
- package/dist/augments/prisma.d.ts +21 -0
- package/dist/augments/prisma.js +21 -0
- package/dist/augments/shell.d.ts +87 -0
- package/dist/augments/shell.js +152 -0
- package/dist/docker/containers/container-info.d.ts +28 -0
- package/dist/docker/containers/container-info.js +24 -0
- package/dist/docker/containers/container-status.d.ts +17 -0
- package/dist/docker/containers/container-status.js +53 -0
- package/dist/docker/containers/copy-to-container.d.ts +7 -0
- package/dist/docker/containers/copy-to-container.js +10 -0
- package/dist/docker/containers/docker-command-inputs.d.ts +20 -0
- package/dist/docker/containers/docker-command-inputs.js +36 -0
- package/dist/docker/containers/kill-container.d.ts +10 -0
- package/dist/docker/containers/kill-container.js +12 -0
- package/dist/docker/containers/run-command.d.ts +12 -0
- package/dist/docker/containers/run-command.js +25 -0
- package/dist/docker/containers/run-container.d.ts +15 -0
- package/dist/docker/containers/run-container.js +47 -0
- package/dist/docker/containers/run-container.mock.d.ts +2 -0
- package/dist/docker/containers/run-container.mock.js +14 -0
- package/dist/docker/containers/try-or-kill-container.d.ts +6 -0
- package/dist/docker/containers/try-or-kill-container.js +14 -0
- package/dist/docker/docker-image.d.ts +9 -0
- package/dist/docker/docker-image.js +36 -0
- package/dist/docker/docker-startup.d.ts +2 -0
- package/dist/docker/docker-startup.js +40 -0
- package/dist/file-paths.mock.d.ts +17 -0
- package/dist/file-paths.mock.js +19 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +16 -0
- package/dist/prisma/prisma-client.d.ts +9 -0
- package/dist/prisma/prisma-client.js +28 -0
- package/dist/prisma/prisma-database.d.ts +3 -0
- package/dist/prisma/prisma-database.js +22 -0
- package/dist/prisma/prisma-database.mock.d.ts +1 -0
- package/dist/prisma/prisma-database.mock.js +7 -0
- package/dist/prisma/prisma-errors.d.ts +12 -0
- package/dist/prisma/prisma-errors.js +18 -0
- package/dist/prisma/prisma-migrations.d.ts +17 -0
- package/dist/prisma/prisma-migrations.js +105 -0
- package/dist/prisma/run-prisma-command.d.ts +8 -0
- package/dist/prisma/run-prisma-command.js +48 -0
- package/package.json +58 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { docker } from '../../augments/docker.js';
|
|
2
|
+
export async function runMockLongLivingContainer(containerName, args = {}) {
|
|
3
|
+
await docker.container.run({
|
|
4
|
+
containerName: containerName,
|
|
5
|
+
detach: true,
|
|
6
|
+
imageName: 'alpine:3.20.2',
|
|
7
|
+
dockerFlags: [
|
|
8
|
+
'-i',
|
|
9
|
+
'-t',
|
|
10
|
+
],
|
|
11
|
+
command: 'sh',
|
|
12
|
+
...args,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { MaybePromise } from '@augment-vir/core';
|
|
2
|
+
/**
|
|
3
|
+
* Runs a callback (which presumably runs a command within the given `containerName`) and kills the
|
|
4
|
+
* given `containerName` container if the callback fails.
|
|
5
|
+
*/
|
|
6
|
+
export declare function tryOrKillContainer<T>(containerNameOrId: string, callback: (containerNameOrId: string) => MaybePromise<T>): Promise<Awaited<T>>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { killContainer } from './kill-container.js';
|
|
2
|
+
/**
|
|
3
|
+
* Runs a callback (which presumably runs a command within the given `containerName`) and kills the
|
|
4
|
+
* given `containerName` container if the callback fails.
|
|
5
|
+
*/
|
|
6
|
+
export async function tryOrKillContainer(containerNameOrId, callback) {
|
|
7
|
+
try {
|
|
8
|
+
return await callback(containerNameOrId);
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
await killContainer(containerNameOrId);
|
|
12
|
+
throw error;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function updateImage(
|
|
2
|
+
/** @example 'alpine:3.20.2' */
|
|
3
|
+
imageName: string): Promise<void>;
|
|
4
|
+
export declare function isImageInLocalRegistry(
|
|
5
|
+
/** @example 'alpine:3.20.2' */
|
|
6
|
+
imageName: string): Promise<boolean>;
|
|
7
|
+
export declare function removeImageFromLocalRegistry(
|
|
8
|
+
/** @example 'alpine:3.20.2' */
|
|
9
|
+
imageName: string): Promise<void>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ensureError } from '@augment-vir/core';
|
|
2
|
+
import { runShellCommand } from '../augments/shell.js';
|
|
3
|
+
export async function updateImage(
|
|
4
|
+
/** @example 'alpine:3.20.2' */
|
|
5
|
+
imageName) {
|
|
6
|
+
if (await isImageInLocalRegistry(imageName)) {
|
|
7
|
+
/** If image already exists then we don't need to update it. */
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
await runShellCommand(`docker pull '${imageName}'`, {
|
|
11
|
+
rejectOnError: true,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export async function isImageInLocalRegistry(
|
|
15
|
+
/** @example 'alpine:3.20.2' */
|
|
16
|
+
imageName) {
|
|
17
|
+
const output = await runShellCommand(`docker inspect '${imageName}'`);
|
|
18
|
+
return output.exitCode === 0;
|
|
19
|
+
}
|
|
20
|
+
export async function removeImageFromLocalRegistry(
|
|
21
|
+
/** @example 'alpine:3.20.2' */
|
|
22
|
+
imageName) {
|
|
23
|
+
try {
|
|
24
|
+
await runShellCommand(`docker image rm '${imageName}'`, { rejectOnError: true });
|
|
25
|
+
}
|
|
26
|
+
catch (caught) {
|
|
27
|
+
const error = ensureError(caught);
|
|
28
|
+
if (error.message.includes('No such image:')) {
|
|
29
|
+
/** Ignore the case where the image has already been deleted. */
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
/** An edge case that I don't know how to intentionally trigger. */
|
|
33
|
+
/* node:coverage ignore next 2 */
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { waitUntil } from '@augment-vir/assert';
|
|
2
|
+
import { currentOperatingSystem, OperatingSystem } from '../augments/os/operating-system.js';
|
|
3
|
+
import { runShellCommand } from '../augments/shell.js';
|
|
4
|
+
export async function isDockerRunning() {
|
|
5
|
+
const output = await runShellCommand('docker info');
|
|
6
|
+
return output.exitCode === 0;
|
|
7
|
+
}
|
|
8
|
+
const startDockerCommands = {
|
|
9
|
+
/**
|
|
10
|
+
* Officially supported for the following distros:
|
|
11
|
+
*
|
|
12
|
+
* - [Ubuntu](https://docs.docker.com/desktop/install/ubuntu/#launch-docker-desktop)
|
|
13
|
+
* - [Debian](https://docs.docker.com/desktop/install/debian/#launch-docker-desktop)
|
|
14
|
+
* - [Fedora](https://docs.docker.com/desktop/install/fedora/#launch-docker-desktop)
|
|
15
|
+
* - [Arch](https://docs.docker.com/desktop/install/archlinux/#launch-docker-desktop)
|
|
16
|
+
*/
|
|
17
|
+
[OperatingSystem.Linux]: 'systemctl --user start docker-desktop',
|
|
18
|
+
[OperatingSystem.Mac]: 'open -a Docker',
|
|
19
|
+
[OperatingSystem.Windows]: String.raw `/c/Program\ Files/Docker/Docker/Docker\ Desktop.exe`,
|
|
20
|
+
};
|
|
21
|
+
export async function startDocker() {
|
|
22
|
+
const command = startDockerCommands[currentOperatingSystem];
|
|
23
|
+
/** We */
|
|
24
|
+
/* node:coverage disable */
|
|
25
|
+
if (await isDockerRunning()) {
|
|
26
|
+
/** Docker is already running. Nothing to do. */
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
await waitUntil.isTrue(async () => {
|
|
30
|
+
await runShellCommand(command, { rejectOnError: true });
|
|
31
|
+
return isDockerRunning();
|
|
32
|
+
}, {
|
|
33
|
+
interval: {
|
|
34
|
+
seconds: 1,
|
|
35
|
+
},
|
|
36
|
+
timeout: {
|
|
37
|
+
minutes: 1,
|
|
38
|
+
},
|
|
39
|
+
}, 'Failed to start Docker.');
|
|
40
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const monoRepoDirPath: string;
|
|
2
|
+
export declare const notCommittedDirPath: string;
|
|
3
|
+
export declare const nodePackageDir: string;
|
|
4
|
+
export declare const testFilesDir: string;
|
|
5
|
+
export declare const longRunningFilePath: string;
|
|
6
|
+
export declare const longRunningFileWithStderr: string;
|
|
7
|
+
export declare const workspaceQueryDir: string;
|
|
8
|
+
export declare const workspaceQueryPackageJsonPath: string;
|
|
9
|
+
export declare const tempWorkspaceQueryFile: string;
|
|
10
|
+
export declare const recursiveFileReadDir: string;
|
|
11
|
+
export declare const invalidPackageDirPath: string;
|
|
12
|
+
export declare const testPrismaSchemaPath: string;
|
|
13
|
+
export declare const testPrismaSchema2Path: string;
|
|
14
|
+
export declare const testInvalidPrismaSchemaPath: string;
|
|
15
|
+
export declare const testSqliteDbPath: string;
|
|
16
|
+
export declare const generatedPrismaClientDirPath: string;
|
|
17
|
+
export declare const testPrismaMigrationsDirPath: string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { dirname, join } from 'node:path';
|
|
2
|
+
export const monoRepoDirPath = dirname(dirname(dirname(import.meta.dirname)));
|
|
3
|
+
export const notCommittedDirPath = join(monoRepoDirPath, '.not-committed');
|
|
4
|
+
export const nodePackageDir = dirname(import.meta.dirname);
|
|
5
|
+
export const testFilesDir = join(nodePackageDir, 'test-files');
|
|
6
|
+
const longRunningFileDir = join(testFilesDir, 'long-running-test-file');
|
|
7
|
+
export const longRunningFilePath = join(longRunningFileDir, 'long-running-file.ts');
|
|
8
|
+
export const longRunningFileWithStderr = join(longRunningFileDir, 'long-running-file-with-stderr.ts');
|
|
9
|
+
export const workspaceQueryDir = join(testFilesDir, 'workspace-query');
|
|
10
|
+
export const workspaceQueryPackageJsonPath = join(workspaceQueryDir, 'package.json');
|
|
11
|
+
export const tempWorkspaceQueryFile = join(workspaceQueryDir, 'temp-workspace-query-output.ts');
|
|
12
|
+
export const recursiveFileReadDir = join(testFilesDir, 'recursive-reading');
|
|
13
|
+
export const invalidPackageDirPath = join(testFilesDir, 'invalid-package');
|
|
14
|
+
export const testPrismaSchemaPath = join(testFilesDir, 'schema.prisma');
|
|
15
|
+
export const testPrismaSchema2Path = join(testFilesDir, 'schema2.prisma');
|
|
16
|
+
export const testInvalidPrismaSchemaPath = join(testFilesDir, 'invalid-schema.prisma');
|
|
17
|
+
export const testSqliteDbPath = join(notCommittedDirPath, 'dev.db');
|
|
18
|
+
export const generatedPrismaClientDirPath = join(nodePackageDir, 'node_modules', '.prisma');
|
|
19
|
+
export const testPrismaMigrationsDirPath = join(testFilesDir, 'migrations');
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export * from './augments/console/question.js';
|
|
2
|
+
export * from './augments/docker.js';
|
|
3
|
+
export * from './augments/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/shell.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export * from './augments/console/question.js';
|
|
2
|
+
export * from './augments/docker.js';
|
|
3
|
+
export * from './augments/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/shell.js';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs Prisma generators included in the given Prisma schema (which usually includes the Prisma JS
|
|
3
|
+
* client). This will work even if the database doesn't exist yet.
|
|
4
|
+
*/
|
|
5
|
+
export declare function generatePrismaClient(schemaFilePath: string, env?: Record<string, string>): Promise<void>;
|
|
6
|
+
export declare function isGeneratedPrismaClientCurrent({ jsClientOutputDir, schemaFilePath, }: {
|
|
7
|
+
schemaFilePath: string;
|
|
8
|
+
jsClientOutputDir: string;
|
|
9
|
+
}): Promise<boolean>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { collapseWhiteSpace } from '@augment-vir/common';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { runPrismaCommand } from './run-prisma-command.js';
|
|
6
|
+
/**
|
|
7
|
+
* Runs Prisma generators included in the given Prisma schema (which usually includes the Prisma JS
|
|
8
|
+
* client). This will work even if the database doesn't exist yet.
|
|
9
|
+
*/
|
|
10
|
+
export async function generatePrismaClient(schemaFilePath, env = {}) {
|
|
11
|
+
await runPrismaCommand({ command: 'generate' }, schemaFilePath, env);
|
|
12
|
+
}
|
|
13
|
+
async function areSchemasEqual(originalSchema, generatedClientSchema) {
|
|
14
|
+
if (!existsSync(originalSchema)) {
|
|
15
|
+
throw new Error(`Schema file does not exist: '${originalSchema}'`);
|
|
16
|
+
}
|
|
17
|
+
else if (!existsSync(generatedClientSchema)) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
const originalSchemaContents = String(await readFile(originalSchema));
|
|
21
|
+
const generatedClientSchemaContents = String(await readFile(generatedClientSchema));
|
|
22
|
+
return (collapseWhiteSpace(originalSchemaContents, { keepNewLines: true }) ===
|
|
23
|
+
collapseWhiteSpace(generatedClientSchemaContents, { keepNewLines: true }));
|
|
24
|
+
}
|
|
25
|
+
export async function isGeneratedPrismaClientCurrent({ jsClientOutputDir, schemaFilePath, }) {
|
|
26
|
+
const clientSchemaFilePath = join(jsClientOutputDir, 'schema.prisma');
|
|
27
|
+
return areSchemasEqual(schemaFilePath, clientSchemaFilePath);
|
|
28
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare function getPrismaDiff(schemaFilePath: string, env?: Record<string, string>): Promise<string>;
|
|
2
|
+
export declare function doesPrismaDiffExist(schemaFilePath: string, env?: Record<string, string>): Promise<boolean>;
|
|
3
|
+
export declare function resetDevPrismaDatabase(schemaFilePath: string, env?: Record<string, string>): Promise<void>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { runPrismaCommand } from './run-prisma-command.js';
|
|
2
|
+
export async function getPrismaDiff(schemaFilePath, env = {}) {
|
|
3
|
+
const command = [
|
|
4
|
+
'migrate',
|
|
5
|
+
'diff',
|
|
6
|
+
`--from-schema-datamodel='${schemaFilePath}'`,
|
|
7
|
+
`--to-schema-datasource='${schemaFilePath}'`,
|
|
8
|
+
].join(' ');
|
|
9
|
+
const results = await runPrismaCommand({ command }, undefined, env);
|
|
10
|
+
if (results.stdout.trim() === 'No difference detected.') {
|
|
11
|
+
return '';
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
return results.stdout.trim();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function doesPrismaDiffExist(schemaFilePath, env = {}) {
|
|
18
|
+
return !!(await getPrismaDiff(schemaFilePath, env));
|
|
19
|
+
}
|
|
20
|
+
export async function resetDevPrismaDatabase(schemaFilePath, env = {}) {
|
|
21
|
+
await runPrismaCommand({ command: 'migrate reset --force' }, schemaFilePath, env);
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function clearTestDatabaseOutputs(): Promise<void>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { rm } from 'node:fs/promises';
|
|
2
|
+
import { generatedPrismaClientDirPath, notCommittedDirPath, testPrismaMigrationsDirPath, } from '../file-paths.mock.js';
|
|
3
|
+
export async function clearTestDatabaseOutputs() {
|
|
4
|
+
await rm(generatedPrismaClientDirPath, { force: true, recursive: true });
|
|
5
|
+
await rm(notCommittedDirPath, { force: true, recursive: true });
|
|
6
|
+
await rm(testPrismaMigrationsDirPath, { force: true, recursive: true });
|
|
7
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare class PrismaSchemaError extends Error {
|
|
2
|
+
readonly name = "PrismaSchemaError";
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
export declare class PrismaMigrationNeededError extends Error {
|
|
6
|
+
readonly name = "PrismaMigrationNeededError";
|
|
7
|
+
constructor(schemaFilePath: string);
|
|
8
|
+
}
|
|
9
|
+
export declare class PrismaResetNeededError extends Error {
|
|
10
|
+
readonly name = "PrismaResetNeededError";
|
|
11
|
+
constructor(schemaFilePath: string);
|
|
12
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export class PrismaSchemaError extends Error {
|
|
2
|
+
name = 'PrismaSchemaError';
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export class PrismaMigrationNeededError extends Error {
|
|
8
|
+
name = 'PrismaMigrationNeededError';
|
|
9
|
+
constructor(schemaFilePath) {
|
|
10
|
+
super(`A new Prisma migration is needed for '${schemaFilePath}'`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export class PrismaResetNeededError extends Error {
|
|
14
|
+
name = 'PrismaResetNeededError';
|
|
15
|
+
constructor(schemaFilePath) {
|
|
16
|
+
super(`A database reset is needed for '${schemaFilePath}'`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type PrismaMigrationStatus = {
|
|
2
|
+
totalMigrations: number;
|
|
3
|
+
unappliedMigrations: string[];
|
|
4
|
+
};
|
|
5
|
+
export declare function applyPrismaMigrationsToProd(schemaFilePath: string, env?: Record<string, string>): Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* @throws `PrismaMigrationNeededError` when a new migration is required so the user needs to input
|
|
8
|
+
* a name.
|
|
9
|
+
* @throws `PrismaResetNeededError` when there's a migration mismatch with the database and it needs
|
|
10
|
+
* to be reset.
|
|
11
|
+
*/
|
|
12
|
+
export declare function applyPrismaMigrationsToDev(schemaFilePath: string, env?: Record<string, string>): Promise<void>;
|
|
13
|
+
export declare function getMigrationStatus(schemaFilePath: string, env?: Record<string, string>): Promise<PrismaMigrationStatus>;
|
|
14
|
+
export declare function createPrismaMigration({ migrationName, createOnly, }: {
|
|
15
|
+
migrationName: string;
|
|
16
|
+
createOnly?: boolean | undefined;
|
|
17
|
+
}, schemaFilePath: string, env?: Record<string, string>): Promise<void>;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { check } from '@augment-vir/assert';
|
|
2
|
+
import { log, safeMatch, toEnsuredNumber } from '@augment-vir/common';
|
|
3
|
+
import terminate from 'terminate';
|
|
4
|
+
import { runShellCommand } from '../augments/shell.js';
|
|
5
|
+
import { PrismaMigrationNeededError, PrismaResetNeededError } from './prisma-errors.js';
|
|
6
|
+
import { runPrismaCommand, verifyOutput } from './run-prisma-command.js';
|
|
7
|
+
export async function applyPrismaMigrationsToProd(schemaFilePath, env = {}) {
|
|
8
|
+
await runPrismaCommand({ command: 'migrate deploy' }, schemaFilePath, env);
|
|
9
|
+
}
|
|
10
|
+
var DbChangeRequired;
|
|
11
|
+
(function (DbChangeRequired) {
|
|
12
|
+
DbChangeRequired["MigrationNeeded"] = "migration-needed";
|
|
13
|
+
DbChangeRequired["ResetNeeded"] = "reset-needed";
|
|
14
|
+
})(DbChangeRequired || (DbChangeRequired = {}));
|
|
15
|
+
/**
|
|
16
|
+
* @throws `PrismaMigrationNeededError` when a new migration is required so the user needs to input
|
|
17
|
+
* a name.
|
|
18
|
+
* @throws `PrismaResetNeededError` when there's a migration mismatch with the database and it needs
|
|
19
|
+
* to be reset.
|
|
20
|
+
*/
|
|
21
|
+
export async function applyPrismaMigrationsToDev(schemaFilePath, env = {}) {
|
|
22
|
+
const command = [
|
|
23
|
+
'prisma',
|
|
24
|
+
'migrate',
|
|
25
|
+
'dev',
|
|
26
|
+
`--schema='${schemaFilePath}'`,
|
|
27
|
+
].join(' ');
|
|
28
|
+
log.faint(`> ${command}`);
|
|
29
|
+
let dbRequirement = undefined;
|
|
30
|
+
const result = await runShellCommand(command, {
|
|
31
|
+
env: {
|
|
32
|
+
...process.env,
|
|
33
|
+
...env,
|
|
34
|
+
},
|
|
35
|
+
stdoutCallback(stdout, childProcess) {
|
|
36
|
+
if (stdout.includes('Enter a name for the new migration')) {
|
|
37
|
+
if (childProcess.pid) {
|
|
38
|
+
terminate(childProcess.pid);
|
|
39
|
+
}
|
|
40
|
+
dbRequirement = DbChangeRequired.MigrationNeeded;
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
stderrCallback(stderr, childProcess) {
|
|
44
|
+
if (stderr.includes('Prisma Migrate has detected that the environment is non-interactive, which is not supported')) {
|
|
45
|
+
if (childProcess.pid) {
|
|
46
|
+
terminate(childProcess.pid);
|
|
47
|
+
}
|
|
48
|
+
dbRequirement = DbChangeRequired.ResetNeeded;
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
if (dbRequirement === DbChangeRequired.MigrationNeeded) {
|
|
53
|
+
throw new PrismaMigrationNeededError(schemaFilePath);
|
|
54
|
+
}
|
|
55
|
+
else if (dbRequirement === DbChangeRequired.ResetNeeded) {
|
|
56
|
+
throw new PrismaResetNeededError(schemaFilePath);
|
|
57
|
+
}
|
|
58
|
+
verifyOutput(schemaFilePath, result, false);
|
|
59
|
+
}
|
|
60
|
+
export async function getMigrationStatus(schemaFilePath, env = {}) {
|
|
61
|
+
const output = await runPrismaCommand({
|
|
62
|
+
command: 'migrate status',
|
|
63
|
+
ignoreExitCode: true,
|
|
64
|
+
}, schemaFilePath, env);
|
|
65
|
+
const listedMigrations = {
|
|
66
|
+
totalMigrations: 0,
|
|
67
|
+
unappliedMigrations: [],
|
|
68
|
+
};
|
|
69
|
+
let foundNotAppliedMigrations = false;
|
|
70
|
+
output.stdout.split('\n').some((rawLine) => {
|
|
71
|
+
const line = rawLine.trim();
|
|
72
|
+
if (foundNotAppliedMigrations) {
|
|
73
|
+
if (line) {
|
|
74
|
+
listedMigrations.unappliedMigrations.push(line);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
/** We're done parsing. */
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else if (line.endsWith('not yet been applied:')) {
|
|
82
|
+
foundNotAppliedMigrations = true;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
const [, countMatch,] = safeMatch(line, /^([\d,]+) migrations? found in/);
|
|
86
|
+
if (countMatch) {
|
|
87
|
+
listedMigrations.totalMigrations = toEnsuredNumber(countMatch);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/** Still need to keep parsing. */
|
|
91
|
+
return false;
|
|
92
|
+
});
|
|
93
|
+
return listedMigrations;
|
|
94
|
+
}
|
|
95
|
+
export async function createPrismaMigration({ migrationName, createOnly = false, }, schemaFilePath, env = {}) {
|
|
96
|
+
const command = [
|
|
97
|
+
'migrate',
|
|
98
|
+
'dev',
|
|
99
|
+
`--name='${migrationName}'`,
|
|
100
|
+
createOnly ? '--create-only' : '',
|
|
101
|
+
]
|
|
102
|
+
.filter(check.isTruthy)
|
|
103
|
+
.join(' ');
|
|
104
|
+
await runPrismaCommand({ command }, schemaFilePath, env);
|
|
105
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ShellOutput } from '../augments/shell.js';
|
|
2
|
+
export declare function runPrismaCommand({ command, ignoreExitCode, }: {
|
|
3
|
+
command: string;
|
|
4
|
+
ignoreExitCode?: boolean | undefined;
|
|
5
|
+
},
|
|
6
|
+
/** Set to `undefined` to omit the `--schema` flag. */
|
|
7
|
+
schemaFilePath: string | undefined, env?: Record<string, string> | undefined): Promise<Readonly<ShellOutput>>;
|
|
8
|
+
export declare function verifyOutput(schemaFilePath: string, shellOutput: Readonly<ShellOutput>, ignoreExitCode: boolean): Readonly<ShellOutput>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { log } from '@augment-vir/common';
|
|
2
|
+
import { runShellCommand } from '../augments/shell.js';
|
|
3
|
+
import { PrismaSchemaError } from './prisma-errors.js';
|
|
4
|
+
const prismaCommandsThatSupportNoHints = [
|
|
5
|
+
'generate',
|
|
6
|
+
];
|
|
7
|
+
export async function runPrismaCommand({ command, ignoreExitCode = false, },
|
|
8
|
+
/** Set to `undefined` to omit the `--schema` flag. */
|
|
9
|
+
schemaFilePath, env = {}) {
|
|
10
|
+
const schemaFileArgs = schemaFilePath
|
|
11
|
+
? [
|
|
12
|
+
'--schema',
|
|
13
|
+
schemaFilePath,
|
|
14
|
+
]
|
|
15
|
+
: [];
|
|
16
|
+
/** Disable Prisma's in-CLI ads. */
|
|
17
|
+
const noHintsArg = prismaCommandsThatSupportNoHints.some((commandName) => command.startsWith(commandName))
|
|
18
|
+
? '--no-hints'
|
|
19
|
+
: '';
|
|
20
|
+
const fullCommand = [
|
|
21
|
+
'prisma',
|
|
22
|
+
command,
|
|
23
|
+
...schemaFileArgs,
|
|
24
|
+
noHintsArg,
|
|
25
|
+
].join(' ');
|
|
26
|
+
log.faint(`> ${fullCommand}`);
|
|
27
|
+
const result = await runShellCommand(fullCommand, {
|
|
28
|
+
env: {
|
|
29
|
+
...process.env,
|
|
30
|
+
...env,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
return verifyOutput(schemaFilePath || '', result, ignoreExitCode);
|
|
34
|
+
}
|
|
35
|
+
export function verifyOutput(schemaFilePath, shellOutput, ignoreExitCode) {
|
|
36
|
+
if (shellOutput.stderr.includes('Validation Error Count')) {
|
|
37
|
+
throw new PrismaSchemaError(`Invalid schema file at '${schemaFilePath}':\n\n${shellOutput.stderr}`);
|
|
38
|
+
}
|
|
39
|
+
else if (shellOutput.stderr.includes('does not exist at')) {
|
|
40
|
+
throw new PrismaSchemaError(`Database does not exist: ${shellOutput.stderr}`);
|
|
41
|
+
}
|
|
42
|
+
else if (shellOutput.exitCode === 0 || ignoreExitCode) {
|
|
43
|
+
return shellOutput;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
throw new Error(shellOutput.stdout + shellOutput.stderr);
|
|
47
|
+
}
|
|
48
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@augment-vir/node",
|
|
3
|
+
"version": "30.0.0",
|
|
4
|
+
"homepage": "https://github.com/electrovir/augment-vir",
|
|
5
|
+
"bugs": {
|
|
6
|
+
"url": "https://github.com/electrovir/augment-vir/issues"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/electrovir/augment-vir.git"
|
|
11
|
+
},
|
|
12
|
+
"license": "(MIT or CC0 1.0)",
|
|
13
|
+
"author": {
|
|
14
|
+
"name": "electrovir",
|
|
15
|
+
"url": "https://github.com/electrovir"
|
|
16
|
+
},
|
|
17
|
+
"type": "module",
|
|
18
|
+
"main": "dist/index.js",
|
|
19
|
+
"module": "dist/index.js",
|
|
20
|
+
"types": "dist/index.d.ts",
|
|
21
|
+
"scripts": {
|
|
22
|
+
"compile": "virmator compile",
|
|
23
|
+
"docs": "virmator docs",
|
|
24
|
+
"test": "virmator --no-deps test node --test-concurrency 1",
|
|
25
|
+
"test:coverage": "virmator test node coverage --test-concurrency 1",
|
|
26
|
+
"test:docs": "virmator docs check",
|
|
27
|
+
"test:update": "npm test"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@augment-vir/assert": "^30.0.0",
|
|
31
|
+
"@augment-vir/common": "^30.0.0",
|
|
32
|
+
"@date-vir/duration": "^6.0.0",
|
|
33
|
+
"ansi-styles": "^6.2.1",
|
|
34
|
+
"terminate": "^2.8.0",
|
|
35
|
+
"type-fest": "^4.25.0",
|
|
36
|
+
"typed-event-target": "^3.4.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@augment-vir/test": "^30.0.0",
|
|
40
|
+
"@prisma/client": "^5.19.0",
|
|
41
|
+
"@types/node": "^22.5.0",
|
|
42
|
+
"@web/dev-server-esbuild": "^1.0.2",
|
|
43
|
+
"@web/test-runner": "^0.18.3",
|
|
44
|
+
"@web/test-runner-commands": "^0.9.0",
|
|
45
|
+
"@web/test-runner-playwright": "^0.11.0",
|
|
46
|
+
"@web/test-runner-visual-regression": "^0.9.0",
|
|
47
|
+
"c8": "^10.1.2",
|
|
48
|
+
"concurrently": "^8.2.2",
|
|
49
|
+
"istanbul-smart-text-reporter": "^1.1.4",
|
|
50
|
+
"prisma": "^5.19.0"
|
|
51
|
+
},
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=22"
|
|
54
|
+
},
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"access": "public"
|
|
57
|
+
}
|
|
58
|
+
}
|