@rtorcato/js-tooling 2.8.0 → 2.9.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rtorcato/js-tooling",
3
- "version": "2.8.0",
3
+ "version": "2.9.0",
4
4
  "description": "JavaScript and TypeScript tooling for Node.js, React, Next.js, and Vitest.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -63,6 +63,10 @@
63
63
  "tooling/vitest/vitest.setup.d.mts",
64
64
  "tooling/vitest/jsdom-shims.mjs",
65
65
  "tooling/vitest/jsdom-shims.d.mts",
66
+ "tooling/tests/exports-resolution.mjs",
67
+ "tooling/tests/exports-resolution.d.mts",
68
+ "tooling/tests/ssr-safety.mjs",
69
+ "tooling/tests/ssr-safety.d.mts",
66
70
  "tooling/tsup/index.ts",
67
71
  "tooling/biome/biome.json",
68
72
  "tooling/semantic-release/*.mjs",
@@ -142,6 +146,14 @@
142
146
  "./semantic-release/docker": {
143
147
  "types": "./tooling/semantic-release/docker.d.mts",
144
148
  "import": "./tooling/semantic-release/docker.mjs"
149
+ },
150
+ "./tests/exports-resolution": {
151
+ "types": "./tooling/tests/exports-resolution.d.mts",
152
+ "import": "./tooling/tests/exports-resolution.mjs"
153
+ },
154
+ "./tests/ssr-safety": {
155
+ "types": "./tooling/tests/ssr-safety.d.mts",
156
+ "import": "./tooling/tests/ssr-safety.mjs"
145
157
  }
146
158
  },
