@augment-vir/node 31.2.1 → 31.4.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.
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Finds the path to an npm package executable bin by searching in all ancestor `node_modules/.bin`
3
+ * folders, starting at the given `startPath`.
4
+ *
5
+ * @category Node : Npm
6
+ * @category Package : @augment-vir/node
7
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
8
+ */
9
+ export declare function findNpmBinPath({ binName, startPath, }: {
10
+ startPath: string;
11
+ binName: string;
12
+ }): string | undefined;
@@ -0,0 +1,21 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { findAncestor } from '../path/ancestor.js';
4
+ /**
5
+ * Finds the path to an npm package executable bin by searching in all ancestor `node_modules/.bin`
6
+ * folders, starting at the given `startPath`.
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 function findNpmBinPath({ binName, startPath, }) {
13
+ const binPath = join('node_modules', '.bin', binName);
14
+ const ancestor = findAncestor(startPath, (ancestor) => {
15
+ return existsSync(join(ancestor, binPath));
16
+ });
17
+ if (!ancestor) {
18
+ return undefined;
19
+ }
20
+ return join(ancestor, binPath);
21
+ }
@@ -1,6 +1,31 @@
1
1
  import { type MaybePromise } from '@augment-vir/common';
2
- export declare function findAncestor(currentPath: string, callback: (path: string) => Promise<boolean>): Promise<string | undefined>;
3
- export declare function findAncestor(currentPath: string, callback: (path: string) => boolean): string | undefined;
2
+ /**
3
+ * Find an ancestor file path that matches the given `callback`. If no matches are found all the way
4
+ * up until the system root, this returns `undefined`.
5
+ *
6
+ * @category Path : Node
7
+ * @category Package : @augment-vir/node
8
+ * @returns `undefined` if no matches are found.
9
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
10
+ */
11
+ export declare function findAncestor(currentPath: string, callback: (path: string) => Promise<boolean>): Promise<string | undefined>; /**
12
+ * Find an ancestor file path that matches the given `callback`. If no matches are found all the
13
+ * way up until the system root, this returns `undefined`.
14
+ *
15
+ * @category Path : Node
16
+ * @category Package : @augment-vir/node
17
+ * @returns `undefined` if no matches are found.
18
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
19
+ */
20
+ export declare function findAncestor(currentPath: string, callback: (path: string) => boolean): string | undefined; /**
21
+ * Find an ancestor file path that matches the given `callback`. If no matches are found all the
22
+ * way up until the system root, this returns `undefined`.
23
+ *
24
+ * @category Path : Node
25
+ * @category Package : @augment-vir/node
26
+ * @returns `undefined` if no matches are found.
27
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
28
+ */
4
29
  export declare function findAncestor(currentPath: string, callback: (path: string) => MaybePromise<boolean>): MaybePromise<string | undefined>;
5
30
  /**
6
31
  * Join a list of paths to the given `parentDirPath`. This is particularly useful for getting full
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Determines the path that will be imported into the given `startingPoint` from the given
3
+ * `importPath`. Resolves symlinks, tries to map `.js` extensions to `.ts` extensions when needed,
4
+ * and tries to find the best `node_modules` import for package imports.
5
+ *
6
+ * @category Path : Node
7
+ * @category Package : @augment-vir/node
8
+ * @returns `undefined` if no matches are found.
9
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
10
+ */
11
+ export declare function resolveImportPath(importerFilePath: string, importPath: string): string | undefined;
@@ -0,0 +1,78 @@
1
+ import { check } from '@augment-vir/assert';
2
+ import { filterMap, getObjectTypedEntries } from '@augment-vir/common';
3
+ import { existsSync, realpathSync } from 'node:fs';
4
+ import { dirname, join, resolve, sep } from 'node:path';
5
+ import { readTsconfig } from '../typescript/read-tsconfig.js';
6
+ import { findAncestor } from './ancestor.js';
7
+ import { replaceWithWindowsPathIfNeeded } from './os-path.js';
8
+ /**
9
+ * Determines the path that will be imported into the given `startingPoint` from the given
10
+ * `importPath`. Resolves symlinks, tries to map `.js` extensions to `.ts` extensions when needed,
11
+ * and tries to find the best `node_modules` import for package imports.
12
+ *
13
+ * @category Path : Node
14
+ * @category Package : @augment-vir/node
15
+ * @returns `undefined` if no matches are found.
16
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
17
+ */
18
+ export function resolveImportPath(importerFilePath, importPath) {
19
+ const foundTsconfig = readTsconfig(importerFilePath);
20
+ const mappedImportPath = foundTsconfig
21
+ ? mapImportPath(importPath, foundTsconfig.tsconfig, foundTsconfig.path)
22
+ : importPath;
23
+ if (mappedImportPath.startsWith(sep) || mappedImportPath.startsWith('/')) {
24
+ /** Absolute import */
25
+ return mapFilePath(replaceWithWindowsPathIfNeeded(mappedImportPath));
26
+ }
27
+ else if (mappedImportPath.startsWith('.')) {
28
+ /** Relative import */
29
+ return mapFilePath(resolve(dirname(importerFilePath), replaceWithWindowsPathIfNeeded(mappedImportPath)));
30
+ }
31
+ else {
32
+ /** Package import */
33
+ const isOrgPackage = mappedImportPath.startsWith('@');
34
+ const importPathParts = mappedImportPath.split('/');
35
+ const packagePath = isOrgPackage
36
+ ? join(...importPathParts.slice(0, 2))
37
+ : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
38
+ importPathParts[0];
39
+ const relevantNodeModulesParent = findAncestor(importerFilePath, (ancestorPath) => {
40
+ const isMatch = existsSync(join(ancestorPath, 'node_modules', packagePath));
41
+ return isMatch;
42
+ });
43
+ if (!relevantNodeModulesParent) {
44
+ return undefined;
45
+ }
46
+ return mapFilePath(join(relevantNodeModulesParent, 'node_modules', replaceWithWindowsPathIfNeeded(mappedImportPath)));
47
+ }
48
+ }
49
+ function mapFilePath(path) {
50
+ if (existsSync(path)) {
51
+ return realpathSync(path);
52
+ }
53
+ const tsPath = path.replace(/\.js$/, '.ts');
54
+ if (path.endsWith('.js') && existsSync(tsPath)) {
55
+ return realpathSync(tsPath);
56
+ }
57
+ return path;
58
+ }
59
+ function mapImportPath(importPath, tsconfig, tsconfigPath) {
60
+ const paths = tsconfig.options.paths;
61
+ if (!paths) {
62
+ return importPath;
63
+ }
64
+ const mappedPaths = filterMap(getObjectTypedEntries(paths), ([alias, paths]) => {
65
+ const aliasRegex = new RegExp('^' + String(alias).replace(/\*/g, '(.*)') + '$');
66
+ const match = importPath.match(aliasRegex);
67
+ if (!match) {
68
+ return undefined;
69
+ }
70
+ /** An empty paths is invalid anyway. */
71
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
72
+ return paths[0].replace(/\*/g, match[1]);
73
+ }, check.isTruthy);
74
+ if (!mappedPaths[0]) {
75
+ return importPath;
76
+ }
77
+ return resolve(tsconfig.options.baseUrl || dirname(tsconfigPath), mappedPaths[0]);
78
+ }
@@ -12,17 +12,17 @@ export type { PrismaMigrationStatus } from '../prisma/prisma-migrations.js';
12
12
  *
