@ts-ioc-container/bundler 0.2.0 → 1.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/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.
package/cjm/build.js CHANGED
@@ -38,7 +38,7 @@ function build({ config = config_1.DEFAULT_CONFIG_FILE, cwd = process.cwd(), che
38
38
  }
39
39
  return { file: bundle.output, bundle: bundle.name, status, registrations, content };
40
40
  });
41
- return { config: resolved.file, outputs };
41
+ return { config: resolved.file, outputs, warnings: resolved.warnings };
42
42
  }
43
43
  function predicateResolver({ override, conventional, fileOf, load, }) {
44
44
  const loaded = new Map();
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
@@ -61,7 +61,7 @@ function toSelector(value, field) {
61
61
  function toBundle(value, field, dir) {
62
62
  if (!isObject(value))
63
63
  return fail(field, 'an object');
64
- const { output, name, namespaces, select, exclude, include, filterExports } = value;
64
+ const { output, name, namespaces, select, exclude, additionalExclude, include, filterExports } = value;
65
65
  if (!isNonEmptyString(output))
66
66
  return fail(`${field}.output`, 'a non-empty string');
67
67
  if (name !== undefined && !isIdentifier(name))
@@ -70,6 +70,9 @@ function toBundle(value, field, dir) {
70
70
  return fail(`${field}.namespaces`, 'a non-empty array');
71
71
  if (exclude !== undefined && !isStringArray(exclude))
72
72
  return fail(`${field}.exclude`, 'an array of strings');
73
+ if (additionalExclude !== undefined && !isStringArray(additionalExclude)) {
74
+ return fail(`${field}.additionalExclude`, 'an array of strings');
75
+ }
73
76
  if (include !== undefined && !isNonEmptyString(include))
74
77
  return fail(`${field}.include`, 'a non-empty string');
75
78
  if (filterExports !== undefined && !isNonEmptyString(filterExports)) {
@@ -80,11 +83,25 @@ function toBundle(value, field, dir) {
80
83
  name: name ?? exports.DEFAULT_BUNDLE_NAME,
81
84
  namespaces: namespaces.map((ns, i) => toNamespace(ns, `${field}.namespaces[${i}]`)),
82
85
  select: toSelector(select, `${field}.select`),
83
- exclude: exclude ?? exports.DEFAULT_EXCLUDE,
86
+ exclude: [...(exclude ?? exports.DEFAULT_EXCLUDE), ...(additionalExclude ?? [])],
84
87
  include: include === undefined ? undefined : node_path_1.default.resolve(dir, include),
85
88
  filterExports: filterExports === undefined ? undefined : node_path_1.default.resolve(dir, filterExports),
86
89
  };
87
90
  }
91
+ function excludeWarnings(value, field) {
92
+ if (!isObject(value))
93
+ return [];
94
+ const exclude = value.exclude;
95
+ if (!isStringArray(exclude) || exclude.length === 0)
96
+ return [];
97
+ const missing = exports.DEFAULT_EXCLUDE.filter((glob) => !exclude.includes(glob));
98
+ if (missing.length === 0)
99
+ return [];
100
+ return [
101
+ `${field}.exclude replaces the default excludes and omits ${missing.map((glob) => `"${glob}"`).join(', ')}; ` +
102
+ `add them back or use ${field}.additionalExclude`,
103
+ ];
104
+ }
88
105
  function resolveConfig(content, file) {
89
106
  const dir = node_path_1.default.dirname(file);
90
107
  if (!isObject(content))
@@ -102,6 +119,7 @@ function resolveConfig(content, file) {
102
119
  tsconfig: { file: node_path_1.default.resolve(dir, tsconfig ?? 'tsconfig.json'), required: tsconfig !== undefined },
103
120
  importExtension,
104
121
  bundles: bundles.map((m, i) => toBundle(m, `bundles[${i}]`, dir)),
122
+ warnings: bundles.flatMap((m, i) => excludeWarnings(m, `bundles[${i}]`)),
105
123
  };
106
124
  }
107
125
  function loadConfig(file) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ts-ioc-container/bundler",
3
- "version": "0.2.0",
3
+ "version": "1.1.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",
@@ -61,7 +61,7 @@
61
61
  "typescript": "5.8.3",
62
62
  "unplugin-swc": "^1.6.0",
63
63
  "vitest": "^4.1.11",
64
- "ts-ioc-container": "72.3.2"
64
+ "ts-ioc-container": "73.0.0"
65
65
  },
66
66
  "engines": {
67
67
  "node": ">= 18"
package/tic.schema.json CHANGED
@@ -115,7 +115,14 @@
115
115
  "**/__tests__/**",
116
116
  "**/node_modules/**"
117
117
  ],
118
- "description": "Globs, relative to this file, of files never scanned. Replaces the default when given."
118
+ "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."
119
+ },
120
+ "additionalExclude": {
121
+ "type": "array",
122
+ "items": {
123
+ "type": "string"
124
+ },
125
+ "description": "Globs, relative to this file, of files never scanned, added on top of `exclude` (or the default excludes when `exclude` is omitted)."
119
126
  },
120
127
  "include": {
121
128
  "type": "string",
@@ -27,6 +27,8 @@ export interface BuildResult {
27
27
  /** Absolute path of the config that was built. */
28
28
  config: string;
29
29
  outputs: OutputResult[];
30
+ /** Non-fatal problems found while resolving the config, e.g. an `exclude` that drops a default test glob. */
31
+ warnings: string[];
30
32
  }
31
33
  /**
32
34
  * Generates one bundle per config entry. Every bundle is generated before any
@@ -35,6 +35,12 @@ export interface BundleConfig {
35
35
  select?: ClassSelector;
36
36
  /** Globs, relative to the config file, of files never scanned. Replaces {@link DEFAULT_EXCLUDE} when given. */
37
37
  exclude?: string[];
38
+ /**
39
+ * Globs, relative to the config file, of files never scanned, added on top of
40
+ * `exclude` (or {@link DEFAULT_EXCLUDE} when `exclude` is omitted). Use this to
41
+ * add one exclusion without restating the defaults.
42
+ */
43
+ additionalExclude?: string[];
38
44
  /**
39
45
  * A file exporting an `InclusionPredicate`, relative to the config file. Overrides the
40
46
  * conventional `tic.include.*` next to the config.
@@ -68,12 +74,15 @@ export interface ResolvedConfig {
68
74
  };
69
75
  importExtension?: string;
70
76
  bundles: ResolvedBundle[];
77
+ /** Non-fatal problems found while resolving, e.g. an `exclude` that drops a default test glob. */
78
+ warnings: string[];
71
79
  }
72
80
  export interface ResolvedBundle {
73
81
  output: string;
74
82
  name: string;
75
83
  namespaces: Required<NamespaceConfig>[];
76
84
  select: ResolvedSelector;
85
+ /** The effective list: the configured `exclude` (or the defaults) followed by `additionalExclude`. */
77
86
  exclude: string[];
78
87
  /** Absolute path of the bundle's own predicate file, when it names one. */
79
88
  include?: string;