@jterrazz/typescript 10.1.5 → 10.1.7
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/bin/typescript.sh
CHANGED
|
@@ -17,14 +17,17 @@ else
|
|
|
17
17
|
PROJECT_ROOT=$(pwd)
|
|
18
18
|
fi
|
|
19
19
|
|
|
20
|
-
#
|
|
20
|
+
# The PHYSICAL directory this script lives in, symlinks resolved on every hop.
|
|
21
|
+
# `cd -P` matters: under pnpm the package sits behind a symlinked prefix, and
|
|
22
|
+
# a shim found from the logical path execs a relative target that lands
|
|
23
|
+
# beside the wrong parent.
|
|
21
24
|
SCRIPT_PATH="$0"
|
|
22
25
|
while [ -L "$SCRIPT_PATH" ]; do
|
|
23
|
-
LINK_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
|
|
26
|
+
LINK_DIR="$(cd -P "$(dirname "$SCRIPT_PATH")" && pwd)"
|
|
24
27
|
SCRIPT_PATH="$(readlink "$SCRIPT_PATH")"
|
|
25
28
|
[[ $SCRIPT_PATH != /* ]] && SCRIPT_PATH="$LINK_DIR/$SCRIPT_PATH"
|
|
26
29
|
done
|
|
27
|
-
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
|
|
30
|
+
SCRIPT_DIR="$(cd -P "$(dirname "$SCRIPT_PATH")" && pwd)"
|
|
28
31
|
PACKAGE_ROOT="$SCRIPT_DIR/.."
|
|
29
32
|
|
|
30
33
|
# Find binaries - check package's node_modules first, then project's (handles npm hoisting)
|
package/lib/check-names.js
CHANGED
|
@@ -63,6 +63,13 @@ const SHORTCUTS = new Set([
|
|
|
63
63
|
*/
|
|
64
64
|
const SWEPT = new Set(['apps', 'bin', 'lib', 'packages', 'specs', 'src', 'tests']);
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* A Next.js route segment is a URL: what a directory below `app/` is called is
|
|
68
|
+
* the address the product serves, not a name the tree chose. The router root
|
|
69
|
+
* itself is judged like any directory; what it contains is not.
|
|
70
|
+
*/
|
|
71
|
+
const ROUTER_ROOT = 'app';
|
|
72
|
+
|
|
66
73
|
/** A `_` opens a row — a fixture, a golden, a template — and a row is not a subject. */
|
|
67
74
|
const isOfTheRow = (name) => name.startsWith('_');
|
|
68
75
|
|
|
@@ -81,7 +88,8 @@ const stemOf = (name) => name.split('.')[0] ?? '';
|
|
|
81
88
|
|
|
82
89
|
/**
|
|
83
90
|
* Every name a set of paths puts on the tree, each mapped to the path that
|
|
84
|
-
* carries it: every directory below a swept root
|
|
91
|
+
* carries it: every directory below a swept root that is not a route, and
|
|
92
|
+
* every file's stem.
|
|
85
93
|
*/
|
|
86
94
|
function namesOf(files) {
|
|
87
95
|
const named = new Map();
|
|
@@ -92,8 +100,11 @@ function namesOf(files) {
|
|
|
92
100
|
continue;
|
|
93
101
|
}
|
|
94
102
|
|
|
95
|
-
/* From the second segment on: the root's own name is not the project's
|
|
96
|
-
|
|
103
|
+
/* From the second segment on: the root's own name is not the project's,
|
|
104
|
+
* and below a router root a directory is a route. */
|
|
105
|
+
const routerRoot = segments.indexOf(ROUTER_ROOT, 1);
|
|
106
|
+
const lastJudged = routerRoot === -1 ? segments.length - 1 : routerRoot + 1;
|
|
107
|
+
for (let index = 1; index < Math.min(lastJudged, segments.length - 1); index += 1) {
|
|
97
108
|
const path = segments.slice(0, index + 1).join('/');
|
|
98
109
|
named.set(path, segments[index]);
|
|
99
110
|
}
|
|
@@ -91,7 +91,15 @@ function resolveConfig(root, oxlint) {
|
|
|
91
91
|
|
|
92
92
|
let rules;
|
|
93
93
|
try {
|
|
94
|
-
|
|
94
|
+
const resolved = JSON.parse(printed);
|
|
95
|
+
rules = { ...resolved.rules };
|
|
96
|
+
/* A rule armed only in an override — a test-file rule, a layer's
|
|
97
|
+
* `no-restricted-imports` — is live for the files it names. */
|
|
98
|
+
for (const override of resolved.overrides ?? []) {
|
|
99
|
+
for (const [name, level] of Object.entries(override.rules ?? {})) {
|
|
100
|
+
rules[name] ??= level;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
95
103
|
} catch {
|
|
96
104
|
return null;
|
|
97
105
|
}
|
package/package.json
CHANGED
|
@@ -22,9 +22,25 @@ import { fragment, on, scoped } from '../_contract.js';
|
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Build a fragment from a layer map. Each layer is
|
|
25
|
-
* `{ name, files, deny, allow?, message }`: `files` are the
|
|
26
|
-
* to the layer, `deny` the specifier globs it may not
|
|
27
|
-
* exceptions carved out of them
|
|
25
|
+
* `{ name, files, deny, allow?, allowTypeImports?, message }`: `files` are the
|
|
26
|
+
* globs that BELONG to the layer, `deny` the specifier globs it may not
|
|
27
|
+
* import, `allow` the exceptions carved out of them, and `allowTypeImports`
|
|
28
|
+
* lets a type-only import cross — a type is a contract, not a dependency at
|
|
29
|
+
* runtime.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {object} Layer One row of a layer map.
|
|
33
|
+
* @property {string} name What the layer is called in a refusal.
|
|
34
|
+
* @property {readonly string[]} files The globs that belong to the layer.
|
|
35
|
+
* @property {readonly string[]} deny The specifier globs it may not import.
|
|
36
|
+
* @property {readonly string[]} [allow] Exceptions carved out of `deny`.
|
|
37
|
+
* @property {boolean} [allowTypeImports] Whether a type-only import may cross.
|
|
38
|
+
* @property {string} message What a refusal says.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {{ id?: string, map: readonly Layer[] }} definition The map, and the id the fragment answers to.
|
|
43
|
+
* @returns {Omit<ReturnType<typeof fragment>, 'overrides'> & { overrides: readonly import('../_contract.js').Scoped[] }} The fragment, one override per layer.
|
|
28
44
|
*/
|
|
29
45
|
export function layers({ id = 'layers', map }) {
|
|
30
46
|
assertDisjoint(id, map);
|
|
@@ -44,6 +60,9 @@ export function layers({ id = 'layers', map }) {
|
|
|
44
60
|
...(layer.allow ?? []).map((glob) => `!${glob}`),
|
|
45
61
|
],
|
|
46
62
|
message: layer.message,
|
|
63
|
+
...(layer.allowTypeImports === true
|
|
64
|
+
? { allowTypeImports: true }
|
|
65
|
+
: {}),
|
|
47
66
|
},
|
|
48
67
|
],
|
|
49
68
|
},
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { expect, test } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { layers } from './layers.js';
|
|
4
|
+
|
|
5
|
+
const LAYER = {
|
|
6
|
+
deny: ['**/adapters/**'],
|
|
7
|
+
files: ['src/ports/**/*.ts'],
|
|
8
|
+
message: 'a port names no adapter',
|
|
9
|
+
name: 'ports',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
test('a layer forbids the specifiers it denies and nothing else about its type imports', () => {
|
|
13
|
+
// Given - a layer that says nothing about type imports
|
|
14
|
+
// When - the map is compiled
|
|
15
|
+
const [override] = layers({ map: [LAYER] }).overrides;
|
|
16
|
+
|
|
17
|
+
// Then - the pattern carries the deny glob and no type-import exemption
|
|
18
|
+
expect(override?.decisions['no-restricted-imports']?.options).toStrictEqual([
|
|
19
|
+
{ patterns: [{ group: ['**/adapters/**'], message: 'a port names no adapter' }] },
|
|
20
|
+
]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('a layer that allows type imports lets a contract cross without its runtime', () => {
|
|
24
|
+
// Given - the same layer with type imports allowed
|
|
25
|
+
// When - the map is compiled
|
|
26
|
+
const [override] = layers({ map: [{ ...LAYER, allowTypeImports: true }] }).overrides;
|
|
27
|
+
|
|
28
|
+
// Then - the pattern says so to oxlint
|
|
29
|
+
expect(override?.decisions['no-restricted-imports']?.options).toStrictEqual([
|
|
30
|
+
{
|
|
31
|
+
patterns: [
|
|
32
|
+
{
|
|
33
|
+
allowTypeImports: true,
|
|
34
|
+
group: ['**/adapters/**'],
|
|
35
|
+
message: 'a port names no adapter',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('two layers claiming one files glob are refused before oxlint erases one of them', () => {
|
|
43
|
+
// Given - two layers on the same glob
|
|
44
|
+
const map = [LAYER, { ...LAYER, name: 'twin' }];
|
|
45
|
+
|
|
46
|
+
// Then - the map is refused by name
|
|
47
|
+
expect(() => layers({ map })).toThrow('belongs to both "ports" and "twin"');
|
|
48
|
+
});
|