@ts-ioc-container/bundler 1.5.0 → 2.1.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 +47 -31
- package/README.md +157 -280
- package/cjm/app.js +39 -0
- package/cjm/bin.js +2 -2
- package/cjm/cli/Application.js +58 -0
- package/cjm/cli/IErrorHandler.js +5 -0
- package/cjm/cli/decorators.js +9 -0
- package/cjm/cli/index.js +17 -0
- package/cjm/cli/options.js +25 -0
- package/cjm/cli/usage.js +19 -0
- package/cjm/domain/CliIo.js +7 -0
- package/cjm/domain/GlobalConfig.js +7 -0
- package/cjm/exceptions/DomainException.js +54 -0
- package/cjm/exceptions/ExceptionHandler.js +38 -0
- package/cjm/features/build/BuildConfig.js +183 -0
- package/cjm/features/build/BuildController.js +91 -0
- package/cjm/features/build/BuildModule.js +16 -0
- package/cjm/{ImportPaths.js → features/build/domain/ImportPaths.js} +7 -29
- package/cjm/features/build/domain/configFiles.js +35 -0
- package/cjm/{emit.js → features/build/domain/emit.js} +10 -9
- package/cjm/{scan.js → features/build/domain/scan.js} +6 -3
- package/cjm/features/build/domain/tokenCollisions.js +29 -0
- package/cjm/features/build/domain/tsconfig.js +78 -0
- package/cjm/features/build/services/BundleBuilder.js +116 -0
- package/cjm/features/build/services/TicConfigService.js +68 -0
- package/cjm/features/help/HelpController.js +39 -0
- package/cjm/features/help/HelpModule.js +12 -0
- package/cjm/features/help/VersionController.js +45 -0
- package/cjm/index.js +87 -28
- package/cjm/modules/CommonModule.js +28 -0
- package/cjm/schema/ticConfigSchema.js +24 -0
- package/cjm/services/ConsoleLogger.js +36 -0
- package/cjm/services/HandlebarsRenderService.js +33 -0
- package/cjm/services/NodeFileSystemService.js +32 -0
- package/cjm/services/OutputService.js +33 -0
- package/package.json +10 -7
- package/tic.schema.json +118 -147
- package/tpl/index.cjs +36 -16
- package/typings/app.d.ts +24 -0
- package/typings/cli/Application.d.ts +16 -0
- package/typings/cli/Application.d.ts.map +1 -0
- package/typings/cli/IErrorHandler.d.ts +7 -0
- package/typings/cli/IErrorHandler.d.ts.map +1 -0
- package/typings/cli/decorators.d.ts +10 -0
- package/typings/cli/decorators.d.ts.map +1 -0
- package/typings/cli/index.d.ts +6 -0
- package/typings/cli/index.d.ts.map +1 -0
- package/typings/cli/options.d.ts +23 -0
- package/typings/cli/options.d.ts.map +1 -0
- package/typings/cli/usage.d.ts +2 -0
- package/typings/cli/usage.d.ts.map +1 -0
- package/typings/domain/CliIo.d.ts +10 -0
- package/typings/domain/CliIo.d.ts.map +1 -0
- package/typings/domain/GlobalConfig.d.ts +10 -0
- package/typings/domain/GlobalConfig.d.ts.map +1 -0
- package/typings/exceptions/DomainException.d.ts +37 -0
- package/typings/exceptions/DomainException.d.ts.map +1 -0
- package/typings/exceptions/ExceptionHandler.d.ts +12 -0
- package/typings/exceptions/ExceptionHandler.d.ts.map +1 -0
- package/typings/features/build/BuildConfig.d.ts +179 -0
- package/typings/features/build/BuildConfig.d.ts.map +1 -0
- package/typings/features/build/BuildController.d.ts +32 -0
- package/typings/features/build/BuildController.d.ts.map +1 -0
- package/typings/features/build/BuildModule.d.ts +5 -0
- package/typings/features/build/BuildModule.d.ts.map +1 -0
- package/typings/{ImportPaths.d.ts → features/build/domain/ImportPaths.d.ts} +4 -16
- package/typings/features/build/domain/ImportPaths.d.ts.map +1 -0
- package/typings/features/build/domain/configFiles.d.ts +13 -0
- package/typings/features/build/domain/configFiles.d.ts.map +1 -0
- package/typings/features/build/domain/emit.d.ts +42 -0
- package/typings/features/build/domain/emit.d.ts.map +1 -0
- package/typings/features/build/domain/glob.d.ts.map +1 -0
- package/typings/{scan.d.ts → features/build/domain/scan.d.ts} +4 -2
- package/typings/features/build/domain/scan.d.ts.map +1 -0
- package/typings/features/build/domain/tokenCollisions.d.ts +14 -0
- package/typings/features/build/domain/tokenCollisions.d.ts.map +1 -0
- package/typings/features/build/domain/tsconfig.d.ts +25 -0
- package/typings/features/build/domain/tsconfig.d.ts.map +1 -0
- package/typings/features/build/services/BundleBuilder.d.ts +68 -0
- package/typings/features/build/services/BundleBuilder.d.ts.map +1 -0
- package/typings/features/build/services/TicConfigService.d.ts +37 -0
- package/typings/features/build/services/TicConfigService.d.ts.map +1 -0
- package/typings/features/help/HelpController.d.ts +8 -0
- package/typings/features/help/HelpController.d.ts.map +1 -0
- package/typings/features/help/HelpModule.d.ts +5 -0
- package/typings/features/help/HelpModule.d.ts.map +1 -0
- package/typings/features/help/VersionController.d.ts +8 -0
- package/typings/features/help/VersionController.d.ts.map +1 -0
- package/typings/index.d.ts +25 -11
- package/typings/modules/CommonModule.d.ts +9 -0
- package/typings/modules/CommonModule.d.ts.map +1 -0
- package/typings/schema/ticConfigSchema.d.ts +7 -0
- package/typings/schema/ticConfigSchema.d.ts.map +1 -0
- package/typings/services/ConsoleLogger.d.ts +20 -0
- package/typings/services/ConsoleLogger.d.ts.map +1 -0
- package/typings/services/HandlebarsRenderService.d.ts +23 -0
- package/typings/services/HandlebarsRenderService.d.ts.map +1 -0
- package/typings/services/NodeFileSystemService.d.ts +21 -0
- package/typings/services/NodeFileSystemService.d.ts.map +1 -0
- package/typings/services/OutputService.d.ts +13 -0
- package/typings/services/OutputService.d.ts.map +1 -0
- package/cjm/build.js +0 -132
- package/cjm/cli.js +0 -84
- package/cjm/config.js +0 -162
- package/cjm/errors.js +0 -15
- package/cjm/exportPredicate.js +0 -13
- package/cjm/inclusion.js +0 -24
- package/cjm/predicateFile.js +0 -30
- package/cjm/render.js +0 -15
- package/cjm/utils.js +0 -2
- package/typings/build.d.ts +0 -45
- package/typings/cli.d.ts +0 -8
- package/typings/config.d.ts +0 -123
- package/typings/emit.d.ts +0 -18
- package/typings/errors.d.ts +0 -13
- package/typings/exportPredicate.d.ts +0 -36
- package/typings/inclusion.d.ts +0 -42
- package/typings/predicateFile.d.ts +0 -16
- package/typings/render.d.ts +0 -8
- package/typings/utils.d.ts +0 -3
- /package/cjm/{glob.js → features/build/domain/glob.js} +0 -0
- /package/typings/{glob.d.ts → features/build/domain/glob.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @ts-ioc-container/bundler
|
|
2
2
|
|
|
3
3
|
**Bundles your dependencies into a single container module.** `tic build` scans
|
|
4
|
-
your
|
|
5
|
-
registers every class in them, ready for
|
|
4
|
+
what your tsconfig compiles — or the folders you name, by path or tsconfig alias —
|
|
5
|
+
and generates one typed bundle that registers every class in them, ready for
|
|
6
6
|
[`ts-ioc-container`](https://www.npmjs.com/package/ts-ioc-container):
|
|
7
7
|
|
|
8
8
|
```ts
|
|
@@ -14,9 +14,8 @@ Adding a service means writing the class — no hand-maintained list of
|
|
|
14
14
|
TypeScript, generated at build time, so it type-checks, bundles and tree-shakes
|
|
15
15
|
like code you wrote yourself.
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
`tsconfig.json` `paths` alias (`@app/services`).
|
|
17
|
+
A bundle is configured like a tsconfig that `extends` yours: by default it
|
|
18
|
+
takes the files your tsconfig compiles, and narrows or overrides them.
|
|
20
19
|
|
|
21
20
|
## Install
|
|
22
21
|
|
|
@@ -30,347 +29,225 @@ below use the shortcut.
|
|
|
30
29
|
|
|
31
30
|
`typescript` (>= 5) is a peer dependency.
|
|
32
31
|
|
|
33
|
-
##
|
|
32
|
+
## Zero config
|
|
34
33
|
|
|
35
|
-
`
|
|
34
|
+
With a `tsconfig.json`, there is nothing to configure:
|
|
36
35
|
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
"$schema": "./node_modules/@ts-ioc-container/bundler/tic.schema.json",
|
|
40
|
-
"bundles": [
|
|
41
|
-
{
|
|
42
|
-
"output": "src/di/app.bundle.ts",
|
|
43
|
-
"name": "AppBundle",
|
|
44
|
-
"namespaces": ["@app/services", { "path": "./src/infra", "recursive": false }]
|
|
45
|
-
}
|
|
46
|
-
]
|
|
47
|
-
}
|
|
36
|
+
```bash
|
|
37
|
+
tic build
|
|
48
38
|
```
|
|
49
39
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
| `tsconfig` | `tsconfig.json` (may be absent) | Source of `paths` aliases and of the import extension |
|
|
53
|
-
| `importExtension` | `.js` under node16/nodenext, else none | Extension of generated imports |
|
|
54
|
-
| `bundles[].output` | — | The bundle file (convention: `*.bundle.ts`) |
|
|
55
|
-
| `bundles[].name` | `Bundle` | Name of the generated class, an `IContainerModule` |
|
|
56
|
-
| `bundles[].namespaces` | — | Folders to scan; `{ path, recursive }` to stop at the folder itself |
|
|
57
|
-
| `bundles[].select` | every exported class | Which classes of a file are registered — see [Selecting classes](#selecting-classes) |
|
|
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) |
|
|
60
|
-
| `bundles[].filterExports` | `tic.exports.*` next to the config | File exporting an `ExportPredicate` — see [Filtering classes](#filtering-classes-after-parsing) |
|
|
61
|
-
| `bundles[].include` | `tic.include.*` next to the config | File exporting an `InclusionPredicate` — see [Including files](#including-files-with-a-predicate) |
|
|
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
|
-
}
|
|
40
|
+
```text
|
|
41
|
+
wrote src/base.bundle.ts (12 registrations)
|
|
78
42
|
```
|
|
79
43
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
44
|
+
The bundle registers every exported class decorated with `@register` among the
|
|
45
|
+
files your `tsconfig.json` compiles, test files excluded, and lands at the root
|
|
46
|
+
of those sources — `rootDir`, else their common folder (`src/`), so `tsc`
|
|
47
|
+
compiles it too. It exports `BaseBundle`:
|
|
84
48
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
By default every exported, non-abstract class of a scanned file is registered.
|
|
88
|
-
`select` narrows that; a class must meet every criterion that is set:
|
|
49
|
+
```ts
|
|
50
|
+
import { BaseBundle } from './base.bundle';
|
|
89
51
|
|
|
90
|
-
|
|
91
|
-
"select": {
|
|
92
|
-
"export": "named",
|
|
93
|
-
"decorators": ["register", "repository"],
|
|
94
|
-
"nameGlob": "*Service"
|
|
95
|
-
}
|
|
52
|
+
const container = new Container().useModule(new BaseBundle());
|
|
96
53
|
```
|
|
97
54
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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 |
|
|
55
|
+
`tic build` works on the package it is invoked in: it walks up from the working
|
|
56
|
+
directory to the nearest `package.json` — the project root, or in a monorepo
|
|
57
|
+
the package — and uses the `tsconfig.json` there. It never falls back to a
|
|
58
|
+
workspace root's tsconfig.
|
|
106
59
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
### Excluding a single class
|
|
60
|
+
## Configure
|
|
110
61
|
|
|
111
|
-
|
|
112
|
-
|
|
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:
|
|
62
|
+
To change a default, add a config file next to your `tsconfig.json`. One config
|
|
63
|
+
file describes one bundle and is named `<name>.bundle.json` — `app.bundle.json`:
|
|
115
64
|
|
|
116
65
|
```json
|
|
117
|
-
|
|
118
|
-
"
|
|
119
|
-
"
|
|
120
|
-
"excludeNameGlob": "*Fake"
|
|
66
|
+
{
|
|
67
|
+
"$schema": "./node_modules/@ts-ioc-container/bundler/tic.schema.json",
|
|
68
|
+
"output": "src/di/app.bundle.ts"
|
|
121
69
|
}
|
|
122
70
|
```
|
|
123
71
|
|
|
124
|
-
|
|
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:
|
|
72
|
+
or, in YAML, `<name>.bundle.yaml` / `<name>.bundle.yml` — same fields, same schema:
|
|
130
73
|
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
74
|
+
```yaml
|
|
75
|
+
# yaml-language-server: $schema=./node_modules/@ts-ioc-container/bundler/tic.schema.json
|
|
76
|
+
output: src/di/app.bundle.ts
|
|
77
|
+
files:
|
|
78
|
+
include: ['**/*.service.ts']
|
|
135
79
|
```
|
|
136
80
|
|
|
137
|
-
An
|
|
138
|
-
|
|
81
|
+
An empty YAML file is a bundle with every setting at its default. Describing
|
|
82
|
+
one bundle in two formats (`app.bundle.json` and `app.bundle.yaml`) is an error.
|
|
139
83
|
|
|
140
|
-
|
|
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:
|
|
84
|
+
Every field is optional; `{}` is the zero-config bundle.
|
|
144
85
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
86
|
+
| Field | Default | Meaning |
|
|
87
|
+
| ----------------- | -------------------------------------- | --------------------------------------------------------------------------------------------- |
|
|
88
|
+
| `output` | `<root>/<name>.bundle.ts` | The bundle file; `<root>` as in [Zero config](#zero-config). Required without a tsconfig |
|
|
89
|
+
| `name` | the config's stem, else `base` | The bundle's name (`production.bundle.json` → `production`): names the default output and the class, `ProductionBundle` |
|
|
90
|
+
| `tags` | `[]` | Tags associated with the bundle |
|
|
91
|
+
| `extends` | `./tsconfig.json` (may be absent) | The tsconfig to build on: its file set, `paths` aliases and import extension; one named here must exist |
|
|
92
|
+
| `importExtension` | `.js` under node16/nodenext, else none | Extension of generated imports |
|
|
93
|
+
| `files` | what the tsconfig compiles, no tests | Which files are parsed — see [Selecting files](#selecting-files) |
|
|
94
|
+
| `classes` | exported classes with `@register` | Which classes of a parsed file are registered — see [Selecting classes](#selecting-classes) |
|
|
150
95
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
`@perPage('stations')` vs `@perPage('sessions')`, each scope registers its own
|
|
154
|
-
class and nothing is lost.
|
|
96
|
+
An unknown field is an error, so a misspelled or removed option never goes
|
|
97
|
+
unnoticed.
|
|
155
98
|
|
|
156
|
-
##
|
|
99
|
+
## Several bundles
|
|
157
100
|
|
|
158
|
-
|
|
101
|
+
Need more than one bundle — per environment, per app? Add a config per bundle.
|
|
102
|
+
`tic build` builds every `*.bundle.json`, `*.bundle.yaml` and `*.bundle.yml` at
|
|
103
|
+
the package root:
|
|
159
104
|
|
|
160
|
-
```
|
|
161
|
-
|
|
105
|
+
```text
|
|
106
|
+
production.bundle.json → src/di/production.bundle.ts
|
|
107
|
+
development.bundle.json → src/di/development.bundle.ts
|
|
108
|
+
test.bundle.json → src/di/test.bundle.ts
|
|
162
109
|
```
|
|
163
110
|
|
|
164
|
-
|
|
165
|
-
(`src/services/Logger.ts`). A file takes part only when it matches no `exclude`
|
|
166
|
-
glob **and** the predicate returns `true`.
|
|
167
|
-
|
|
168
|
-
By convention there is nothing to configure: put a `tic.include.ts` (or `.cjs`,
|
|
169
|
-
`.js`, `.mjs`, `.cts`, `.mts`) next to `tic.config.json` and its default export
|
|
170
|
-
applies to every bundle.
|
|
111
|
+
Each one can extend its own tsconfig and select its own files:
|
|
171
112
|
|
|
172
|
-
```
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
113
|
+
```jsonc
|
|
114
|
+
// production.bundle.json
|
|
115
|
+
{
|
|
116
|
+
"extends": "./tsconfig.production.json",
|
|
117
|
+
"files": { "exclude": ["**/*.development.ts", "**/*.test.ts", "**/*.spec.ts", "**/__tests__/**", "**/node_modules/**"] }
|
|
118
|
+
}
|
|
178
119
|
```
|
|
179
120
|
|
|
180
|
-
|
|
181
|
-
| ------------------------------------ | ------------- | ------------------------------ |
|
|
182
|
-
| `build({ include })` (programmatic) | every bundle | everything |
|
|
183
|
-
| `bundles[].include: "./path/file"` | that bundle | the conventional file |
|
|
184
|
-
| `tic.include.*` next to the config | every bundle | — (used when nothing is named) |
|
|
121
|
+
It writes `src/production.bundle.ts`, exporting `ProductionBundle`.
|
|
185
122
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
(22.18+, 23.6+). Like any `require`d module a file is loaded **once per
|
|
189
|
-
process**, so read environment variables inside the predicate, not at the top
|
|
190
|
-
of the file.
|
|
123
|
+
A bundle generated by one config is never registered by another, even when it
|
|
124
|
+
sits in a folder the other scans.
|
|
191
125
|
|
|
192
|
-
|
|
126
|
+
## Selection in two stages
|
|
193
127
|
|
|
194
|
-
A
|
|
195
|
-
and the extension — `StripeGateway.production.ts` → `['production']`,
|
|
196
|
-
`Report.production.eu.ts` → `['production', 'eu']`, `Shared.ts` → `[]`. Write
|
|
197
|
-
a predicate over them and wrap it with `byTags`:
|
|
128
|
+
A bundle picks its registrations in two stages:
|
|
198
129
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
130
|
+
1. **`files`** — decided by path alone, before anything is read. Only files that
|
|
131
|
+
pass are parsed.
|
|
132
|
+
2. **`classes`** — decided per class, on the files stage 1 let through.
|
|
202
133
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
134
|
+
Parsing is where the time goes, so when your project names files by convention
|
|
135
|
+
(`user.service.ts`, `user.repository.ts`), say so in `files.include` and the
|
|
136
|
+
bundler never reads anything else:
|
|
206
137
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
## Filtering classes after parsing
|
|
216
|
-
|
|
217
|
-
`include` decides on whole files before they are read. To decide class by
|
|
218
|
-
class, right after a file is parsed, use an `ExportPredicate`. It runs on every
|
|
219
|
-
exported, non-abstract class that passes `select`:
|
|
220
|
-
|
|
221
|
-
```ts
|
|
222
|
-
type FilterPredicate<Target> = (value: Target) => boolean;
|
|
223
|
-
type ExportPredicate = FilterPredicate<ExportContext>;
|
|
224
|
-
|
|
225
|
-
interface ExportContext {
|
|
226
|
-
filename: string; // relative to the config: 'src/Mailer.ts'
|
|
227
|
-
exportName: string; // 'default' for a default export
|
|
228
|
-
className: string; // an anonymous default export is named after its file
|
|
229
|
-
isDefault: boolean;
|
|
230
|
-
decorators: string[]; // renamed imports resolved: `register as reg` -> 'register'
|
|
231
|
-
tags: string[]; // file-name tags, as byTags reads them
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"output": "src/di/app.bundle.ts",
|
|
141
|
+
"files": {
|
|
142
|
+
"paths": ["@app/services"],
|
|
143
|
+
"include": ["**/*.service.ts", "**/*.repository.ts"]
|
|
144
|
+
},
|
|
145
|
+
"classes": { "decorators": ["register"] }
|
|
232
146
|
}
|
|
233
147
|
```
|
|
234
148
|
|
|
235
|
-
|
|
236
|
-
// tic.exports.ts, next to tic.config.json: picked up by convention
|
|
237
|
-
import type { ExportPredicate } from '@ts-ioc-container/bundler';
|
|
238
|
-
|
|
239
|
-
const filterExports: ExportPredicate = ({ exportName }) => !/(Stub|Mock|Fake)$/.test(exportName);
|
|
240
|
-
export default filterExports;
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
It is found and loaded like an inclusion predicate: `build({ filterExports })`,
|
|
244
|
-
else the bundle's `filterExports` file, else a `tic.exports.*` next to the
|
|
245
|
-
config. Examples (test doubles sharing a file, decorator combinations, tags
|
|
246
|
-
with class names) run as tests in
|
|
247
|
-
[`__tests__/examples/inclusion-predicates.spec.ts`](__tests__/examples/inclusion-predicates.spec.ts).
|
|
248
|
-
|
|
249
|
-
## Recipe: generate per environment
|
|
250
|
-
|
|
251
|
-
Keep environment-specific implementations side by side, named by convention:
|
|
252
|
-
`*.production.ts` joins only production, `*.development.ts` only development,
|
|
253
|
-
and every other file is shared.
|
|
254
|
-
|
|
255
|
-
```text
|
|
256
|
-
src/services/
|
|
257
|
-
├── Shared.ts → every environment
|
|
258
|
-
├── StripeGateway.production.ts → production only
|
|
259
|
-
└── FakeGateway.development.ts → development only
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
One helper turns the convention into a predicate — the environment tags a file
|
|
263
|
-
carries must all be the wanted one, and a file without any is shared:
|
|
149
|
+
## Selecting files
|
|
264
150
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
151
|
+
The candidates are the files the extended tsconfig compiles (its `files` /
|
|
152
|
+
`include` / `exclude`, following its own `extends`). `paths` overrides that set
|
|
153
|
+
the way a child tsconfig's `include` overrides its parent's: the bundle then scans
|
|
154
|
+
those folders instead — relative to the config file (`./src/services`) or tsconfig
|
|
155
|
+
`paths` aliases (`@app/services`), recursively unless an entry says
|
|
156
|
+
`{ "path": "...", "recursive": false }`. Without a tsconfig, `paths` is required.
|
|
269
157
|
|
|
270
|
-
|
|
271
|
-
|
|
158
|
+
A candidate is parsed when it matches **one of** `include` and **none of**
|
|
159
|
+
`exclude`. Globs are relative to the config file and `/`-separated; `**` spans
|
|
160
|
+
folders.
|
|
272
161
|
|
|
273
|
-
|
|
162
|
+
| Rule | Default | Meaning |
|
|
163
|
+
| --------- | ----------------------------------------- | ---------------------------------------------------------- |
|
|
164
|
+
| `paths` | what the tsconfig compiles | Folders to scan instead |
|
|
165
|
+
| `include` | every source file | Globs a file must match one of, e.g. `"**/*.service.ts"` |
|
|
166
|
+
| `exclude` | test files, `__tests__/`, `node_modules/` | Globs of files never read; replaces the default when given |
|
|
274
167
|
|
|
275
|
-
|
|
276
|
-
depend on who ran `tic build`, so `tic build --check` stays stable in CI.
|
|
168
|
+
A non-empty `exclude` replaces the defaults, so restate the ones you still want:
|
|
277
169
|
|
|
278
170
|
```json
|
|
279
|
-
{
|
|
280
|
-
"
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
"output": "src/di/app.development.bundle.ts",
|
|
289
|
-
"name": "AppBundle",
|
|
290
|
-
"namespaces": ["@app/services"],
|
|
291
|
-
"include": "./tic/development.cjs"
|
|
292
|
-
}
|
|
171
|
+
"files": {
|
|
172
|
+
"exclude": [
|
|
173
|
+
"**/*.spec.ts",
|
|
174
|
+
"**/*.test.ts",
|
|
175
|
+
"**/*.spec.tsx",
|
|
176
|
+
"**/*.test.tsx",
|
|
177
|
+
"**/__tests__/**",
|
|
178
|
+
"**/node_modules/**",
|
|
179
|
+
"frontend/api/generated/**"
|
|
293
180
|
]
|
|
294
181
|
}
|
|
295
182
|
```
|
|
296
183
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
module.exports = require('./for-env.cjs')('development');
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
To keep development code out of the production build, the production entry
|
|
305
|
-
point must import **only** the production bundle — a bundler includes what is
|
|
306
|
-
imported, and a registration keeps its class alive, so nothing can be dropped
|
|
307
|
-
after the fact. Give each environment its own entry:
|
|
184
|
+
If it omits a default glob the build warns, since test classes usually carry
|
|
185
|
+
the same `@register` decorators as production ones. `"exclude": []` deliberately
|
|
186
|
+
parses everything, including tests. Giving only `include` keeps the default
|
|
187
|
+
`exclude`, so `**/*.service.ts` still skips `user.service.spec.ts`.
|
|
308
188
|
|
|
309
|
-
|
|
310
|
-
// src/main.production.ts — production build entry
|
|
311
|
-
import { Container } from 'ts-ioc-container';
|
|
312
|
-
import { AppBundle } from './di/app.production.bundle';
|
|
313
|
-
|
|
314
|
-
export const container = new Container().useModule(new AppBundle());
|
|
315
|
-
```
|
|
189
|
+
## Selecting classes
|
|
316
190
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
import { AppBundle } from './di/app.development.bundle';
|
|
191
|
+
By default every exported, non-abstract class of a parsed file that carries
|
|
192
|
+
`@register` is registered. `classes` changes that; a class must meet every
|
|
193
|
+
criterion that is set:
|
|
321
194
|
|
|
322
|
-
|
|
195
|
+
```json
|
|
196
|
+
"classes": {
|
|
197
|
+
"export": "named",
|
|
198
|
+
"decorators": ["register", "repository"],
|
|
199
|
+
"name": "*Service"
|
|
200
|
+
}
|
|
323
201
|
```
|
|
324
202
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
? await import('./di/app.production.bundle')
|
|
333
|
-
: await import('./di/app.development.bundle');
|
|
334
|
-
```
|
|
203
|
+
| Criterion | Default | Meaning |
|
|
204
|
+
| ------------------ | ------- | ----------------------------------------------------------------------------------------------------------- |
|
|
205
|
+
| `export` | `any` | `any`, `named` or `default` — which exports count |
|
|
206
|
+
| `decorators` | `["register"]` | The class must carry one of these, by name — also renamed imports and `@ioc.register()`; list composed ones. `[]` requires none |
|
|
207
|
+
| `name` | — | Glob the class name must match, e.g. `"*Service"`; an anonymous default export is named after its file (`user-service.ts` → `UserService`) |
|
|
208
|
+
| `excludeClasses` | — | Class names to drop, e.g. `["MockDashboardRepository"]` |
|
|
209
|
+
| `excludeName` | — | Glob the class name must **not** match, e.g. `"*Mock"` |
|
|
335
210
|
|
|
336
|
-
|
|
337
|
-
| ---------------------------------------------------------------- | ------------------------------------------- |
|
|
338
|
-
| One entry per environment, importing its own bundle | ✅ none |
|
|
339
|
-
| Recipe B below — only the production bundle exists at build time | ✅ none |
|
|
340
|
-
| Dynamic `import()` of the bundle | ⚠️ emitted as a separate chunk, never loaded |
|
|
341
|
-
| Static imports of both bundles plus a runtime condition | ❌ both bundles, in full |
|
|
211
|
+
Abstract and non-exported classes are never registered.
|
|
342
212
|
|
|
343
|
-
|
|
344
|
-
`process.env.NODE_ENV === 'production'`: the classes it imports run decorators
|
|
345
|
-
at load time, which bundlers treat as side effects and keep.
|
|
213
|
+
### Excluding a single class
|
|
346
214
|
|
|
347
|
-
|
|
215
|
+
When a real implementation and a test-only stand-in are both decorated and both
|
|
216
|
+
bind the same token, registration is last-wins and the stand-in silently wins.
|
|
217
|
+
`excludeClasses` and `excludeName` drop a class right in the config:
|
|
348
218
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
219
|
+
```json
|
|
220
|
+
"classes": {
|
|
221
|
+
"decorators": ["repository", "service"],
|
|
222
|
+
"excludeClasses": ["MockDashboardRepository"],
|
|
223
|
+
"excludeName": "*Fake"
|
|
224
|
+
}
|
|
225
|
+
```
|
|
352
226
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
const forEnv = require('./tic/for-env.cjs');
|
|
227
|
+
Both apply after every other criterion, so they can be combined with
|
|
228
|
+
`decorators` and `name` freely.
|
|
356
229
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
230
|
+
As a safety net, the build warns when two selected classes pass the same
|
|
231
|
+
plain-identifier first argument to a decorator — usually the binding token, as in
|
|
232
|
+
`@repository(IDashboardRepositoryToken)`. The check is syntactic (aliased imports
|
|
233
|
+
are not resolved), so it can only be a heuristic:
|
|
360
234
|
|
|
361
|
-
```
|
|
362
|
-
|
|
235
|
+
```text
|
|
236
|
+
tic: warning: app.bundle.json: decorator token "IDashboardRepositoryToken" is passed by
|
|
237
|
+
HttpDashboardRepository, MockDashboardRepository; registration is last-wins,
|
|
238
|
+
exclude one with classes.excludeClasses
|
|
363
239
|
```
|
|
364
240
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
241
|
+
Classes that are scope-gated are not last-wins, so they do not warn: when the
|
|
242
|
+
colliding classes share a decorator called with different arguments, e.g.
|
|
243
|
+
`@perPage('stations')` vs `@perPage('sessions')`, each scope registers its own
|
|
244
|
+
class and nothing is lost.
|
|
368
245
|
|
|
369
246
|
## Build
|
|
370
247
|
|
|
371
248
|
```bash
|
|
372
|
-
tic build #
|
|
373
|
-
tic build -c
|
|
249
|
+
tic build # every *.bundle.{json,yaml,yml} of this package, else its tsconfig.json
|
|
250
|
+
tic build -c app.bundle.json -c admin/admin.bundle.json # only these
|
|
374
251
|
tic build --check # CI: write nothing, exit 1 if a bundle is out of date
|
|
375
252
|
```
|
|
376
253
|
|
|
@@ -378,8 +255,8 @@ The output is plain TypeScript over the public `ts-ioc-container` API:
|
|
|
378
255
|
|
|
379
256
|
```ts
|
|
380
257
|
// src/di/app.bundle.ts
|
|
381
|
-
// Generated by `tic build` from ../../
|
|
382
|
-
//
|
|
258
|
+
// Generated by `tic build` from ../../app.bundle.json. Do not edit by hand.
|
|
259
|
+
// Paths: @app/services, @app/infra
|
|
383
260
|
import { type IContainer, type IContainerModule, type IRegistration, Registration } from 'ts-ioc-container';
|
|
384
261
|
import { MemoryLogger } from '@app/infra/logging/MemoryLogger';
|
|
385
262
|
import { Greeter } from '@app/services/Greeter';
|
package/cjm/app.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createContainer = createContainer;
|
|
4
|
+
exports.run = run;
|
|
5
|
+
exports.build = build;
|
|
6
|
+
exports.loadConfig = loadConfig;
|
|
7
|
+
require("reflect-metadata");
|
|
8
|
+
const ts_ioc_container_1 = require("ts-ioc-container");
|
|
9
|
+
const Application_1 = require("./cli/Application");
|
|
10
|
+
const CliIo_1 = require("./domain/CliIo");
|
|
11
|
+
const BuildModule_1 = require("./features/build/BuildModule");
|
|
12
|
+
const BundleBuilder_1 = require("./features/build/services/BundleBuilder");
|
|
13
|
+
const TicConfigService_1 = require("./features/build/services/TicConfigService");
|
|
14
|
+
const HelpModule_1 = require("./features/help/HelpModule");
|
|
15
|
+
const CommonModule_1 = require("./modules/CommonModule");
|
|
16
|
+
function createContainer(io = (0, CliIo_1.processIo)()) {
|
|
17
|
+
return new ts_ioc_container_1.Container({ tags: ['root'] })
|
|
18
|
+
.useModule(new CommonModule_1.CommonModule(io))
|
|
19
|
+
.useModule(new BuildModule_1.BuildModule())
|
|
20
|
+
.useModule(new HelpModule_1.HelpModule());
|
|
21
|
+
}
|
|
22
|
+
function withContainer(io, fn) {
|
|
23
|
+
const container = createContainer(io);
|
|
24
|
+
try {
|
|
25
|
+
return fn(container);
|
|
26
|
+
}
|
|
27
|
+
finally {
|
|
28
|
+
container.dispose();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function run(argv, io = (0, CliIo_1.processIo)()) {
|
|
32
|
+
return Application_1.Application.bootstrap(createContainer(io)).run(...argv);
|
|
33
|
+
}
|
|
34
|
+
function build({ cwd = process.cwd(), ...request } = {}) {
|
|
35
|
+
return withContainer({ ...(0, CliIo_1.processIo)(), cwd }, (container) => BundleBuilder_1.IBundleBuilderKey.resolve(container).build(request));
|
|
36
|
+
}
|
|
37
|
+
function loadConfig(file) {
|
|
38
|
+
return withContainer((0, CliIo_1.processIo)(), (container) => TicConfigService_1.ITicConfigServiceKey.resolve(container).load(file));
|
|
39
|
+
}
|
package/cjm/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
const
|
|
5
|
-
process.exitCode = (0,
|
|
4
|
+
const app_1 = require("./app");
|
|
5
|
+
process.exitCode = (0, app_1.run)(process.argv.slice(2));
|