@ts-ioc-container/bundler 3.1.0 → 5.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 +48 -48
- package/README.md +199 -144
- package/cjm/app.js +2 -2
- package/cjm/cli/Application.js +4 -5
- package/cjm/cli/options.js +4 -2
- package/cjm/cli/usage.js +11 -8
- package/cjm/domain/CliIo.js +7 -1
- package/cjm/exceptions/DomainException.js +7 -7
- package/cjm/features/build/BuildConfig.js +126 -77
- package/cjm/features/build/BuildController.js +36 -46
- package/cjm/features/build/domain/ImportPaths.js +31 -20
- package/cjm/features/build/domain/emit.js +2 -2
- package/cjm/features/build/domain/scan.js +20 -4
- package/cjm/features/build/domain/tokenCollisions.js +1 -1
- package/cjm/features/build/domain/tsconfig.js +10 -35
- package/cjm/features/build/services/BundleBuilder.js +35 -30
- package/cjm/features/build/services/TicConfigService.js +10 -31
- package/cjm/index.js +9 -10
- package/cjm/schema/ticConfigSchema.js +5 -1
- package/package.json +2 -2
- package/tic.schema.json +132 -77
- package/tpl/index.cjs +15 -15
- package/typings/app.d.ts +7 -6
- package/typings/cli/Application.d.ts.map +1 -1
- package/typings/cli/options.d.ts +7 -1
- package/typings/cli/options.d.ts.map +1 -1
- package/typings/cli/usage.d.ts.map +1 -1
- package/typings/domain/CliIo.d.ts +2 -0
- package/typings/domain/CliIo.d.ts.map +1 -1
- package/typings/domain/GlobalConfig.d.ts +1 -1
- package/typings/domain/GlobalConfig.d.ts.map +1 -1
- package/typings/exceptions/DomainException.d.ts +4 -4
- package/typings/exceptions/DomainException.d.ts.map +1 -1
- package/typings/features/build/BuildConfig.d.ts +102 -102
- package/typings/features/build/BuildConfig.d.ts.map +1 -1
- package/typings/features/build/BuildController.d.ts +23 -17
- package/typings/features/build/BuildController.d.ts.map +1 -1
- package/typings/features/build/domain/ImportPaths.d.ts +16 -14
- package/typings/features/build/domain/ImportPaths.d.ts.map +1 -1
- package/typings/features/build/domain/emit.d.ts +4 -4
- package/typings/features/build/domain/emit.d.ts.map +1 -1
- package/typings/features/build/domain/scan.d.ts +2 -0
- package/typings/features/build/domain/scan.d.ts.map +1 -1
- package/typings/features/build/domain/tsconfig.d.ts +9 -16
- package/typings/features/build/domain/tsconfig.d.ts.map +1 -1
- package/typings/features/build/services/BundleBuilder.d.ts +25 -29
- package/typings/features/build/services/BundleBuilder.d.ts.map +1 -1
- package/typings/features/build/services/TicConfigService.d.ts +11 -15
- package/typings/features/build/services/TicConfigService.d.ts.map +1 -1
- package/typings/index.d.ts +6 -7
- package/typings/schema/ticConfigSchema.d.ts.map +1 -1
- package/cjm/features/build/domain/configFiles.js +0 -35
- package/typings/features/build/domain/configFiles.d.ts +0 -13
- package/typings/features/build/domain/configFiles.d.ts.map +0 -1
package/cjm/cli/Application.js
CHANGED
|
@@ -36,11 +36,10 @@ class Application {
|
|
|
36
36
|
if (!this.scope.hasRegistration(command))
|
|
37
37
|
throw new DomainException_1.UnknownCommandError(command);
|
|
38
38
|
const controller = this.scope.resolve(command);
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const actions = collector.getActions(controller, { scope: this.scope });
|
|
39
|
+
const actionsOf = (key) => new ts_ioc_container_1.HookCollector({ key, createExecutionContext: (0, ts_ioc_container_1.createHookContextFactory)({ args: argv }) }).getActions(controller, { scope: this.scope });
|
|
40
|
+
let actions = actionsOf(action);
|
|
41
|
+
if (actions.length === 0 && action !== decorators_1.DEFAULT_ACTION)
|
|
42
|
+
actions = actionsOf(decorators_1.DEFAULT_ACTION);
|
|
44
43
|
if (actions.length === 0)
|
|
45
44
|
throw new DomainException_1.UnknownActionError(command, action);
|
|
46
45
|
for (const item of actions)
|
package/cjm/cli/options.js
CHANGED
|
@@ -6,10 +6,12 @@ const zod_1 = require("zod");
|
|
|
6
6
|
const DomainException_1 = require("../exceptions/DomainException");
|
|
7
7
|
const commandArgs = ({ args = [] }) => args.map(String);
|
|
8
8
|
exports.commandArgs = commandArgs;
|
|
9
|
-
const parseOptions = (options) => (argv) => {
|
|
9
|
+
const parseOptions = (options, { positionals = false } = {}) => (argv) => {
|
|
10
10
|
const firstFlag = argv.findIndex((arg) => arg.startsWith('-'));
|
|
11
|
+
const args = positionals ? argv.slice(1) : firstFlag >= 0 ? argv.slice(firstFlag) : [];
|
|
11
12
|
try {
|
|
12
|
-
|
|
13
|
+
const parsed = (0, node_util_1.parseArgs)({ args, options, strict: true, allowPositionals: positionals });
|
|
14
|
+
return positionals ? { ...parsed.values, positionals: parsed.positionals } : { ...parsed.values };
|
|
13
15
|
}
|
|
14
16
|
catch (e) {
|
|
15
17
|
throw new DomainException_1.UsageError(e.message);
|
package/cjm/cli/usage.js
CHANGED
|
@@ -6,14 +6,17 @@ exports.USAGE = [
|
|
|
6
6
|
' tic is a shortcut for ts-ioc-container',
|
|
7
7
|
'',
|
|
8
8
|
'Commands:',
|
|
9
|
-
' ts-ioc-container build [
|
|
10
|
-
'
|
|
11
|
-
' ts-ioc-container help
|
|
12
|
-
' ts-ioc-container version
|
|
9
|
+
' ts-ioc-container build [<config>] print the bundle a config file (.json, .yaml, .yml) describes;',
|
|
10
|
+
' without one (or with -), read the config from stdin',
|
|
11
|
+
' ts-ioc-container help show this help',
|
|
12
|
+
' ts-ioc-container version show the version',
|
|
13
13
|
'',
|
|
14
14
|
'Options:',
|
|
15
|
-
' -
|
|
16
|
-
' --
|
|
17
|
-
'
|
|
18
|
-
'
|
|
15
|
+
' -h, --help show this help',
|
|
16
|
+
' -v, --version show the version',
|
|
17
|
+
'',
|
|
18
|
+
'The bundle goes to stdout and warnings to stderr; saving it is up to you:',
|
|
19
|
+
' tic build app.bundle.json > src/di/app.bundle.ts',
|
|
20
|
+
' cat app.bundle.yml | tic build > src/di/app.bundle.ts # config from stdin (must set name)',
|
|
21
|
+
' tic build app.bundle.json | diff - src/di/app.bundle.ts # CI: is it current?',
|
|
19
22
|
].join('\n');
|
package/cjm/domain/CliIo.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.processIo = exports.CliIoKey = void 0;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
4
5
|
const ts_ioc_container_1 = require("ts-ioc-container");
|
|
5
6
|
exports.CliIoKey = new ts_ioc_container_1.SingleToken('CliIo');
|
|
6
|
-
const processIo = () => ({
|
|
7
|
+
const processIo = () => ({
|
|
8
|
+
cwd: process.cwd(),
|
|
9
|
+
stdout: console.log,
|
|
10
|
+
stderr: console.error,
|
|
11
|
+
stdin: () => (process.stdin.isTTY ? undefined : (0, node_fs_1.readFileSync)(0, 'utf8')),
|
|
12
|
+
});
|
|
7
13
|
exports.processIo = processIo;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.UsageError = exports.UnknownActionError = exports.UnknownCommandError = exports.MissingCommandError = exports.NamespaceNotFoundError = exports.TicConfigError = exports.TicError = void 0;
|
|
4
4
|
class TicError extends Error {
|
|
5
5
|
code;
|
|
6
6
|
constructor(code, message) {
|
|
@@ -16,6 +16,12 @@ class TicConfigError extends TicError {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
exports.TicConfigError = TicConfigError;
|
|
19
|
+
class NamespaceNotFoundError extends TicError {
|
|
20
|
+
constructor(message) {
|
|
21
|
+
super('NAMESPACE_NOT_FOUND', message);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.NamespaceNotFoundError = NamespaceNotFoundError;
|
|
19
25
|
class MissingCommandError extends TicError {
|
|
20
26
|
constructor(usage) {
|
|
21
27
|
super('MISSING_COMMAND', `missing command\n${usage}`);
|
|
@@ -40,9 +46,3 @@ class UsageError extends TicError {
|
|
|
40
46
|
}
|
|
41
47
|
}
|
|
42
48
|
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;
|
|
@@ -3,15 +3,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
6
|
+
exports.configFormat = exports.STDIN = exports.CONFIG_FILE_SUFFIXES = exports.BUNDLE_CONFIG_SCHEMA = exports.DEFAULT_EXCLUDE = exports.DEFAULT_DECORATORS = exports.DEFAULT_TSCONFIG = void 0;
|
|
7
|
+
exports.parseConfig = parseConfig;
|
|
7
8
|
exports.toClassName = toClassName;
|
|
8
9
|
exports.resolveConfig = resolveConfig;
|
|
9
10
|
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const yaml_1 = require("yaml");
|
|
10
12
|
const zod_1 = require("zod");
|
|
11
13
|
const DomainException_1 = require("../../exceptions/DomainException");
|
|
12
14
|
const glob_1 = require("./domain/glob");
|
|
13
|
-
exports.
|
|
14
|
-
exports.DEFAULT_BUNDLE_NAME = 'base';
|
|
15
|
+
exports.DEFAULT_TSCONFIG = './tsconfig.json';
|
|
15
16
|
exports.DEFAULT_DECORATORS = ['register'];
|
|
16
17
|
exports.DEFAULT_EXCLUDE = [
|
|
17
18
|
'**/*.spec.ts',
|
|
@@ -29,9 +30,34 @@ const strings = (what, minItems) => zod_1.z
|
|
|
29
30
|
.custom((value) => Array.isArray(value) && value.length >= minItems && value.every(isNonEmptyString), expected(what))
|
|
30
31
|
.meta({ type: 'array', items: { type: 'string', minLength: 1 }, ...(minItems > 0 && { minItems }) });
|
|
31
32
|
const stringList = () => strings('an array of strings', 0);
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
.
|
|
33
|
+
const globList = (what, minItems) => zod_1.z
|
|
34
|
+
.custom((value) => isNonEmptyString(value) || (Array.isArray(value) && value.length >= minItems && value.every(isNonEmptyString)), expected(what))
|
|
35
|
+
.meta({
|
|
36
|
+
anyOf: [
|
|
37
|
+
{ type: 'string', minLength: 1 },
|
|
38
|
+
{ type: 'array', items: { type: 'string', minLength: 1 }, ...(minItems > 0 && { minItems }) },
|
|
39
|
+
],
|
|
40
|
+
});
|
|
41
|
+
const globs = () => globList('a glob or a non-empty array of globs', 1);
|
|
42
|
+
const toList = (value) => (Array.isArray(value) ? value : [value]);
|
|
43
|
+
const toGlobs = (value) => value === undefined ? undefined : toList(value).map(glob_1.globToRegExp);
|
|
44
|
+
const FOLDERS_SCHEMA = zod_1.z.array(nonEmptyString(), expected('a non-empty array')).min(1, expected('a non-empty array'));
|
|
45
|
+
const GLOB_OBJECT_SCHEMA = zod_1.z.strictObject({
|
|
46
|
+
glob: zod_1.z
|
|
47
|
+
.union([nonEmptyString(), FOLDERS_SCHEMA], expected('a folder or a non-empty array of folders'))
|
|
48
|
+
.describe('Folder, or folders, to scan, sub-folders included: paths relative to the config file (./src/services) or tsconfig paths aliases (@app/services).'),
|
|
49
|
+
exclude: globList('a glob or an array of globs', 0)
|
|
50
|
+
.default(exports.DEFAULT_EXCLUDE)
|
|
51
|
+
.describe('Glob, or globs, of files never read. Replaces the default when given. A non-empty value that omits a default glob makes `tic build` warn.'),
|
|
52
|
+
}, expected('an object'));
|
|
53
|
+
const GLOB_SCHEMA = zod_1.z
|
|
54
|
+
.union([
|
|
55
|
+
nonEmptyString().describe('One folder to scan, with the default exclude: shorthand for { "glob": [...] }.'),
|
|
56
|
+
FOLDERS_SCHEMA.describe('Folders to scan, with the default exclude: shorthand for { "glob": [...] }.'),
|
|
57
|
+
GLOB_OBJECT_SCHEMA,
|
|
58
|
+
], expected('a folder, a non-empty array of folders or an object with "glob"'))
|
|
59
|
+
.describe('Which files this bundle reads and parses — decided by path alone, before parsing. `glob` names the folders the files come from; within them a file is parsed unless it matches one of `exclude`. Globs are relative to the config file. A folder, or a list of folders, is shorthand for { "glob": [...] }.');
|
|
60
|
+
const CLASS_OBJECT_SCHEMA = zod_1.z.strictObject({
|
|
35
61
|
export: zod_1.z
|
|
36
62
|
.enum(['any', 'named', 'default'], expected('"any", "named" or "default"'))
|
|
37
63
|
.default('any')
|
|
@@ -39,57 +65,40 @@ const CLASSES_SCHEMA = zod_1.z
|
|
|
39
65
|
decorators: stringList()
|
|
40
66
|
.default(exports.DEFAULT_DECORATORS)
|
|
41
67
|
.describe('The class must carry one of these decorators, recognised by name (e.g. "register", or a composed decorator). Default ["register"]; [] requires none.'),
|
|
42
|
-
|
|
43
|
-
.optional()
|
|
44
|
-
.describe('Glob the class name must match, e.g. "*Service". An anonymous default export is named after its file.'),
|
|
45
|
-
excludeClasses: nonEmptyStringList()
|
|
46
|
-
.optional()
|
|
47
|
-
.describe('Class names to drop after selection, e.g. one test double sitting next to the real registration in the same file.'),
|
|
48
|
-
excludeName: nonEmptyString()
|
|
49
|
-
.optional()
|
|
50
|
-
.describe('Glob the class name must NOT match, e.g. "*Mock". An anonymous default export is named after its file.'),
|
|
51
|
-
}, expected('an object'))
|
|
52
|
-
.describe('Which classes of a scanned file are registered. A class is selected when it is exported, not abstract, and meets every criterion set here; by default that is every exported class decorated with @register.');
|
|
53
|
-
const COMPILER_OPTIONS_SCHEMA = zod_1.z
|
|
54
|
-
.looseObject({
|
|
55
|
-
importExtension: zod_1.z
|
|
56
|
-
.string(expected('a string'))
|
|
57
|
-
.optional()
|
|
58
|
-
.describe('Extension of generated imports, e.g. ".js". Inferred from the tsconfig: ".js" under node16/nodenext resolution, none otherwise.'),
|
|
59
|
-
classes: CLASSES_SCHEMA.optional(),
|
|
60
|
-
paths: zod_1.z
|
|
61
|
-
.record(zod_1.z.string(), zod_1.z.array(zod_1.z.string()))
|
|
68
|
+
glob: globs()
|
|
62
69
|
.optional()
|
|
63
|
-
.describe('
|
|
64
|
-
|
|
65
|
-
.string()
|
|
70
|
+
.describe('Glob, or globs, the class name must match one of, e.g. "*Service" or ["*Service", "*Repository"]. An anonymous default export is named after its file.'),
|
|
71
|
+
exclude: globs()
|
|
66
72
|
.optional()
|
|
67
|
-
.describe('
|
|
68
|
-
}, expected('an object'))
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
.
|
|
72
|
-
.
|
|
73
|
+
.describe('Glob, or globs, the class name must match none of, applied after every other criterion. A plain name is a glob too: ["MockUserRepository", "*Fake"].'),
|
|
74
|
+
}, expected('an object'));
|
|
75
|
+
const CLASSES_SCHEMA = zod_1.z
|
|
76
|
+
.union([
|
|
77
|
+
nonEmptyString().describe('One class-name glob, with the default export and decorators: shorthand for { "glob": "..." }.'),
|
|
78
|
+
zod_1.z
|
|
79
|
+
.array(nonEmptyString(), expected('a non-empty array'))
|
|
80
|
+
.min(1, expected('a non-empty array'))
|
|
81
|
+
.describe('Class-name globs, with the default export and decorators: shorthand for { "glob": [...] }.'),
|
|
82
|
+
CLASS_OBJECT_SCHEMA,
|
|
83
|
+
], expected('a glob, a non-empty array of globs or an object'))
|
|
84
|
+
.describe('Which classes of a parsed file are registered. A class is selected when it is exported, not abstract, and meets every criterion set here; by default that is every exported class decorated with @register. A glob, or a list of globs, is shorthand for { "glob": ... }.');
|
|
73
85
|
exports.BUNDLE_CONFIG_SCHEMA = zod_1.z.strictObject({
|
|
74
86
|
$schema: zod_1.z.string().optional(),
|
|
75
|
-
extends: nonEmptyString()
|
|
76
|
-
.optional()
|
|
77
|
-
.meta({ default: exports.DEFAULT_EXTENDS })
|
|
78
|
-
.describe("The tsconfig this bundle extends, as a tsconfig's own `extends`: its file set, `paths` aliases and module resolution apply unless this file overrides them. Relative to this file. The default may be absent; a tsconfig named here must exist."),
|
|
79
|
-
baseUrl: nonEmptyString()
|
|
80
|
-
.optional()
|
|
81
|
-
.describe("The base folder this config's relative `include`, `exclude` and `output` resolve against, so they need not repeat a shared prefix such as `src`. Relative to this file; default this file's directory. Also handed to TypeScript as `compilerOptions.baseUrl` (unless that is set too), so generated imports use it."),
|
|
82
|
-
output: nonEmptyString()
|
|
83
|
-
.optional()
|
|
84
|
-
.describe('The bundle file (convention: *.bundle.ts). Default `<root>/<name>.bundle.ts`: <root> is the compiled rootDir, else the common folder of the files compiled.'),
|
|
85
87
|
name: zod_1.z
|
|
86
88
|
.string(expected('letters, digits, "-" or "_", starting with a letter'))
|
|
87
89
|
.regex(BUNDLE_NAME, expected('letters, digits, "-" or "_", starting with a letter'))
|
|
88
90
|
.optional()
|
|
89
|
-
.describe('The bundle\'s name: letters, digits, "-" and "_", starting with a letter. Default:
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
.describe('The bundle\'s name: letters, digits, "-" and "_", starting with a letter. Default: the config file\'s stem (production.bundle.json → production); required for a config file named otherwise. It names the generated IContainerModule class (ProductionBundle).'),
|
|
92
|
+
tsconfig: nonEmptyString()
|
|
93
|
+
.optional()
|
|
94
|
+
.meta({ default: exports.DEFAULT_TSCONFIG })
|
|
95
|
+
.describe('The tsconfig whose `paths` aliases glob.glob may name and generated imports are written in, and whose module resolution sets the import extension. It adds no files: the bundle scans glob.glob alone. Relative to the config file. The default may be absent; a tsconfig named here must exist.'),
|
|
96
|
+
importExtension: zod_1.z
|
|
97
|
+
.string(expected('a string'))
|
|
98
|
+
.optional()
|
|
99
|
+
.describe('Extension of generated imports, e.g. ".js". Inferred from the tsconfig: ".js" under node16/nodenext resolution, none otherwise.'),
|
|
100
|
+
glob: GLOB_SCHEMA,
|
|
101
|
+
className: CLASSES_SCHEMA.optional(),
|
|
93
102
|
}, expected('an object'));
|
|
94
103
|
function formatPath(segments) {
|
|
95
104
|
const formatted = segments
|
|
@@ -98,33 +107,75 @@ function formatPath(segments) {
|
|
|
98
107
|
.replace(/^\./, '');
|
|
99
108
|
return formatted || 'config';
|
|
100
109
|
}
|
|
101
|
-
function
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
110
|
+
function matchedBranch(issue) {
|
|
111
|
+
const typeMatched = issue.errors.filter((branch) => branch.every((inner) => inner.code !== 'invalid_type' || inner.path.length > 0));
|
|
112
|
+
return typeMatched.length === 1 ? typeMatched[0] : undefined;
|
|
113
|
+
}
|
|
114
|
+
function formatIssue(issue, prefix = []) {
|
|
115
|
+
const at = [...prefix, ...issue.path];
|
|
116
|
+
if (issue.code === 'unrecognized_keys')
|
|
117
|
+
return issue.keys.map((key) => `${formatPath([...at, key])}: unknown field`);
|
|
118
|
+
const branch = issue.code === 'invalid_union' ? matchedBranch(issue) : undefined;
|
|
119
|
+
if (branch)
|
|
120
|
+
return branch.flatMap((inner) => formatIssue(inner, at));
|
|
121
|
+
return [`${formatPath(at)}: ${issue.message}`];
|
|
106
122
|
}
|
|
107
123
|
const formatIssues = (error) => error.issues.flatMap((issue) => formatIssue(issue)).join('; ');
|
|
108
|
-
function
|
|
109
|
-
if (
|
|
124
|
+
function toGlobObject(glob) {
|
|
125
|
+
if (typeof glob === 'string' || Array.isArray(glob))
|
|
126
|
+
return { glob, exclude: exports.DEFAULT_EXCLUDE };
|
|
127
|
+
return glob;
|
|
128
|
+
}
|
|
129
|
+
function toGlobSelector(glob) {
|
|
130
|
+
return {
|
|
131
|
+
glob: toList(glob.glob),
|
|
132
|
+
exclude: toList(glob.exclude).map(glob_1.globToRegExp),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function toClassSelector(className) {
|
|
136
|
+
if (className === undefined)
|
|
110
137
|
return { export: 'any', decorators: exports.DEFAULT_DECORATORS };
|
|
111
|
-
|
|
138
|
+
if (typeof className === 'string' || Array.isArray(className)) {
|
|
139
|
+
return { export: 'any', decorators: exports.DEFAULT_DECORATORS, glob: toGlobs(className) };
|
|
140
|
+
}
|
|
141
|
+
const { decorators, glob, exclude, ...rest } = className;
|
|
112
142
|
return {
|
|
113
143
|
...rest,
|
|
114
144
|
decorators: decorators.length > 0 ? decorators : undefined,
|
|
115
|
-
|
|
116
|
-
|
|
145
|
+
glob: toGlobs(glob),
|
|
146
|
+
exclude: toGlobs(exclude),
|
|
117
147
|
};
|
|
118
148
|
}
|
|
149
|
+
function excludeWarnings(glob) {
|
|
150
|
+
const exclude = toList(glob.exclude);
|
|
151
|
+
if (exclude.length === 0)
|
|
152
|
+
return [];
|
|
153
|
+
const missing = exports.DEFAULT_EXCLUDE.filter((glob) => !exclude.includes(glob));
|
|
154
|
+
if (missing.length === 0)
|
|
155
|
+
return [];
|
|
156
|
+
return [
|
|
157
|
+
`glob.exclude replaces the default excludes and omits ${missing.map((glob) => `"${glob}"`).join(', ')}; ` +
|
|
158
|
+
`add them back`,
|
|
159
|
+
];
|
|
160
|
+
}
|
|
119
161
|
exports.CONFIG_FILE_SUFFIXES = ['.bundle.json', '.bundle.yaml', '.bundle.yml'];
|
|
120
|
-
const configSuffix = (file) => exports.CONFIG_FILE_SUFFIXES.find((suffix) => file.endsWith(suffix));
|
|
121
|
-
exports.configSuffix = configSuffix;
|
|
122
162
|
function configStem(file) {
|
|
123
163
|
const base = node_path_1.default.basename(file);
|
|
124
|
-
const suffix = (
|
|
164
|
+
const suffix = exports.CONFIG_FILE_SUFFIXES.find((candidate) => base.endsWith(candidate));
|
|
125
165
|
const stem = suffix ? base.slice(0, -suffix.length) : '';
|
|
126
166
|
return BUNDLE_NAME.test(stem) ? stem : undefined;
|
|
127
167
|
}
|
|
168
|
+
exports.STDIN = '-';
|
|
169
|
+
const configFormat = (file) => file.endsWith('.json') ? 'json' : file.endsWith('.yaml') || file.endsWith('.yml') ? 'yaml' : undefined;
|
|
170
|
+
exports.configFormat = configFormat;
|
|
171
|
+
function parseConfig(text, format) {
|
|
172
|
+
try {
|
|
173
|
+
return format === 'yaml' ? ((0, yaml_1.parse)(text) ?? {}) : JSON.parse(text);
|
|
174
|
+
}
|
|
175
|
+
catch (e) {
|
|
176
|
+
throw new DomainException_1.TicConfigError(`not valid ${format.toUpperCase()}: ${e.message}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
128
179
|
function toClassName(name) {
|
|
129
180
|
const words = name.split(/[-_]+/).filter(Boolean);
|
|
130
181
|
return `${words.map((word) => word[0].toUpperCase() + word.slice(1)).join('')}Bundle`;
|
|
@@ -133,26 +184,24 @@ function resolveConfig(content, file) {
|
|
|
133
184
|
const result = exports.BUNDLE_CONFIG_SCHEMA.safeParse(content);
|
|
134
185
|
if (!result.success)
|
|
135
186
|
throw new DomainException_1.TicConfigError(formatIssues(result.error));
|
|
136
|
-
const {
|
|
137
|
-
const
|
|
187
|
+
const { name, tsconfig, importExtension, glob, className } = result.data;
|
|
188
|
+
const bundleName = name ?? configStem(file);
|
|
189
|
+
if (bundleName === undefined) {
|
|
190
|
+
throw new DomainException_1.TicConfigError(node_path_1.default.basename(file) === exports.STDIN
|
|
191
|
+
? 'name: required for a config read from stdin'
|
|
192
|
+
: `name: required when the config file is not named <name>${exports.CONFIG_FILE_SUFFIXES.join(' / ')}`);
|
|
193
|
+
}
|
|
138
194
|
const dir = node_path_1.default.dirname(file);
|
|
139
|
-
const
|
|
140
|
-
const includeGlobs = include ?? (baseUrl === undefined ? undefined : ['.']);
|
|
141
|
-
const rebase = (globs) => globs?.map((glob) => (node_path_1.default.isAbsolute(glob) ? glob : (0, glob_1.toPosix)(node_path_1.default.relative(dir, node_path_1.default.resolve(base, glob))) || '.'));
|
|
142
|
-
const bundleName = name ?? configStem(file) ?? exports.DEFAULT_BUNDLE_NAME;
|
|
195
|
+
const globObject = toGlobObject(glob);
|
|
143
196
|
return {
|
|
144
197
|
file,
|
|
145
198
|
dir,
|
|
146
|
-
output: output === undefined ? undefined : node_path_1.default.resolve(base, output),
|
|
147
199
|
name: bundleName,
|
|
148
|
-
|
|
149
|
-
tsconfig: { file: node_path_1.default.resolve(dir, tsconfig ?? exports.
|
|
150
|
-
overrides: {
|
|
151
|
-
include: rebase(includeGlobs),
|
|
152
|
-
exclude: rebase(exclude),
|
|
153
|
-
compilerOptions: baseUrl === undefined ? tsCompilerOptions : { baseUrl, ...tsCompilerOptions },
|
|
154
|
-
},
|
|
200
|
+
bundleClassName: toClassName(bundleName),
|
|
201
|
+
tsconfig: { file: node_path_1.default.resolve(dir, tsconfig ?? exports.DEFAULT_TSCONFIG), required: tsconfig !== undefined },
|
|
155
202
|
importExtension,
|
|
156
|
-
|
|
203
|
+
glob: toGlobSelector(globObject),
|
|
204
|
+
className: toClassSelector(className),
|
|
205
|
+
warnings: excludeWarnings(globObject),
|
|
157
206
|
};
|
|
158
207
|
}
|
|
@@ -11,65 +11,56 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
11
11
|
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
12
|
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
13
|
};
|
|
14
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
15
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
16
|
-
};
|
|
17
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
15
|
exports.BuildController = exports.BUILD_OPTIONS = void 0;
|
|
19
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
20
16
|
const ts_ioc_container_1 = require("ts-ioc-container");
|
|
21
17
|
const zod_1 = require("zod");
|
|
22
18
|
const cli_1 = require("../../cli");
|
|
23
|
-
const
|
|
19
|
+
const CliIo_1 = require("../../domain/CliIo");
|
|
24
20
|
const DomainException_1 = require("../../exceptions/DomainException");
|
|
25
21
|
const ConsoleLogger_1 = require("../../services/ConsoleLogger");
|
|
26
22
|
const OutputService_1 = require("../../services/OutputService");
|
|
23
|
+
const BuildConfig_1 = require("./BuildConfig");
|
|
27
24
|
const BundleBuilder_1 = require("./services/BundleBuilder");
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
config: { type: 'string', short: 'c', multiple: true },
|
|
35
|
-
check: { type: 'boolean' },
|
|
25
|
+
exports.BUILD_OPTIONS = zod_1.z.object({ positionals: zod_1.z.array(zod_1.z.string().min(1)) }).transform(({ positionals }, ctx) => {
|
|
26
|
+
if (positionals.length > 1) {
|
|
27
|
+
ctx.addIssue({ code: 'custom', path: ['config'], message: `expected one <config>, got ${positionals.join(' ')}` });
|
|
28
|
+
return zod_1.z.NEVER;
|
|
29
|
+
}
|
|
30
|
+
return { config: positionals[0] ?? BuildConfig_1.STDIN };
|
|
36
31
|
});
|
|
37
|
-
const
|
|
38
|
-
const plural = (n) => `${n} registration${n === 1 ? '' : 's'}`;
|
|
32
|
+
const buildArgs = (0, cli_1.parseOptions)({}, { positionals: true });
|
|
39
33
|
let BuildController = class BuildController {
|
|
40
|
-
|
|
41
|
-
configs;
|
|
34
|
+
io;
|
|
42
35
|
builder;
|
|
43
36
|
output;
|
|
44
37
|
logger;
|
|
45
|
-
constructor(
|
|
46
|
-
this.
|
|
47
|
-
this.configs = configs;
|
|
38
|
+
constructor(io, builder, output, logger) {
|
|
39
|
+
this.io = io;
|
|
48
40
|
this.builder = builder;
|
|
49
41
|
this.output = output;
|
|
50
42
|
this.logger = logger;
|
|
51
43
|
}
|
|
52
|
-
build(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
44
|
+
build({ config }) {
|
|
45
|
+
const label = config === BuildConfig_1.STDIN ? '<stdin>' : config;
|
|
46
|
+
try {
|
|
47
|
+
const { content, warnings } = this.builder.build(config === BuildConfig_1.STDIN ? { text: this.stdin() } : { config });
|
|
48
|
+
for (const warning of warnings)
|
|
49
|
+
this.logger.warn(`${label}: ${warning}`);
|
|
50
|
+
this.output.write(content.replace(/\n$/, ''));
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
if (e instanceof DomainException_1.TicError && !(e instanceof DomainException_1.UsageError))
|
|
54
|
+
e.message = `${label}: ${e.message}`;
|
|
55
|
+
throw e;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
stdin() {
|
|
59
|
+
const text = this.io.stdin?.();
|
|
60
|
+
if (text === undefined) {
|
|
61
|
+
throw new DomainException_1.UsageError('missing <config>: name a config file, or pipe one in (cat app.bundle.json | tic build)');
|
|
70
62
|
}
|
|
71
|
-
|
|
72
|
-
throw new DomainException_1.StaleBundlesError(stale);
|
|
63
|
+
return text;
|
|
73
64
|
}
|
|
74
65
|
};
|
|
75
66
|
exports.BuildController = BuildController;
|
|
@@ -82,10 +73,9 @@ __decorate([
|
|
|
82
73
|
], BuildController.prototype, "build", null);
|
|
83
74
|
exports.BuildController = BuildController = __decorate([
|
|
84
75
|
(0, ts_ioc_container_1.register)('build'),
|
|
85
|
-
__param(0, (0, ts_ioc_container_1.inject)((0,
|
|
86
|
-
__param(1, (0, ts_ioc_container_1.inject)((0, ts_ioc_container_1.by)(
|
|
87
|
-
__param(2, (0, ts_ioc_container_1.inject)((0, ts_ioc_container_1.by)(
|
|
88
|
-
__param(3, (0, ts_ioc_container_1.inject)((0, ts_ioc_container_1.by)(
|
|
89
|
-
|
|
90
|
-
__metadata("design:paramtypes", [String, Object, Object, Object, Object])
|
|
76
|
+
__param(0, (0, ts_ioc_container_1.inject)((0, ts_ioc_container_1.by)(CliIo_1.CliIoKey))),
|
|
77
|
+
__param(1, (0, ts_ioc_container_1.inject)((0, ts_ioc_container_1.by)(BundleBuilder_1.IBundleBuilderKey))),
|
|
78
|
+
__param(2, (0, ts_ioc_container_1.inject)((0, ts_ioc_container_1.by)(OutputService_1.IOutputServiceKey))),
|
|
79
|
+
__param(3, (0, ts_ioc_container_1.inject)((0, ts_ioc_container_1.by)(ConsoleLogger_1.ILoggerKey))),
|
|
80
|
+
__metadata("design:paramtypes", [Object, Object, Object, Object])
|
|
91
81
|
], BuildController);
|
|
@@ -37,10 +37,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.ImportPaths = void 0;
|
|
40
|
+
const node_fs_1 = require("node:fs");
|
|
40
41
|
const node_path_1 = __importDefault(require("node:path"));
|
|
41
42
|
const ts = __importStar(require("typescript"));
|
|
43
|
+
const DomainException_1 = require("../../../exceptions/DomainException");
|
|
42
44
|
const glob_1 = require("./glob");
|
|
43
45
|
const SOURCE_EXTENSION = /\.(tsx?|mts|cts)$/;
|
|
46
|
+
const isDirectory = (dir) => (0, node_fs_1.existsSync)(dir) && (0, node_fs_1.statSync)(dir).isDirectory();
|
|
47
|
+
const isRelative = (spec) => spec.startsWith('.') || node_path_1.default.isAbsolute(spec);
|
|
44
48
|
function splitWildcard(pattern) {
|
|
45
49
|
const star = pattern.indexOf('*');
|
|
46
50
|
return star === -1 ? undefined : [pattern.slice(0, star), pattern.slice(star + 1)];
|
|
@@ -48,7 +52,6 @@ function splitWildcard(pattern) {
|
|
|
48
52
|
class ImportPaths {
|
|
49
53
|
aliases;
|
|
50
54
|
extension;
|
|
51
|
-
baseUrl;
|
|
52
55
|
static fromOptions(options, tsconfigFile, importExtension) {
|
|
53
56
|
const { paths = {}, baseUrl, pathsBasePath, moduleResolution } = options;
|
|
54
57
|
const base = baseUrl ?? (typeof pathsBasePath === 'string' ? pathsBasePath : node_path_1.default.dirname(tsconfigFile));
|
|
@@ -57,30 +60,26 @@ class ImportPaths {
|
|
|
57
60
|
targets: targets.map((target) => (0, glob_1.toPosix)(node_path_1.default.resolve(base, target))),
|
|
58
61
|
}));
|
|
59
62
|
const nodeEsm = moduleResolution === ts.ModuleResolutionKind.Node16 || moduleResolution === ts.ModuleResolutionKind.NodeNext;
|
|
60
|
-
return new ImportPaths(aliases, importExtension ?? (nodeEsm ? '.js' : '')
|
|
63
|
+
return new ImportPaths(aliases, importExtension ?? (nodeEsm ? '.js' : ''));
|
|
61
64
|
}
|
|
62
|
-
constructor(aliases, extension
|
|
65
|
+
constructor(aliases, extension) {
|
|
63
66
|
this.aliases = aliases;
|
|
64
67
|
this.extension = extension;
|
|
65
|
-
this.baseUrl = baseUrl;
|
|
66
68
|
}
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return `${relative.startsWith('.') ? relative : `./${relative}`}${this.extension}`;
|
|
69
|
+
resolveNamespace(namespace, baseDir) {
|
|
70
|
+
const folder = node_path_1.default.resolve(baseDir, namespace);
|
|
71
|
+
if (isDirectory(folder))
|
|
72
|
+
return folder;
|
|
73
|
+
if (!isRelative(namespace)) {
|
|
74
|
+
const found = [namespace, `${namespace}/`].flatMap((spec) => this.aliasTargets(spec)).find(isDirectory);
|
|
75
|
+
if (found)
|
|
76
|
+
return node_path_1.default.normalize(found);
|
|
77
|
+
}
|
|
78
|
+
throw new DomainException_1.NamespaceNotFoundError(`namespace "${namespace}" is neither a folder relative to ${baseDir} nor a tsconfig paths alias of one`);
|
|
78
79
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const prefix = this.baseUrl.endsWith('/') ? this.baseUrl : `${this.baseUrl}/`;
|
|
83
|
-
return bare.startsWith(prefix) ? bare.slice(prefix.length) : undefined;
|
|
80
|
+
specifier(file) {
|
|
81
|
+
const target = (0, glob_1.toPosix)(file);
|
|
82
|
+
return this.bestAlias(target, target.replace(SOURCE_EXTENSION, ''))?.spec;
|
|
84
83
|
}
|
|
85
84
|
bestAlias(target, bare) {
|
|
86
85
|
let best;
|
|
@@ -93,6 +92,18 @@ class ImportPaths {
|
|
|
93
92
|
}
|
|
94
93
|
return best;
|
|
95
94
|
}
|
|
95
|
+
aliasTargets(spec) {
|
|
96
|
+
return this.aliases.flatMap(({ pattern, targets }) => {
|
|
97
|
+
const wildcard = splitWildcard(pattern);
|
|
98
|
+
if (!wildcard)
|
|
99
|
+
return pattern === spec ? targets : [];
|
|
100
|
+
const [prefix, suffix] = wildcard;
|
|
101
|
+
if (!spec.startsWith(prefix) || !spec.endsWith(suffix) || spec.length < prefix.length + suffix.length)
|
|
102
|
+
return [];
|
|
103
|
+
const captured = spec.slice(prefix.length, spec.length - suffix.length);
|
|
104
|
+
return targets.map((target) => target.replace('*', captured));
|
|
105
|
+
});
|
|
106
|
+
}
|
|
96
107
|
matchAlias(pattern, aliasTarget, file, bare) {
|
|
97
108
|
const wildcard = splitWildcard(aliasTarget);
|
|
98
109
|
if (!wildcard) {
|
|
@@ -7,7 +7,7 @@ const HandlebarsRenderService_1 = require("../../../services/HandlebarsRenderSer
|
|
|
7
7
|
exports.GENERATED_HEADER = '// Generated by `tic build`';
|
|
8
8
|
exports.BUNDLE_PROTOCOL = 'Bundle.ts.hbs';
|
|
9
9
|
const RUNTIME_IMPORTS = ['IContainer', 'IContainerModule', 'IRegistration', 'Registration'];
|
|
10
|
-
function bundleView({ name,
|
|
10
|
+
function bundleView({ name, paths, classes }) {
|
|
11
11
|
const taken = new Set([...RUNTIME_IMPORTS, 'registrations', name]);
|
|
12
12
|
const uniqueName = (wanted) => {
|
|
13
13
|
let candidate = wanted;
|
|
@@ -27,7 +27,7 @@ function bundleView({ name, configPath, classes }) {
|
|
|
27
27
|
entry.named.push(binding === cls.exportName ? binding : `${cls.exportName} as ${binding}`);
|
|
28
28
|
return binding;
|
|
29
29
|
});
|
|
30
|
-
return {
|
|
30
|
+
return { paths, name, imports: [...byFile.values()], registrations: bindings };
|
|
31
31
|
}
|
|
32
32
|
function emitBundle(input) {
|
|
33
33
|
return (0, HandlebarsRenderService_1.renderProtocol)(exports.BUNDLE_PROTOCOL, bundleView(input));
|