@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.
|
|
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: {
|