@naturalcycles/dev-lib 20.64.0 → 20.65.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.
@@ -15,30 +15,43 @@ import type { InlineConfig } from 'vitest/node'
15
15
  */
16
16
  export function defineVitestConfig(config?: Partial<ViteUserConfig>, cwd?: string): ViteUserConfig
17
17
 
18
+ /**
19
+ * For the ROOT vitest.config.ts of a monorepo (Vitest `projects` mode):
20
+ *
21
+ * export default defineVitestMonorepoConfig({
22
+ * test: {
23
+ * projects: ['./packages/*'],
24
+ * // overrides here, e.g:
25
+ * // coverage: { enabled: false },
26
+ * },
27
+ * })
28
+ *
29
+ * Sets the root-only options (reporters, outputFile, coverage, silent, watch,
30
+ * slowTestThreshold, sequence.sequencer), which Vitest reads from the root config only and
31
+ * ignores on the per-project configs (defineVitestConfig in each package).
32
+ */
33
+ export function defineVitestMonorepoConfig(config: Partial<ViteUserConfig>): ViteUserConfig
34
+
18
35
  /**
19
36
  * Pass `import.meta.dirname` as cwd if running from a monorepo.
20
37
  */
21
38
  export function getSharedConfig(cwd?: string): InlineConfig
22
39
 
23
40
  /**
24
- * Reporters for the monorepo root config (Vitest `projects` mode), where
25
- * `reporters` is a root-only option. Returns the compact 'agent' reporter for
26
- * agents, otherwise the default reporters plus a monorepo-wide SummaryReporter.
41
+ * @deprecated Use defineVitestMonorepoConfig for the monorepo root config instead. It sets
42
+ * `reporters` together with all the other root-only options.
27
43
  */
28
44
  export function getRootReporters(): InlineConfig['reporters']
29
45
 
30
46
  /**
31
- * `outputFile` for the junit/json reporters at the monorepo root config (Vitest
32
- * `projects` mode), where `outputFile` is a root-only option. Pair it with
33
- * getRootReporters so the junit/json report is written in the aggregate root run.
47
+ * @deprecated Use defineVitestMonorepoConfig for the monorepo root config instead. It sets
48
+ * `outputFile` together with all the other root-only options.
34
49
  */
35
50
  export function getRootOutputFile(): InlineConfig['outputFile']
36
51
 
37
52
  /**
38
- * `coverage` for the monorepo root config (Vitest `projects` mode), where
39
- * `coverage` is a root-only option. Without it on the root config no coverage
40
- * report is produced when running via `projects`. Produces a single unified
41
- * report spanning all projects.
53
+ * @deprecated Use defineVitestMonorepoConfig for the monorepo root config instead. It sets
54
+ * `coverage` together with all the other root-only options.
42
55
  */
43
56
  export function getRootCoverage(): InlineConfig['coverage']
44
57
 
@@ -9,7 +9,7 @@ export { CollectReporter } from './collectReporter.js'
9
9
 
10
10
  const runsInIDE = doesItRunInIDE()
11
11
  const testType = getTestType(runsInIDE)
12
- const silent = shouldBeSilent(runsInIDE)
12
+ const silent = getSilent(runsInIDE)
13
13
  const { include, exclude } = getIncludeAndExclude(testType)
14
14
  const isCI = !!process.env.CI
15
15
  const coverageEnabled = isCI && testType === 'unit'
@@ -27,6 +27,9 @@ if (testType === 'unit') {
27
27
  }
28
28
 
29
29
  if (silent) {
30
+ // Tells the tests that vitest is suppressing console output, so they keep the output vitest
31
+ // can't intercept quiet too: child process stdio (see exec2.test.ts) and dev-lib's testLogger,
32
+ // which writes to process.stdout directly.
30
33
  process.env.TEST_SILENT = 'true'
31
34
  }
32
35
 
@@ -37,6 +40,9 @@ if (silent) {
37
40
  * // overrides here, e.g:
38
41
  * // bail: 1,
39
42
  * })
43
+ *
44
+ * In a monorepo: use it in the vitest.config.ts of each package (passing `import.meta.dirname`
45
+ * as cwd), and defineVitestMonorepoConfig in the root vitest.config.ts.
40
46
  */
