@williamthorsen/nmr 0.23.0 → 0.25.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/.readyup/kits/default.js +479 -0
- package/.readyup/manifest.json +16 -0
- package/AGENTS.md +3 -1
- package/README.md +343 -114
- package/dist/esm/check-cache.d.ts +76 -0
- package/dist/esm/check-cache.js +224 -0
- package/dist/esm/cli-build.js +8 -2
- package/dist/esm/commands/build-output.d.ts +13 -0
- package/dist/esm/commands/build-output.js +36 -0
- package/dist/esm/commands/build.d.ts +1 -6
- package/dist/esm/commands/build.js +27 -42
- package/dist/esm/commands/clean.js +10 -3
- package/dist/esm/config.d.ts +5 -7
- package/dist/esm/config.js +115 -15
- package/dist/esm/context.d.ts +1 -1
- package/dist/esm/context.js +2 -2
- package/dist/esm/default-scripts.js +14 -9
- package/dist/esm/defineConfig.d.ts +3 -0
- package/dist/esm/defineConfig.js +3 -0
- package/dist/esm/help.d.ts +1 -1
- package/dist/esm/help.js +1 -0
- package/dist/esm/helpers/duration.d.ts +2 -0
- package/dist/esm/helpers/duration.js +21 -0
- package/dist/esm/helpers/type-guards.d.ts +1 -0
- package/dist/esm/helpers/type-guards.js +3 -0
- package/dist/esm/resolver.d.ts +1 -1
- package/dist/esm/runCli.js +229 -27
- package/dist/esm/tiers.d.ts +7 -0
- package/dist/esm/tiers.js +35 -0
- package/dist/esm/types.d.ts +15 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/vitest.d.ts +6 -2
- package/dist/esm/vitest.js +51 -31
- package/package.json +9 -7
- package/dist/esm/index.d.ts +0 -2
- package/dist/esm/index.js +0 -1
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { readdirSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
const TEST_DIR = '__tests__';
|
|
4
|
+
const TEST_EXTENSIONS = '{ts,tsx}';
|
|
5
|
+
const TEST_GLOB_PREFIX = `**/${TEST_DIR}/**`;
|
|
6
|
+
const TEST_FILE_PATTERN = /\.test\.tsx?$/;
|
|
7
|
+
export const ALL_TEST_PATTERNS = [`${TEST_GLOB_PREFIX}/*.test.${TEST_EXTENSIONS}`];
|
|
8
|
+
export function buildTierPatterns(tier) {
|
|
9
|
+
return [`${TEST_GLOB_PREFIX}/*.${tier}.test.${TEST_EXTENSIONS}`];
|
|
10
|
+
}
|
|
11
|
+
export function findTestFiles(rootDir) {
|
|
12
|
+
const found = [];
|
|
13
|
+
collectTestFiles(rootDir, '', false, found);
|
|
14
|
+
return found.toSorted();
|
|
15
|
+
}
|
|
16
|
+
export function hasTierInfix(filePath) {
|
|
17
|
+
const tiers = TIER_NAMES;
|
|
18
|
+
return tiers.includes(path.basename(filePath).split('.').at(-3) ?? '');
|
|
19
|
+
}
|
|
20
|
+
export const TEST_COLLECTION_EXCLUDE = ['.git', 'coverage', 'dist', 'node_modules'];
|
|
21
|
+
export const TIER_NAMES = ['unit', 'tool', 'localhost', 'remote'];
|
|
22
|
+
function collectTestFiles(dir, relativeDir, inTestDir, found) {
|
|
23
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
const relativePath = relativeDir === '' ? entry.name : `${relativeDir}/${entry.name}`;
|
|
26
|
+
if (entry.isDirectory()) {
|
|
27
|
+
if (TEST_COLLECTION_EXCLUDE.includes(entry.name))
|
|
28
|
+
continue;
|
|
29
|
+
collectTestFiles(path.join(dir, entry.name), relativePath, inTestDir || entry.name === TEST_DIR, found);
|
|
30
|
+
}
|
|
31
|
+
else if (inTestDir && TEST_FILE_PATTERN.test(entry.name)) {
|
|
32
|
+
found.push(relativePath);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface BuildConfig {
|
|
2
|
+
extraIgnorePatterns?: string[];
|
|
3
|
+
}
|
|
4
|
+
export interface CheckCacheConfig {
|
|
5
|
+
enabled?: boolean;
|
|
6
|
+
excludeCommands?: string[];
|
|
7
|
+
extraCommands?: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface NmrConfig {
|
|
10
|
+
build?: BuildConfig;
|
|
11
|
+
checkCache?: CheckCacheConfig;
|
|
12
|
+
devBin?: Record<string, string>;
|
|
13
|
+
workspaceScripts?: Record<string, string | string[]>;
|
|
14
|
+
rootScripts?: Record<string, string | string[]>;
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/esm/vitest.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ViteUserConfig } from 'vitest/config';
|
|
2
2
|
import type { InlineConfig, ProjectConfig } from 'vitest/node';
|
|
3
|
+
import type { TierName } from './tiers.js';
|
|
4
|
+
export type { TierName } from './tiers.js';
|
|
3
5
|
export type RootTestOptions = Omit<InlineConfig, keyof ProjectConfig>;
|
|
4
6
|
export type RootOverrides = Omit<ViteUserConfig, 'test'> & {
|
|
5
7
|
test?: RootTestOptions;
|
|
@@ -7,9 +9,11 @@ export type RootOverrides = Omit<ViteUserConfig, 'test'> & {
|
|
|
7
9
|
export interface VitestConfigOptions {
|
|
8
10
|
root?: RootOverrides;
|
|
9
11
|
project?: ProjectConfig;
|
|
12
|
+
tiers?: Partial<Record<TierName, ProjectConfig>>;
|
|
10
13
|
}
|
|
11
14
|
export interface RootVitestConfigOptions extends VitestConfigOptions {
|
|
12
15
|
monorepoRoot: string;
|
|
13
16
|
}
|
|
14
|
-
|
|
15
|
-
export declare function
|
|
17
|
+
type RootConfigLayers = [...(VitestConfigOptions | undefined)[], RootVitestConfigOptions];
|
|
18
|
+
export declare function defineVitestConfig(...layers: (VitestConfigOptions | undefined)[]): ViteUserConfig;
|
|
19
|
+
export declare function defineRootVitestConfig(...layers: RootConfigLayers): ViteUserConfig;
|
package/dist/esm/vitest.js
CHANGED
|
@@ -1,37 +1,33 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { defaultExclude, mergeConfig } from 'vitest/config';
|
|
3
|
+
import { isObject } from "./helpers/type-guards.js";
|
|
4
|
+
import { ALL_TEST_PATTERNS, buildTierPatterns, TEST_COLLECTION_EXCLUDE, TIER_NAMES } from "./tiers.js";
|
|
3
5
|
import { getWorkspacePackageDirs } from "./workspace.js";
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const COVERAGE_EXCLUDE = [
|
|
8
|
-
|
|
9
|
-
'**/index.ts',
|
|
10
|
-
'**/mock*.{ts,tsx}',
|
|
11
|
-
'**/*.d.ts',
|
|
12
|
-
'**/*.types.ts',
|
|
13
|
-
];
|
|
14
|
-
const BUILD_OUTPUT_EXCLUDE = ['**/dist/**'];
|
|
6
|
+
const [RESIDUAL_TIER, ...NAMED_TIERS] = TIER_NAMES;
|
|
7
|
+
const TIERED_PATTERNS = NAMED_TIERS.flatMap(buildTierPatterns);
|
|
8
|
+
const TIER_TIMEOUT = 30_000;
|
|
9
|
+
const COVERAGE_EXCLUDE = ['**/__{fixtures,mocks,tests}__/**', '**/index.ts', '**/*.d.ts'];
|
|
10
|
+
const COLLECTION_EXCLUDE = [...new Set([...defaultExclude, ...TEST_COLLECTION_EXCLUDE.map((dir) => `**/${dir}/**`)])];
|
|
15
11
|
const PACKAGE_COVERAGE_INCLUDE = ['**/src/**/*.{ts,tsx}'];
|
|
16
|
-
const MISSING_MONOREPO_ROOT = 'defineRootVitestConfig requires `monorepoRoot`, an absolute path to the directory holding pnpm-workspace.yaml. Pass `import.meta.dirname` from the root config.';
|
|
17
|
-
export function defineVitestConfig(
|
|
18
|
-
return buildConfig(
|
|
12
|
+
const MISSING_MONOREPO_ROOT = 'defineRootVitestConfig requires `monorepoRoot`, an absolute path to the directory holding pnpm-workspace.yaml. Pass `import.meta.dirname` in the last options layer, from the root config.';
|
|
13
|
+
export function defineVitestConfig(...layers) {
|
|
14
|
+
return buildConfig(layers, { coverageInclude: PACKAGE_COVERAGE_INCLUDE });
|
|
19
15
|
}
|
|
20
|
-
export function defineRootVitestConfig(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
const monorepoRoot = options.monorepoRoot;
|
|
16
|
+
export function defineRootVitestConfig(...layers) {
|
|
17
|
+
const lastLayer = layers.at(-1);
|
|
18
|
+
const monorepoRoot = isObject(lastLayer) ? lastLayer.monorepoRoot : undefined;
|
|
25
19
|
if (typeof monorepoRoot !== 'string' || !path.isAbsolute(monorepoRoot)) {
|
|
26
20
|
throw new TypeError(MISSING_MONOREPO_ROOT);
|
|
27
21
|
}
|
|
28
|
-
return buildConfig(
|
|
22
|
+
return buildConfig(layers, {
|
|
29
23
|
coverageInclude: [],
|
|
30
24
|
projectExclude: getWorkspaceExcludePatterns(monorepoRoot),
|
|
31
25
|
projectRoot: monorepoRoot,
|
|
32
26
|
});
|
|
33
27
|
}
|
|
34
|
-
function buildConfig(
|
|
28
|
+
function buildConfig(declaredLayers, { coverageInclude, projectExclude = [], projectRoot }) {
|
|
29
|
+
const layers = declaredLayers.filter((layer) => layer !== undefined);
|
|
30
|
+
assertKnownTiers(layers);
|
|
35
31
|
const config = {
|
|
36
32
|
test: {
|
|
37
33
|
coverage: {
|
|
@@ -41,28 +37,52 @@ function buildConfig(options, { coverageInclude, projectExclude = [], projectRoo
|
|
|
41
37
|
provider: 'v8',
|
|
42
38
|
},
|
|
43
39
|
passWithNoTests: true,
|
|
44
|
-
projects: buildProjects(
|
|
40
|
+
projects: buildProjects(layers, projectExclude, projectRoot),
|
|
45
41
|
silent: 'passed-only',
|
|
46
42
|
watch: false,
|
|
47
43
|
},
|
|
48
44
|
};
|
|
49
|
-
return
|
|
45
|
+
return layers.reduce((merged, { root }) => (root ? mergeConfig(merged, root) : merged), config);
|
|
50
46
|
}
|
|
51
|
-
function buildProjects(
|
|
52
|
-
const
|
|
53
|
-
{ exclude:
|
|
54
|
-
|
|
55
|
-
|
|
47
|
+
function buildProjects(layers, extraExclude, projectRoot) {
|
|
48
|
+
const projectTiers = [
|
|
49
|
+
{ exclude: TIERED_PATTERNS, include: ALL_TEST_PATTERNS, name: RESIDUAL_TIER },
|
|
50
|
+
...NAMED_TIERS.map((tier) => ({
|
|
51
|
+
exclude: [],
|
|
52
|
+
include: buildTierPatterns(tier),
|
|
53
|
+
name: tier,
|
|
54
|
+
timeout: TIER_TIMEOUT,
|
|
55
|
+
})),
|
|
56
56
|
];
|
|
57
|
-
return
|
|
57
|
+
return projectTiers.map(({ exclude, include, name, timeout }) => {
|
|
58
58
|
const project = {
|
|
59
59
|
extends: true,
|
|
60
60
|
...(projectRoot !== undefined && { root: projectRoot }),
|
|
61
|
-
test: {
|
|
61
|
+
test: {
|
|
62
|
+
exclude: [...COLLECTION_EXCLUDE, ...exclude, ...extraExclude],
|
|
63
|
+
include,
|
|
64
|
+
name,
|
|
65
|
+
...(timeout !== undefined && { hookTimeout: timeout, testTimeout: timeout }),
|
|
66
|
+
},
|
|
62
67
|
};
|
|
63
|
-
return
|
|
68
|
+
return layers.reduce((merged, layer) => applyLayer(merged, layer, name), project);
|
|
64
69
|
});
|
|
65
70
|
}
|
|
71
|
+
function applyLayer(project, layer, tier) {
|
|
72
|
+
const withProject = layer.project ? mergeConfig(project, { test: layer.project }) : project;
|
|
73
|
+
const targeted = layer.tiers?.[tier];
|
|
74
|
+
return targeted ? mergeConfig(withProject, { test: targeted }) : withProject;
|
|
75
|
+
}
|
|
76
|
+
function assertKnownTiers(layers) {
|
|
77
|
+
const known = TIER_NAMES;
|
|
78
|
+
for (const { tiers } of layers) {
|
|
79
|
+
for (const name of Object.keys(tiers ?? {})) {
|
|
80
|
+
if (!known.includes(name)) {
|
|
81
|
+
throw new TypeError(`Unknown tier "${name}" in \`tiers\`. Valid tiers: ${TIER_NAMES.join(', ')}.`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
66
86
|
function getWorkspaceExcludePatterns(monorepoRoot) {
|
|
67
87
|
return getWorkspacePackageDirs(monorepoRoot)
|
|
68
88
|
.map((dir) => `${path.relative(monorepoRoot, dir).split(path.sep).join('/')}/**`)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@williamthorsen/nmr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Context-aware script runner for pnpm monorepos",
|
|
6
6
|
"keywords": [
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
"author": "William Thorsen <william@thorsen.dev> (https://github.com/williamthorsen)",
|
|
23
23
|
"type": "module",
|
|
24
24
|
"exports": {
|
|
25
|
-
"
|
|
26
|
-
"types": "./dist/esm/
|
|
27
|
-
"default": "./dist/esm/
|
|
25
|
+
"./config": {
|
|
26
|
+
"types": "./dist/esm/defineConfig.d.ts",
|
|
27
|
+
"default": "./dist/esm/defineConfig.js"
|
|
28
28
|
},
|
|
29
29
|
"./prettier": {
|
|
30
30
|
"types": "./dist/esm/prettier.d.ts",
|
|
@@ -58,20 +58,22 @@
|
|
|
58
58
|
"nmr-taze": "bin/nmr-taze.js"
|
|
59
59
|
},
|
|
60
60
|
"files": [
|
|
61
|
+
".readyup/kits/*.js",
|
|
62
|
+
".readyup/manifest.json",
|
|
61
63
|
"AGENTS.md",
|
|
62
64
|
"bin",
|
|
63
65
|
"dist"
|
|
64
66
|
],
|
|
65
67
|
"dependencies": {
|
|
66
68
|
"glob": "13.0.6",
|
|
67
|
-
"jiti": "2.7.0",
|
|
68
69
|
"prettier-plugin-sh": "0.19.0",
|
|
69
70
|
"taze": "19.16.0",
|
|
70
71
|
"yaml": "2.9.0",
|
|
71
|
-
"@williamthorsen/nmr-core": "0.
|
|
72
|
+
"@williamthorsen/nmr-core": "0.9.0"
|
|
72
73
|
},
|
|
73
74
|
"devDependencies": {
|
|
74
75
|
"prettier": "3.9.6",
|
|
76
|
+
"readyup": "0.23.0",
|
|
75
77
|
"tinyglobby": "0.2.17",
|
|
76
78
|
"vitest": "4.1.10"
|
|
77
79
|
},
|
|
@@ -95,6 +97,6 @@
|
|
|
95
97
|
"access": "public"
|
|
96
98
|
},
|
|
97
99
|
"scripts": {
|
|
98
|
-
"build:post": "nmr-sync-agent-files"
|
|
100
|
+
"build:post": "nmr-sync-agent-files && rdy compile"
|
|
99
101
|
}
|
|
100
102
|
}
|
package/dist/esm/index.d.ts
DELETED
package/dist/esm/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { defineConfig } from "./config.js";
|