13
13
  * - Deploy to production
14
14
  *
15
- * - {@link prisma.migration.applyProd}
15
+ * - `prisma.migration.applyProd()`
16
16
  * - Update dev environment
17
17
  *
18
- * - Apply migrations: {@link prisma.migration.applyDev}
18
+ * - Apply migrations: `prisma.migration.applyDev`
19
19
  *
20
20
  * - If throws {@link PrismaMigrationNeededError}, prompt user for a new migration name and pass it to
21
- * {@link prisma.migration.create}
22
- * - If throws {@link PrismaResetNeededError}, reset the database with {@link prisma.database.resetDev}
21
+ * `prisma.migration.create`
22
+ * - If throws {@link PrismaResetNeededError}, reset the database with `prisma.database.resetDev`
23
23
  * - Generate client: `prisma.client.isCurrent`
24
24
  *
25
- * - If `false`, run {@link prisma.client.generate}
25
+ * - If `false`, run `prisma.client.generate`
26
26
  *
27
27
  * @category Prisma : Node
28
28
  * @category Package : @augment-vir/node
@@ -50,7 +50,7 @@ export declare const prisma: {
50
50
  applyProd: typeof applyPrismaMigrationsToProd;
51
51
  /**
52
52
  * Apply all migrations. Meant for a development environment, with less protections than
53
- * {@link prisma.migration.applyProd}.
53
+ * `prisma.migration.applyProd()`
54
54
  *
55
55
  * @throws `PrismaMigrationNeededError` when a new migration is required so the user needs
56
56
  * to input a name.
@@ -70,8 +70,8 @@ export declare const prisma: {
70
70
  */
71
71
  resetDev: typeof resetDevPrismaDatabase;
72
72
  /**
73
- * Uses {@link prisma.database.diff} to detect if there are any differences between the
74
- * current database and the Prisma schema that should control it.
73
+ * Uses `prisma.database.diff` to detect if there are any differences between the current
74
+ * database and the Prisma schema that should control it.
75
75
  */
76
76
  hasDiff: typeof doesPrismaDiffExist;
77
77
  /**
@@ -102,8 +102,8 @@ export declare const prisma: {
102
102
  */
103
103
  isCurrent: typeof isGeneratedPrismaClientCurrent;
104
104
  /**
105
- * Adds a collection of create data to a database through a `PrismaClient` instance. This is
106
- * particularly useful for setting up mocks in a mock PrismaClient.
105
+ * Adds a collection of create data entries to a database through a `PrismaClient` instance.
106
+ * This is particularly useful for setting up mocks in a mock PrismaClient.
107
107
  *
108
108
  * @example
109
109
  *
@@ -10,17 +10,17 @@ export * from '../prisma/prisma-errors.js';
10
10
  *
11
11
  * - Deploy to production
12
12
  *
13
- * - {@link prisma.migration.applyProd}
13
+ * - `prisma.migration.applyProd()`
14
14
  * - Update dev environment
15
15
  *
16
- * - Apply migrations: {@link prisma.migration.applyDev}
16
+ * - Apply migrations: `prisma.migration.applyDev`
17
17
  *
18
18
  * - If throws {@link PrismaMigrationNeededError}, prompt user for a new migration name and pass it to
19
- * {@link prisma.migration.create}
20
- * - If throws {@link PrismaResetNeededError}, reset the database with {@link prisma.database.resetDev}
19
+ * `prisma.migration.create`
20
+ * - If throws {@link PrismaResetNeededError}, reset the database with `prisma.database.resetDev`
21
21
  * - Generate client: `prisma.client.isCurrent`
22
22
  *
23
- * - If `false`, run {@link prisma.client.generate}
23
+ * - If `false`, run `prisma.client.generate`
24
24
  *
25
25
  * @category Prisma : Node
26
26
  * @category Package : @augment-vir/node
@@ -48,7 +48,7 @@ export const prisma = {
48
48
  applyProd: applyPrismaMigrationsToProd,
49
49
  /**
50
50
  * Apply all migrations. Meant for a development environment, with less protections than
51
- * {@link prisma.migration.applyProd}.
51
+ * `prisma.migration.applyProd()`
52
52
  *
53
53
  * @throws `PrismaMigrationNeededError` when a new migration is required so the user needs
54
54
  * to input a name.
@@ -68,8 +68,8 @@ export const prisma = {
68
68
  */
69
69
  resetDev: resetDevPrismaDatabase,
70
70
  /**
71
- * Uses {@link prisma.database.diff} to detect if there are any differences between the
72
- * current database and the Prisma schema that should control it.
71
+ * Uses `prisma.database.diff` to detect if there are any differences between the current
72
+ * database and the Prisma schema that should control it.
73
73
  */
74
74
  hasDiff: doesPrismaDiffExist,
75
75
  /**
@@ -100,8 +100,8 @@ export const prisma = {
100
100
  */
101
101
  isCurrent: isGeneratedPrismaClientCurrent,
102
102
  /**
103
- * Adds a collection of create data to a database through a `PrismaClient` instance. This is
104
- * particularly useful for setting up mocks in a mock PrismaClient.
103
+ * Adds a collection of create data entries to a database through a `PrismaClient` instance.
104
+ * This is particularly useful for setting up mocks in a mock PrismaClient.
105
105
  *
106
106
  * @example
107
107
  *
@@ -142,7 +142,7 @@ export const prisma = {
142
142
  * ]);
143
143
  * ```
144
144
  */
145
- addData: addData,
145
+ addData,
146
146
  /**
147
147
  * Dump data from the current database through a `PrismaClient` instance.
148
148
  *
@@ -6,7 +6,9 @@
6
6
  * @category Package : @augment-vir/node
7
7
  * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
8
8
  */
9
- export declare const ExtensionToRunner: Record<string, string>;
9
+ export declare const ExtensionToRunner: Record<string, string | {
10
+ npx: string;
11
+ }>;
10
12
  /**
11
13
  * Runs a script path as if it had been run directly, as much as possible.
12
14
  *
@@ -14,7 +16,7 @@ export declare const ExtensionToRunner: Record<string, string>;
14
16
  * @category Package : @augment-vir/node
15
17
  * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
16
18
  */
17
- export declare function runCliScript(path: string,
19
+ export declare function runCliScript(scriptPath: string,
18
20
  /** This should just be `__filename` (for CJS) or `import.meta.filename` (for ESM). */
19
21
  cliScriptFilePath: string,
20
22
  /**
@@ -1,6 +1,8 @@
1
1
  /* node:coverage disable */
2
2
  /** This file cannot be tested because it calls `process.exit`. */
3
- import { extname } from 'node:path';
3
+ import { check } from '@augment-vir/assert';
4
+ import { dirname, extname } from 'node:path';
5
+ import { findNpmBinPath } from '../npm/find-bin-path.js';
4
6
  import { interpolationSafeWindowsPath } from '../path/os-path.js';
5
7
  import { extractRelevantArgs } from './relevant-args.js';
6
8
  import { runShellCommand } from './shell.js';
@@ -12,7 +14,9 @@ import { runShellCommand } from './shell.js';
12
14
  * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
13
15
  */
14
16
  export const ExtensionToRunner = {
15
- '.ts': 'tsx',
17
+ '.ts': {
18
+ npx: 'tsx',
19
+ },
16
20
  '.js': 'node',
17
21
  '.sh': 'bash',
18
22
  };
@@ -23,7 +27,7 @@ export const ExtensionToRunner = {
23
27
  * @category Package : @augment-vir/node
24
28
  * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
25
29
  */
26
- export async function runCliScript(path,
30
+ export async function runCliScript(scriptPath,
27
31
  /** This should just be `__filename` (for CJS) or `import.meta.filename` (for ESM). */
28
32
  cliScriptFilePath,
29
33
  /**
@@ -36,12 +40,18 @@ binName) {
36
40
  binName,
37
41
  fileName: cliScriptFilePath,
38
42
  });
39
- const extension = extname(path);
43
+ const extension = extname(scriptPath);
40
44
  const runner = ExtensionToRunner[extension];
41
45
  if (!runner) {
42
46
  throw new Error("No runner configured for file extension '${extension}' in '${path}'");
43
47
  }
44
- const results = await runShellCommand(interpolationSafeWindowsPath([runner, path, ...args].join(' ')), {
48
+ const runnerPath = check.isString(runner)
49
+ ? runner
50
+ : findNpmBinPath({
51
+ binName: runner.npx,
52
+ startPath: dirname(cliScriptFilePath),
53
+ }) || runner.npx;
54
+ const results = await runShellCommand(interpolationSafeWindowsPath([runnerPath, scriptPath, ...args].join(' ')), {
45
55
  hookUpToConsole: true,
46
56
  });
47
57
  process.exit(results.exitCode || 0);
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Finds the closest `tsconfig.json` file and parses it.
3
+ *
4
+ * @category Path : Node
5
+ * @category Package : @augment-vir/node
6
+ * @returns `undefined` if no tsconfig was found or if a found tsconfig fails to parse.
7
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
8
+ */
9
+ export declare function readTsconfig(startingPath: string): {
10
+ tsconfig: import("typescript").ParsedCommandLine;
11
+ path: string;
12
+ } | undefined;
@@ -0,0 +1,32 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { parseJsonConfigFileContent, readConfigFile, sys } from 'typescript';
4
+ import { findAncestor } from '../path/ancestor.js';
5
+ /**
6
+ * Finds the closest `tsconfig.json` file and parses it.
7
+ *
8
+ * @category Path : Node
9
+ * @category Package : @augment-vir/node
10
+ * @returns `undefined` if no tsconfig was found or if a found tsconfig fails to parse.
11
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
12
+ */
13
+ export function readTsconfig(startingPath) {
14
+ const tsconfigDirPath = findAncestor(startingPath, (ancestorPath) => existsSync(join(ancestorPath, 'tsconfig.json')));
15
+ const tsconfigPath = tsconfigDirPath ? join(tsconfigDirPath, 'tsconfig.json') : undefined;
16
+ if (!tsconfigPath) {
17
+ return undefined;
18
+ }
19
+ // eslint-disable-next-line @typescript-eslint/unbound-method
20
+ const { config, error } = readConfigFile(tsconfigPath, sys.readFile);
21
+ if (error) {
22
+ return undefined;
23
+ }
24
+ const parsedConfig = parseJsonConfigFileContent(config, sys, dirname(tsconfigPath));
25
+ if (parsedConfig.errors.length) {
26
+ return undefined;
27
+ }
28
+ return {
29
+ tsconfig: parsedConfig,
30
+ path: tsconfigPath,
31
+ };
32
+ }
@@ -1,7 +1,7 @@
1
1
  import type { JsonCompatibleArray, JsonCompatibleObject } from '@augment-vir/common';
2
2
  import type { DockerContainerStatus } from './container-status.js';
3
3
  /**
4
- * Properties on {@link DockerContainerInfo}.State, retrieved from {@link getContainerInfo}.
4
+ * Properties on {@link DockerContainerInfo}.State, retrieved from `docker.container.getInfo()`.
5
5
  *
6
6
  * @category Node : Docker : Util
7
7
  * @category Package : @augment-vir/node
@@ -22,8 +22,8 @@ export type DockerContainerInfoState = {
22
22
  };
23
23
  /** This type signature is incomplete. Add to it as necessary. */
24
24
  /**
25
- * Properties on the output from {@link getContainerInfo}. Not all these properties are filled in all
26
- * the way, particularly most of properties with nested objects.
25
+ * Properties on the output from `docker.container.getInfo()`. Not all these properties are filled
26
+ * in all the way, particularly most of properties with nested objects.
27
27
  *
28
28
  * @category Node : Docker : Util
29
29
  * @category Package : @augment-vir/node
package/dist/index.d.ts CHANGED
@@ -6,14 +6,17 @@ export * from './augments/fs/read-dir.js';
6
6
  export * from './augments/fs/read-file.js';
7
7
  export * from './augments/fs/symlink.js';
8
8
  export * from './augments/fs/write.js';
9
+ export * from './augments/npm/find-bin-path.js';
9
10
  export * from './augments/npm/query-workspace.js';
10
11
  export * from './augments/npm/read-package-json.js';
11
12
  export * from './augments/os/operating-system.js';
12
13
  export * from './augments/path/ancestor.js';
13
14
  export * from './augments/path/os-path.js';
15
+ export * from './augments/path/resolve-import.js';
14
16
  export * from './augments/path/root.js';
15
17
  export * from './augments/prisma.js';
16
18
  export * from './augments/terminal/question.js';
17
19
  export * from './augments/terminal/relevant-args.js';
18
20
  export * from './augments/terminal/run-cli-script.js';
19
21
  export * from './augments/terminal/shell.js';
22
+ export * from './augments/typescript/read-tsconfig.js';
package/dist/index.js CHANGED
@@ -6,14 +6,17 @@ export * from './augments/fs/read-dir.js';
6
6
  export * from './augments/fs/read-file.js';
7
7
  export * from './augments/fs/symlink.js';
8
8
  export * from './augments/fs/write.js';
9
+ export * from './augments/npm/find-bin-path.js';
9
10
  export * from './augments/npm/query-workspace.js';
10
11
  export * from './augments/npm/read-package-json.js';
11
12
  export * from './augments/os/operating-system.js';
12
13
  export * from './augments/path/ancestor.js';
13
14
  export * from './augments/path/os-path.js';
15
+ export * from './augments/path/resolve-import.js';
14
16
  export * from './augments/path/root.js';
15
17
  export * from './augments/prisma.js';
16
18
  export * from './augments/terminal/question.js';
17
19
  export * from './augments/terminal/relevant-args.js';
18
20
  export * from './augments/terminal/run-cli-script.js';
19
21
  export * from './augments/terminal/shell.js';
22
+ export * from './augments/typescript/read-tsconfig.js';
@@ -1,8 +1,8 @@
1
1
  import { BasePrismaClient, PrismaAllModelsCreate, type PartialWithUndefined, type PrismaAllBasicModels, type PrismaModelName } from '@augment-vir/common';
2
2
  import type { IsAny } from 'type-fest';
3
3
  /**
4
- * Params for {@link addData}. This is similar to {@link PrismaAllModelsCreate} but allows an array of
5
- * {@link PrismaAllModelsCreate} for sequential data creation.
4
+ * Params for `prisma.client.addData()`. This is similar to {@link PrismaAllModelsCreate} but allows
5
+ * an array of {@link PrismaAllModelsCreate} for sequential data creation.
6
6
  *
7
7
  * @category Prisma : Node
8
8
  * @category Package : @augment-vir/node
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@augment-vir/node",
3
- "version": "31.2.1",
3
+ "version": "31.4.0",
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,27 +37,31 @@
37
37
  "test:update": "npm test"
38
38
  },
39
39
  "dependencies": {
40
- "@augment-vir/assert": "^31.2.1",
41
- "@augment-vir/common": "^31.2.1",
42
- "@date-vir/duration": "^7.0.1",
40
+ "@augment-vir/assert": "^31.4.0",
41
+ "@augment-vir/common": "^31.4.0",
42
+ "@date-vir/duration": "^7.1.1",
43
43
  "ansi-styles": "^6.2.1",
44
44
  "terminate": "^2.8.0",
45
- "type-fest": "^4.29.0",
45
+ "type-fest": "^4.31.0",
46
46
  "typed-event-target": "^4.0.2"
47
47
  },
48
48
  "devDependencies": {
49
- "@augment-vir/test": "^31.2.1",
50
- "@prisma/client": "^5.22.0",
51
- "@types/node": "^22.10.0",
49
+ "@augment-vir/test": "^31.4.0",
50
+ "@prisma/client": "^6.1.0",
51
+ "@types/node": "^22.10.5",
52
52
  "@web/dev-server-esbuild": "^1.0.3",
53
53
  "@web/test-runner": "^0.19.0",
54
54
  "@web/test-runner-commands": "^0.9.0",
55
55
  "@web/test-runner-playwright": "^0.11.0",
56
56
  "@web/test-runner-visual-regression": "^0.10.0",
57
- "c8": "^10.1.2",
58
- "concurrently": "^9.1.0",
57
+ "c8": "^10.1.3",
58
+ "concurrently": "^9.1.2",
59
59
  "istanbul-smart-text-reporter": "^1.1.5",
60
- "prisma": "^5.22.0"
60
+ "prisma": "^6.1.0",
61
+ "typescript": "^5.7.2"
62
+ },
63
+ "peerDependencies": {
64
+ "typescript": "*"
61
65
  },
62
66
  "engines": {
63
67
  "node": ">=22"
@@ -0,0 +1,31 @@
1
+ import {existsSync} from 'node:fs';
2
+ import {join} from 'node:path';
3
+ import {findAncestor} from '../path/ancestor.js';
4
+
5
+ /**
6
+ * Finds the path to an npm package executable bin by searching in all ancestor `node_modules/.bin`
7
+ * folders, starting at the given `startPath`.
8
+ *
9
+ * @category Node : Npm
10
+ * @category Package : @augment-vir/node
11
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
12
+ */
13
+ export function findNpmBinPath({
14
+ binName,
15
+ startPath,
16
+ }: {
17
+ startPath: string;
18
+ binName: string;
19
+ }): string | undefined {
20
+ const binPath = join('node_modules', '.bin', binName);
21
+
22
+ const ancestor = findAncestor(startPath, (ancestor) => {
23
+ return existsSync(join(ancestor, binPath));
24
+ });
25
+
26
+ if (!ancestor) {
27
+ return undefined;
28
+ }
29
+
30
+ return join(ancestor, binPath);
31
+ }
@@ -3,14 +3,39 @@ import {type MaybePromise} from '@augment-vir/common';
3
3
  import {dirname, join} from 'node:path';
4
4
  import {systemRootPath} from './root.js';
5
5
 
6
+ /**
7
+ * Find an ancestor file path that matches the given `callback`. If no matches are found all the way
8
+ * up until the system root, this returns `undefined`.
9
+ *
10
+ * @category Path : Node
11
+ * @category Package : @augment-vir/node
12
+ * @returns `undefined` if no matches are found.
13
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
14
+ */
6
15
  export function findAncestor(
7
16
  currentPath: string,
8
17
  callback: (path: string) => Promise<boolean>,
9
- ): Promise<string | undefined>;
18
+ ): Promise<string | undefined>; /**
19
+ * Find an ancestor file path that matches the given `callback`. If no matches are found all the
20
+ * way up until the system root, this returns `undefined`.
21
+ *
22
+ * @category Path : Node
23
+ * @category Package : @augment-vir/node
24
+ * @returns `undefined` if no matches are found.
25
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
26
+ */
10
27
  export function findAncestor(
11
28
  currentPath: string,
12
29
  callback: (path: string) => boolean,
13
- ): string | undefined;
30
+ ): string | undefined; /**
31
+ * Find an ancestor file path that matches the given `callback`. If no matches are found all the
32
+ * way up until the system root, this returns `undefined`.
33
+ *
34
+ * @category Path : Node
35
+ * @category Package : @augment-vir/node
36
+ * @returns `undefined` if no matches are found.
37
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
38
+ */
14
39
  export function findAncestor(
15
40
  currentPath: string,
16
41
  callback: (path: string) => MaybePromise<boolean>,
@@ -0,0 +1,116 @@
1
+ import {check} from '@augment-vir/assert';
2
+ import {filterMap, getObjectTypedEntries} from '@augment-vir/common';
3
+ import {existsSync, realpathSync} from 'node:fs';
4
+ import {dirname, join, resolve, sep} from 'node:path';
5
+ import {ParsedCommandLine} from 'typescript';
6
+ import {readTsconfig} from '../typescript/read-tsconfig.js';
7
+ import {findAncestor} from './ancestor.js';
8
+ import {replaceWithWindowsPathIfNeeded} from './os-path.js';
9
+
10
+ /**
11
+ * Determines the path that will be imported into the given `startingPoint` from the given
12
+ * `importPath`. Resolves symlinks, tries to map `.js` extensions to `.ts` extensions when needed,
13
+ * and tries to find the best `node_modules` import for package imports.
14
+ *
15
+ * @category Path : Node
16
+ * @category Package : @augment-vir/node
17
+ * @returns `undefined` if no matches are found.
18
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
19
+ */
20
+ export function resolveImportPath(
21
+ importerFilePath: string,
22
+ importPath: string,
23
+ ): string | undefined {
24
+ const foundTsconfig = readTsconfig(importerFilePath);
25
+
26
+ const mappedImportPath = foundTsconfig
27
+ ? mapImportPath(importPath, foundTsconfig.tsconfig, foundTsconfig.path)
28
+ : importPath;
29
+
30
+ if (mappedImportPath.startsWith(sep) || mappedImportPath.startsWith('/')) {
31
+ /** Absolute import */
32
+
33
+ return mapFilePath(replaceWithWindowsPathIfNeeded(mappedImportPath));
34
+ } else if (mappedImportPath.startsWith('.')) {
35
+ /** Relative import */
36
+ return mapFilePath(
37
+ resolve(dirname(importerFilePath), replaceWithWindowsPathIfNeeded(mappedImportPath)),
38
+ );
39
+ } else {
40
+ /** Package import */
41
+
42
+ const isOrgPackage = mappedImportPath.startsWith('@');
43
+ const importPathParts = mappedImportPath.split('/');
44
+
45
+ const packagePath: string = isOrgPackage
46
+ ? join(...importPathParts.slice(0, 2))
47
+ : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
48
+ importPathParts[0]!;
49
+
50
+ const relevantNodeModulesParent = findAncestor(importerFilePath, (ancestorPath) => {
51
+ const isMatch = existsSync(join(ancestorPath, 'node_modules', packagePath));
52
+
53
+ return isMatch;
54
+ });
55
+
56
+ if (!relevantNodeModulesParent) {
57
+ return undefined;
58
+ }
59
+
60
+ return mapFilePath(
61
+ join(
62
+ relevantNodeModulesParent,
63
+ 'node_modules',
64
+ replaceWithWindowsPathIfNeeded(mappedImportPath),
65
+ ),
66
+ );
67
+ }
68
+ }
69
+
70
+ function mapFilePath(path: string): string {
71
+ if (existsSync(path)) {
72
+ return realpathSync(path);
73
+ }
74
+
75
+ const tsPath = path.replace(/\.js$/, '.ts');
76
+
77
+ if (path.endsWith('.js') && existsSync(tsPath)) {
78
+ return realpathSync(tsPath);
79
+ }
80
+
81
+ return path;
82
+ }
83
+
84
+ function mapImportPath(
85
+ importPath: string,
86
+ tsconfig: ParsedCommandLine,
87
+ tsconfigPath: string,
88
+ ): string {
89
+ const paths = tsconfig.options.paths;
90
+ if (!paths) {
91
+ return importPath;
92
+ }
93
+
94
+ const mappedPaths = filterMap(
95
+ getObjectTypedEntries(paths),
96
+ ([alias, paths]) => {
97
+ const aliasRegex = new RegExp('^' + String(alias).replace(/\*/g, '(.*)') + '$');
98
+ const match = importPath.match(aliasRegex);
99
+
100
+ if (!match) {
101
+ return undefined;
102
+ }
103
+
104
+ /** An empty paths is invalid anyway. */
105
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
106
+ return paths[0]!.replace(/\*/g, match[1]!);
107
+ },
108
+ check.isTruthy,
109
+ );
110
+
111
+ if (!mappedPaths[0]) {
112
+ return importPath;
113
+ }
114
+
115
+ return resolve(tsconfig.options.baseUrl || dirname(tsconfigPath), mappedPaths[0]);
116
+ }
@@ -29,17 +29,17 @@ export type {PrismaMigrationStatus} from '../prisma/prisma-migrations.js';
29
29
  *
30
30
  * - Deploy to production
31
31
  *
32
- * - {@link prisma.migration.applyProd}
32
+ * - `prisma.migration.applyProd()`
33
33
  * - Update dev environment
34
34
  *
35
- * - Apply migrations: {@link prisma.migration.applyDev}
35
+ * - Apply migrations: `prisma.migration.applyDev`
36
36
  *
37
37
  * - If throws {@link PrismaMigrationNeededError}, prompt user for a new migration name and pass it to
38
- * {@link prisma.migration.create}
39
- * - If throws {@link PrismaResetNeededError}, reset the database with {@link prisma.database.resetDev}
38
+ * `prisma.migration.create`
39
+ * - If throws {@link PrismaResetNeededError}, reset the database with `prisma.database.resetDev`
40
40
  * - Generate client: `prisma.client.isCurrent`
41
41
  *
42
- * - If `false`, run {@link prisma.client.generate}
42
+ * - If `false`, run `prisma.client.generate`
43
43
  *
44
44
  * @category Prisma : Node
45
45
  * @category Package : @augment-vir/node
@@ -67,7 +67,7 @@ export const prisma = {
67
67
  applyProd: applyPrismaMigrationsToProd,
68
68
  /**
69
69
  * Apply all migrations. Meant for a development environment, with less protections than
70
- * {@link prisma.migration.applyProd}.
70
+ * `prisma.migration.applyProd()`
71
71
  *
72
72
  * @throws `PrismaMigrationNeededError` when a new migration is required so the user needs
73
73
  * to input a name.
@@ -87,8 +87,8 @@ export const prisma = {
87
87
  */
88
88
  resetDev: resetDevPrismaDatabase,
89
89
  /**
90
- * Uses {@link prisma.database.diff} to detect if there are any differences between the
91
- * current database and the Prisma schema that should control it.
90
+ * Uses `prisma.database.diff` to detect if there are any differences between the current
91
+ * database and the Prisma schema that should control it.
92
92
  */
93
93
  hasDiff: doesPrismaDiffExist,
94
94
  /**
@@ -119,8 +119,8 @@ export const prisma = {
119
119
  */
120
120
  isCurrent: isGeneratedPrismaClientCurrent,
121
121
  /**
122
- * Adds a collection of create data to a database through a `PrismaClient` instance. This is
123
- * particularly useful for setting up mocks in a mock PrismaClient.
122
+ * Adds a collection of create data entries to a database through a `PrismaClient` instance.
123
+ * This is particularly useful for setting up mocks in a mock PrismaClient.
124
124
  *
125
125
  * @example
126
126
  *
@@ -161,7 +161,7 @@ export const prisma = {
161
161
  * ]);
162
162
  * ```
163
163
  */
164
- addData: addData,
164
+ addData,
165
165
  /**
166
166
  * Dump data from the current database through a `PrismaClient` instance.
167
167
  *
@@ -1,7 +1,9 @@
1
1
  /* node:coverage disable */
2
2
  /** This file cannot be tested because it calls `process.exit`. */
3
3
 
4
- import {extname} from 'node:path';
4
+ import {check} from '@augment-vir/assert';
5
+ import {dirname, extname} from 'node:path';
6
+ import {findNpmBinPath} from '../npm/find-bin-path.js';
5
7
  import {interpolationSafeWindowsPath} from '../path/os-path.js';
6
8
  import {extractRelevantArgs} from './relevant-args.js';
7
9
  import {runShellCommand} from './shell.js';
@@ -13,8 +15,10 @@ import {runShellCommand} from './shell.js';
13
15
  * @category Package : @augment-vir/node
14
16
  * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
15
17
  */
16
- export const ExtensionToRunner: Record<string, string> = {
17
- '.ts': 'tsx',
18
+ export const ExtensionToRunner: Record<string, string | {npx: string}> = {
19
+ '.ts': {
20
+ npx: 'tsx',
21
+ },
18
22
  '.js': 'node',
19
23
  '.sh': 'bash',
20
24
  };
@@ -27,7 +31,7 @@ export const ExtensionToRunner: Record<string, string> = {
27
31
  * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
28
32
  */
29
33
  export async function runCliScript(
30
- path: string,
34
+ scriptPath: string,
31
35
  /** This should just be `__filename` (for CJS) or `import.meta.filename` (for ESM). */
32
36
  cliScriptFilePath: string,
33
37
  /**
@@ -42,7 +46,7 @@ export async function runCliScript(
42
46
  fileName: cliScriptFilePath,
43
47
  });
44
48
 
45
- const extension = extname(path);
49
+ const extension = extname(scriptPath);
46
50
 
47
51
  const runner = ExtensionToRunner[extension];
48
52
 
@@ -50,8 +54,15 @@ export async function runCliScript(
50
54
  throw new Error("No runner configured for file extension '${extension}' in '${path}'");
51
55
  }
52
56
 
57
+ const runnerPath = check.isString(runner)
58
+ ? runner
59
+ : findNpmBinPath({
60
+ binName: runner.npx,
61
+ startPath: dirname(cliScriptFilePath),
62
+ }) || runner.npx;
63
+
53
64
  const results = await runShellCommand(
54
- interpolationSafeWindowsPath([runner, path, ...args].join(' ')),
65
+ interpolationSafeWindowsPath([runnerPath, scriptPath, ...args].join(' ')),
55
66
  {
56
67
  hookUpToConsole: true,
57
68
  },
@@ -0,0 +1,41 @@
1
+ import {existsSync} from 'node:fs';
2
+ import {dirname, join} from 'node:path';
3
+ import {parseJsonConfigFileContent, readConfigFile, sys} from 'typescript';
4
+ import {findAncestor} from '../path/ancestor.js';
5
+
6
+ /**
7
+ * Finds the closest `tsconfig.json` file and parses it.
8
+ *
9
+ * @category Path : Node
10
+ * @category Package : @augment-vir/node
11
+ * @returns `undefined` if no tsconfig was found or if a found tsconfig fails to parse.
12
+ * @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
13
+ */
14
+ export function readTsconfig(startingPath: string) {
15
+ const tsconfigDirPath = findAncestor(startingPath, (ancestorPath) =>
16
+ existsSync(join(ancestorPath, 'tsconfig.json')),
17
+ );
18
+ const tsconfigPath = tsconfigDirPath ? join(tsconfigDirPath, 'tsconfig.json') : undefined;
19
+
20
+ if (!tsconfigPath) {
21
+ return undefined;
22
+ }
23
+
24
+ // eslint-disable-next-line @typescript-eslint/unbound-method
25
+ const {config, error} = readConfigFile(tsconfigPath, sys.readFile);
26
+
27
+ if (error) {
28
+ return undefined;
29
+ }
30
+
31
+ const parsedConfig = parseJsonConfigFileContent(config, sys, dirname(tsconfigPath));
32
+
33
+ if (parsedConfig.errors.length) {
34
+ return undefined;
35
+ }
36
+
37
+ return {
38
+ tsconfig: parsedConfig,
39
+ path: tsconfigPath,
40
+ };
41
+ }
@@ -3,7 +3,7 @@ import {runShellCommand} from '../../augments/terminal/shell.js';
3
3
  import type {DockerContainerStatus} from './container-status.js';
4
4
 
5
5
  /**
6
- * Properties on {@link DockerContainerInfo}.State, retrieved from {@link getContainerInfo}.
6
+ * Properties on {@link DockerContainerInfo}.State, retrieved from `docker.container.getInfo()`.
7
7
  *
8
8
  * @category Node : Docker : Util
9
9
  * @category Package : @augment-vir/node
@@ -26,8 +26,8 @@ export type DockerContainerInfoState = {
26
26
  /** This type signature is incomplete. Add to it as necessary. */
27
27
 
28
28
  /**
29
- * Properties on the output from {@link getContainerInfo}. Not all these properties are filled in all
30
- * the way, particularly most of properties with nested objects.
29
+ * Properties on the output from `docker.container.getInfo()`. Not all these properties are filled
30
+ * in all the way, particularly most of properties with nested objects.
31
31
  *
32
32
  * @category Node : Docker : Util
33
33
  * @category Package : @augment-vir/node
package/src/index.ts CHANGED
@@ -6,14 +6,17 @@ export * from './augments/fs/read-dir.js';
6
6
  export * from './augments/fs/read-file.js';
7
7
  export * from './augments/fs/symlink.js';
8
8
  export * from './augments/fs/write.js';
9
+ export * from './augments/npm/find-bin-path.js';
9
10
  export * from './augments/npm/query-workspace.js';
10
11
  export * from './augments/npm/read-package-json.js';
11
12
  export * from './augments/os/operating-system.js';
12
13
  export * from './augments/path/ancestor.js';
13
14
  export * from './augments/path/os-path.js';
15
+ export * from './augments/path/resolve-import.js';
14
16
  export * from './augments/path/root.js';
15
17
  export * from './augments/prisma.js';
16
18
  export * from './augments/terminal/question.js';
17
19
  export * from './augments/terminal/relevant-args.js';
18
20
  export * from './augments/terminal/run-cli-script.js';
19
21
  export * from './augments/terminal/shell.js';
22
+ export * from './augments/typescript/read-tsconfig.js';
@@ -20,8 +20,8 @@ import {
20
20
  import type {IsAny} from 'type-fest';
21
21
 
22
22
  /**
23
- * Params for {@link addData}. This is similar to {@link PrismaAllModelsCreate} but allows an array of
24
- * {@link PrismaAllModelsCreate} for sequential data creation.
23
+ * Params for `prisma.client.addData()`. This is similar to {@link PrismaAllModelsCreate} but allows
24
+ * an array of {@link PrismaAllModelsCreate} for sequential data creation.
25
25
  *
26
26
  * @category Prisma : Node
27
27
  * @category Package : @augment-vir/node