@ts-ioc-container/bundler 2.0.0 → 3.0.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 +36 -23
- package/README.md +64 -84
- 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 +48 -0
- package/cjm/exceptions/ExceptionHandler.js +38 -0
- package/cjm/features/build/BuildConfig.js +146 -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} +0 -27
- package/cjm/features/build/domain/configFiles.js +35 -0
- package/cjm/{emit.js → features/build/domain/emit.js} +9 -11
- package/cjm/{scan.js → features/build/domain/scan.js} +0 -18
- package/cjm/features/build/domain/tokenCollisions.js +29 -0
- package/cjm/{tsconfig.js → features/build/domain/tsconfig.js} +17 -11
- package/cjm/features/build/services/BundleBuilder.js +93 -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 +83 -26
- package/cjm/modules/CommonModule.js +28 -0
- package/cjm/schema/ticConfigSchema.js +20 -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 +9 -7
- package/tic.schema.json +79 -113
- package/tpl/index.cjs +19 -41
- package/typings/app.d.ts +23 -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 +33 -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 +159 -0
- package/typings/features/build/BuildConfig.d.ts.map +1 -0
- package/typings/features/build/BuildController.d.ts +31 -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} +2 -9
- 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 +36 -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} +1 -3
- 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 +21 -0
- package/typings/features/build/domain/tsconfig.d.ts.map +1 -0
- package/typings/features/build/services/BundleBuilder.d.ts +65 -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 -9
- 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 -106
- package/cjm/cli.js +0 -109
- package/cjm/config.js +0 -199
- package/cjm/errors.js +0 -15
- package/cjm/render.js +0 -15
- package/typings/build.d.ts +0 -42
- package/typings/cli.d.ts +0 -8
- package/typings/config.d.ts +0 -162
- package/typings/emit.d.ts +0 -23
- package/typings/errors.d.ts +0 -13
- package/typings/render.d.ts +0 -8
- package/typings/tsconfig.d.ts +0 -25
- /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/AGENTS.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# AGENTS.md — @ts-ioc-container/bundler
|
|
2
2
|
|
|
3
3
|
Bundles an application's dependencies into a single container module for
|
|
4
|
-
`ts-ioc-container`: the `tic` CLI scans
|
|
4
|
+
`ts-ioc-container`: the `tic` CLI scans what a tsconfig compiles and generates a typed bundle
|
|
5
5
|
(`useModule(new AppBundle())`). Discovery happens at build time (ADR 0022);
|
|
6
6
|
nothing here runs inside the container, and the core package is not changed by
|
|
7
7
|
it.
|
|
@@ -9,8 +9,10 @@ it.
|
|
|
9
9
|
- The CLI is `ts-ioc-container`, with `tic` as a shortcut (both `bin` entries,
|
|
10
10
|
same program). One config file describes one bundle: `<name>.bundle.json`, or the same in
|
|
11
11
|
YAML as `<name>.bundle.yaml` / `.yml` (an empty YAML file = all defaults)
|
|
12
|
-
(schema: `tic.schema.json`),
|
|
13
|
-
`
|
|
12
|
+
(schema: `tic.schema.json`), shaped like a tsconfig — `extends`, `include`,
|
|
13
|
+
`exclude`, `compilerOptions` — plus the bundler's `output` and `name` at
|
|
14
|
+
the top level and `compilerOptions.importExtension` / `compilerOptions.classes`;
|
|
15
|
+
all optional. `tic build` works on
|
|
14
16
|
the package it is invoked in (nearest `package.json` up from the working
|
|
15
17
|
directory; never a workspace root): it builds every `*.bundle.{json,yaml,yml}` there
|
|
16
18
|
(or the ones named with repeatable `-c`), else — zero config — one bundle
|
|
@@ -18,24 +20,23 @@ it.
|
|
|
18
20
|
applied with `container.useModule(new AppBundle())`. Several bundles (e.g.
|
|
19
21
|
`production.bundle.json`, `development.bundle.json`) mean several configs.
|
|
20
22
|
`--check` writes nothing and exits 1 when an output is stale — run it in CI.
|
|
21
|
-
- A config `extends`
|
|
22
|
-
named explicitly must exist)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
- A config is a tsconfig that `extends` another (default `./tsconfig.json`, may
|
|
24
|
+
be absent; one named explicitly must exist): TypeScript parses it with the
|
|
25
|
+
bundler's own fields removed, so the files it compiles (its `include` /
|
|
26
|
+
`exclude` replacing the parent's) are the bundle's file set, and the merged
|
|
27
|
+
`paths` aliases and module resolution shape the generated imports (most
|
|
28
|
+
specific alias, else a relative path). Every compiler option but `classes`
|
|
29
|
+
and `importExtension` goes to TypeScript, which reports unknown ones.
|
|
30
|
+
`output` defaults to `<root>/<name>.bundle.ts`: root = compiled `rootDir`,
|
|
31
|
+
else the common folder of the files compiled. `name` defaults to the config's
|
|
32
|
+
stem (`production.bundle.json` → `production`), else `base`; the class is
|
|
28
33
|
`toClassName(name)` — `ProductionBundle`, `BaseBundle`.
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
are relative to the config. With file naming conventions, `include` is the
|
|
36
|
-
speed lever — nothing else is read. Files `tic build` generated are never input.
|
|
37
|
-
- `classes: { export, decorators, name, excludeClasses, excludeName }`
|
|
38
|
-
then picks classes of the parsed files; default is every exported,
|
|
34
|
+
- Test files, `__tests__/` and `node_modules/` (`DEFAULT_EXCLUDE`) are never
|
|
35
|
+
scanned, whatever is compiled; neither are files `tic build` generated. With
|
|
36
|
+
file naming conventions, a narrow `include` is the speed lever — nothing else
|
|
37
|
+
is read.
|
|
38
|
+
- `compilerOptions.classes: { export, decorators, name, excludeClasses, excludeName }`
|
|
39
|
+
picks classes of the scanned files; default is every exported,
|
|
39
40
|
non-abstract class decorated with `@register` (`decorators` defaults to
|
|
40
41
|
`["register"]`; `[]` requires none); positive criteria must all hold and then `excludeClasses` (exact
|
|
41
42
|
names) and `excludeName` (glob) drop classes.
|
|
@@ -45,7 +46,19 @@ it.
|
|
|
45
46
|
(scope-gated, e.g. `@perPage('a')` vs `@perPage('b')`).
|
|
46
47
|
- Never edit a `*.bundle.ts` by hand — change the classes or the config and
|
|
47
48
|
rerun `tic build`.
|
|
48
|
-
- Unknown config fields are errors,
|
|
49
|
+
- Unknown config fields are errors, except inside `compilerOptions`, where
|
|
50
|
+
TypeScript reports unknown compiler options.
|
|
49
51
|
- Programmatic API: `build({ config, cwd, check })` (one config, one output),
|
|
50
|
-
`findConfigFiles(dir)`, `run(argv, io)`, `loadConfig(file)`;
|
|
51
|
-
|
|
52
|
+
`findConfigFiles(dir)`, `run(argv, io)`, `loadConfig(file)`; every error the
|
|
53
|
+
bundler raises on purpose is a `TicError` with a stable `code`
|
|
54
|
+
(`TicConfigError` = `INVALID_CONFIG`, `UsageError`,
|
|
55
|
+
`StaleBundlesError`, …).
|
|
56
|
+
- Internals follow `release-monorepo-semantically` (ADR 0023): `createContainer(io)`
|
|
57
|
+
in `lib/app.ts` is the composition root; `Application` resolves the controller
|
|
58
|
+
registered under the command name (`@register('build')`) and runs its
|
|
59
|
+
`@onDefault(invoke)` action, which parses its own options with
|
|
60
|
+
`@inject(pipe(commandArgs, parseOptions(spec), validate(SCHEMA)))`. Work lives in
|
|
61
|
+
services behind tokens (`IBundleBuilderKey`, `ITicConfigServiceKey`, …); pure
|
|
62
|
+
logic in `lib/features/build/domain/`. A new command is a controller plus a module.
|
|
63
|
+
- The config schema is `BUNDLE_CONFIG_SCHEMA` (zod); `tic.schema.json` is generated
|
|
64
|
+
from it with `pnpm run generate:schema` — never edit it by hand.
|
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
|
-
what your tsconfig compiles
|
|
5
|
-
|
|
4
|
+
what your tsconfig compiles and generates one typed bundle that registers every
|
|
5
|
+
class in it, ready for
|
|
6
6
|
[`ts-ioc-container`](https://www.npmjs.com/package/ts-ioc-container):
|
|
7
7
|
|
|
8
8
|
```ts
|
|
@@ -14,8 +14,9 @@ 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
|
-
A bundle is
|
|
18
|
-
|
|
17
|
+
A bundle config **is** a tsconfig that `extends` yours — `include`, `exclude`
|
|
18
|
+
and `compilerOptions` mean what they mean to `tsc` — plus a few fields of the
|
|
19
|
+
bundler's own.
|
|
19
20
|
|
|
20
21
|
## Install
|
|
21
22
|
|
|
@@ -73,28 +74,43 @@ or, in YAML, `<name>.bundle.yaml` / `<name>.bundle.yml` — same fields, same sc
|
|
|
73
74
|
|
|
74
75
|
```yaml
|
|
75
76
|
# yaml-language-server: $schema=./node_modules/@ts-ioc-container/bundler/tic.schema.json
|
|
77
|
+
extends: ./tsconfig.json
|
|
76
78
|
output: src/di/app.bundle.ts
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
include: [src/**/*.service.ts]
|
|
80
|
+
compilerOptions:
|
|
81
|
+
classes:
|
|
82
|
+
decorators: [register, service]
|
|
79
83
|
```
|
|
80
84
|
|
|
81
85
|
An empty YAML file is a bundle with every setting at its default. Describing
|
|
82
86
|
one bundle in two formats (`app.bundle.json` and `app.bundle.yaml`) is an error.
|
|
83
87
|
|
|
84
|
-
Every field is optional; `{}` is the zero-config bundle.
|
|
88
|
+
Every field is optional; `{}` is the zero-config bundle. The tsconfig fields:
|
|
85
89
|
|
|
86
|
-
| Field | Default
|
|
87
|
-
| ----------------- |
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
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) |
|
|
90
|
+
| Field | Default | Meaning |
|
|
91
|
+
| ----------------- | --------------------------------- | --------------------------------------------------------------------------------------------- |
|
|
92
|
+
| `extends` | `./tsconfig.json` (may be absent) | The tsconfig this config extends; one named here must exist |
|
|
93
|
+
| `include` | the extended tsconfig's | Which files are scanned — see [Selecting files](#selecting-files) |
|
|
94
|
+
| `exclude` | the extended tsconfig's | Which of them are left out — see [Selecting files](#selecting-files) |
|
|
95
|
+
| `compilerOptions` | the extended tsconfig's | tsconfig compiler options (`paths`, `rootDir`, `moduleResolution`, …) plus the bundler's below |
|
|
95
96
|
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
The bundler's own fields, at the top level:
|
|
98
|
+
|
|
99
|
+
| Field | Default | Meaning |
|
|
100
|
+
| -------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
|
|
101
|
+
| `output` | `<root>/<name>.bundle.ts` | The bundle file; `<root>` as in [Zero config](#zero-config) |
|
|
102
|
+
| `name` | the config's stem, else `base` | The bundle's name (`production.bundle.json` → `production`): names the default output and the class, `ProductionBundle` |
|
|
103
|
+
|
|
104
|
+
and under `compilerOptions`:
|
|
105
|
+
|
|
106
|
+
| Option | Default | Meaning |
|
|
107
|
+
| ----------------- | -------------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
108
|
+
| `classes` | exported classes with `@register` | Which classes of a scanned file are registered — see [Selecting classes](#selecting-classes) |
|
|
109
|
+
| `importExtension` | `.js` under node16/nodenext, else none | Extension of generated imports |
|
|
110
|
+
|
|
111
|
+
An unknown top-level field is an error, so a misspelled or removed option never
|
|
112
|
+
goes unnoticed. Every other compiler option is handed to TypeScript, which
|
|
113
|
+
reports an unknown one (`Unknown compiler option 'pathz'. Did you mean 'paths'?`).
|
|
98
114
|
|
|
99
115
|
## Several bundles
|
|
100
116
|
|
|
@@ -114,7 +130,7 @@ Each one can extend its own tsconfig and select its own files:
|
|
|
114
130
|
// production.bundle.json
|
|
115
131
|
{
|
|
116
132
|
"extends": "./tsconfig.production.json",
|
|
117
|
-
"
|
|
133
|
+
"exclude": ["src/**/*.development.ts"]
|
|
118
134
|
}
|
|
119
135
|
```
|
|
120
136
|
|
|
@@ -123,80 +139,43 @@ It writes `src/production.bundle.ts`, exporting `ProductionBundle`.
|
|
|
123
139
|
A bundle generated by one config is never registered by another, even when it
|
|
124
140
|
sits in a folder the other scans.
|
|
125
141
|
|
|
126
|
-
##
|
|
142
|
+
## Selecting files
|
|
127
143
|
|
|
128
|
-
|
|
144
|
+
The files a bundle scans are the files its config compiles, as `tsc` would see
|
|
145
|
+
them: the extended tsconfig's `files` / `include` / `exclude` (following its own
|
|
146
|
+
`extends`), with this config's `include` and `exclude` replacing the parent's —
|
|
147
|
+
exactly as in a child tsconfig. Globs are relative to the config file.
|
|
129
148
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
149
|
+
Test files (`*.spec.ts`, `*.test.ts`, their `.tsx` twins and `__tests__/`) and
|
|
150
|
+
`node_modules/` are never scanned, whatever the tsconfig compiles, since test
|
|
151
|
+
classes usually carry the same `@register` decorators as production ones. Files
|
|
152
|
+
`tic build` generated are never input either.
|
|
133
153
|
|
|
134
154
|
Parsing is where the time goes, so when your project names files by convention
|
|
135
|
-
(`user.service.ts`, `user.repository.ts`), say so in `
|
|
136
|
-
|
|
155
|
+
(`user.service.ts`, `user.repository.ts`), say so in `include` and the bundler
|
|
156
|
+
never reads anything else:
|
|
137
157
|
|
|
138
158
|
```json
|
|
139
159
|
{
|
|
140
160
|
"output": "src/di/app.bundle.ts",
|
|
141
|
-
"
|
|
142
|
-
|
|
143
|
-
"include": ["**/*.service.ts", "**/*.repository.ts"]
|
|
144
|
-
},
|
|
145
|
-
"classes": { "decorators": ["register"] }
|
|
146
|
-
}
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
## Selecting files
|
|
150
|
-
|
|
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.
|
|
157
|
-
|
|
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.
|
|
161
|
-
|
|
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 |
|
|
167
|
-
|
|
168
|
-
A non-empty `exclude` replaces the defaults, so restate the ones you still want:
|
|
169
|
-
|
|
170
|
-
```json
|
|
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/**"
|
|
180
|
-
]
|
|
161
|
+
"include": ["src/**/*.service.ts", "src/**/*.repository.ts"],
|
|
162
|
+
"exclude": ["src/legacy/**"]
|
|
181
163
|
}
|
|
182
164
|
```
|
|
183
165
|
|
|
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`.
|
|
188
|
-
|
|
189
166
|
## Selecting classes
|
|
190
167
|
|
|
191
|
-
By default every exported, non-abstract class of a
|
|
192
|
-
`@register` is registered. `classes` changes that; a class must
|
|
193
|
-
criterion that is set:
|
|
168
|
+
By default every exported, non-abstract class of a scanned file that carries
|
|
169
|
+
`@register` is registered. `compilerOptions.classes` changes that; a class must
|
|
170
|
+
meet every criterion that is set:
|
|
194
171
|
|
|
195
172
|
```json
|
|
196
|
-
"
|
|
197
|
-
"
|
|
198
|
-
|
|
199
|
-
|
|
173
|
+
"compilerOptions": {
|
|
174
|
+
"classes": {
|
|
175
|
+
"export": "named",
|
|
176
|
+
"decorators": ["register", "repository"],
|
|
177
|
+
"name": "*Service"
|
|
178
|
+
}
|
|
200
179
|
}
|
|
201
180
|
```
|
|
202
181
|
|
|
@@ -217,10 +196,12 @@ bind the same token, registration is last-wins and the stand-in silently wins.
|
|
|
217
196
|
`excludeClasses` and `excludeName` drop a class right in the config:
|
|
218
197
|
|
|
219
198
|
```json
|
|
220
|
-
"
|
|
221
|
-
"
|
|
222
|
-
|
|
223
|
-
|
|
199
|
+
"compilerOptions": {
|
|
200
|
+
"classes": {
|
|
201
|
+
"decorators": ["repository", "service"],
|
|
202
|
+
"excludeClasses": ["MockDashboardRepository"],
|
|
203
|
+
"excludeName": "*Fake"
|
|
204
|
+
}
|
|
224
205
|
}
|
|
225
206
|
```
|
|
226
207
|
|
|
@@ -235,7 +216,7 @@ are not resolved), so it can only be a heuristic:
|
|
|
235
216
|
```text
|
|
236
217
|
tic: warning: app.bundle.json: decorator token "IDashboardRepositoryToken" is passed by
|
|
237
218
|
HttpDashboardRepository, MockDashboardRepository; registration is last-wins,
|
|
238
|
-
exclude one with classes.excludeClasses
|
|
219
|
+
exclude one with compilerOptions.classes.excludeClasses
|
|
239
220
|
```
|
|
240
221
|
|
|
241
222
|
Classes that are scope-gated are not last-wins, so they do not warn: when the
|
|
@@ -256,7 +237,6 @@ The output is plain TypeScript over the public `ts-ioc-container` API:
|
|
|
256
237
|
```ts
|
|
257
238
|
// src/di/app.bundle.ts
|
|
258
239
|
// Generated by `tic build` from ../../app.bundle.json. Do not edit by hand.
|
|
259
|
-
// Paths: @app/services, @app/infra
|
|
260
240
|
import { type IContainer, type IContainerModule, type IRegistration, Registration } from 'ts-ioc-container';
|
|
261
241
|
import { MemoryLogger } from '@app/infra/logging/MemoryLogger';
|
|
262
242
|
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));
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Application = void 0;
|
|
4
|
+
const ts_ioc_container_1 = require("ts-ioc-container");
|
|
5
|
+
const DomainException_1 = require("../exceptions/DomainException");
|
|
6
|
+
const decorators_1 = require("./decorators");
|
|
7
|
+
const IErrorHandler_1 = require("./IErrorHandler");
|
|
8
|
+
const usage_1 = require("./usage");
|
|
9
|
+
const COMMAND_ALIASES = {
|
|
10
|
+
'--help': 'help',
|
|
11
|
+
'-h': 'help',
|
|
12
|
+
'--version': 'version',
|
|
13
|
+
'-v': 'version',
|
|
14
|
+
};
|
|
15
|
+
function parseCommandAndAction(argv) {
|
|
16
|
+
const [first, maybeAction] = argv;
|
|
17
|
+
if (!first)
|
|
18
|
+
throw new DomainException_1.MissingCommandError(usage_1.USAGE);
|
|
19
|
+
const command = COMMAND_ALIASES[first] ?? first;
|
|
20
|
+
const action = maybeAction && !maybeAction.startsWith('-') ? maybeAction : decorators_1.DEFAULT_ACTION;
|
|
21
|
+
return { command, action };
|
|
22
|
+
}
|
|
23
|
+
class Application {
|
|
24
|
+
scope;
|
|
25
|
+
errorHandler;
|
|
26
|
+
static bootstrap(container) {
|
|
27
|
+
return new Application(container, IErrorHandler_1.IErrorHandlerKey.resolve(container));
|
|
28
|
+
}
|
|
29
|
+
constructor(scope, errorHandler) {
|
|
30
|
+
this.scope = scope;
|
|
31
|
+
this.errorHandler = errorHandler;
|
|
32
|
+
}
|
|
33
|
+
run(...argv) {
|
|
34
|
+
try {
|
|
35
|
+
const { command, action } = parseCommandAndAction(argv);
|
|
36
|
+
if (!this.scope.hasRegistration(command))
|
|
37
|
+
throw new DomainException_1.UnknownCommandError(command);
|
|
38
|
+
const controller = this.scope.resolve(command);
|
|
39
|
+
const collector = new ts_ioc_container_1.HookCollector({
|
|
40
|
+
key: action,
|
|
41
|
+
createExecutionContext: (0, ts_ioc_container_1.createHookContextFactory)({ args: argv }),
|
|
42
|
+
});
|
|
43
|
+
const actions = collector.getActions(controller, { scope: this.scope });
|
|
44
|
+
if (actions.length === 0)
|
|
45
|
+
throw new DomainException_1.UnknownActionError(command, action);
|
|
46
|
+
for (const item of actions)
|
|
47
|
+
(0, ts_ioc_container_1.toTask)(item)();
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
return this.errorHandler.handleError(error);
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
this.scope.dispose();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.Application = Application;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.onDefault = exports.action = exports.DEFAULT_ACTION = void 0;
|
|
4
|
+
const ts_ioc_container_1 = require("ts-ioc-container");
|
|
5
|
+
exports.DEFAULT_ACTION = 'default';
|
|
6
|
+
const action = (name, ...hooks) => (0, ts_ioc_container_1.hook)(name, (0, ts_ioc_container_1.sequential)(...hooks));
|
|
7
|
+
exports.action = action;
|
|
8
|
+
const onDefault = (...hooks) => (0, exports.action)(exports.DEFAULT_ACTION, ...hooks);
|
|
9
|
+
exports.onDefault = onDefault;
|
package/cjm/cli/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.USAGE = exports.IErrorHandlerKey = exports.Application = exports.validate = exports.parseOptions = exports.commandArgs = exports.onDefault = exports.DEFAULT_ACTION = exports.action = void 0;
|
|
4
|
+
var decorators_1 = require("./decorators");
|
|
5
|
+
Object.defineProperty(exports, "action", { enumerable: true, get: function () { return decorators_1.action; } });
|
|
6
|
+
Object.defineProperty(exports, "DEFAULT_ACTION", { enumerable: true, get: function () { return decorators_1.DEFAULT_ACTION; } });
|
|
7
|
+
Object.defineProperty(exports, "onDefault", { enumerable: true, get: function () { return decorators_1.onDefault; } });
|
|
8
|
+
var options_1 = require("./options");
|
|
9
|
+
Object.defineProperty(exports, "commandArgs", { enumerable: true, get: function () { return options_1.commandArgs; } });
|
|
10
|
+
Object.defineProperty(exports, "parseOptions", { enumerable: true, get: function () { return options_1.parseOptions; } });
|
|
11
|
+
Object.defineProperty(exports, "validate", { enumerable: true, get: function () { return options_1.validate; } });
|
|
12
|
+
var Application_1 = require("./Application");
|
|
13
|
+
Object.defineProperty(exports, "Application", { enumerable: true, get: function () { return Application_1.Application; } });
|
|
14
|
+
var IErrorHandler_1 = require("./IErrorHandler");
|
|
15
|
+
Object.defineProperty(exports, "IErrorHandlerKey", { enumerable: true, get: function () { return IErrorHandler_1.IErrorHandlerKey; } });
|
|
16
|
+
var usage_1 = require("./usage");
|
|
17
|
+
Object.defineProperty(exports, "USAGE", { enumerable: true, get: function () { return usage_1.USAGE; } });
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validate = exports.parseOptions = exports.commandArgs = void 0;
|
|
4
|
+
const node_util_1 = require("node:util");
|
|
5
|
+
const zod_1 = require("zod");
|
|
6
|
+
const DomainException_1 = require("../exceptions/DomainException");
|
|
7
|
+
const commandArgs = ({ args = [] }) => args.map(String);
|
|
8
|
+
exports.commandArgs = commandArgs;
|
|
9
|
+
const parseOptions = (options) => (argv) => {
|
|
10
|
+
const firstFlag = argv.findIndex((arg) => arg.startsWith('-'));
|
|
11
|
+
try {
|
|
12
|
+
return { ...(0, node_util_1.parseArgs)({ args: firstFlag >= 0 ? argv.slice(firstFlag) : [], options, strict: true }).values };
|
|
13
|
+
}
|
|
14
|
+
catch (e) {
|
|
15
|
+
throw new DomainException_1.UsageError(e.message);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
exports.parseOptions = parseOptions;
|
|
19
|
+
const validate = (schema) => (value) => {
|
|
20
|
+
const result = schema.safeParse(value);
|
|
21
|
+
if (!result.success)
|
|
22
|
+
throw new DomainException_1.UsageError(zod_1.z.prettifyError(result.error));
|
|
23
|
+
return result.data;
|
|
24
|
+
};
|
|
25
|
+
exports.validate = validate;
|
package/cjm/cli/usage.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.USAGE = void 0;
|
|
4
|
+
exports.USAGE = [
|
|
5
|
+
'Usage: ts-ioc-container <command> [options]',
|
|
6
|
+
' tic is a shortcut for ts-ioc-container',
|
|
7
|
+
'',
|
|
8
|
+
'Commands:',
|
|
9
|
+
' ts-ioc-container build [--config <path>]... [--check] generate one bundle per *.bundle.{json,yaml,yml} config',
|
|
10
|
+
' (none: one bundle from tsconfig.json, all defaults)',
|
|
11
|
+
' ts-ioc-container help show this help',
|
|
12
|
+
' ts-ioc-container version show the version',
|
|
13
|
+
'',
|
|
14
|
+
'Options:',
|
|
15
|
+
' -c, --config <path> build only this config; repeatable (default: every *.bundle.{json,yaml,yml} of the package, else tsconfig.json)',
|
|
16
|
+
' --check write nothing; exit 1 when a generated bundle is out of date',
|
|
17
|
+
' -h, --help show this help',
|
|
18
|
+
' -v, --version show the version',
|
|
19
|
+
].join('\n');
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.processIo = exports.CliIoKey = void 0;
|
|
4
|
+
const ts_ioc_container_1 = require("ts-ioc-container");
|
|
5
|
+
exports.CliIoKey = new ts_ioc_container_1.SingleToken('CliIo');
|
|
6
|
+
const processIo = () => ({ cwd: process.cwd(), stdout: console.log, stderr: console.error });
|
|
7
|
+
exports.processIo = processIo;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.globalConfig = exports.GlobalConfigKey = void 0;
|
|
4
|
+
const ts_ioc_container_1 = require("ts-ioc-container");
|
|
5
|
+
exports.GlobalConfigKey = new ts_ioc_container_1.SingleToken('GlobalConfig');
|
|
6
|
+
const globalConfig = (key) => ({ scope }) => exports.GlobalConfigKey.resolve(scope)[key];
|
|
7
|
+
exports.globalConfig = globalConfig;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StaleBundlesError = exports.UsageError = exports.UnknownActionError = exports.UnknownCommandError = exports.MissingCommandError = exports.TicConfigError = exports.TicError = void 0;
|
|
4
|
+
class TicError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
constructor(code, message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.name = new.target.name;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
exports.TicError = TicError;
|
|
13
|
+
class TicConfigError extends TicError {
|
|
14
|
+
constructor(message) {
|
|
15
|
+
super('INVALID_CONFIG', message);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.TicConfigError = TicConfigError;
|
|
19
|
+
class MissingCommandError extends TicError {
|
|
20
|
+
constructor(usage) {
|
|
21
|
+
super('MISSING_COMMAND', `missing command\n${usage}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.MissingCommandError = MissingCommandError;
|
|
25
|
+
class UnknownCommandError extends TicError {
|
|
26
|
+
constructor(command) {
|
|
27
|
+
super('UNKNOWN_COMMAND', `unknown command "${command}"`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.UnknownCommandError = UnknownCommandError;
|
|
31
|
+
class UnknownActionError extends TicError {
|
|
32
|
+
constructor(command, action) {
|
|
33
|
+
super('UNKNOWN_ACTION', `unknown action "${action}" for command "${command}"`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.UnknownActionError = UnknownActionError;
|
|
37
|
+
class UsageError extends TicError {
|
|
38
|
+
constructor(message) {
|
|
39
|
+
super('USAGE', message);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.UsageError = UsageError;
|
|
43
|
+
class StaleBundlesError extends TicError {
|
|
44
|
+
constructor(count) {
|
|
45
|
+
super('STALE_BUNDLES', `${count} generated bundle${count === 1 ? ' is' : 's are'} out of date — run \`tic build\``);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
exports.StaleBundlesError = StaleBundlesError;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.ExceptionHandler = void 0;
|
|
16
|
+
const ts_ioc_container_1 = require("ts-ioc-container");
|
|
17
|
+
const IErrorHandler_1 = require("../cli/IErrorHandler");
|
|
18
|
+
const ConsoleLogger_1 = require("../services/ConsoleLogger");
|
|
19
|
+
const DomainException_1 = require("./DomainException");
|
|
20
|
+
let ExceptionHandler = class ExceptionHandler {
|
|
21
|
+
logger;
|
|
22
|
+
constructor(logger) {
|
|
23
|
+
this.logger = logger;
|
|
24
|
+
}
|
|
25
|
+
handleError(error) {
|
|
26
|
+
if (error instanceof DomainException_1.TicError)
|
|
27
|
+
this.logger.error(error.message);
|
|
28
|
+
else
|
|
29
|
+
this.logger.error(error instanceof Error ? (error.stack ?? error.message) : String(error));
|
|
30
|
+
return 1;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
exports.ExceptionHandler = ExceptionHandler;
|
|
34
|
+
exports.ExceptionHandler = ExceptionHandler = __decorate([
|
|
35
|
+
(0, ts_ioc_container_1.register)(IErrorHandler_1.IErrorHandlerKey, (0, ts_ioc_container_1.singleton)()),
|
|
36
|
+
__param(0, (0, ts_ioc_container_1.inject)((0, ts_ioc_container_1.by)(ConsoleLogger_1.ILoggerKey))),
|
|
37
|
+
__metadata("design:paramtypes", [Object])
|
|
38
|
+
], ExceptionHandler);
|