@ts-ioc-container/bundler 1.1.0 → 1.3.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/AGENTS.md +8 -2
- package/README.md +55 -5
- package/cjm/ImportPaths.js +20 -6
- package/cjm/build.js +46 -4
- package/cjm/config.js +26 -3
- package/cjm/scan.js +23 -2
- package/package.json +1 -1
- package/tic.schema.json +23 -0
- package/typings/ImportPaths.d.ts +9 -0
- package/typings/build.d.ts +5 -1
- package/typings/config.d.ts +17 -1
- package/typings/scan.d.ts +15 -0
package/AGENTS.md
CHANGED
|
@@ -13,8 +13,14 @@ it.
|
|
|
13
13
|
- A namespace is a folder: relative to the config file, or a tsconfig `paths`
|
|
14
14
|
alias. Generated imports use the most specific alias, else a relative path.
|
|
15
15
|
- Default selection is every exported, non-abstract class. A module's
|
|
16
|
-
`select: { export, decorators, nameGlob
|
|
17
|
-
|
|
16
|
+
`select: { export, decorators, nameGlob, excludeClasses, excludeNameGlob, excludeAliases }`
|
|
17
|
+
narrows it; positive criteria must all hold and then `excludeClasses` (exact
|
|
18
|
+
names), `excludeNameGlob` (glob) and `excludeAliases` (tsconfig `paths` aliases
|
|
19
|
+
the file resolves through, e.g. `services` for `@services/*`) drop classes.
|
|
20
|
+
Decorators match by name, so composed ones must be listed. The build warns when
|
|
21
|
+
two selected classes pass the same plain-identifier first decorator argument (a
|
|
22
|
+
same-token heuristic), unless a decorator they share has differing arguments
|
|
23
|
+
(scope-gated, e.g. `@perPage('a')` vs `@perPage('b')`).
|
|
18
24
|
- Never edit a `*.bundle.ts` by hand — change the classes or the config and
|
|
19
25
|
rerun `tic build`.
|
|
20
26
|
- Files take part when they match no `exclude` glob **and** an
|
package/README.md
CHANGED
|
@@ -95,14 +95,64 @@ By default every exported, non-abstract class of a scanned file is registered.
|
|
|
95
95
|
}
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
-
| Criterion
|
|
99
|
-
|
|
|
100
|
-
| `export`
|
|
101
|
-
| `decorators`
|
|
102
|
-
| `nameGlob`
|
|
98
|
+
| Criterion | Default | Meaning |
|
|
99
|
+
| ------------------ | ------- | ----------------------------------------------------------------------------------------------------------- |
|
|
100
|
+
| `export` | `any` | `any`, `named` or `default` — which exports count |
|
|
101
|
+
| `decorators` | — | The class must carry one of these, by name — also renamed imports and `@ioc.register()`; list composed ones |
|
|
102
|
+
| `nameGlob` | — | Glob on the class name; an anonymous default export is named after its file (`user-service.ts` → `UserService`) |
|
|
103
|
+
| `excludeClasses` | — | Class names to drop, e.g. `["MockDashboardRepository"]` |
|
|
104
|
+
| `excludeNameGlob` | — | Glob the class name must **not** match, e.g. `"*Mock"` |
|
|
105
|
+
| `excludeAliases` | — | tsconfig `paths` aliases to drop, e.g. `["services"]` for a `services/*` or `@services/*` alias |
|
|
103
106
|
|
|
104
107
|
Abstract and non-exported classes are never registered.
|
|
105
108
|
|
|
109
|
+
### Excluding a single class
|
|
110
|
+
|
|
111
|
+
When a real implementation and a test-only stand-in are both decorated and both
|
|
112
|
+
bind the same token, registration is last-wins and the stand-in silently wins. A
|
|
113
|
+
`tic.exports.*` predicate works, but forces a hand-maintained name list in code;
|
|
114
|
+
`excludeClasses` and `excludeNameGlob` drop a class right in the config:
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
"select": {
|
|
118
|
+
"decorators": ["repository", "service"],
|
|
119
|
+
"excludeClasses": ["MockDashboardRepository"],
|
|
120
|
+
"excludeNameGlob": "*Fake"
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Both apply after every other criterion, so they can be combined with
|
|
125
|
+
`decorators` and `nameGlob` freely. Reach for `filterExports` when the rule
|
|
126
|
+
needs more than the class name.
|
|
127
|
+
|
|
128
|
+
`excludeAliases` drops classes by the tsconfig `paths` alias their file resolves
|
|
129
|
+
through — handy for excluding a whole namespace without naming its classes:
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
"select": {
|
|
133
|
+
"excludeAliases": ["services"]
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
An entry matches a `paths` pattern such as `services/*` or `@services/*`; a
|
|
138
|
+
leading `@` and a trailing `/*` may be omitted.
|
|
139
|
+
|
|
140
|
+
As a safety net, the build warns when two selected classes pass the same
|
|
141
|
+
plain-identifier first argument to a decorator — usually the binding token, as in
|
|
142
|
+
`@repository(IDashboardRepositoryToken)`. The check is syntactic (aliased imports
|
|
143
|
+
are not resolved), so it can only be a heuristic:
|
|
144
|
+
|
|
145
|
+
```text
|
|
146
|
+
tic: warning: bundles[0]: decorator token "IDashboardRepositoryToken" is passed by
|
|
147
|
+
HttpDashboardRepository, MockDashboardRepository; registration is last-wins,
|
|
148
|
+
exclude one with select.excludeClasses
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Classes that are scope-gated are not last-wins, so they do not warn: when the
|
|
152
|
+
colliding classes share a decorator called with different arguments, e.g.
|
|
153
|
+
`@perPage('stations')` vs `@perPage('sessions')`, each scope registers its own
|
|
154
|
+
class and nothing is lost.
|
|
155
|
+
|
|
106
156
|
## Including files with a predicate
|
|
107
157
|
|
|
108
158
|
When globs aren't enough, decide in code which files take part:
|
package/cjm/ImportPaths.js
CHANGED
|
@@ -37,6 +37,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.ImportPaths = void 0;
|
|
40
|
+
exports.normalizeAliasName = normalizeAliasName;
|
|
40
41
|
const node_fs_1 = require("node:fs");
|
|
41
42
|
const node_path_1 = __importDefault(require("node:path"));
|
|
42
43
|
const ts = __importStar(require("typescript"));
|
|
@@ -92,6 +93,19 @@ class ImportPaths {
|
|
|
92
93
|
specifier(fromFile, toFile) {
|
|
93
94
|
const target = (0, glob_1.toPosix)(toFile);
|
|
94
95
|
const bare = target.replace(SOURCE_EXTENSION, '');
|
|
96
|
+
const best = this.bestAlias(target, bare);
|
|
97
|
+
if (best)
|
|
98
|
+
return best.spec;
|
|
99
|
+
const relative = (0, glob_1.toPosix)(node_path_1.default.relative(node_path_1.default.dirname(fromFile), bare));
|
|
100
|
+
return `${relative.startsWith('.') ? relative : `./${relative}`}${this.extension}`;
|
|
101
|
+
}
|
|
102
|
+
aliasName(file) {
|
|
103
|
+
const target = (0, glob_1.toPosix)(file);
|
|
104
|
+
const bare = target.replace(SOURCE_EXTENSION, '');
|
|
105
|
+
const best = this.bestAlias(target, bare);
|
|
106
|
+
return best && normalizeAliasName(best.pattern);
|
|
107
|
+
}
|
|
108
|
+
bestAlias(target, bare) {
|
|
95
109
|
let best;
|
|
96
110
|
for (const { pattern, targets } of this.aliases) {
|
|
97
111
|
for (const aliasTarget of targets) {
|
|
@@ -100,10 +114,7 @@ class ImportPaths {
|
|
|
100
114
|
best = match;
|
|
101
115
|
}
|
|
102
116
|
}
|
|
103
|
-
|
|
104
|
-
return best.spec;
|
|
105
|
-
const relative = (0, glob_1.toPosix)(node_path_1.default.relative(node_path_1.default.dirname(fromFile), bare));
|
|
106
|
-
return `${relative.startsWith('.') ? relative : `./${relative}`}${this.extension}`;
|
|
117
|
+
return best;
|
|
107
118
|
}
|
|
108
119
|
aliasTargets(spec) {
|
|
109
120
|
return this.aliases.flatMap(({ pattern, targets }) => {
|
|
@@ -121,14 +132,17 @@ class ImportPaths {
|
|
|
121
132
|
const wildcard = splitWildcard(aliasTarget);
|
|
122
133
|
if (!wildcard) {
|
|
123
134
|
const exact = aliasTarget === file || aliasTarget.replace(SOURCE_EXTENSION, '') === bare;
|
|
124
|
-
return exact && !splitWildcard(pattern) ? { spec: pattern, score: Infinity } : undefined;
|
|
135
|
+
return exact && !splitWildcard(pattern) ? { spec: pattern, score: Infinity, pattern } : undefined;
|
|
125
136
|
}
|
|
126
137
|
const [prefix, suffix] = wildcard;
|
|
127
138
|
if (!bare.startsWith(prefix) || !bare.endsWith(suffix) || bare.length < prefix.length + suffix.length) {
|
|
128
139
|
return undefined;
|
|
129
140
|
}
|
|
130
141
|
const captured = bare.slice(prefix.length, bare.length - suffix.length);
|
|
131
|
-
return { spec: `${pattern.replace('*', captured)}${this.extension}`, score: prefix.length };
|
|
142
|
+
return { spec: `${pattern.replace('*', captured)}${this.extension}`, score: prefix.length, pattern };
|
|
132
143
|
}
|
|
133
144
|
}
|
|
134
145
|
exports.ImportPaths = ImportPaths;
|
|
146
|
+
function normalizeAliasName(pattern) {
|
|
147
|
+
return pattern.replace(/^@/, '').replace(/\/?\*$/, '');
|
|
148
|
+
}
|
package/cjm/build.js
CHANGED
|
@@ -28,7 +28,10 @@ function build({ config = config_1.DEFAULT_CONFIG_FILE, cwd = process.cwd(), che
|
|
|
28
28
|
fileOf: (bundle) => bundle.filterExports,
|
|
29
29
|
load: exportPredicate_1.loadExportPredicate,
|
|
30
30
|
});
|
|
31
|
-
const generated = resolved.bundles.map((bundle) => generate(resolved, bundle, paths, {
|
|
31
|
+
const generated = resolved.bundles.map((bundle, i) => generate(resolved, bundle, `bundles[${i}]`, paths, {
|
|
32
|
+
include: includeFor(bundle),
|
|
33
|
+
filterExports: filterExportsFor(bundle),
|
|
34
|
+
}));
|
|
32
35
|
const outputs = generated.map(({ bundle, content, registrations }) => {
|
|
33
36
|
const current = (0, node_fs_1.existsSync)(bundle.output) ? (0, node_fs_1.readFileSync)(bundle.output, 'utf8') : undefined;
|
|
34
37
|
const status = current === content ? 'unchanged' : check ? 'stale' : 'written';
|
|
@@ -38,7 +41,11 @@ function build({ config = config_1.DEFAULT_CONFIG_FILE, cwd = process.cwd(), che
|
|
|
38
41
|
}
|
|
39
42
|
return { file: bundle.output, bundle: bundle.name, status, registrations, content };
|
|
40
43
|
});
|
|
41
|
-
return {
|
|
44
|
+
return {
|
|
45
|
+
config: resolved.file,
|
|
46
|
+
outputs,
|
|
47
|
+
warnings: [...resolved.warnings, ...generated.flatMap((g) => g.warnings)],
|
|
48
|
+
};
|
|
42
49
|
}
|
|
43
50
|
function predicateResolver({ override, conventional, fileOf, load, }) {
|
|
44
51
|
const loaded = new Map();
|
|
@@ -53,7 +60,7 @@ function predicateResolver({ override, conventional, fileOf, load, }) {
|
|
|
53
60
|
return loaded.get(file);
|
|
54
61
|
};
|
|
55
62
|
}
|
|
56
|
-
function generate(config, bundle, paths, { include, filterExports }) {
|
|
63
|
+
function generate(config, bundle, field, paths, { include, filterExports }) {
|
|
57
64
|
const relative = (file) => (0, glob_1.toPosix)(node_path_1.default.relative(config.dir, file));
|
|
58
65
|
const outputs = new Set(config.bundles.map((m) => m.output));
|
|
59
66
|
const excludes = bundle.exclude.map(glob_1.globToRegExp);
|
|
@@ -69,10 +76,19 @@ function generate(config, bundle, paths, { include, filterExports }) {
|
|
|
69
76
|
for (const file of (0, scan_1.listSourceFiles)(dir, namespace.recursive, isExcluded))
|
|
70
77
|
files.add(file);
|
|
71
78
|
}
|
|
79
|
+
const excludedAliases = new Set((bundle.select.excludeAliases ?? []).map(ImportPaths_1.normalizeAliasName));
|
|
80
|
+
const isAliasExcluded = (file) => {
|
|
81
|
+
if (excludedAliases.size === 0)
|
|
82
|
+
return false;
|
|
83
|
+
const alias = paths.aliasName(file);
|
|
84
|
+
return alias !== undefined && excludedAliases.has(alias);
|
|
85
|
+
};
|
|
72
86
|
const classes = [...files]
|
|
73
87
|
.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
|
|
74
88
|
.flatMap((file) => (0, scan_1.findClasses)(file, bundle.select))
|
|
75
89
|
.filter((cls) => {
|
|
90
|
+
if (isAliasExcluded(cls.file))
|
|
91
|
+
return false;
|
|
76
92
|
if (!filterExports)
|
|
77
93
|
return true;
|
|
78
94
|
const filename = relative(cls.file);
|
|
@@ -86,5 +102,31 @@ function generate(config, bundle, paths, { include, filterExports }) {
|
|
|
86
102
|
namespaces: bundle.namespaces.map((ns) => ns.path),
|
|
87
103
|
classes,
|
|
88
104
|
});
|
|
89
|
-
return { bundle, content, registrations: classes.length };
|
|
105
|
+
return { bundle, content, registrations: classes.length, warnings: tokenCollisions(classes, field) };
|
|
106
|
+
}
|
|
107
|
+
function tokenCollisions(classes, field) {
|
|
108
|
+
const owners = new Map();
|
|
109
|
+
for (const cls of classes) {
|
|
110
|
+
for (const token of cls.tokens) {
|
|
111
|
+
const group = owners.get(token) ?? [];
|
|
112
|
+
if (!group.some((owner) => owner.className === cls.className))
|
|
113
|
+
group.push(cls);
|
|
114
|
+
owners.set(token, group);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return [...owners]
|
|
118
|
+
.filter(([, group]) => group.length > 1 && !scopeDisjoint(group))
|
|
119
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
120
|
+
.map(([token, group]) => {
|
|
121
|
+
const names = group.map((cls) => cls.className);
|
|
122
|
+
return (`${field}: decorator token "${token}" is passed by ${names.join(', ')}; ` +
|
|
123
|
+
`registration is last-wins, exclude one with select.excludeClasses`);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function scopeDisjoint(group) {
|
|
127
|
+
const [first, ...rest] = group;
|
|
128
|
+
return first.decoratorCalls.some(({ name, args }) => rest.every((cls) => {
|
|
129
|
+
const same = cls.decoratorCalls.find((call) => call.name === name);
|
|
130
|
+
return same !== undefined && same.args.join('\u0000') !== args.join('\u0000');
|
|
131
|
+
}));
|
|
90
132
|
}
|
package/cjm/config.js
CHANGED
|
@@ -38,7 +38,14 @@ function toNamespace(value, field) {
|
|
|
38
38
|
return fail(`${field}.recursive`, 'a boolean');
|
|
39
39
|
return { path: value.path, recursive: value.recursive ?? true };
|
|
40
40
|
}
|
|
41
|
-
const SELECTOR_FIELDS = new Set([
|
|
41
|
+
const SELECTOR_FIELDS = new Set([
|
|
42
|
+
'export',
|
|
43
|
+
'decorators',
|
|
44
|
+
'nameGlob',
|
|
45
|
+
'excludeClasses',
|
|
46
|
+
'excludeNameGlob',
|
|
47
|
+
'excludeAliases',
|
|
48
|
+
]);
|
|
42
49
|
function toSelector(value, field) {
|
|
43
50
|
if (value === undefined)
|
|
44
51
|
return { export: 'any' };
|
|
@@ -47,7 +54,7 @@ function toSelector(value, field) {
|
|
|
47
54
|
const unknown = Object.keys(value).find((key) => !SELECTOR_FIELDS.has(key));
|
|
48
55
|
if (unknown)
|
|
49
56
|
throw new errors_1.TicConfigError(`${field}.${unknown}: unknown field`);
|
|
50
|
-
const { export: kind, decorators, nameGlob } = value;
|
|
57
|
+
const { export: kind, decorators, nameGlob, excludeClasses, excludeNameGlob, excludeAliases } = value;
|
|
51
58
|
if (kind !== undefined && kind !== 'any' && kind !== 'named' && kind !== 'default') {
|
|
52
59
|
return fail(`${field}.export`, '"any", "named" or "default"');
|
|
53
60
|
}
|
|
@@ -56,7 +63,23 @@ function toSelector(value, field) {
|
|
|
56
63
|
}
|
|
57
64
|
if (nameGlob !== undefined && !isNonEmptyString(nameGlob))
|
|
58
65
|
return fail(`${field}.nameGlob`, 'a non-empty string');
|
|
59
|
-
|
|
66
|
+
if (excludeClasses !== undefined && !(isStringArray(excludeClasses) && excludeClasses.length > 0)) {
|
|
67
|
+
return fail(`${field}.excludeClasses`, 'a non-empty array of strings');
|
|
68
|
+
}
|
|
69
|
+
if (excludeNameGlob !== undefined && !isNonEmptyString(excludeNameGlob)) {
|
|
70
|
+
return fail(`${field}.excludeNameGlob`, 'a non-empty string');
|
|
71
|
+
}
|
|
72
|
+
if (excludeAliases !== undefined && !(isStringArray(excludeAliases) && excludeAliases.length > 0)) {
|
|
73
|
+
return fail(`${field}.excludeAliases`, 'a non-empty array of strings');
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
export: kind ?? 'any',
|
|
77
|
+
decorators,
|
|
78
|
+
nameGlob: nameGlob === undefined ? undefined : (0, glob_1.globToRegExp)(nameGlob),
|
|
79
|
+
excludeClasses,
|
|
80
|
+
excludeNameGlob: excludeNameGlob === undefined ? undefined : (0, glob_1.globToRegExp)(excludeNameGlob),
|
|
81
|
+
excludeAliases,
|
|
82
|
+
};
|
|
60
83
|
}
|
|
61
84
|
function toBundle(value, field, dir) {
|
|
62
85
|
if (!isObject(value))
|
package/cjm/scan.js
CHANGED
|
@@ -114,6 +114,17 @@ function findClasses(file, selector, text = (0, node_fs_1.readFileSync)(file, 'u
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
const decoratorsOf = (node) => (ts.getDecorators(node) ?? []).map((d) => decoratorName(d, imports)).filter((n) => !!n);
|
|
117
|
+
const decoratorCallsOf = (node) => (ts.getDecorators(node) ?? []).flatMap((d) => {
|
|
118
|
+
const name = decoratorName(d, imports);
|
|
119
|
+
if (!name)
|
|
120
|
+
return [];
|
|
121
|
+
const args = ts.isCallExpression(d.expression) ? d.expression.arguments.map((arg) => arg.getText(source)) : [];
|
|
122
|
+
return [{ name, args }];
|
|
123
|
+
});
|
|
124
|
+
const tokensOf = (node) => (ts.getDecorators(node) ?? [])
|
|
125
|
+
.map((d) => (ts.isCallExpression(d.expression) ? d.expression.arguments[0] : undefined))
|
|
126
|
+
.filter((arg) => !!arg && ts.isIdentifier(arg))
|
|
127
|
+
.map((arg) => arg.text);
|
|
117
128
|
return exported
|
|
118
129
|
.map(({ node, local, exportName }) => ({ node: node ?? (local ? classes.get(local) : undefined), exportName }))
|
|
119
130
|
.filter((e) => !!e.node)
|
|
@@ -122,6 +133,8 @@ function findClasses(file, selector, text = (0, node_fs_1.readFileSync)(file, 'u
|
|
|
122
133
|
exportName,
|
|
123
134
|
className: node.name?.text ?? nameFromFile(file),
|
|
124
135
|
decorators: decoratorsOf(node),
|
|
136
|
+
decoratorCalls: decoratorCallsOf(node),
|
|
137
|
+
tokens: tokensOf(node),
|
|
125
138
|
}))
|
|
126
139
|
.filter(({ node, exportName, className, decorators }) => {
|
|
127
140
|
if (hasModifier(node, ts.SyntaxKind.AbstractKeyword))
|
|
@@ -130,15 +143,23 @@ function findClasses(file, selector, text = (0, node_fs_1.readFileSync)(file, 'u
|
|
|
130
143
|
return false;
|
|
131
144
|
if (selector.decorators && !selector.decorators.some((name) => decorators.includes(name)))
|
|
132
145
|
return false;
|
|
133
|
-
|
|
146
|
+
if (selector.nameGlob && !selector.nameGlob.test(className))
|
|
147
|
+
return false;
|
|
148
|
+
if (selector.excludeClasses?.includes(className))
|
|
149
|
+
return false;
|
|
150
|
+
if (selector.excludeNameGlob?.test(className))
|
|
151
|
+
return false;
|
|
152
|
+
return true;
|
|
134
153
|
})
|
|
135
154
|
.sort((a, b) => a.node.pos - b.node.pos)
|
|
136
|
-
.map(({ exportName, className, decorators }) => ({
|
|
155
|
+
.map(({ exportName, className, decorators, decoratorCalls, tokens }) => ({
|
|
137
156
|
file,
|
|
138
157
|
exportName,
|
|
139
158
|
localName: exportName === 'default' ? className : exportName,
|
|
140
159
|
className,
|
|
141
160
|
isDefault: exportName === 'default',
|
|
142
161
|
decorators,
|
|
162
|
+
decoratorCalls,
|
|
163
|
+
tokens,
|
|
143
164
|
}));
|
|
144
165
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ts-ioc-container/bundler",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Bundles your dependencies into a single container module for ts-ioc-container: the `tic` CLI scans folders (by path or tsconfig alias) and generates one typed bundle that registers every class — `container.useModule(new AppBundle())`.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
package/tic.schema.json
CHANGED
|
@@ -99,6 +99,29 @@
|
|
|
99
99
|
"type": "string",
|
|
100
100
|
"minLength": 1,
|
|
101
101
|
"description": "Glob the class name must match, e.g. \"*Service\". An anonymous default export is named after its file."
|
|
102
|
+
},
|
|
103
|
+
"excludeClasses": {
|
|
104
|
+
"type": "array",
|
|
105
|
+
"minItems": 1,
|
|
106
|
+
"items": {
|
|
107
|
+
"type": "string",
|
|
108
|
+
"minLength": 1
|
|
109
|
+
},
|
|
110
|
+
"description": "Class names to drop after selection, e.g. one test double sitting next to the real registration in the same file."
|
|
111
|
+
},
|
|
112
|
+
"excludeNameGlob": {
|
|
113
|
+
"type": "string",
|
|
114
|
+
"minLength": 1,
|
|
115
|
+
"description": "Glob the class name must NOT match, e.g. \"*Mock\". An anonymous default export is named after its file."
|
|
116
|
+
},
|
|
117
|
+
"excludeAliases": {
|
|
118
|
+
"type": "array",
|
|
119
|
+
"minItems": 1,
|
|
120
|
+
"items": {
|
|
121
|
+
"type": "string",
|
|
122
|
+
"minLength": 1
|
|
123
|
+
},
|
|
124
|
+
"description": "tsconfig paths aliases to drop after selection, e.g. [\"services\"] for a `services/*` (or `@services/*`) alias. A class whose file resolves through one of these aliases is dropped. Names may omit a leading `@` and a trailing `/*`."
|
|
102
125
|
}
|
|
103
126
|
}
|
|
104
127
|
},
|
package/typings/ImportPaths.d.ts
CHANGED
|
@@ -29,9 +29,18 @@ export declare class ImportPaths {
|
|
|
29
29
|
resolveNamespace(namespace: string, baseDir: string): string;
|
|
30
30
|
/** The specifier `fromFile` imports `toFile` by: its most specific alias, else a relative path. */
|
|
31
31
|
specifier(fromFile: string, toFile: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* The tsconfig `paths` alias `file` is covered by, normalized to its name
|
|
34
|
+
* (`@app/services/*` -> `app/services`), or `undefined` when no alias covers it.
|
|
35
|
+
* Used to drop classes by alias (`select.excludeAliases`).
|
|
36
|
+
*/
|
|
37
|
+
aliasName(file: string): string | undefined;
|
|
38
|
+
private bestAlias;
|
|
32
39
|
private aliasTargets;
|
|
33
40
|
/** A longer matched target prefix is a more specific alias; an exact file alias beats every wildcard. */
|
|
34
41
|
private matchAlias;
|
|
35
42
|
}
|
|
43
|
+
/** The name of a `paths` alias: `@app/services/*` and `@app/services` both become `app/services`. */
|
|
44
|
+
export declare function normalizeAliasName(pattern: string): string;
|
|
36
45
|
export {};
|
|
37
46
|
//# sourceMappingURL=ImportPaths.d.ts.map
|
package/typings/build.d.ts
CHANGED
|
@@ -27,7 +27,11 @@ export interface BuildResult {
|
|
|
27
27
|
/** Absolute path of the config that was built. */
|
|
28
28
|
config: string;
|
|
29
29
|
outputs: OutputResult[];
|
|
30
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Non-fatal problems found while building, e.g. an `exclude` that drops a default
|
|
32
|
+
* test glob or two selected classes passing the same decorator token. Each warning
|
|
33
|
+
* names the bundle field it belongs to.
|
|
34
|
+
*/
|
|
31
35
|
warnings: string[];
|
|
32
36
|
}
|
|
33
37
|
/**
|
package/typings/config.d.ts
CHANGED
|
@@ -12,12 +12,28 @@ export interface ClassSelector {
|
|
|
12
12
|
decorators?: string[];
|
|
13
13
|
/** A glob the class name must match, e.g. `*Service`. An anonymous default export is named after its file. */
|
|
14
14
|
nameGlob?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Class names to drop after selection, e.g. one test double sitting next to the
|
|
17
|
+
* real registration in the same file. Unlike `filterExports` this needs no file.
|
|
18
|
+
*/
|
|
19
|
+
excludeClasses?: string[];
|
|
20
|
+
/** A glob the class name must NOT match, e.g. `*Mock`. An anonymous default export is named after its file. */
|
|
21
|
+
excludeNameGlob?: string;
|
|
22
|
+
/**
|
|
23
|
+
* tsconfig `paths` aliases to drop after selection: a class whose file resolves
|
|
24
|
+
* through one of these aliases is dropped, e.g. `["services"]` for a `services/*`
|
|
25
|
+
* (or `@services/*`) alias. Names may omit a leading `@` and a trailing `/*`.
|
|
26
|
+
*/
|
|
27
|
+
excludeAliases?: string[];
|
|
15
28
|
}
|
|
16
|
-
/** A {@link ClassSelector} with its defaults filled in and its
|
|
29
|
+
/** A {@link ClassSelector} with its defaults filled in and its globs compiled. */
|
|
17
30
|
export interface ResolvedSelector {
|
|
18
31
|
export: ExportKind;
|
|
19
32
|
decorators?: string[];
|
|
20
33
|
nameGlob?: RegExp;
|
|
34
|
+
excludeClasses?: string[];
|
|
35
|
+
excludeNameGlob?: RegExp;
|
|
36
|
+
excludeAliases?: string[];
|
|
21
37
|
}
|
|
22
38
|
export interface NamespaceConfig {
|
|
23
39
|
/** A folder relative to the config file (`./src/services`) or a tsconfig `paths` alias (`@app/services`). */
|
package/typings/scan.d.ts
CHANGED
|
@@ -11,6 +11,21 @@ export interface DiscoveredClass {
|
|
|
11
11
|
isDefault: boolean;
|
|
12
12
|
/** Names of the class's decorators, renamed imports resolved. */
|
|
13
13
|
decorators: string[];
|
|
14
|
+
/**
|
|
15
|
+
* Plain-identifier first arguments of the class's decorators, e.g. the token in
|
|
16
|
+
* `@repository(Token)`; an empty array when a decorator takes none. Syntax only:
|
|
17
|
+
* used to warn when two classes pass the same identifier.
|
|
18
|
+
*/
|
|
19
|
+
tokens: string[];
|
|
20
|
+
/**
|
|
21
|
+
* Each class decorator's name and the source text of its arguments, e.g.
|
|
22
|
+
* `perPage` with `["'stations'"]`. Syntax only: lets the token-collision check
|
|
23
|
+
* tell scope-gated classes (`@perPage('a')` vs `@perPage('b')`) apart.
|
|
24
|
+
*/
|
|
25
|
+
decoratorCalls: {
|
|
26
|
+
name: string;
|
|
27
|
+
args: string[];
|
|
28
|
+
}[];
|
|
14
29
|
}
|
|
15
30
|
/** Source files of `dir`, sorted by path so the generated output is stable across machines. */
|
|
16
31
|
export declare function listSourceFiles(dir: string, recursive: boolean, isExcluded: (file: string) => boolean): string[];
|