@hublo/sentinel 1.2.0-alpha.17 → 1.2.0-alpha.19
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/dist/bin/sentinel.js
CHANGED
|
@@ -183,7 +183,7 @@ function webpackBuildTarget(cwd) {
|
|
|
183
183
|
const project = readProjectJson(cwd);
|
|
184
184
|
const target = project?.targets?.build;
|
|
185
185
|
if (target?.executor !== DEPRECATED_NX_BUILDER) return void 0;
|
|
186
|
-
const { outputPath, main, webpackConfig } = target.options ?? {};
|
|
186
|
+
const { outputPath, main, webpackConfig, transformers, assets } = target.options ?? {};
|
|
187
187
|
if (typeof project?.name !== "string" || typeof outputPath !== "string" || typeof main !== "string" || typeof webpackConfig !== "string") {
|
|
188
188
|
return void 0;
|
|
189
189
|
}
|
|
@@ -192,7 +192,9 @@ function webpackBuildTarget(cwd) {
|
|
|
192
192
|
outputPath,
|
|
193
193
|
main,
|
|
194
194
|
webpackConfig,
|
|
195
|
-
configurations: target.configurations
|
|
195
|
+
configurations: target.configurations,
|
|
196
|
+
transformers,
|
|
197
|
+
assets
|
|
196
198
|
};
|
|
197
199
|
}
|
|
198
200
|
function refusalFor(cwd, target) {
|
|
@@ -526,6 +528,9 @@ function selfCommand(cwd, flags) {
|
|
|
526
528
|
if (!isSelfAdoption(cwd) || !own.bin) return void 0;
|
|
527
529
|
return `node ${own.bin.replace(/^\.\//, "")} ${flags}`;
|
|
528
530
|
}
|
|
531
|
+
function adoptedPackageName(cwd) {
|
|
532
|
+
return readProjectPackageJson(cwd).name ?? readNxProjectName(cwd) ?? basename2(cwd);
|
|
533
|
+
}
|
|
529
534
|
function manifestOperation(cwd, scripts) {
|
|
530
535
|
const own = readOwnPackage();
|
|
531
536
|
const value = {
|
|
@@ -538,7 +543,7 @@ function manifestOperation(cwd, scripts) {
|
|
|
538
543
|
return {
|
|
539
544
|
kind: "merge-json",
|
|
540
545
|
path: "package.json",
|
|
541
|
-
value: { name:
|
|
546
|
+
value: { name: adoptedPackageName(cwd), private: true, ...value }
|
|
542
547
|
};
|
|
543
548
|
}
|
|
544
549
|
|
|
@@ -670,6 +675,8 @@ var NEST_CONFIG_FILE = "vite.config.mts";
|
|
|
670
675
|
function nestConfigSource(cwd, target) {
|
|
671
676
|
const hops = workspaceRootHops(cwd, target);
|
|
672
677
|
const up = Array.from({ length: hops }, () => "..").join("/");
|
|
678
|
+
const carried = (name, value) => value === void 0 || value.length === 0 ? "" : ` ${name}: ${JSON.stringify(value)},
|
|
679
|
+
`;
|
|
673
680
|
return `import path from 'node:path'
|
|
674
681
|
|
|
675
682
|
import { nestService } from '${BUILD_PRESET_SPECIFIERS.nest}'
|
|
@@ -679,7 +686,7 @@ export default nestService({
|
|
|
679
686
|
root: __dirname,
|
|
680
687
|
workspaceRoot: path.resolve(__dirname, '${up}'),
|
|
681
688
|
outDir: '${target.outputPath}',
|
|
682
|
-
})
|
|
689
|
+
${carried("transformers", target.transformers)}${carried("assets", target.assets)}})
|
|
683
690
|
`;
|
|
684
691
|
}
|
|
685
692
|
function nestBuildOutputs(target) {
|
|
@@ -829,13 +836,9 @@ function rewriteBuildImports(source, fileName = "vite.config.ts") {
|
|
|
829
836
|
}
|
|
830
837
|
|
|
831
838
|
// src/roles/build/plan.ts
|
|
832
|
-
function packageNameFor(cwd) {
|
|
833
|
-
return readProjectPackageJson(cwd).name;
|
|
834
|
-
}
|
|
835
839
|
function candidateOutputs() {
|
|
836
840
|
return BUILD_OUTPUT_CANDIDATES.map((name) => `{projectRoot}/${name}`);
|
|
837
841
|
}
|
|
838
|
-
var NO_PACKAGE_NAME = `this module's package.json declares no \`name\`, so the nx build target would have no way to reach it: the command is \`pnpm --filter <name> run build\`, and an unnamed package matches nothing. Name the package and re-run; nothing was written.`;
|
|
839
842
|
function ownedBuildDependencies(cwd) {
|
|
840
843
|
const manifest = readProjectPackageJson(cwd);
|
|
841
844
|
const keys = [];
|
|
@@ -908,8 +911,6 @@ function planReactApp(context, configFile) {
|
|
|
908
911
|
const operations = [];
|
|
909
912
|
const notEsm = esmBlocker(configFile, readProjectPackageJson(context.cwd).type);
|
|
910
913
|
if (notEsm !== void 0) return { operations: [], skipped: notEsm };
|
|
911
|
-
const packageName = packageNameFor(context.cwd);
|
|
912
|
-
if (packageName === void 0) return { operations: [], skipped: NO_PACKAGE_NAME };
|
|
913
914
|
const rewrite = rewriteBuildImports(readConfigSource(context.cwd, configFile), configFile);
|
|
914
915
|
if (rewrite.kind === "blocked") {
|
|
915
916
|
return {
|
|
@@ -942,7 +943,10 @@ function planReactApp(context, configFile) {
|
|
|
942
943
|
operations.push(
|
|
943
944
|
...nxTargetOperations({
|
|
944
945
|
cwd: context.cwd,
|
|
945
|
-
targets: buildTarget({
|
|
946
|
+
targets: buildTarget({
|
|
947
|
+
moduleName: adoptedPackageName(context.cwd),
|
|
948
|
+
outputs: candidateOutputs()
|
|
949
|
+
})
|
|
946
950
|
})
|
|
947
951
|
);
|
|
948
952
|
const owned = ownedBuildDependencies(context.cwd);
|
|
@@ -955,8 +959,6 @@ function planReactApp(context, configFile) {
|
|
|
955
959
|
return { operations, notes };
|
|
956
960
|
}
|
|
957
961
|
function planNestService(context, webpackTarget) {
|
|
958
|
-
const packageName = packageNameFor(context.cwd);
|
|
959
|
-
if (packageName === void 0) return { operations: [], skipped: NO_PACKAGE_NAME };
|
|
960
962
|
const outputs = webpackTarget === void 0 ? nestOutputsFromConfig(context.cwd) : nestBuildOutputs(webpackTarget);
|
|
961
963
|
if (outputs === void 0) {
|
|
962
964
|
return {
|
|
@@ -968,7 +970,7 @@ function planNestService(context, webpackTarget) {
|
|
|
968
970
|
...nxTargetOperations({
|
|
969
971
|
cwd: context.cwd,
|
|
970
972
|
targets: buildTarget({
|
|
971
|
-
moduleName:
|
|
973
|
+
moduleName: adoptedPackageName(context.cwd),
|
|
972
974
|
configFiles: [NEST_CONFIG_FILE],
|
|
973
975
|
outputs
|
|
974
976
|
})
|
|
@@ -6518,4 +6520,4 @@ export {
|
|
|
6518
6520
|
detectFramework,
|
|
6519
6521
|
dispatch
|
|
6520
6522
|
};
|
|
6521
|
-
//# sourceMappingURL=chunk-
|
|
6523
|
+
//# sourceMappingURL=chunk-RKVGZNJ7.js.map
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import { UserConfig, Plugin } from 'vite';
|
|
2
2
|
export { Plugin, PluginOption, UserConfig, UserConfigExport, defineConfig, loadEnv, mergeConfig } from 'vite';
|
|
3
3
|
|
|
4
|
+
/** A transformer as the target declares it, passed through to the config unchanged. */
|
|
5
|
+
interface TransformerDeclaration$1 {
|
|
6
|
+
name: string;
|
|
7
|
+
options?: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
/** An asset as the target declares it, in `@nx/webpack`'s own shape. */
|
|
10
|
+
interface AssetDeclaration {
|
|
11
|
+
glob: string;
|
|
12
|
+
/** Relative to the workspace root, as the target declares it. */
|
|
13
|
+
input: string;
|
|
14
|
+
/** Relative to the output directory. */
|
|
15
|
+
output: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
4
18
|
interface NestServiceOptions {
|
|
5
19
|
/** The nx project name. Keys the dependency graph, and names the module in messages. */
|
|
6
20
|
project: string;
|
|
@@ -18,6 +32,23 @@ interface NestServiceOptions {
|
|
|
18
32
|
tsconfig?: string;
|
|
19
33
|
/** Output directory, relative to `workspaceRoot`. */
|
|
20
34
|
outDir?: string;
|
|
35
|
+
/**
|
|
36
|
+
* TypeScript transformers this service compiles with, as its build target declared them.
|
|
37
|
+
*
|
|
38
|
+
* Read off the webpack target by `--init` and written here, never inferred: what a service
|
|
39
|
+
* compiles with is the service's own. Eight services and BFFs run `@nestjs/swagger/plugin`,
|
|
40
|
+
* which writes the `@ApiProperty` decorators their DTOs do not declare by hand. Building
|
|
41
|
+
* without it succeeds and silently costs them most of their published contract (measured on
|
|
42
|
+
* `institution`: 385 insertions, 1378 deletions in the committed OpenAPI document).
|
|
43
|
+
*/
|
|
44
|
+
transformers?: readonly TransformerDeclaration$1[];
|
|
45
|
+
/**
|
|
46
|
+
* Files copied into the output beside the bundle, in the shape `@nx/webpack` declared them.
|
|
47
|
+
*
|
|
48
|
+
* One service here uses it: `planning-period` ships the pug, css and ttf templates it renders
|
|
49
|
+
* printed documents from. Without them the service starts and fails on its first print.
|
|
50
|
+
*/
|
|
51
|
+
assets?: readonly AssetDeclaration[];
|
|
21
52
|
/**
|
|
22
53
|
* Anything this service needs that the shape does not give it.
|
|
23
54
|
*
|
|
@@ -29,6 +60,13 @@ interface NestServiceOptions {
|
|
|
29
60
|
}
|
|
30
61
|
declare const nestService: (options: NestServiceOptions) => UserConfig;
|
|
31
62
|
|
|
63
|
+
/** A TypeScript transformer a service runs at build time, as its build target declares it. */
|
|
64
|
+
interface TransformerDeclaration {
|
|
65
|
+
/** The package to load it from, e.g. `@nestjs/swagger/plugin`. */
|
|
66
|
+
name: string;
|
|
67
|
+
/** Passed to the transformer's own factory, unread here. */
|
|
68
|
+
options?: Record<string, unknown>;
|
|
69
|
+
}
|
|
32
70
|
interface DecoratorMetadataOptions {
|
|
33
71
|
/** The module being built. Its tsconfig is the one whose emit must be preserved. */
|
|
34
72
|
root: string;
|
|
@@ -38,6 +76,8 @@ interface DecoratorMetadataOptions {
|
|
|
38
76
|
* Relative paths resolve against `root`.
|
|
39
77
|
*/
|
|
40
78
|
tsconfig?: string;
|
|
79
|
+
/** The transformers this service declared, translated from its webpack target. */
|
|
80
|
+
transformers?: readonly TransformerDeclaration[];
|
|
41
81
|
}
|
|
42
82
|
declare const decoratorMetadata: (options: DecoratorMetadataOptions) => Plugin;
|
|
43
83
|
|
|
@@ -1,37 +1,121 @@
|
|
|
1
1
|
// src/roles/build/nest/nest-service.ts
|
|
2
|
-
import { join as
|
|
2
|
+
import { join as join4 } from "path";
|
|
3
3
|
import { mergeConfig } from "vite";
|
|
4
4
|
|
|
5
|
+
// src/roles/build/nest/copy-assets.ts
|
|
6
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
7
|
+
import { dirname, join, relative, sep } from "path";
|
|
8
|
+
function globToRegExp(glob) {
|
|
9
|
+
let pattern = "";
|
|
10
|
+
for (let index = 0; index < glob.length; index++) {
|
|
11
|
+
const char = glob[index] ?? "";
|
|
12
|
+
if (char === "*") {
|
|
13
|
+
if (glob[index + 1] === "*") {
|
|
14
|
+
pattern += glob[index + 2] === "/" ? "(?:.*/)?" : ".*";
|
|
15
|
+
index += glob[index + 2] === "/" ? 2 : 1;
|
|
16
|
+
} else {
|
|
17
|
+
pattern += "[^/]*";
|
|
18
|
+
}
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (char === "{") {
|
|
22
|
+
const close = glob.indexOf("}", index);
|
|
23
|
+
if (close === -1)
|
|
24
|
+
throw new Error(`sentinel build(nest): unbalanced \`{\` in asset glob ${glob}`);
|
|
25
|
+
const alternatives = glob.slice(index + 1, close).split(",");
|
|
26
|
+
pattern += `(?:${alternatives.map((one) => one.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})`;
|
|
27
|
+
index = close;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
pattern += char.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
31
|
+
}
|
|
32
|
+
return new RegExp(`^${pattern}$`);
|
|
33
|
+
}
|
|
34
|
+
function filesUnder(root) {
|
|
35
|
+
const found = [];
|
|
36
|
+
const walk = (dir) => {
|
|
37
|
+
for (const entry of readdirSync(dir)) {
|
|
38
|
+
const full = join(dir, entry);
|
|
39
|
+
if (statSync(full).isDirectory()) walk(full);
|
|
40
|
+
else found.push(relative(root, full).split(sep).join("/"));
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
walk(root);
|
|
44
|
+
return found;
|
|
45
|
+
}
|
|
46
|
+
function resolveAssets(options) {
|
|
47
|
+
const copies = [];
|
|
48
|
+
for (const asset of options.assets) {
|
|
49
|
+
const input = join(options.workspaceRoot, asset.input);
|
|
50
|
+
if (!existsSync(input)) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`sentinel build(nest): the build target declares an asset from \`${asset.input}\`, which does not exist. webpack copied it into the output, so the built service would be missing files it reads at runtime.`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
const matcher = globToRegExp(asset.glob);
|
|
56
|
+
const matched = filesUnder(input).filter((file) => matcher.test(file));
|
|
57
|
+
if (matched.length === 0) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`sentinel build(nest): the asset glob \`${asset.glob}\` under \`${asset.input}\` matches no file. An entry that matches nothing is the same silent loss as no entry at all.`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
for (const file of matched) {
|
|
63
|
+
copies.push({ from: join(input, file), to: join(options.outDir, asset.output, file) });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return copies;
|
|
67
|
+
}
|
|
68
|
+
var copyAssets = (options) => ({
|
|
69
|
+
name: "sentinel:copy-assets",
|
|
70
|
+
// After the bundle is written and the output directory has stopped being emptied.
|
|
71
|
+
closeBundle() {
|
|
72
|
+
for (const { from, to } of resolveAssets(options)) {
|
|
73
|
+
mkdirSync(dirname(to), { recursive: true });
|
|
74
|
+
cpSync(from, to);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
5
79
|
// src/roles/build/nest/decorator-metadata.ts
|
|
6
|
-
import { existsSync } from "fs";
|
|
80
|
+
import { existsSync as existsSync2 } from "fs";
|
|
81
|
+
import { createRequire } from "module";
|
|
7
82
|
import path from "path";
|
|
8
|
-
import ts from "typescript";
|
|
9
83
|
var TYPESCRIPT_SOURCE = /\.ts$/;
|
|
10
84
|
var TSCONFIG_CANDIDATES = ["tsconfig.app.json", "tsconfig.json"];
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
85
|
+
function loadCompiler(root) {
|
|
86
|
+
const require2 = createRequire(path.join(root, "noop.js"));
|
|
87
|
+
let compiler;
|
|
88
|
+
try {
|
|
89
|
+
compiler = require2("typescript");
|
|
90
|
+
} catch {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`sentinel build(nest): no \`typescript\` resolvable from ${root}. The build emits decorator metadata with the module's own compiler, so one has to be installed there.`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
if (typeof compiler.createProgram !== "function") {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`sentinel build(nest): the typescript resolved from ${root} (${compiler.version ?? "unknown"}) has no createProgram API. TypeScript 7 is the native port and exposes none; the emit API lives under @typescript/typescript6. Point this module's \`typescript\` at a compiler with an emit API, which is the one its \`typecheck\` target already uses.`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
return compiler;
|
|
101
|
+
}
|
|
102
|
+
function resolveTsconfig(options) {
|
|
18
103
|
if (options.tsconfig) {
|
|
19
104
|
const explicit = path.resolve(options.root, options.tsconfig);
|
|
20
|
-
if (!
|
|
105
|
+
if (!existsSync2(explicit)) {
|
|
21
106
|
throw new Error(`sentinel build(nest): tsconfig not found at ${explicit}`);
|
|
22
107
|
}
|
|
23
108
|
return explicit;
|
|
24
109
|
}
|
|
25
110
|
for (const candidate of TSCONFIG_CANDIDATES) {
|
|
26
111
|
const found = path.join(options.root, candidate);
|
|
27
|
-
if (
|
|
112
|
+
if (existsSync2(found)) return found;
|
|
28
113
|
}
|
|
29
114
|
throw new Error(
|
|
30
115
|
`sentinel build(nest): no ${TSCONFIG_CANDIDATES.join(" or ")} in ${options.root}. Pass \`tsconfig\` if this service keeps it elsewhere.`
|
|
31
116
|
);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
assertCompilerApi();
|
|
117
|
+
}
|
|
118
|
+
function readConfig(ts, options) {
|
|
35
119
|
const configPath = resolveTsconfig(options);
|
|
36
120
|
const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, {
|
|
37
121
|
...ts.sys,
|
|
@@ -42,53 +126,111 @@ var readCompilerOptions = (options) => {
|
|
|
42
126
|
}
|
|
43
127
|
});
|
|
44
128
|
return {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
129
|
+
fileNames: parsed?.fileNames ?? [],
|
|
130
|
+
compilerOptions: {
|
|
131
|
+
...parsed?.options,
|
|
132
|
+
// ESM out, so Rollup sees imports and exports rather than an opaque `require` it cannot
|
|
133
|
+
// follow. The service still SHIPS as CJS: that conversion is the bundler's, further down.
|
|
134
|
+
module: ts.ModuleKind.ESNext,
|
|
135
|
+
// Vite consumes the map; the tsconfig's own answer is about a different pipeline.
|
|
136
|
+
sourceMap: true,
|
|
137
|
+
inlineSourceMap: false,
|
|
138
|
+
inlineSources: false,
|
|
139
|
+
// Types are another target's job, and emitting them here would write into the source tree.
|
|
140
|
+
declaration: false,
|
|
141
|
+
declarationMap: false,
|
|
142
|
+
emitDeclarationOnly: false,
|
|
143
|
+
noEmit: false,
|
|
144
|
+
// `composite` projects refuse to emit without a `tsBuildInfoFile`, and there is no
|
|
145
|
+
// incremental build here to inform.
|
|
146
|
+
composite: false,
|
|
147
|
+
incremental: false
|
|
148
|
+
}
|
|
62
149
|
};
|
|
63
|
-
}
|
|
150
|
+
}
|
|
151
|
+
function loadTransformers(root, program, declared) {
|
|
152
|
+
const require2 = createRequire(path.join(root, "noop.js"));
|
|
153
|
+
return declared.map(({ name, options }) => {
|
|
154
|
+
let loaded;
|
|
155
|
+
try {
|
|
156
|
+
loaded = require2(name);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
`sentinel build(nest): the build target declares the transformer \`${name}\`, which cannot be loaded from ${root}: ${error instanceof Error ? error.message : String(error)}`,
|
|
160
|
+
{ cause: error }
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
if (typeof loaded.before !== "function") {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`sentinel build(nest): \`${name}\` exports no \`before\` factory, so it cannot run as a TypeScript transformer. Building without it would drop whatever it contributes.`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
const factory = loaded.before;
|
|
169
|
+
return factory(options, program);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
64
172
|
var decoratorMetadata = (options) => {
|
|
173
|
+
let ts;
|
|
174
|
+
let program;
|
|
65
175
|
let compilerOptions;
|
|
176
|
+
let before = [];
|
|
177
|
+
const withoutTypes = [];
|
|
66
178
|
return {
|
|
67
179
|
name: "sentinel:decorator-metadata",
|
|
68
|
-
// Before Vite's own transform, so esbuild never sees the decorators it cannot handle
|
|
180
|
+
// Before Vite's own transform, so esbuild never sees the decorators it cannot handle, and so
|
|
181
|
+
// the source this plugin reads from the Program is still the source on disk.
|
|
69
182
|
enforce: "pre",
|
|
70
183
|
buildStart() {
|
|
71
|
-
|
|
184
|
+
ts = loadCompiler(options.root);
|
|
185
|
+
const config = readConfig(ts, options);
|
|
186
|
+
compilerOptions = config.compilerOptions;
|
|
187
|
+
program = ts.createProgram(config.fileNames, config.compilerOptions);
|
|
188
|
+
before = loadTransformers(options.root, program, options.transformers ?? []);
|
|
72
189
|
},
|
|
73
190
|
transform(code, id) {
|
|
74
|
-
if (!TYPESCRIPT_SOURCE.test(id) || id.includes("node_modules"))
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
if (compilerOptions === void 0) {
|
|
191
|
+
if (!TYPESCRIPT_SOURCE.test(id) || id.includes("node_modules")) return null;
|
|
192
|
+
if (ts === void 0 || program === void 0 || compilerOptions === void 0) {
|
|
78
193
|
throw new Error(
|
|
79
|
-
`sentinel build(nest): compiler
|
|
194
|
+
`sentinel build(nest): the compiler was never prepared, so ${id} would be emitted with defaults rather than this module's tsconfig.`
|
|
80
195
|
);
|
|
81
196
|
}
|
|
82
|
-
const
|
|
83
|
-
|
|
197
|
+
const sourceFile = program.getSourceFile(id);
|
|
198
|
+
if (sourceFile === void 0 || sourceFile.text !== code) {
|
|
199
|
+
withoutTypes.push(id);
|
|
200
|
+
const output = ts.transpileModule(code, { fileName: id, compilerOptions });
|
|
201
|
+
return { code: output.outputText, map: output.sourceMapText ?? null };
|
|
202
|
+
}
|
|
203
|
+
let emitted;
|
|
204
|
+
let map;
|
|
205
|
+
program.emit(
|
|
206
|
+
sourceFile,
|
|
207
|
+
(fileName, text) => {
|
|
208
|
+
if (fileName.endsWith(".map")) map = text;
|
|
209
|
+
else emitted = text;
|
|
210
|
+
},
|
|
211
|
+
void 0,
|
|
212
|
+
false,
|
|
213
|
+
{ before }
|
|
214
|
+
);
|
|
215
|
+
if (emitted === void 0) {
|
|
216
|
+
throw new Error(`sentinel build(nest): TypeScript emitted nothing for ${id}.`);
|
|
217
|
+
}
|
|
218
|
+
return { code: emitted, map: map ?? null };
|
|
219
|
+
},
|
|
220
|
+
closeBundle() {
|
|
221
|
+
if (withoutTypes.length === 0) return;
|
|
222
|
+
const shown = withoutTypes.slice(0, 5).map((file) => path.relative(options.root, file));
|
|
223
|
+
this.warn(
|
|
224
|
+
`sentinel build(nest): ${withoutTypes.length} file(s) were emitted without type information, so their decorator metadata may differ from what ts-loader produced (${shown.join(", ")}${withoutTypes.length > shown.length ? ", \u2026" : ""}). They are outside this module's tsconfig, or another plugin rewrote them first.`
|
|
225
|
+
);
|
|
84
226
|
}
|
|
85
227
|
};
|
|
86
228
|
};
|
|
87
229
|
|
|
88
230
|
// src/roles/build/nest/node-manifest.ts
|
|
89
|
-
import { existsSync as
|
|
231
|
+
import { existsSync as existsSync3, writeFileSync } from "fs";
|
|
90
232
|
import { isBuiltin } from "module";
|
|
91
|
-
import { join } from "path";
|
|
233
|
+
import { join as join2 } from "path";
|
|
92
234
|
var DEFAULT_PROVIDED_BY_IMAGE = ["@prisma"];
|
|
93
235
|
var externalPackages = (bundle) => {
|
|
94
236
|
const emitted = new Set(Object.keys(bundle));
|
|
@@ -116,7 +258,7 @@ var nodeManifest = (options) => {
|
|
|
116
258
|
},
|
|
117
259
|
async closeBundle() {
|
|
118
260
|
const entry = options.entry ?? "main.js";
|
|
119
|
-
if (!
|
|
261
|
+
if (!existsSync3(join2(options.outDir, entry))) return;
|
|
120
262
|
const { createProjectGraphAsync } = await import("nx/src/devkit-exports.js");
|
|
121
263
|
const { createPackageJson, createLockFile, getLockFileName } = await import("@nx/js");
|
|
122
264
|
const graph = await createProjectGraphAsync({ exitOnError: false });
|
|
@@ -131,10 +273,10 @@ var nodeManifest = (options) => {
|
|
|
131
273
|
);
|
|
132
274
|
}
|
|
133
275
|
assertManifestCovers(required, manifest, options);
|
|
134
|
-
writeFileSync(
|
|
276
|
+
writeFileSync(join2(options.outDir, "package.json"), `${JSON.stringify(manifest, null, 2)}
|
|
135
277
|
`);
|
|
136
278
|
writeFileSync(
|
|
137
|
-
|
|
279
|
+
join2(options.outDir, getLockFileName("pnpm")),
|
|
138
280
|
createLockFile(manifest, graph, "pnpm")
|
|
139
281
|
);
|
|
140
282
|
}
|
|
@@ -152,17 +294,17 @@ var assertManifestCovers = (required, manifest, options) => {
|
|
|
152
294
|
|
|
153
295
|
// src/roles/build/nest/tsconfig-aliases.ts
|
|
154
296
|
import { readFileSync } from "fs";
|
|
155
|
-
import { join as
|
|
297
|
+
import { join as join3 } from "path";
|
|
156
298
|
var stripLineComments = (json) => json.replace(/^\s*\/\/.*$/gm, "");
|
|
157
299
|
var toAlias = (workspaceRoot, pattern, target) => {
|
|
158
300
|
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, "(.*)");
|
|
159
301
|
return {
|
|
160
302
|
find: new RegExp(`^${escaped}$`),
|
|
161
|
-
replacement:
|
|
303
|
+
replacement: join3(workspaceRoot, target.replace(/\*/g, "$1"))
|
|
162
304
|
};
|
|
163
305
|
};
|
|
164
306
|
var tsconfigAliases = (workspaceRoot, file = "tsconfig.base.json") => {
|
|
165
|
-
const raw = readFileSync(
|
|
307
|
+
const raw = readFileSync(join3(workspaceRoot, file), "utf8");
|
|
166
308
|
const { compilerOptions } = JSON.parse(stripLineComments(raw));
|
|
167
309
|
return Object.entries(compilerOptions?.paths ?? {}).flatMap(([pattern, targets]) => {
|
|
168
310
|
const [target] = targets;
|
|
@@ -172,11 +314,16 @@ var tsconfigAliases = (workspaceRoot, file = "tsconfig.base.json") => {
|
|
|
172
314
|
|
|
173
315
|
// src/roles/build/nest/nest-service.ts
|
|
174
316
|
var baseConfig = (options) => {
|
|
175
|
-
const outDir =
|
|
176
|
-
const entry =
|
|
317
|
+
const outDir = join4(options.workspaceRoot, options.outDir ?? `dist/${options.project}`);
|
|
318
|
+
const entry = join4(options.root, options.entry ?? "src/main.ts");
|
|
177
319
|
return {
|
|
178
320
|
plugins: [
|
|
179
|
-
decoratorMetadata({
|
|
321
|
+
decoratorMetadata({
|
|
322
|
+
root: options.root,
|
|
323
|
+
tsconfig: options.tsconfig,
|
|
324
|
+
transformers: options.transformers
|
|
325
|
+
}),
|
|
326
|
+
...options.assets?.length ? [copyAssets({ workspaceRoot: options.workspaceRoot, outDir, assets: options.assets })] : [],
|
|
180
327
|
nodeManifest({
|
|
181
328
|
project: options.project,
|
|
182
329
|
workspaceRoot: options.workspaceRoot,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hublo/sentinel",
|
|
3
|
-
"version": "1.2.0-alpha.
|
|
3
|
+
"version": "1.2.0-alpha.19",
|
|
4
4
|
"description": "One CLI that guards code health across Hublo repos: shared lint/typescript/build/test presets, static & dynamic analysis, and architecture checks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|