@hublo/sentinel 1.2.0-alpha.6 → 1.2.0-alpha.8

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.
@@ -21,7 +21,7 @@ import {
21
21
  registerAdapters,
22
22
  resolve,
23
23
  resolveBin
24
- } from "../chunk-XPWM77PB.js";
24
+ } from "../chunk-A25QVPXF.js";
25
25
 
26
26
  // bin/sentinel.ts
27
27
  import { program } from "commander";
@@ -1199,8 +1199,9 @@ function listSourceFiles(cwd, extensions) {
1199
1199
 
1200
1200
  // src/core/named-scope.ts
1201
1201
  var TOLERATE_EMPTY_FLAG = "--no-error-on-unmatched-pattern";
1202
- function scopeFlags(paths) {
1203
- return paths.length > 0 ? [TOLERATE_EMPTY_FLAG] : [];
1202
+ function scopeFlags(paths, options = []) {
1203
+ if (paths.length === 0 || options.includes(TOLERATE_EMPTY_FLAG)) return [];
1204
+ return [TOLERATE_EMPTY_FLAG];
1204
1205
  }
1205
1206
 
1206
1207
  // src/core/settings.ts
@@ -1911,7 +1912,7 @@ var OxfmtAdapter = class extends BaseAdapter {
1911
1912
  });
1912
1913
  const result = spawnSync3(
1913
1914
  oxfmt,
1914
- [mode, ...scopeFlags(paths), ...options, ...paths.length > 0 ? paths : ["."]],
1915
+ [mode, ...scopeFlags(paths, options), ...options, ...paths.length > 0 ? paths : ["."]],
1915
1916
  {
1916
1917
  cwd: ctx.cwd,
1917
1918
  stdio: "inherit"
@@ -4956,7 +4957,7 @@ ${result.stderr ?? ""}`);
4956
4957
  valueFlags: OXLINT_VALUE_FLAGS
4957
4958
  });
4958
4959
  const targets = paths.length > 0 ? paths : ["."];
4959
- const emptyScope = scopeFlags(paths);
4960
+ const emptyScope = scopeFlags(paths, passedOptions);
4960
4961
  const lint = (extra) => {
4961
4962
  const passed = [...extra, ...emptyScope, ...passedOptions];
4962
4963
  const result = spawnSync4(oxlint, ["-c", LINT_CONFIG_FILE, ...passed, ...targets], {
@@ -6280,4 +6281,4 @@ export {
6280
6281
  detectFramework,
6281
6282
  dispatch
6282
6283
  };
6283
- //# sourceMappingURL=chunk-XPWM77PB.js.map
6284
+ //# sourceMappingURL=chunk-A25QVPXF.js.map
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  registerAdapters,
8
8
  resolve,
9
9
  setDefaultRunner
10
- } from "./chunk-XPWM77PB.js";
10
+ } from "./chunk-A25QVPXF.js";
11
11
  export {
12
12
  BaseAdapter,
13
13
  all,
@@ -50,6 +50,16 @@ interface NodeManifestOptions {
50
50
  outDir: string;
51
51
  /** Entry file name, written as `main` so `node .` resolves inside the image. */
52
52
  entry?: string;
53
+ /**
54
+ * Packages the IMAGE provides, which are therefore allowed to be required without being
55
+ * declared in the manifest.
56
+ *
57
+ * The generated Prisma clients are the case this exists for: the Dockerfile copies
58
+ * `node_modules/@prisma` from its own stage, so they resolve at runtime while no module
59
+ * declares them. Everything else that is required and undeclared is a bug, and the check
60
+ * below refuses the build.
61
+ */
62
+ providedByImage?: readonly string[];
53
63
  }
54
64
  /**
55
65
  * Writes the manifest and lockfile once the bundle is on disk.
@@ -80,28 +80,61 @@ var decoratorMetadata = (options) => {
80
80
 
81
81
  // src/roles/build/nest/node-manifest.ts
82
82
  import { existsSync as existsSync2, writeFileSync } from "fs";
83
+ import { isBuiltin } from "module";
83
84
  import { join } from "path";
84
- var nodeManifest = (options) => ({
85
- name: "sentinel:node-manifest",
86
- apply: "build",
87
- async closeBundle() {
88
- if (!existsSync2(options.outDir)) return;
89
- const { createProjectGraphAsync } = await import("nx/src/devkit-exports.js");
90
- const { createPackageJson, createLockFile, getLockFileName } = await import("@nx/js");
91
- const graph = await createProjectGraphAsync({ exitOnError: false });
92
- const manifest = createPackageJson(options.project, graph, {
93
- root: options.workspaceRoot,
94
- isProduction: true
95
- });
96
- manifest.main = options.entry ?? "main.js";
97
- writeFileSync(join(options.outDir, "package.json"), `${JSON.stringify(manifest, null, 2)}
98
- `);
99
- writeFileSync(
100
- join(options.outDir, getLockFileName("pnpm")),
101
- createLockFile(manifest, graph, "pnpm")
102
- );
85
+ var DEFAULT_PROVIDED_BY_IMAGE = ["@prisma"];
86
+ var externalPackages = (bundle) => {
87
+ const emitted = new Set(Object.keys(bundle));
88
+ const packages = /* @__PURE__ */ new Set();
89
+ for (const chunk of Object.values(bundle)) {
90
+ for (const imported of chunk.imports ?? []) {
91
+ if (emitted.has(imported)) continue;
92
+ if (imported.startsWith(".") || imported.startsWith("/")) continue;
93
+ if (isBuiltin(imported)) continue;
94
+ const parts = imported.split("/");
95
+ const name = imported.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
96
+ if (name) packages.add(name);
97
+ }
103
98
  }
104
- });
99
+ return packages;
100
+ };
101
+ var nodeManifest = (options) => {
102
+ let required = /* @__PURE__ */ new Set();
103
+ return {
104
+ name: "sentinel:node-manifest",
105
+ apply: "build",
106
+ generateBundle(_outputOptions, bundle) {
107
+ required = externalPackages(bundle);
108
+ },
109
+ async closeBundle() {
110
+ if (!existsSync2(options.outDir)) return;
111
+ const { createProjectGraphAsync } = await import("nx/src/devkit-exports.js");
112
+ const { createPackageJson, createLockFile, getLockFileName } = await import("@nx/js");
113
+ const graph = await createProjectGraphAsync({ exitOnError: false });
114
+ const manifest = createPackageJson(options.project, graph, {
115
+ root: options.workspaceRoot,
116
+ isProduction: true
117
+ });
118
+ manifest.main = options.entry ?? "main.js";
119
+ assertManifestCovers(required, manifest, options);
120
+ writeFileSync(join(options.outDir, "package.json"), `${JSON.stringify(manifest, null, 2)}
121
+ `);
122
+ writeFileSync(
123
+ join(options.outDir, getLockFileName("pnpm")),
124
+ createLockFile(manifest, graph, "pnpm")
125
+ );
126
+ }
127
+ };
128
+ };
129
+ var assertManifestCovers = (required, manifest, options) => {
130
+ const declared = new Set(Object.keys(manifest.dependencies ?? {}));
131
+ const provided = options.providedByImage ?? DEFAULT_PROVIDED_BY_IMAGE;
132
+ const missing = [...required].filter((name) => !declared.has(name)).filter((name) => !provided.some((prefix) => name === prefix || name.startsWith(`${prefix}/`))).sort();
133
+ if (missing.length === 0) return;
134
+ throw new Error(
135
+ `sentinel build(nest): the bundle requires ${missing.length} package(s) the generated manifest does not declare, so the image would not install them: ${missing.join(", ")}. Either declare them in the module's package.json, inline them with \`ssr.noExternal\`, or list them in \`providedByImage\` if the Dockerfile copies them in.`
136
+ );
137
+ };
105
138
 
106
139
  // src/roles/build/nest/tsconfig-aliases.ts
107
140
  import { readFileSync } from "fs";
@@ -137,6 +170,15 @@ var baseConfig = (options) => {
137
170
  })
138
171
  ],
139
172
  resolve: { alias: tsconfigAliases(options.workspaceRoot) },
173
+ ssr: {
174
+ // `importHelpers: true` in the workspace tsconfig makes TypeScript emit `require("tslib")`
175
+ // for `__decorate` and `__metadata`, so every decorated file depends on it. Hoisted into
176
+ // one scope Rollup inlined it and nobody noticed; preserving modules left it external, and
177
+ // the manifest nx generates from the project graph does not list it, because no module
178
+ // DECLARES tslib. The image therefore did not install it and the service died on its first
179
+ // require, only in the image: locally and in CI the workspace root has tslib.
180
+ noExternal: ["tslib"]
181
+ },
140
182
  build: {
141
183
  // An SSR build targets Node and leaves real packages external, which is what
142
184
  // `webpack-node-externals` did: the image installs them from the generated manifest.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hublo/sentinel",
3
- "version": "1.2.0-alpha.6",
3
+ "version": "1.2.0-alpha.8",
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",