@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.
@@ -22,7 +22,7 @@ import {
22
22
  registerAdapters,
23
23
  resolve,
24
24
  resolveBin
25
- } from "../chunk-CWJX5DVF.js";
25
+ } from "../chunk-RKVGZNJ7.js";
26
26
 
27
27
  // bin/sentinel.ts
28
28
  import { program } from "commander";
@@ -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: readNxProjectName(cwd) ?? basename2(cwd), private: true, ...value }
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({ moduleName: packageName, outputs: candidateOutputs() })
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: packageName,
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-CWJX5DVF.js.map
6523
+ //# sourceMappingURL=chunk-RKVGZNJ7.js.map
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  registerAdapters,
8
8
  resolve,
9
9
  setDefaultRunner
10
- } from "./chunk-CWJX5DVF.js";
10
+ } from "./chunk-RKVGZNJ7.js";
11
11
  export {
12
12
  BaseAdapter,
13
13
  all,
@@ -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 join3 } from "path";
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
- var assertCompilerApi = () => {
12
- if (typeof ts.transpileModule === "function") return;
13
- throw new Error(
14
- `sentinel build(nest): the resolved typescript (${ts.version ?? "unknown version"}) has no transpileModule API. TypeScript 7 is the native port and exposes none; the emit API lives under @typescript/typescript6. Sentinel pins its own compiler, so this means one was substituted, usually by a pnpm override or an alias on \`typescript\`.`
15
- );
16
- };
17
- var resolveTsconfig = (options) => {
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 (!existsSync(explicit)) {
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 (existsSync(found)) return found;
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
- var readCompilerOptions = (options) => {
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
- ...parsed?.options,
46
- // ESM out, so Rollup sees imports and exports rather than an opaque `require` it cannot
47
- // follow. The service still SHIPS as CJS: that conversion is the bundler's, further down.
48
- module: ts.ModuleKind.ESNext,
49
- // Vite consumes the map; the tsconfig's own answer is about a different pipeline.
50
- sourceMap: true,
51
- inlineSourceMap: false,
52
- inlineSources: false,
53
- // Types are another target's job, and `transpileModule` could not emit them anyway.
54
- declaration: false,
55
- declarationMap: false,
56
- emitDeclarationOnly: false,
57
- noEmit: false,
58
- // `composite` projects refuse to emit without a `tsBuildInfoFile`, and there is no
59
- // incremental build here to inform.
60
- composite: false,
61
- incremental: false
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
- compilerOptions = readCompilerOptions(options);
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
- return null;
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 options were never read, so ${id} would be transpiled with defaults rather than this module's tsconfig.`
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 output = ts.transpileModule(code, { fileName: id, compilerOptions });
83
- return { code: output.outputText, map: output.sourceMapText ?? null };
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 existsSync2, writeFileSync } from "fs";
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 (!existsSync2(join(options.outDir, entry))) return;
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(join(options.outDir, "package.json"), `${JSON.stringify(manifest, null, 2)}
276
+ writeFileSync(join2(options.outDir, "package.json"), `${JSON.stringify(manifest, null, 2)}
135
277
  `);
136
278
  writeFileSync(
137
- join(options.outDir, getLockFileName("pnpm")),
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 join2 } from "path";
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: join2(workspaceRoot, target.replace(/\*/g, "$1"))
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(join2(workspaceRoot, file), "utf8");
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 = join3(options.workspaceRoot, options.outDir ?? `dist/${options.project}`);
176
- const entry = join3(options.root, options.entry ?? "src/main.ts");
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({ root: options.root, tsconfig: options.tsconfig }),
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.17",
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",