@ts-ioc-container/bundler 1.0.0 → 1.2.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 +5 -2
- package/README.md +60 -5
- package/cjm/build.js +27 -4
- package/cjm/cli.js +3 -1
- package/cjm/config.js +35 -5
- package/cjm/scan.js +14 -2
- package/package.json +1 -1
- package/tic.schema.json +22 -1
- package/typings/build.d.ts +6 -0
- package/typings/config.d.ts +19 -1
- package/typings/scan.d.ts +6 -0
package/AGENTS.md
CHANGED
|
@@ -13,8 +13,11 @@ 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 }`
|
|
17
|
+
narrows it; positive criteria must all hold and then `excludeClasses` (exact
|
|
18
|
+
names) and `excludeNameGlob` (glob) drop classes. Decorators match by name, so
|
|
19
|
+
composed ones must be listed. The build warns when two selected classes pass the
|
|
20
|
+
same plain-identifier first decorator argument (a same-token heuristic).
|
|
18
21
|
- Never edit a `*.bundle.ts` by hand — change the classes or the config and
|
|
19
22
|
rerun `tic build`.
|
|
20
23
|
- Files take part when they match no `exclude` glob **and** an
|
package/README.md
CHANGED
|
@@ -56,9 +56,32 @@ below use the shortcut.
|
|
|
56
56
|
| `bundles[].namespaces` | — | Folders to scan; `{ path, recursive }` to stop at the folder itself |
|
|
57
57
|
| `bundles[].select` | every exported class | Which classes of a file are registered — see [Selecting classes](#selecting-classes) |
|
|
58
58
|
| `bundles[].exclude` | test files, `__tests__/`, `node_modules/` | Globs (relative to the config) never scanned; replaces the default |
|
|
59
|
+
| `bundles[].additionalExclude`| — | Globs never scanned, added on top of `exclude` (or the defaults) — see [Excluding files](#excluding-files) |
|
|
59
60
|
| `bundles[].filterExports` | `tic.exports.*` next to the config | File exporting an `ExportPredicate` — see [Filtering classes](#filtering-classes-after-parsing) |
|
|
60
61
|
| `bundles[].include` | `tic.include.*` next to the config | File exporting an `InclusionPredicate` — see [Including files](#including-files-with-a-predicate) |
|
|
61
62
|
|
|
63
|
+
## Excluding files
|
|
64
|
+
|
|
65
|
+
By default test files, `__tests__/` and `node_modules/` are never scanned. To
|
|
66
|
+
add one exclusion without restating those defaults, use `additionalExclude`:
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"bundles": [
|
|
71
|
+
{
|
|
72
|
+
"output": "src/di/app.bundle.ts",
|
|
73
|
+
"namespaces": ["@app/services"],
|
|
74
|
+
"additionalExclude": ["frontend/api/generated/**", "**/Mock*.ts"]
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
A non-empty `exclude` replaces the defaults; if it omits a default glob the
|
|
81
|
+
build warns, since test classes usually carry the same `@register` decorators as
|
|
82
|
+
production ones. `exclude: []` deliberately scans everything, including tests —
|
|
83
|
+
combine it with `include` when only some tests should join.
|
|
84
|
+
|
|
62
85
|
## Selecting classes
|
|
63
86
|
|
|
64
87
|
By default every exported, non-abstract class of a scanned file is registered.
|
|
@@ -72,14 +95,46 @@ By default every exported, non-abstract class of a scanned file is registered.
|
|
|
72
95
|
}
|
|
73
96
|
```
|
|
74
97
|
|
|
75
|
-
| Criterion
|
|
76
|
-
|
|
|
77
|
-
| `export`
|
|
78
|
-
| `decorators`
|
|
79
|
-
| `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"` |
|
|
80
105
|
|
|
81
106
|
Abstract and non-exported classes are never registered.
|
|
82
107
|
|
|
108
|
+
### Excluding a single class
|
|
109
|
+
|
|
110
|
+
When a real implementation and a test-only stand-in are both decorated and both
|
|
111
|
+
bind the same token, registration is last-wins and the stand-in silently wins. A
|
|
112
|
+
`tic.exports.*` predicate works, but forces a hand-maintained name list in code;
|
|
113
|
+
`excludeClasses` and `excludeNameGlob` drop a class right in the config:
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
"select": {
|
|
117
|
+
"decorators": ["repository", "service"],
|
|
118
|
+
"excludeClasses": ["MockDashboardRepository"],
|
|
119
|
+
"excludeNameGlob": "*Fake"
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Both apply after every other criterion, so they can be combined with
|
|
124
|
+
`decorators` and `nameGlob` freely. Reach for `filterExports` when the rule
|
|
125
|
+
needs more than the class name.
|
|
126
|
+
|
|
127
|
+
As a safety net, the build warns when two selected classes pass the same
|
|
128
|
+
plain-identifier first argument to a decorator — usually the binding token, as in
|
|
129
|
+
`@repository(IDashboardRepositoryToken)`. The check is syntactic (aliased imports
|
|
130
|
+
are not resolved), so it can only be a heuristic:
|
|
131
|
+
|
|
132
|
+
```text
|
|
133
|
+
tic: warning: bundles[0]: decorator token "IDashboardRepositoryToken" is passed by
|
|
134
|
+
HttpDashboardRepository, MockDashboardRepository; registration is last-wins,
|
|
135
|
+
exclude one with select.excludeClasses
|
|
136
|
+
```
|
|
137
|
+
|
|
83
138
|
## Including files with a predicate
|
|
84
139
|
|
|
85
140
|
When globs aren't enough, decide in code which files take part:
|
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);
|
|
@@ -86,5 +93,21 @@ function generate(config, bundle, paths, { include, filterExports }) {
|
|
|
86
93
|
namespaces: bundle.namespaces.map((ns) => ns.path),
|
|
87
94
|
classes,
|
|
88
95
|
});
|
|
89
|
-
return { bundle, content, registrations: classes.length };
|
|
96
|
+
return { bundle, content, registrations: classes.length, warnings: tokenCollisions(classes, field) };
|
|
97
|
+
}
|
|
98
|
+
function tokenCollisions(classes, field) {
|
|
99
|
+
const owners = new Map();
|
|
100
|
+
for (const { className, tokens } of classes) {
|
|
101
|
+
for (const token of tokens) {
|
|
102
|
+
const names = owners.get(token) ?? [];
|
|
103
|
+
if (!names.includes(className))
|
|
104
|
+
names.push(className);
|
|
105
|
+
owners.set(token, names);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return [...owners]
|
|
109
|
+
.filter(([, names]) => names.length > 1)
|
|
110
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
111
|
+
.map(([token, names]) => `${field}: decorator token "${token}" is passed by ${names.join(', ')}; ` +
|
|
112
|
+
`registration is last-wins, exclude one with select.excludeClasses`);
|
|
90
113
|
}
|
package/cjm/cli.js
CHANGED
|
@@ -62,7 +62,9 @@ function run(argv, io = { cwd: process.cwd(), stdout: console.log, stderr: conso
|
|
|
62
62
|
return 1;
|
|
63
63
|
}
|
|
64
64
|
const { config, check } = parseBuildArgs(rest);
|
|
65
|
-
const { outputs } = (0, build_1.build)({ config, check, cwd: io.cwd });
|
|
65
|
+
const { outputs, warnings } = (0, build_1.build)({ config, check, cwd: io.cwd });
|
|
66
|
+
for (const warning of warnings)
|
|
67
|
+
io.stderr(`tic: warning: ${warning}`);
|
|
66
68
|
for (const { status, file, registrations } of outputs) {
|
|
67
69
|
io.stdout(`${VERB[status].padEnd(10)}${node_path_1.default.relative(io.cwd, file)} (${plural(registrations)})`);
|
|
68
70
|
}
|
package/cjm/config.js
CHANGED
|
@@ -38,7 +38,7 @@ 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(['export', 'decorators', 'nameGlob']);
|
|
41
|
+
const SELECTOR_FIELDS = new Set(['export', 'decorators', 'nameGlob', 'excludeClasses', 'excludeNameGlob']);
|
|
42
42
|
function toSelector(value, field) {
|
|
43
43
|
if (value === undefined)
|
|
44
44
|
return { export: 'any' };
|
|
@@ -47,7 +47,7 @@ function toSelector(value, field) {
|
|
|
47
47
|
const unknown = Object.keys(value).find((key) => !SELECTOR_FIELDS.has(key));
|
|
48
48
|
if (unknown)
|
|
49
49
|
throw new errors_1.TicConfigError(`${field}.${unknown}: unknown field`);
|
|
50
|
-
const { export: kind, decorators, nameGlob } = value;
|
|
50
|
+
const { export: kind, decorators, nameGlob, excludeClasses, excludeNameGlob } = value;
|
|
51
51
|
if (kind !== undefined && kind !== 'any' && kind !== 'named' && kind !== 'default') {
|
|
52
52
|
return fail(`${field}.export`, '"any", "named" or "default"');
|
|
53
53
|
}
|
|
@@ -56,12 +56,24 @@ function toSelector(value, field) {
|
|
|
56
56
|
}
|
|
57
57
|
if (nameGlob !== undefined && !isNonEmptyString(nameGlob))
|
|
58
58
|
return fail(`${field}.nameGlob`, 'a non-empty string');
|
|
59
|
-
|
|
59
|
+
if (excludeClasses !== undefined && !(isStringArray(excludeClasses) && excludeClasses.length > 0)) {
|
|
60
|
+
return fail(`${field}.excludeClasses`, 'a non-empty array of strings');
|
|
61
|
+
}
|
|
62
|
+
if (excludeNameGlob !== undefined && !isNonEmptyString(excludeNameGlob)) {
|
|
63
|
+
return fail(`${field}.excludeNameGlob`, 'a non-empty string');
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
export: kind ?? 'any',
|
|
67
|
+
decorators,
|
|
68
|
+
nameGlob: nameGlob === undefined ? undefined : (0, glob_1.globToRegExp)(nameGlob),
|
|
69
|
+
excludeClasses,
|
|
70
|
+
excludeNameGlob: excludeNameGlob === undefined ? undefined : (0, glob_1.globToRegExp)(excludeNameGlob),
|
|
71
|
+
};
|
|
60
72
|
}
|
|
61
73
|
function toBundle(value, field, dir) {
|
|
62
74
|
if (!isObject(value))
|
|
63
75
|
return fail(field, 'an object');
|
|
64
|
-
const { output, name, namespaces, select, exclude, include, filterExports } = value;
|
|
76
|
+
const { output, name, namespaces, select, exclude, additionalExclude, include, filterExports } = value;
|
|
65
77
|
if (!isNonEmptyString(output))
|
|
66
78
|
return fail(`${field}.output`, 'a non-empty string');
|
|
67
79
|
if (name !== undefined && !isIdentifier(name))
|
|
@@ -70,6 +82,9 @@ function toBundle(value, field, dir) {
|
|
|
70
82
|
return fail(`${field}.namespaces`, 'a non-empty array');
|
|
71
83
|
if (exclude !== undefined && !isStringArray(exclude))
|
|
72
84
|
return fail(`${field}.exclude`, 'an array of strings');
|
|
85
|
+
if (additionalExclude !== undefined && !isStringArray(additionalExclude)) {
|
|
86
|
+
return fail(`${field}.additionalExclude`, 'an array of strings');
|
|
87
|
+
}
|
|
73
88
|
if (include !== undefined && !isNonEmptyString(include))
|
|
74
89
|
return fail(`${field}.include`, 'a non-empty string');
|
|
75
90
|
if (filterExports !== undefined && !isNonEmptyString(filterExports)) {
|
|
@@ -80,11 +95,25 @@ function toBundle(value, field, dir) {
|
|
|
80
95
|
name: name ?? exports.DEFAULT_BUNDLE_NAME,
|
|
81
96
|
namespaces: namespaces.map((ns, i) => toNamespace(ns, `${field}.namespaces[${i}]`)),
|
|
82
97
|
select: toSelector(select, `${field}.select`),
|
|
83
|
-
exclude: exclude ?? exports.DEFAULT_EXCLUDE,
|
|
98
|
+
exclude: [...(exclude ?? exports.DEFAULT_EXCLUDE), ...(additionalExclude ?? [])],
|
|
84
99
|
include: include === undefined ? undefined : node_path_1.default.resolve(dir, include),
|
|
85
100
|
filterExports: filterExports === undefined ? undefined : node_path_1.default.resolve(dir, filterExports),
|
|
86
101
|
};
|
|
87
102
|
}
|
|
103
|
+
function excludeWarnings(value, field) {
|
|
104
|
+
if (!isObject(value))
|
|
105
|
+
return [];
|
|
106
|
+
const exclude = value.exclude;
|
|
107
|
+
if (!isStringArray(exclude) || exclude.length === 0)
|
|
108
|
+
return [];
|
|
109
|
+
const missing = exports.DEFAULT_EXCLUDE.filter((glob) => !exclude.includes(glob));
|
|
110
|
+
if (missing.length === 0)
|
|
111
|
+
return [];
|
|
112
|
+
return [
|
|
113
|
+
`${field}.exclude replaces the default excludes and omits ${missing.map((glob) => `"${glob}"`).join(', ')}; ` +
|
|
114
|
+
`add them back or use ${field}.additionalExclude`,
|
|
115
|
+
];
|
|
116
|
+
}
|
|
88
117
|
function resolveConfig(content, file) {
|
|
89
118
|
const dir = node_path_1.default.dirname(file);
|
|
90
119
|
if (!isObject(content))
|
|
@@ -102,6 +131,7 @@ function resolveConfig(content, file) {
|
|
|
102
131
|
tsconfig: { file: node_path_1.default.resolve(dir, tsconfig ?? 'tsconfig.json'), required: tsconfig !== undefined },
|
|
103
132
|
importExtension,
|
|
104
133
|
bundles: bundles.map((m, i) => toBundle(m, `bundles[${i}]`, dir)),
|
|
134
|
+
warnings: bundles.flatMap((m, i) => excludeWarnings(m, `bundles[${i}]`)),
|
|
105
135
|
};
|
|
106
136
|
}
|
|
107
137
|
function loadConfig(file) {
|
package/cjm/scan.js
CHANGED
|
@@ -114,6 +114,10 @@ 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 tokensOf = (node) => (ts.getDecorators(node) ?? [])
|
|
118
|
+
.map((d) => (ts.isCallExpression(d.expression) ? d.expression.arguments[0] : undefined))
|
|
119
|
+
.filter((arg) => !!arg && ts.isIdentifier(arg))
|
|
120
|
+
.map((arg) => arg.text);
|
|
117
121
|
return exported
|
|
118
122
|
.map(({ node, local, exportName }) => ({ node: node ?? (local ? classes.get(local) : undefined), exportName }))
|
|
119
123
|
.filter((e) => !!e.node)
|
|
@@ -122,6 +126,7 @@ function findClasses(file, selector, text = (0, node_fs_1.readFileSync)(file, 'u
|
|
|
122
126
|
exportName,
|
|
123
127
|
className: node.name?.text ?? nameFromFile(file),
|
|
124
128
|
decorators: decoratorsOf(node),
|
|
129
|
+
tokens: tokensOf(node),
|
|
125
130
|
}))
|
|
126
131
|
.filter(({ node, exportName, className, decorators }) => {
|
|
127
132
|
if (hasModifier(node, ts.SyntaxKind.AbstractKeyword))
|
|
@@ -130,15 +135,22 @@ function findClasses(file, selector, text = (0, node_fs_1.readFileSync)(file, 'u
|
|
|
130
135
|
return false;
|
|
131
136
|
if (selector.decorators && !selector.decorators.some((name) => decorators.includes(name)))
|
|
132
137
|
return false;
|
|
133
|
-
|
|
138
|
+
if (selector.nameGlob && !selector.nameGlob.test(className))
|
|
139
|
+
return false;
|
|
140
|
+
if (selector.excludeClasses?.includes(className))
|
|
141
|
+
return false;
|
|
142
|
+
if (selector.excludeNameGlob?.test(className))
|
|
143
|
+
return false;
|
|
144
|
+
return true;
|
|
134
145
|
})
|
|
135
146
|
.sort((a, b) => a.node.pos - b.node.pos)
|
|
136
|
-
.map(({ exportName, className, decorators }) => ({
|
|
147
|
+
.map(({ exportName, className, decorators, tokens }) => ({
|
|
137
148
|
file,
|
|
138
149
|
exportName,
|
|
139
150
|
localName: exportName === 'default' ? className : exportName,
|
|
140
151
|
className,
|
|
141
152
|
isDefault: exportName === 'default',
|
|
142
153
|
decorators,
|
|
154
|
+
tokens,
|
|
143
155
|
}));
|
|
144
156
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ts-ioc-container/bundler",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.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,20 @@
|
|
|
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."
|
|
102
116
|
}
|
|
103
117
|
}
|
|
104
118
|
},
|
|
@@ -115,7 +129,14 @@
|
|
|
115
129
|
"**/__tests__/**",
|
|
116
130
|
"**/node_modules/**"
|
|
117
131
|
],
|
|
118
|
-
"description": "Globs, relative to this file, of files never scanned. Replaces the default when given."
|
|
132
|
+
"description": "Globs, relative to this file, of files never scanned. Replaces the default when given; prefer additionalExclude to add one exclusion without restating the defaults. A non-empty list that omits a default glob makes `tic build` warn."
|
|
133
|
+
},
|
|
134
|
+
"additionalExclude": {
|
|
135
|
+
"type": "array",
|
|
136
|
+
"items": {
|
|
137
|
+
"type": "string"
|
|
138
|
+
},
|
|
139
|
+
"description": "Globs, relative to this file, of files never scanned, added on top of `exclude` (or the default excludes when `exclude` is omitted)."
|
|
119
140
|
},
|
|
120
141
|
"include": {
|
|
121
142
|
"type": "string",
|
package/typings/build.d.ts
CHANGED
|
@@ -27,6 +27,12 @@ export interface BuildResult {
|
|
|
27
27
|
/** Absolute path of the config that was built. */
|
|
28
28
|
config: string;
|
|
29
29
|
outputs: OutputResult[];
|
|
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
|
+
*/
|
|
35
|
+
warnings: string[];
|
|
30
36
|
}
|
|
31
37
|
/**
|
|
32
38
|
* Generates one bundle per config entry. Every bundle is generated before any
|
package/typings/config.d.ts
CHANGED
|
@@ -12,12 +12,21 @@ 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;
|
|
15
22
|
}
|
|
16
|
-
/** A {@link ClassSelector} with its defaults filled in and its
|
|
23
|
+
/** A {@link ClassSelector} with its defaults filled in and its globs compiled. */
|
|
17
24
|
export interface ResolvedSelector {
|
|
18
25
|
export: ExportKind;
|
|
19
26
|
decorators?: string[];
|
|
20
27
|
nameGlob?: RegExp;
|
|
28
|
+
excludeClasses?: string[];
|
|
29
|
+
excludeNameGlob?: RegExp;
|
|
21
30
|
}
|
|
22
31
|
export interface NamespaceConfig {
|
|
23
32
|
/** A folder relative to the config file (`./src/services`) or a tsconfig `paths` alias (`@app/services`). */
|
|
@@ -35,6 +44,12 @@ export interface BundleConfig {
|
|
|
35
44
|
select?: ClassSelector;
|
|
36
45
|
/** Globs, relative to the config file, of files never scanned. Replaces {@link DEFAULT_EXCLUDE} when given. */
|
|
37
46
|
exclude?: string[];
|
|
47
|
+
/**
|
|
48
|
+
* Globs, relative to the config file, of files never scanned, added on top of
|
|
49
|
+
* `exclude` (or {@link DEFAULT_EXCLUDE} when `exclude` is omitted). Use this to
|
|
50
|
+
* add one exclusion without restating the defaults.
|
|
51
|
+
*/
|
|
52
|
+
additionalExclude?: string[];
|
|
38
53
|
/**
|
|
39
54
|
* A file exporting an `InclusionPredicate`, relative to the config file. Overrides the
|
|
40
55
|
* conventional `tic.include.*` next to the config.
|
|
@@ -68,12 +83,15 @@ export interface ResolvedConfig {
|
|
|
68
83
|
};
|
|
69
84
|
importExtension?: string;
|
|
70
85
|
bundles: ResolvedBundle[];
|
|
86
|
+
/** Non-fatal problems found while resolving, e.g. an `exclude` that drops a default test glob. */
|
|
87
|
+
warnings: string[];
|
|
71
88
|
}
|
|
72
89
|
export interface ResolvedBundle {
|
|
73
90
|
output: string;
|
|
74
91
|
name: string;
|
|
75
92
|
namespaces: Required<NamespaceConfig>[];
|
|
76
93
|
select: ResolvedSelector;
|
|
94
|
+
/** The effective list: the configured `exclude` (or the defaults) followed by `additionalExclude`. */
|
|
77
95
|
exclude: string[];
|
|
78
96
|
/** Absolute path of the bundle's own predicate file, when it names one. */
|
|
79
97
|
include?: string;
|
package/typings/scan.d.ts
CHANGED
|
@@ -11,6 +11,12 @@ 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[];
|
|
14
20
|
}
|
|
15
21
|
/** Source files of `dir`, sorted by path so the generated output is stable across machines. */
|
|
16
22
|
export declare function listSourceFiles(dir: string, recursive: boolean, isExcluded: (file: string) => boolean): string[];
|