147
159
  "dependencies": {
@@ -0,0 +1,14 @@
1
+ export interface ExportsResolutionTestOptions {
2
+ /** Absolute path to the package.json whose `exports` map will be validated. */
3
+ packageJsonPath: string
4
+ /** Absolute path to the source directory whose subfolders are cross-checked. */
5
+ srcDir: string
6
+ /** Folder names under `srcDir` to skip (e.g., `'tests'`, `'common'`). */
7
+ excludeDirs?: string[]
8
+ }
9
+
10
+ /**
11
+ * Asserts that a package.json `exports` map stays in sync with its source folders.
12
+ * Call from a Vitest test file; generates one describe block plus one it per folder.
13
+ */
14
+ export function runExportsResolutionTest(options: ExportsResolutionTestOptions): void
@@ -0,0 +1,68 @@
1
+ import { readdirSync, readFileSync } from 'node:fs'
2
+ import { describe, expect, it } from 'vitest'
3
+
4
+ /**
5
+ * Asserts that a package.json `exports` map stays in sync with its source folders.
6
+ *
7
+ * For every folder under `srcDir` (excluding `excludeDirs`), the package.json must
8
+ * expose a matching `./<name>` subpath export. Conversely, every subpath in the
9
+ * `exports` map (other than `.`) must point at a folder that exists in `srcDir`.
10
+ *
11
+ * Call this from a Vitest test file; it generates one `describe` block plus one
12
+ * `it` per folder, so failures pinpoint the drift.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * // src/tests/exports-resolution.test.ts
17
+ * import { fileURLToPath } from 'node:url'
18
+ * import { runExportsResolutionTest } from '@rtorcato/js-tooling/tests/exports-resolution'
19
+ *
20
+ * runExportsResolutionTest({
21
+ * packageJsonPath: fileURLToPath(new URL('../../package.json', import.meta.url)),
22
+ * srcDir: fileURLToPath(new URL('../', import.meta.url)),
23
+ * excludeDirs: ['tests'],
24
+ * })
25
+ * ```
26
+ *
27
+ * @param {object} options
28
+ * @param {string} options.packageJsonPath Absolute path to the package.json under test.
29
+ * @param {string} options.srcDir Absolute path to the source folder whose subdirectories should be cross-checked.
30
+ * @param {string[]} [options.excludeDirs] Folder names under `srcDir` to skip (e.g., `tests`, `common`).
31
+ */
32
+ export function runExportsResolutionTest({ packageJsonPath, srcDir, excludeDirs = [] }) {
33
+ const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'))
34
+
35
+ if (!pkg.exports || typeof pkg.exports !== 'object') {
36
+ throw new Error(`exports-resolution: package.json at ${packageJsonPath} has no exports map`)
37
+ }
38
+
39
+ const exportSubpaths = new Set(
40
+ Object.keys(pkg.exports)
41
+ .filter((k) => k !== '.')
42
+ .map((k) => k.replace(/^\.\//, ''))
43
+ )
44
+
45
+ const excluded = new Set(excludeDirs)
46
+ const moduleDirs = readdirSync(srcDir, { withFileTypes: true })
47
+ .filter((d) => d.isDirectory() && !excluded.has(d.name))
48
+ .map((d) => d.name)
49
+ .sort()
50
+
51
+ describe('package.json exports map', () => {
52
+ it('has at least one module', () => {
53
+ expect(moduleDirs.length).toBeGreaterThan(0)
54
+ })
55
+
56
+ for (const dir of moduleDirs) {
57
+ it(`exposes ./${dir}`, () => {
58
+ expect(exportSubpaths.has(dir)).toBe(true)
59
+ })
60
+ }
61
+
62
+ it('has no exports entries pointing at missing src/ folders', () => {
63
+ const moduleDirSet = new Set(moduleDirs)
64
+ const orphans = [...exportSubpaths].filter((k) => !moduleDirSet.has(k))
65
+ expect(orphans).toEqual([])
66
+ })
67
+ })
68
+ }
@@ -0,0 +1,14 @@
1
+ export interface SsrSafetyTestOptions {
2
+ /** Absolute path to the source directory. */
3
+ srcDir: string
4
+ /** Folder names under `srcDir` to skip (e.g., `'tests'`, `'common'`). */
5
+ excludeDirs?: string[]
6
+ /** Entry filename inside each folder. Default: `'index.ts'`. */
7
+ moduleEntry?: string
8
+ }
9
+
10
+ /**
11
+ * Asserts that every module folder under `srcDir` can be imported in a Node
12
+ * environment without throwing. Call from a Vitest test file.
13
+ */
14
+ export function runSsrSafetyTest(options: SsrSafetyTestOptions): void
@@ -0,0 +1,53 @@
1
+ import { readdirSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+ import { pathToFileURL } from 'node:url'
4
+ import { describe, expect, it } from 'vitest'
5
+
6
+ /**
7
+ * Asserts that every module folder under `srcDir` can be imported in a Node
8
+ * environment without throwing (no DOM, no `window`, no `document`).
9
+ *
10
+ * For each folder under `srcDir` (excluding `excludeDirs`), the helper resolves
11
+ * `<srcDir>/<name>/<moduleEntry>` and `await import(...)` it. If the import
12
+ * throws or the module accesses a missing global at top level, the test fails.
13
+ *
14
+ * Use this as a contract test for libraries that promise SSR safety — i.e., that
15
+ * importing a module is always safe even when its underlying API isn't available.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * // src/tests/ssr-safety.test.ts (run with vitest --environment node)
20
+ * import { fileURLToPath } from 'node:url'
21
+ * import { runSsrSafetyTest } from '@rtorcato/js-tooling/tests/ssr-safety'
22
+ *
23
+ * runSsrSafetyTest({
24
+ * srcDir: fileURLToPath(new URL('../', import.meta.url)),
25
+ * excludeDirs: ['tests'],
26
+ * })
27
+ * ```
28
+ *
29
+ * @param {object} options
30
+ * @param {string} options.srcDir Absolute path to the source folder.
31
+ * @param {string[]} [options.excludeDirs] Folder names under `srcDir` to skip.
32
+ * @param {string} [options.moduleEntry] Entry filename inside each folder. Default: `'index.ts'`.
33
+ */
34
+ export function runSsrSafetyTest({ srcDir, excludeDirs = [], moduleEntry = 'index.ts' }) {
35
+ const excluded = new Set(excludeDirs)
36
+ const moduleDirs = readdirSync(srcDir, { withFileTypes: true })
37
+ .filter((d) => d.isDirectory() && !excluded.has(d.name))
38
+ .map((d) => d.name)
39
+ .sort()
40
+
41
+ describe('SSR safety', () => {
42
+ it('has at least one module', () => {
43
+ expect(moduleDirs.length).toBeGreaterThan(0)
44
+ })
45
+
46
+ for (const dir of moduleDirs) {
47
+ it(`imports ./${dir}/${moduleEntry} without throwing`, async () => {
48
+ const moduleUrl = pathToFileURL(join(srcDir, dir, moduleEntry)).href
49
+ await expect(import(moduleUrl)).resolves.toBeDefined()
50
+ })
51
+ }
52
+ })
53
+ }
@@ -11,13 +11,6 @@ export default defineConfig({
11
11
  coverage: {
12
12
  provider: 'v8',
13
13
  reporter: ['text', 'json', 'html', 'json-summary'],
14
- include: ['src/cli/generators/**/*.ts'],
15
- thresholds: {
16
- statements: 25,
17
- lines: 25,
18
- functions: 40,
19
- branches: 17,
20
- },
21
14
  },
22
15
  },
23
16
  resolve: {