41
47
  export function defineVitestConfig(config, cwd) {
42
48
  const mergedConfig = defineConfig({
@@ -67,26 +73,84 @@ export function defineVitestConfig(config, cwd) {
67
73
  }
68
74
 
69
75
  /**
70
- * Shared config for Vitest.
76
+ * Use it in the ROOT vitest.config.ts of a monorepo (Vitest `projects` mode):
77
+ *
78
+ * export default defineVitestMonorepoConfig({
79
+ * test: {
80
+ * projects: ['./packages/*'],
81
+ * // overrides here, e.g:
82
+ * // coverage: { enabled: false },
83
+ * },
84
+ * })
85
+ *
86
+ * In `projects` mode Vitest has two kinds of config: the root config (this one) and the
87
+ * per-project configs (defineVitestConfig in each package). A set of options is root-only:
88
+ * Vitest reads them from the root config only and silently IGNORES the per-project values
89
+ * (see `NonProjectOptions` in Vitest's types): `reporters`, `outputFile`, `coverage`, `silent`,
90
+ * `watch`, `slowTestThreshold`, `sequence.sequencer`, etc.
91
+ *
92
+ * This helper sets the root-only part of the shared config (see getRootConfig) on the root
93
+ * config, where it takes effect. So the junit/json/coverage reports are written, the
94
+ * SummaryReporter reports the slowest tests across ALL projects, and the
95
+ * VitestAlphabeticSequencer is actually used, monorepo-wide.
96
+ */
97
+ export function defineVitestMonorepoConfig(config) {
98
+ return defineConfig({
99
+ ...config,
100
+ test: {
101
+ ...getRootConfig(),
102
+ ...config?.test,
103
+ },
104
+ })
105
+ }
106
+
107
+ /**
108
+ * Shared config for Vitest: for a single-package repo, or for one project (package) of a
109
+ * monorepo (pass `import.meta.dirname` as cwd).
71
110
  */
72
111
  export function getSharedConfig(cwd) {
73
112
  return {
113
+ // Root-only options. In a single-package repo this config IS the root, so they apply.
114
+ // In a monorepo they are IGNORED here (Vitest reads them from the root config only), so
115
+ // defineVitestMonorepoConfig sets them on the root config instead.
116
+ ...getRootConfig(),
117
+ // Per-project options
74
118
  pool,
75
119
  maxWorkers,
76
120
  isolate: false,
77
- watch: false,
78
121
  // dir: 'src',
79
122
  restoreMocks: true,
80
123
  // restoreMocks does not cover vi.stubEnv/vi.stubGlobal
81
124
  unstubEnvs: true,
82
125
  unstubGlobals: true,
83
- silent,
84
126
  setupFiles: getSetupFiles(testType, cwd),
85
127
  logHeapUsage: true,
86
128
  testTimeout: 60_000,
87
129
  hookTimeout: 60_000,
88
- slowTestThreshold: isCI ? 500 : 300, // higher threshold in CI
89
130
  injectCjsGlobals: false,
131
+ deps: {
132
+ // Disable CJS/ESM interop to match production Node.js behavior.
133
+ // Without this, vitest auto-promotes default exports to named exports,
134
+ // which can mask import bugs (e.g., ejs v4 renderFile issue).
135
+ // Vitest's own default is `true`.
136
+ interopDefault: false,
137
+ },
138
+ include,
139
+ exclude,
140
+ }
141
+ }
142
+
143
+ /**
144
+ * The root-only subset of the shared config: the options Vitest reads from the root config
145
+ * only (`NonProjectOptions` in Vitest's types, plus `sequence.sequencer`), hence the ones that
146
+ * must be set on the root config of a monorepo to take effect there. Shared by getSharedConfig
147
+ * (where they apply in a single-package repo) and defineVitestMonorepoConfig.
148
+ */
149
+ function getRootConfig() {
150
+ return {
151
+ watch: false,
152
+ silent,
153
+ slowTestThreshold: isCI ? 500 : 300, // higher threshold in CI
90
154
  sequence: {
91
155
  sequencer: VitestAlphabeticSequencer,
92
156
  // shuffle: {
@@ -95,46 +159,46 @@ export function getSharedConfig(cwd) {
95
159
  // },
96
160
  // seed: 1, // this makes the order of tests deterministic (but still not alphabetic)
97
161
  },
98
- include,
99
- exclude,
100
162
  reporters: getReporters(junitReporterEnabled, testType),
101
163
  outputFile: getOutputFile(),
102
164
  coverage: getCoverageConfig(),
103
165
  }
104
166
  }
105
167
 
106
- function getReporters(junitReporterEnabled, testType) {
107
- const override = getReporterOverride()
108
- if (override) return override
168
+ /**
169
+ * @deprecated Use defineVitestMonorepoConfig for the monorepo root config instead. It sets
170
+ * `reporters` together with all the other root-only options (see getRootConfig).
171
+ */
172
+ export function getRootReporters() {
173
+ return getReporters(junitReporterEnabled, testType)
174
+ }
109
175
 
110
- const { GITHUB_ACTIONS } = process.env
111
- return [
112
- 'default',
113
- GITHUB_ACTIONS && 'github-actions',
114
- new SummaryReporter(),
115
- ...getJunitReporters(junitReporterEnabled, testType),
116
- ].filter(Boolean)
176
+ /**
177
+ * @deprecated Use defineVitestMonorepoConfig for the monorepo root config instead. It sets
178
+ * `outputFile` together with all the other root-only options (see getRootConfig).
179
+ */
180
+ export function getRootOutputFile() {
181
+ return getOutputFile()
117
182
  }
118
183
 
119
184
  /**
120
- * Reporters for the monorepo root config (Vitest `projects` mode).
121
- *
122
- * `reporters` is a root-only Vitest option: per-project reporters are ignored
123
- * when running via `projects`. So the per-package SummaryReporter never runs in
124
- * the aggregate root run, and - crucially - a root-level SummaryReporter sees
125
- * the test modules of ALL projects at once, letting it report the slowest tests
126
- * across the entire monorepo.
127
- *
128
- * Use it in the root vitest.config.ts:
185
+ * @deprecated Use defineVitestMonorepoConfig for the monorepo root config instead. It sets
186
+ * `coverage` together with all the other root-only options (see getRootConfig).
187
+ */
188
+ export function getRootCoverage() {
189
+ return getCoverageConfig()
190
+ }
191
+
192
+ /**
193
+ * The reporters, shared by the per-package (getSharedConfig) and the monorepo root
194
+ * (defineVitestMonorepoConfig) configs.
129
195
  *
130
- * export default defineConfig({
131
- * test: {
132
- * projects: ['./packages/*'],
133
- * reporters: getRootReporters(),
134
- * },
135
- * })
196
+ * In a monorepo (`projects` mode) `reporters` is root-only: per-project reporters are ignored.
197
+ * So the per-package SummaryReporter never runs in the aggregate root run, and - crucially -
198
+ * a root-level SummaryReporter sees the test modules of ALL projects at once, letting it report
199
+ * the slowest tests across the entire monorepo.
136
200
  */
137
- export function getRootReporters() {
201
+ function getReporters(junitReporterEnabled, testType) {
138
202
  const override = getReporterOverride()
139
203
  if (override) return override
140
204
 
@@ -148,60 +212,19 @@ export function getRootReporters() {
148
212
  }
149
213
 
150
214
  /**
151
- * `outputFile` for the junit/json reporters at the monorepo root config.
152
- *
153
- * Like `reporters`, `outputFile` is a root-only Vitest option: per-project
154
- * `outputFile` is ignored when running via `projects`. So the root config must
155
- * set it (alongside getRootReporters) for the junit/json report to be written
156
- * in the aggregate root run.
157
- *
158
- * Use it in the root vitest.config.ts:
159
- *
160
- * export default defineConfig({
161
- * test: {
162
- * projects: ['./packages/*'],
163
- * reporters: getRootReporters(),
164
- * outputFile: getRootOutputFile(),
165
- * },
166
- * })
167
- */
168
- export function getRootOutputFile() {
169
- return getOutputFile()
170
- }
171
-
172
- /**
173
- * `coverage` config for the monorepo root config (Vitest `projects` mode).
215
+ * The coverage config, shared by the per-package (getSharedConfig) and the monorepo root
216
+ * (defineVitestMonorepoConfig) configs. Only enabled for CI unit-test runs.
174
217
  *
175
- * Like `reporters` and `outputFile`, `coverage` is a root-only Vitest option:
176
- * it is read from the root project only (see Vitest's `getRootProject().
177
- * serializedConfig.coverage`), so the per-package `coverage` set in
178
- * getSharedConfig is IGNORED when running via `projects`. Without this on the
218
+ * In a monorepo (`projects` mode) `coverage` is root-only: it is read from the root project
219
+ * only (see Vitest's `getRootProject().serializedConfig.coverage`), so the per-package
220
+ * `coverage` set in getSharedConfig is IGNORED when running via `projects`. Without it on the
179
221
  * root config no coverage report is produced at all (e.g. the CI upload of
180
222
  * ./coverage/coverage-summary.json finds nothing).
181
223
  *
182
- * The report is a single, unified one spanning all projects: Vitest tracks
183
- * coverage per-project internally but emits one report under `./coverage`.
184
- * Per-project coverage `include` globs are matched unanchored against absolute
185
- * paths, so executed files under `packages/<pkg>/src/**` are still included.
186
- *
187
- * Use it in the root vitest.config.ts:
188
- *
189
- * export default defineConfig({
190
- * test: {
191
- * projects: ['./packages/*'],
192
- * reporters: getRootReporters(),
193
- * outputFile: getRootOutputFile(),
194
- * coverage: getRootCoverage(),
195
- * },
196
- * })
197
- */
198
- export function getRootCoverage() {
199
- return getCoverageConfig()
200
- }
201
-
202
- /**
203
- * The coverage config, shared by the per-package (getSharedConfig) and root
204
- * (getRootCoverage) configs. Only enabled for CI unit-test runs.
224
+ * The report is a single, unified one spanning all projects: Vitest tracks coverage per-project
225
+ * internally but emits one report under `./coverage`. Per-project coverage `include` globs are
226
+ * matched unanchored against absolute paths, so executed files under `packages/<pkg>/src/**`
227
+ * are still included.
205
228
  */
206
229
  function getCoverageConfig() {
207
230
  return {
@@ -240,7 +263,7 @@ function getCoverageConfig() {
240
263
 
241
264
  /**
242
265
  * The junit + json reporters, enabled in CI (except for manual tests).
243
- * Shared by the per-package (getReporters) and root (getRootReporters) configs.
266
+ * Used (via getReporters) by both the per-package and the monorepo root configs.
244
267
  */
245
268
  function getJunitReporters(junitReporterEnabled, testType) {
246
269
  if (!junitReporterEnabled) return []
@@ -310,32 +333,20 @@ function getTestType(runsInIDE) {
310
333
  return process.env.TEST_TYPE || 'unit'
311
334
  }
312
335
 
313
- function shouldBeSilent(runsInIDE) {
314
- if (runsInIDE) {
315
- return false
316
- }
317
- return isRunningAllTests()
318
- }
319
-
320
336
  /**
321
- * Detects if vitest is run with all tests, or with selected individual tests.
337
+ * Console output of failed tests only, in every CLI run - whether it runs all tests or a single
338
+ * file. Pass `--silent=false` to see the output of passing tests too.
339
+ * In the IDE everything is printed, as IDE runs are interactive debugging.
322
340
  */
323
- function isRunningAllTests() {
324
- let vitestArg = false
325
- let hasPositionalArgs = false
326
- process.argv.forEach(a => {
327
- if (a.includes('.bin/vitest')) {
328
- vitestArg = true
329
- return
330
- }
331
- if (!vitestArg) return
332
- if (!a.startsWith('-')) {
333
- hasPositionalArgs = true
334
- }
335
- })
336
- // console.log({vitestArg, hasPositionalArgs}, process.argv)
337
-
338
- return !hasPositionalArgs
341
+ function getSilent(runsInIDE) {
342
+ if (runsInIDE) return false
343
+ // Vitest applies `--silent=false` to `silent` by itself; mirroring it here keeps TEST_SILENT
344
+ // (see above) in sync, so the output vitest can't intercept is shown as well.
345
+ const silentFalse = process.argv.some(
346
+ (a, i) => a === '--silent=false' || (a === '--silent' && process.argv[i + 1] === 'false'),
347
+ )
348
+ if (silentFalse) return false
349
+ return 'passed-only'
339
350
  }
340
351
 
341
352
  function getSetupFiles(testType, cwd = process.cwd()) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/dev-lib",
3
3
  "type": "module",
4
- "version": "20.64.0",
4
+ "version": "20.65.1",
5
5
  "dependencies": {
6
6
  "prompts": "^2",
7
7
  "@naturalcycles/js-lib": "^15",