@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.
Files changed (45) hide show
  1. package/dist/scripts/fix-ts-bin.script.d.ts +1 -0
  2. package/dist/scripts/fix-ts-bin.script.js +48 -0
  3. package/package.json +4 -4
  4. package/src/augments/docker.ts +118 -0
  5. package/src/augments/fs/dir-contents.ts +116 -0
  6. package/src/augments/fs/download.ts +31 -0
  7. package/src/augments/fs/json.ts +87 -0
  8. package/src/augments/fs/read-dir.ts +60 -0
  9. package/src/augments/fs/read-file.ts +18 -0
  10. package/src/augments/fs/symlink.ts +37 -0
  11. package/src/augments/fs/write.ts +18 -0
  12. package/src/augments/npm/query-workspace.ts +47 -0
  13. package/src/augments/npm/read-package-json.ts +28 -0
  14. package/src/augments/os/operating-system.ts +55 -0
  15. package/src/augments/path/ancestor.ts +78 -0
  16. package/src/augments/path/os-path.ts +45 -0
  17. package/src/augments/path/root.ts +14 -0
  18. package/src/augments/prisma.ts +174 -0
  19. package/src/augments/terminal/question.ts +142 -0
  20. package/src/augments/terminal/relevant-args.ts +81 -0
  21. package/src/augments/terminal/run-cli-script.ts +60 -0
  22. package/src/augments/terminal/shell.ts +312 -0
  23. package/src/docker/containers/container-info.ts +83 -0
  24. package/src/docker/containers/container-status.ts +110 -0
  25. package/src/docker/containers/copy-to-container.ts +34 -0
  26. package/src/docker/containers/docker-command-inputs.ts +119 -0
  27. package/src/docker/containers/kill-container.ts +25 -0
  28. package/src/docker/containers/run-command.ts +51 -0
  29. package/src/docker/containers/run-container.mock.ts +17 -0
  30. package/src/docker/containers/run-container.ts +92 -0
  31. package/src/docker/containers/try-or-kill-container.ts +18 -0
  32. package/src/docker/docker-image.ts +56 -0
  33. package/src/docker/docker-startup.ts +49 -0
  34. package/src/docker/run-docker-test.mock.ts +26 -0
  35. package/src/file-paths.mock.ts +29 -0
  36. package/src/index.ts +19 -0
  37. package/src/prisma/disable-ci-env.mock.ts +88 -0
  38. package/src/prisma/model-data.ts +213 -0
  39. package/src/prisma/prisma-client.ts +43 -0
  40. package/src/prisma/prisma-database.mock.ts +31 -0
  41. package/src/prisma/prisma-database.ts +35 -0
  42. package/src/prisma/prisma-errors.ts +45 -0
  43. package/src/prisma/prisma-migrations.ts +149 -0
  44. package/src/prisma/run-prisma-command.ts +59 -0
  45. package/src/scripts/fix-ts-bin.script.ts +60 -0
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,48 @@
1
+ /* node:coverage disable */
2
+ /**
3
+ * To use this script properly, call it directly in your postinstall script. Like this:
4
+ *
5
+ * ```json
6
+ * {
7
+ * "scripts": {
8
+ * "postinstall": "tsx node_modules/@augment-vir/node/src/scripts/fix-ts-bin.script.ts"
9
+ * }
10
+ * }
11
+ * ```
12
+ */
13
+ import { log } from '@augment-vir/common';
14
+ import { interpolationSafeWindowsPath, runShellCommand } from '@augment-vir/node';
15
+ import { rm, writeFile } from 'node:fs/promises';
16
+ import { join, sep } from 'node:path/posix';
17
+ const packagesToFix = [
18
+ {
19
+ packageName: 'mono-vir',
20
+ binName: 'mono-vir',
21
+ scriptPath: join('mono-vir', 'src', 'cli', 'cli.script.ts'),
22
+ },
23
+ {
24
+ packageName: 'virmator',
25
+ binName: 'virmator',
26
+ scriptPath: join('virmator', 'src', 'cli.script.ts'),
27
+ },
28
+ {
29
+ packageName: 'prettier',
30
+ binName: 'prettier',
31
+ scriptPath: join('prettier', 'bin', 'prettier.cjs'),
32
+ },
33
+ ];
34
+ function createBinFileContents({ scriptPath }) {
35
+ return [
36
+ '#!/bin/bash',
37
+ '',
38
+ `npx tsx "$(dirname "$(dirname "$(readlink -f "$0")")")${sep}${scriptPath}" "$@"`,
39
+ ].join('\n');
40
+ }
41
+ async function fixTsBin(packageToFix) {
42
+ const binFilePath = join(process.cwd(), 'node_modules', '.bin', packageToFix.binName);
43
+ await rm(binFilePath, { force: true });
44
+ await writeFile(binFilePath, createBinFileContents(packageToFix));
45
+ await runShellCommand(`chmod +x ${interpolationSafeWindowsPath(binFilePath)}`);
46
+ log.success(`Fixed ${packageToFix.packageName} bin.`);
47
+ }
48
+ await Promise.all(packagesToFix.map(async (packageToFix) => await fixTsBin(packageToFix)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@augment-vir/node",
3
- "version": "31.0.1",
3
+ "version": "31.1.1",
4
4
  "description": "A collection of augments, helpers types, functions, and classes only for Node.js (backend) JavaScript environments.",
5
5
  "keywords": [
6
6
  "augment",
@@ -37,8 +37,8 @@
37
37
  "test:update": "npm test"
38
38
  },
39
39
  "dependencies": {
40
- "@augment-vir/assert": "^31.0.1",
41
- "@augment-vir/common": "^31.0.1",
40
+ "@augment-vir/assert": "^31.1.1",
41
+ "@augment-vir/common": "^31.1.1",
42
42
  "@date-vir/duration": "^7.0.1",
43
43
  "ansi-styles": "^6.2.1",
44
44
  "terminate": "^2.8.0",
@@ -46,7 +46,7 @@
46
46
  "typed-event-target": "^4.0.2"
47
47
  },
48
48
  "devDependencies": {
49
- "@augment-vir/test": "^31.0.1",
49
+ "@augment-vir/test": "^31.1.1",
50
50
  "@prisma/client": "^5.22.0",
51
51
  "@types/node": "^22.10.0",
52
52
  "@web/dev-server-esbuild": "^1.0.3",
@@ -0,0 +1,118 @@
1
+ import {getContainerInfo} from '../docker/containers/container-info.js';
2
+ import {
3
+ getContainerLogs,
4
+ getContainerStatus,
5
+ waitUntilContainerExited,
6
+ waitUntilContainerRemoved,
7
+ waitUntilContainerRunning,
8
+ } from '../docker/containers/container-status.js';
9
+ import {copyToContainer} from '../docker/containers/copy-to-container.js';
10
+ import {
11
+ makeEnvFlags,
12
+ makePortMapFlags,
13
+ makeVolumeFlags,
14
+ } from '../docker/containers/docker-command-inputs.js';
15
+ import {killContainer} from '../docker/containers/kill-container.js';
16
+ import {runContainerCommand} from '../docker/containers/run-command.js';
17
+ import {runContainer} from '../docker/containers/run-container.js';
18
+ import {tryOrKillContainer} from '../docker/containers/try-or-kill-container.js';
19
+ import {
20
+ isImageInLocalRegistry,
21
+ removeImageFromLocalRegistry,
22
+ updateImage,
23
+ } from '../docker/docker-image.js';
24
+ import {isDockerRunning, startDocker} from '../docker/docker-startup.js';
25
+
26
+ export {
27
+ type DockerContainerInfo,
28
+ type DockerContainerInfoState,
29
+ } from '../docker/containers/container-info.js';
30
+ export {
31
+ DockerContainerStatus,
32
+ exitedDockerContainerStatuses,
33
+ } from '../docker/containers/container-status.js';
34
+ export {type CopyToDockerContainerParams} from '../docker/containers/copy-to-container.js';
35
+ export {
36
+ type DockerEnvMap,
37
+ type DockerPortMap,
38
+ type DockerVolumeMap,
39
+ type DockerVolumeMappingType,
40
+ } from '../docker/containers/docker-command-inputs.js';
41
+ export {type RunDockerContainerCommandParams} from '../docker/containers/run-command.js';
42
+ export {type RunDockerContainerParams} from '../docker/containers/run-container.js';
43
+
44
+ /**
45
+ * Centralized Docker API from `@augment-vir/node`.
46
+ *
47
+ * @category Node : Docker
48
+ * @category Package : @augment-vir/node
49
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
50
+ */
51
+ export const docker = {
52
+ /** Detects if the Docker service is running. */
53
+ isRunning: isDockerRunning,
54
+ /**
55
+ * Tries to start Docker based ont he current operating system's supported commands. The success
56
+ * of this operation is heavily dependent on how you have Docker setup on your system.
57
+ */
58
+ start: startDocker,
59
+ image: {
60
+ /** Downloads an image if it is missing from the local registry. */
61
+ update: updateImage,
62
+ /** Removes an image from the local registry. */
63
+ remove: removeImageFromLocalRegistry,
64
+ /** Detects if an image exists in the local registry. */
65
+ exists: isImageInLocalRegistry,
66
+ },
67
+ container: {
68
+ /**
69
+ * Get the current status of a container. If the container does not exist at all, the status
70
+ * will be {@link DockerContainerStatus.Removed}.
71
+ */
72
+ getStatus: getContainerStatus,
73
+ /** Wait until a container is running and responsive. */
74
+ waitUntilRunning: waitUntilContainerRunning,
75
+ /**
76
+ * Wait until a container has a status that can be classified as "exited".
77
+ *
78
+ * @see {@link exitedDockerContainerStatuses}
79
+ */
80
+ waitUntilExited: waitUntilContainerExited,
81
+ /** Wait until a container is completely removed. */
82
+ waitUntilRemoved: waitUntilContainerRemoved,
83
+ /**
84
+ * Runs a callback (which presumably will run a command within the given `containerName`)
85
+ * and kills the container if the callback fails.
86
+ */
87
+ tryOrKill: tryOrKillContainer,
88
+ /** Run a container that isn't already running. */
89
+ run: runContainer,
90
+ /** Kill a container. */
91
+ kill: killContainer,
92
+ /** Copy a file or directory to a container. */
93
+ copyTo: copyToContainer,
94
+ /** Run a command on a container that is already running. */
95
+ runCommand: runContainerCommand,
96
+ /** Run `docker inspect` on a container and return its output. */
97
+ getInfo: getContainerInfo,
98
+ /** Get a container's logs. */
99
+ getLogs: getContainerLogs,
100
+ },
101
+ util: {
102
+ /**
103
+ * Manually create a string of volume mapping flags. This is automatically done already
104
+ * inside the run container methods.
105
+ */
106
+ makeVolumeFlags,
107
+ /**
108
+ * Manually create a string of port mapping flags. This is automatically done already inside
109
+ * the run container methods.
110
+ */
111
+ makePortMapFlags,
112
+ /**
113
+ * Manually create a string of env mapping flags. This is automatically done already inside
114
+ * the run container methods.
115
+ */
116
+ makeEnvFlags,
117
+ },
118
+ };
@@ -0,0 +1,116 @@
1
+ import {check} from '@augment-vir/assert';
2
+ import {getObjectTypedEntries} from '@augment-vir/common';
3
+ import {readdir, readFile, rm, stat} from 'node:fs/promises';
4
+ import {join} from 'node:path';
5
+ import {writeFileAndDir} from './write.js';
6
+
7
+ /**
8
+ * Nested contents read from a file system directory.
9
+ *
10
+ * @category Node : File
11
+ * @category Package : @augment-vir/node
12
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
13
+ */
14
+ export type DirContents = {
15
+ [Path in string]: string | DirContents;
16
+ };
17
+
18
+ /**
19
+ * Read all contents within a directory and store them in an object. Optionally recursive.
20
+ *
21
+ * @category Node : File
22
+ * @category Package : @augment-vir/node
23
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
24
+ */
25
+ export async function readAllDirContents(
26
+ dir: string,
27
+ {
28
+ recursive = false,
29
+ excludeList,
30
+ }: {
31
+ recursive?: boolean;
32
+ excludeList?: ReadonlyArray<string | RegExp> | undefined;
33
+ },
34
+ ): Promise<DirContents> {
35
+ const fileNames = (await readdir(dir)).sort();
36
+
37
+ const allFileContents = await Promise.all(
38
+ fileNames.map(async (fileName) => {
39
+ const filePath = join(dir, fileName);
40
+
41
+ if (
42
+ excludeList?.some((excludeItem) => {
43
+ if (check.isString(excludeItem)) {
44
+ return filePath.includes(excludeItem);
45
+ } else {
46
+ return filePath.match(excludeItem);
47
+ }
48
+ })
49
+ ) {
50
+ return undefined;
51
+ }
52
+
53
+ const isFile = (await stat(filePath)).isFile();
54
+ const contents = isFile
55
+ ? (await readFile(filePath)).toString()
56
+ : recursive
57
+ ? await readAllDirContents(filePath, {recursive, excludeList})
58
+ : undefined;
59
+
60
+ if (check.isObject(contents) && !Object.keys(contents).length) {
61
+ return undefined;
62
+ }
63
+
64
+ return contents;
65
+ }),
66
+ );
67
+
68
+ return fileNames.reduce((accum: DirContents, fileName, index) => {
69
+ if (allFileContents[index] != undefined) {
70
+ const fileContents = allFileContents[index];
71
+ accum[fileName] = fileContents;
72
+ }
73
+ return accum;
74
+ }, {});
75
+ }
76
+
77
+ /**
78
+ * Deletes and entire directory and resets it to the given contents.
79
+ *
80
+ * @category Node : File
81
+ * @category Package : @augment-vir/node
82
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
83
+ */
84
+ export async function resetDirContents(
85
+ rootDir: string,
86
+ contents: Readonly<DirContents>,
87
+ ): Promise<void> {
88
+ await rm(rootDir, {
89
+ force: true,
90
+ recursive: true,
91
+ });
92
+
93
+ await writeDirContents(rootDir, contents);
94
+ }
95
+ /**
96
+ * Write {@link DirContents} to a directory.
97
+ *
98
+ * @category Node : File
99
+ * @category Package : @augment-vir/node
100
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
101
+ */
102
+ export async function writeDirContents(
103
+ rootDir: string,
104
+ contents: Readonly<DirContents>,
105
+ ): Promise<void> {
106
+ await Promise.all(
107
+ getObjectTypedEntries(contents).map(async ([relativePath, content]) => {
108
+ const fullPath = join(rootDir, relativePath);
109
+ if (check.isString(content)) {
110
+ await writeFileAndDir(fullPath, content);
111
+ } else {
112
+ await writeDirContents(fullPath, content);
113
+ }
114
+ }),
115
+ );
116
+ }
@@ -0,0 +1,31 @@
1
+ import {createWriteStream} from 'node:fs';
2
+ import {mkdir} from 'node:fs/promises';
3
+ import {dirname} from 'node:path';
4
+ import {Readable} from 'node:stream';
5
+ import {finished} from 'node:stream/promises';
6
+ import type {ReadableStream} from 'node:stream/web';
7
+
8
+ /**
9
+ * Download a file.
10
+ *
11
+ * @category Node : File
12
+ * @category Package : @augment-vir/node
13
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
14
+ */
15
+ export async function downloadFile({url, writePath}: {url: string; writePath: string}) {
16
+ const response = await fetch(url);
17
+
18
+ if (!response.ok) {
19
+ throw new Error(`${response.status}: ${response.statusText}`);
20
+ }
21
+
22
+ /** Idk how to actually trigger a response with no body. */
23
+ /* node:coverage ignore next 3 */
24
+ if (!response.body) {
25
+ throw new Error(`Response body is missing from '${url}'.`);
26
+ }
27
+
28
+ await mkdir(dirname(writePath), {recursive: true});
29
+ const fileStream = createWriteStream(writePath);
30
+ await finished(Readable.fromWeb(response.body as ReadableStream).pipe(fileStream));
31
+ }
@@ -0,0 +1,87 @@
1
+ import {
2
+ appendJson,
3
+ JsonCompatibleArray,
4
+ JsonCompatibleObject,
5
+ type JsonCompatibleValue,
6
+ } from '@augment-vir/common';
7
+ import type {PartialWithUndefined} from '@augment-vir/core';
8
+ import {mkdir, readFile} from 'node:fs/promises';
9
+ import {dirname} from 'node:path';
10
+ import {writeFileAndDir} from './write.js';
11
+
12
+ /**
13
+ * Read a file and also parse its contents as JSON.
14
+ *
15
+ * @category Node : File
16
+ * @category JSON : Node
17
+ * @category Package : @augment-vir/node
18
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
19
+ * @see
20
+ * - {@link writeJsonFile}
21
+ * - {@link appendJsonFile}
22
+ */
23
+ export async function readJsonFile(path: string): Promise<JsonCompatibleValue | undefined> {
24
+ try {
25
+ const contents = (await readFile(path)).toString();
26
+ return JSON.parse(contents);
27
+ } catch {
28
+ return undefined;
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Options for {@link writeJsonFile}.
34
+ *
35
+ * @category Node : File
36
+ * @category JSON : Node
37
+ * @category Package : @augment-vir/node
38
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
39
+ */
40
+ export type WriteJsonOptions = PartialWithUndefined<{
41
+ includeTrailingNewLine: boolean;
42
+ }>;
43
+
44
+ /**
45
+ * Write to a file and stringify `data` as JSON before doing so.
46
+ *
47
+ * @category Node : File
48
+ * @category JSON : Node
49
+ * @category Package : @augment-vir/node
50
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
51
+ * @see
52
+ * - {@link readJsonFile}
53
+ * - {@link appendJsonFile}
54
+ */
55
+ export async function writeJsonFile(
56
+ path: string,
57
+ data: JsonCompatibleValue,
58
+ options: WriteJsonOptions = {},
59
+ ): Promise<void> {
60
+ await mkdir(dirname(path), {recursive: true});
61
+
62
+ const trailingNewLine = options.includeTrailingNewLine ? '\n' : '';
63
+
64
+ await writeFileAndDir(path, JSON.stringify(data, null, 4) + trailingNewLine);
65
+ }
66
+
67
+ /**
68
+ * Append the given `newData` to the contents of the existing JSON file. If the file does not yet
69
+ * exist, `newData` is written as its only JSON contents.
70
+ *
71
+ * @category Node : File
72
+ * @category JSON : Node
73
+ * @category Package : @augment-vir/node
74
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
75
+ * @see
76
+ * - {@link readJsonFile}
77
+ * - {@link writeJsonFile}
78
+ */
79
+ export async function appendJsonFile(
80
+ path: string,
81
+ newData: JsonCompatibleObject | JsonCompatibleArray,
82
+ options: WriteJsonOptions = {},
83
+ ): Promise<void> {
84
+ const fileJson = await readJsonFile(path);
85
+
86
+ await writeJsonFile(path, appendJson(fileJson, newData), options);
87
+ }
@@ -0,0 +1,60 @@
1
+ import {readdir, stat} from 'node:fs/promises';
2
+ import {join, relative} from 'node:path';
3
+ import type {RequireExactlyOne} from 'type-fest';
4
+
5
+ async function internalReadDirPathsRecursive(dirPath: string, basePath: string): Promise<string[]> {
6
+ const dirContents = await readdir(dirPath);
7
+ const recursiveContents: string[] = (
8
+ await Promise.all(
9
+ dirContents.map(async (fileName): Promise<string | ReadonlyArray<string>> => {
10
+ const filePath = join(dirPath, fileName);
11
+ if ((await stat(filePath)).isDirectory()) {
12
+ return internalReadDirPathsRecursive(filePath, basePath);
13
+ } else {
14
+ return relative(basePath, filePath);
15
+ }
16
+ }),
17
+ )
18
+ ).flat();
19
+
20
+ return recursiveContents;
21
+ }
22
+
23
+ /**
24
+ * Gets all files within a directory and its subdirectories, recursively. Returns an array of paths
25
+ * relative to the given input path.
26
+ *
27
+ * @category Node : File
28
+ * @category Package : @augment-vir/node
29
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
30
+ */
31
+ export async function readDirRecursive(dirPath: string): Promise<string[]> {
32
+ return await internalReadDirPathsRecursive(dirPath, dirPath);
33
+ }
34
+
35
+ /**
36
+ * Reads all files within a single directory and filters them by the given extension or extensions.
37
+ *
38
+ * @category Node : File
39
+ * @category Package : @augment-vir/node
40
+ * @returns That filtered list of paths.
41
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
42
+ */
43
+ export async function readDirFilesByExtension({
44
+ dirPath,
45
+ extension,
46
+ extensions,
47
+ }: {
48
+ dirPath: string;
49
+ } & RequireExactlyOne<{
50
+ extension: string;
51
+ extensions: ReadonlyArray<string>;
52
+ }>) {
53
+ const extensionsToCheck: ReadonlyArray<string> = extensions || [extension];
54
+
55
+ const fileNames = await readdir(dirPath);
56
+
57
+ return fileNames.filter((fileName) =>
58
+ extensionsToCheck.some((extensionToCheck) => fileName.endsWith(extensionToCheck)),
59
+ );
60
+ }
@@ -0,0 +1,18 @@
1
+ import {existsSync} from 'node:fs';
2
+ import {readFile} from 'node:fs/promises';
3
+
4
+ /**
5
+ * Reads a file if it exists, or just return `undefined`.
6
+ *
7
+ * @category Node : File
8
+ * @category Package : @augment-vir/node
9
+ * @returns The file contents as a string if the file exists, otherwise `undefined`.
10
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
11
+ */
12
+ export async function readFileIfExists(path: string): Promise<string | undefined> {
13
+ if (existsSync(path)) {
14
+ return (await readFile(path)).toString();
15
+ } else {
16
+ return undefined;
17
+ }
18
+ }
@@ -0,0 +1,37 @@
1
+ import {existsSync} from 'node:fs';
2
+ import {lstat, readlink, stat, symlink} from 'node:fs/promises';
3
+
4
+ /**
5
+ * Creates a symlink.
6
+ *
7
+ * @category Node : File
8
+ * @category Package : @augment-vir/node
9
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
10
+ */
11
+ export async function createSymlink({
12
+ linkTo,
13
+ symlinkPath,
14
+ }: {
15
+ /**
16
+ * Path that the symlink will link to. If relative, it will be linked relative to the symlink
17
+ * location.
18
+ */
19
+ linkTo: string;
20
+ /** The location and name the symlink file to be created. */
21
+ symlinkPath: string;
22
+ }): Promise<void> {
23
+ if (existsSync(symlinkPath)) {
24
+ if (!(await lstat(symlinkPath)).isSymbolicLink()) {
25
+ throw new Error(
26
+ `Tried to create symlink at '${symlinkPath}' but a non-symlink file already exists in that location.`,
27
+ );
28
+ } else if ((await readlink(symlinkPath)) !== linkTo) {
29
+ throw new Error(
30
+ `Symlink already exists at '${symlinkPath}' but has a differently link path.`,
31
+ );
32
+ }
33
+ } else {
34
+ const isDir = (await stat(linkTo)).isDirectory();
35
+ await symlink(linkTo, symlinkPath, isDir ? 'dir' : 'file');
36
+ }
37
+ }
@@ -0,0 +1,18 @@
1
+ import {mkdir, writeFile} from 'node:fs/promises';
2
+ import {dirname} from 'node:path';
3
+
4
+ /**
5
+ * Writes to the given file path and always ensures that the path's parent directories are all
6
+ * created.
7
+ *
8
+ * @category Node : File
9
+ * @category Package : @augment-vir/node
10
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
11
+ */
12
+ export async function writeFileAndDir(
13
+ path: string,
14
+ contents: string | NodeJS.ArrayBufferView,
15
+ ): Promise<void> {
16
+ await mkdir(dirname(path), {recursive: true});
17
+ await writeFile(path, contents);
18
+ }
@@ -0,0 +1,47 @@
1
+ import type {UnknownObject} from '@augment-vir/core';
2
+ import type {PackageJson} from 'type-fest';
3
+ import {runShellCommand} from '../terminal/shell.js';
4
+
5
+ /**
6
+ * An npm workspace object. This is the return type for {@link queryNpmWorkspace}.
7
+ *
8
+ * @category Node : Npm
9
+ * @category Package : @augment-vir/node
10
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
11
+ */
12
+ export type NpmWorkspace = PackageJson & {
13
+ _id: string;
14
+ deduped: boolean;
15
+ dev: boolean;
16
+ from: string[];
17
+ inBundle: boolean;
18
+ location: string;
19
+ overridden: boolean;
20
+ path: string;
21
+ pkgid: string;
22
+ queryContext: UnknownObject;
23
+ realpath: string;
24
+ resolved: null;
25
+ to: string[];
26
+ };
27
+
28
+ /**
29
+ * Get a list of all NPM workspaces in the given mono-repo directory using npm's CLI.
30
+ *
31
+ * @category Node : Npm
32
+ * @category Package : @augment-vir/node
33
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
34
+ */
35
+ export async function queryNpmWorkspace(cwd: string): Promise<NpmWorkspace[]> {
36
+ const queryOutput = await runShellCommand('npm query .workspace', {
37
+ cwd,
38
+ rejectOnError: true,
39
+ });
40
+
41
+ try {
42
+ return JSON.parse(queryOutput.stdout);
43
+ /* node:coverage ignore next 3 */
44
+ } catch {
45
+ throw new Error(`Failed to read npm workspace data for '${cwd}'`);
46
+ }
47
+ }
@@ -0,0 +1,28 @@
1
+ import {check} from '@augment-vir/assert';
2
+ import {join} from 'node:path';
3
+ import {PackageJson} from 'type-fest';
4
+ import {readJsonFile} from '../fs/json.js';
5
+
6
+ /**
7
+ * Read the `package.json` file contained within the given directory.
8
+ *
9
+ * @category Node : Npm
10
+ * @category Package : @augment-vir/node
11
+ * @throws `TypeError` if the given directory has no `package.json` or the `package.json` is
12
+ * invalid.
13
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
14
+ */
15
+ export async function readPackageJson(dirPath: string): Promise<PackageJson> {
16
+ const packageJsonPath = join(dirPath, 'package.json');
17
+ const packageJson = (await readJsonFile(packageJsonPath)) as PackageJson | undefined;
18
+
19
+ if (!packageJson) {
20
+ throw new TypeError(`package.json file does not exist in '${dirPath}'`);
21
+ }
22
+
23
+ if (!check.isObject(packageJson)) {
24
+ throw new TypeError(`Parsing package.json file did not return an object in '${dirPath}'`);
25
+ }
26
+
27
+ return packageJson;
28
+ }