@hublo/sentinel 1.2.0-alpha.0 → 1.2.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/bin/sentinel.d.ts +5 -0
- package/dist/bin/sentinel.js +57 -8
- package/dist/{chunk-TWL6T237.js → chunk-7WB7MQHD.js} +1128 -581
- package/dist/index.js +1 -2
- package/dist/roles/build/nest/toolchain.d.ts +90 -0
- package/dist/roles/build/nest/toolchain.js +253 -0
- package/dist/roles/build/toolchain.d.ts +5 -0
- package/dist/roles/build/toolchain.js +16 -0
- package/package.json +21 -3
- package/dist/chunk-XSCDJZDY.js +0 -26
- package/dist/roles/build/react-app.d.ts +0 -116
- package/dist/roles/build/react-app.js +0 -111
package/dist/index.js
CHANGED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { UserConfig, Plugin } from 'vite';
|
|
2
|
+
export { Plugin, PluginOption, UserConfig, UserConfigExport, defineConfig, loadEnv, mergeConfig } from 'vite';
|
|
3
|
+
|
|
4
|
+
interface NestServiceOptions {
|
|
5
|
+
/** The nx project name. Keys the dependency graph, and names the module in messages. */
|
|
6
|
+
project: string;
|
|
7
|
+
/** Absolute path to the module's directory, normally `__dirname`. */
|
|
8
|
+
root: string;
|
|
9
|
+
/** Absolute path to the workspace root. */
|
|
10
|
+
workspaceRoot: string;
|
|
11
|
+
/** Entry point, relative to `root`. */
|
|
12
|
+
entry?: string;
|
|
13
|
+
/**
|
|
14
|
+
* The tsconfig whose emit must be preserved, when the service uses neither conventional name.
|
|
15
|
+
*
|
|
16
|
+
* Relative to `root`. Defaults to `tsconfig.app.json`, then `tsconfig.json`.
|
|
17
|
+
*/
|
|
18
|
+
tsconfig?: string;
|
|
19
|
+
/** Output directory, relative to `workspaceRoot`. */
|
|
20
|
+
outDir?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Anything this service needs that the shape does not give it.
|
|
23
|
+
*
|
|
24
|
+
* Merged with Vite's own `mergeConfig`, never with a merge of our own: plugins concatenate
|
|
25
|
+
* and aliases stack the way Vite does it everywhere else, so there is no second set of
|
|
26
|
+
* semantics to learn or to document.
|
|
27
|
+
*/
|
|
28
|
+
overrides?: UserConfig;
|
|
29
|
+
}
|
|
30
|
+
declare const nestService: (options: NestServiceOptions) => UserConfig;
|
|
31
|
+
|
|
32
|
+
interface DecoratorMetadataOptions {
|
|
33
|
+
/** The module being built. Its tsconfig is the one whose emit must be preserved. */
|
|
34
|
+
root: string;
|
|
35
|
+
/**
|
|
36
|
+
* An explicit tsconfig, when the service does not use either conventional name.
|
|
37
|
+
*
|
|
38
|
+
* Relative paths resolve against `root`.
|
|
39
|
+
*/
|
|
40
|
+
tsconfig?: string;
|
|
41
|
+
}
|
|
42
|
+
declare const decoratorMetadata: (options: DecoratorMetadataOptions) => Plugin;
|
|
43
|
+
|
|
44
|
+
interface NodeManifestOptions {
|
|
45
|
+
/** The nx project name, which is how the graph is keyed. */
|
|
46
|
+
project: string;
|
|
47
|
+
/** Absolute path to the workspace root. */
|
|
48
|
+
workspaceRoot: string;
|
|
49
|
+
/** Where the bundle is written; the two files land beside it. */
|
|
50
|
+
outDir: string;
|
|
51
|
+
/** Entry file name, written as `main` so `node .` resolves inside the image. */
|
|
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[];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Writes the manifest and lockfile once the bundle is on disk.
|
|
66
|
+
*
|
|
67
|
+
* `closeBundle` rather than `writeBundle`, so the files land after Vite has finished with the
|
|
68
|
+
* directory and cannot be cleared by `emptyOutDir`.
|
|
69
|
+
*
|
|
70
|
+
* `closeBundle` also runs after a FAILED build, where there is no directory to write into. Left
|
|
71
|
+
* alone this hook then threw `ENOENT` on the manifest, and since it is the last error raised it
|
|
72
|
+
* became the one Vite printed, hiding the failure that actually stopped the build. It cost two
|
|
73
|
+
* diagnoses before being recognised, so the missing directory is now read as what it is: the
|
|
74
|
+
* bundle was never written, and this plugin has nothing to say about why.
|
|
75
|
+
*/
|
|
76
|
+
declare const nodeManifest: (options: NodeManifestOptions) => Plugin;
|
|
77
|
+
|
|
78
|
+
interface Alias {
|
|
79
|
+
find: RegExp;
|
|
80
|
+
replacement: string;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Read the mappings from the workspace's base tsconfig.
|
|
84
|
+
*
|
|
85
|
+
* Longest pattern first, because Vite takes the first alias that matches and the mappings
|
|
86
|
+
* overlap by design: `@front/theme/node` must not be swallowed by `@front/theme`.
|
|
87
|
+
*/
|
|
88
|
+
declare const tsconfigAliases: (workspaceRoot: string, file?: string) => Alias[];
|
|
89
|
+
|
|
90
|
+
export { type Alias, type DecoratorMetadataOptions, type NestServiceOptions, type NodeManifestOptions, decoratorMetadata, nestService, nodeManifest, tsconfigAliases };
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
// src/roles/build/nest/nest-service.ts
|
|
2
|
+
import { join as join3 } from "path";
|
|
3
|
+
import { mergeConfig } from "vite";
|
|
4
|
+
|
|
5
|
+
// src/roles/build/nest/decorator-metadata.ts
|
|
6
|
+
import { existsSync } from "fs";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import ts from "typescript";
|
|
9
|
+
var TYPESCRIPT_SOURCE = /\.ts$/;
|
|
10
|
+
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) => {
|
|
18
|
+
if (options.tsconfig) {
|
|
19
|
+
const explicit = path.resolve(options.root, options.tsconfig);
|
|
20
|
+
if (!existsSync(explicit)) {
|
|
21
|
+
throw new Error(`sentinel build(nest): tsconfig not found at ${explicit}`);
|
|
22
|
+
}
|
|
23
|
+
return explicit;
|
|
24
|
+
}
|
|
25
|
+
for (const candidate of TSCONFIG_CANDIDATES) {
|
|
26
|
+
const found = path.join(options.root, candidate);
|
|
27
|
+
if (existsSync(found)) return found;
|
|
28
|
+
}
|
|
29
|
+
throw new Error(
|
|
30
|
+
`sentinel build(nest): no ${TSCONFIG_CANDIDATES.join(" or ")} in ${options.root}. Pass \`tsconfig\` if this service keeps it elsewhere.`
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
var readCompilerOptions = (options) => {
|
|
34
|
+
assertCompilerApi();
|
|
35
|
+
const configPath = resolveTsconfig(options);
|
|
36
|
+
const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, {
|
|
37
|
+
...ts.sys,
|
|
38
|
+
onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`sentinel build(nest): could not read ${configPath}: ` + ts.flattenDiagnosticMessageText(diagnostic.messageText, " ")
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
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
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
var decoratorMetadata = (options) => {
|
|
65
|
+
let compilerOptions;
|
|
66
|
+
return {
|
|
67
|
+
name: "sentinel:decorator-metadata",
|
|
68
|
+
// Before Vite's own transform, so esbuild never sees the decorators it cannot handle.
|
|
69
|
+
enforce: "pre",
|
|
70
|
+
buildStart() {
|
|
71
|
+
compilerOptions = readCompilerOptions(options);
|
|
72
|
+
},
|
|
73
|
+
transform(code, id) {
|
|
74
|
+
if (!TYPESCRIPT_SOURCE.test(id) || id.includes("node_modules")) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
if (compilerOptions === void 0) {
|
|
78
|
+
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.`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
const output = ts.transpileModule(code, { fileName: id, compilerOptions });
|
|
83
|
+
return { code: output.outputText, map: output.sourceMapText ?? null };
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// src/roles/build/nest/node-manifest.ts
|
|
89
|
+
import { existsSync as existsSync2, writeFileSync } from "fs";
|
|
90
|
+
import { isBuiltin } from "module";
|
|
91
|
+
import { join } from "path";
|
|
92
|
+
var DEFAULT_PROVIDED_BY_IMAGE = ["@prisma"];
|
|
93
|
+
var externalPackages = (bundle) => {
|
|
94
|
+
const emitted = new Set(Object.keys(bundle));
|
|
95
|
+
const packages = /* @__PURE__ */ new Set();
|
|
96
|
+
for (const chunk of Object.values(bundle)) {
|
|
97
|
+
for (const imported of chunk.imports ?? []) {
|
|
98
|
+
if (emitted.has(imported)) continue;
|
|
99
|
+
if (imported.startsWith(".") || imported.startsWith("/")) continue;
|
|
100
|
+
if (isBuiltin(imported)) continue;
|
|
101
|
+
const parts = imported.split("/");
|
|
102
|
+
const name = imported.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
|
|
103
|
+
if (name) packages.add(name);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return packages;
|
|
107
|
+
};
|
|
108
|
+
var nodeManifest = (options) => {
|
|
109
|
+
let required;
|
|
110
|
+
return {
|
|
111
|
+
name: "sentinel:node-manifest",
|
|
112
|
+
apply: "build",
|
|
113
|
+
generateBundle(_outputOptions, bundle) {
|
|
114
|
+
required = externalPackages(bundle);
|
|
115
|
+
},
|
|
116
|
+
async closeBundle() {
|
|
117
|
+
const entry = options.entry ?? "main.js";
|
|
118
|
+
if (!existsSync2(join(options.outDir, entry))) return;
|
|
119
|
+
const { createProjectGraphAsync } = await import("nx/src/devkit-exports.js");
|
|
120
|
+
const { createPackageJson, createLockFile, getLockFileName } = await import("@nx/js");
|
|
121
|
+
const graph = await createProjectGraphAsync({ exitOnError: false });
|
|
122
|
+
const manifest = createPackageJson(options.project, graph, {
|
|
123
|
+
root: options.workspaceRoot,
|
|
124
|
+
isProduction: true
|
|
125
|
+
});
|
|
126
|
+
manifest.main = entry;
|
|
127
|
+
if (required === void 0) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`sentinel build(nest): the bundle was written but never inspected, so the manifest could not be checked against what it requires. Refusing to write one that might not install.`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
assertManifestCovers(required, manifest, options);
|
|
133
|
+
writeFileSync(join(options.outDir, "package.json"), `${JSON.stringify(manifest, null, 2)}
|
|
134
|
+
`);
|
|
135
|
+
writeFileSync(
|
|
136
|
+
join(options.outDir, getLockFileName("pnpm")),
|
|
137
|
+
createLockFile(manifest, graph, "pnpm")
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
var assertManifestCovers = (required, manifest, options) => {
|
|
143
|
+
const declared = new Set(Object.keys(manifest.dependencies ?? {}));
|
|
144
|
+
const provided = options.providedByImage ?? DEFAULT_PROVIDED_BY_IMAGE;
|
|
145
|
+
const missing = [...required].filter((name) => !declared.has(name)).filter((name) => !provided.some((prefix) => name === prefix || name.startsWith(`${prefix}/`))).sort();
|
|
146
|
+
if (missing.length === 0) return;
|
|
147
|
+
throw new Error(
|
|
148
|
+
`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.`
|
|
149
|
+
);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// src/roles/build/nest/tsconfig-aliases.ts
|
|
153
|
+
import { readFileSync } from "fs";
|
|
154
|
+
import { join as join2 } from "path";
|
|
155
|
+
var stripLineComments = (json) => json.replace(/^\s*\/\/.*$/gm, "");
|
|
156
|
+
var toAlias = (workspaceRoot, pattern, target) => {
|
|
157
|
+
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, "(.*)");
|
|
158
|
+
return {
|
|
159
|
+
find: new RegExp(`^${escaped}$`),
|
|
160
|
+
replacement: join2(workspaceRoot, target.replace(/\*/g, "$1"))
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
var tsconfigAliases = (workspaceRoot, file = "tsconfig.base.json") => {
|
|
164
|
+
const raw = readFileSync(join2(workspaceRoot, file), "utf8");
|
|
165
|
+
const { compilerOptions } = JSON.parse(stripLineComments(raw));
|
|
166
|
+
return Object.entries(compilerOptions?.paths ?? {}).flatMap(([pattern, targets]) => {
|
|
167
|
+
const [target] = targets;
|
|
168
|
+
return target === void 0 ? [] : [toAlias(workspaceRoot, pattern, target)];
|
|
169
|
+
}).sort((a, b) => b.find.source.length - a.find.source.length);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// src/roles/build/nest/nest-service.ts
|
|
173
|
+
var baseConfig = (options) => {
|
|
174
|
+
const outDir = join3(options.workspaceRoot, options.outDir ?? `dist/${options.project}`);
|
|
175
|
+
const entry = join3(options.root, options.entry ?? "src/main.ts");
|
|
176
|
+
return {
|
|
177
|
+
plugins: [
|
|
178
|
+
decoratorMetadata({ root: options.root, tsconfig: options.tsconfig }),
|
|
179
|
+
nodeManifest({
|
|
180
|
+
project: options.project,
|
|
181
|
+
workspaceRoot: options.workspaceRoot,
|
|
182
|
+
outDir
|
|
183
|
+
})
|
|
184
|
+
],
|
|
185
|
+
resolve: { alias: tsconfigAliases(options.workspaceRoot) },
|
|
186
|
+
ssr: {
|
|
187
|
+
// `importHelpers: true` in the workspace tsconfig makes TypeScript emit `require("tslib")`
|
|
188
|
+
// for `__decorate` and `__metadata`, so every decorated file depends on it. Hoisted into
|
|
189
|
+
// one scope Rollup inlined it and nobody noticed; preserving modules left it external, and
|
|
190
|
+
// the manifest nx generates from the project graph does not list it, because no module
|
|
191
|
+
// DECLARES tslib. The image therefore did not install it and the service died on its first
|
|
192
|
+
// require, only in the image: locally and in CI the workspace root has tslib.
|
|
193
|
+
noExternal: ["tslib"]
|
|
194
|
+
},
|
|
195
|
+
build: {
|
|
196
|
+
// An SSR build targets Node and leaves real packages external, which is what
|
|
197
|
+
// `webpack-node-externals` did: the image installs them from the generated manifest.
|
|
198
|
+
ssr: entry,
|
|
199
|
+
outDir,
|
|
200
|
+
emptyOutDir: true,
|
|
201
|
+
sourcemap: true,
|
|
202
|
+
target: "node20",
|
|
203
|
+
// The bundle is read by humans when a stack trace points into it, and minifying a
|
|
204
|
+
// server bundle buys nothing: it is never downloaded.
|
|
205
|
+
minify: false,
|
|
206
|
+
rollupOptions: {
|
|
207
|
+
output: {
|
|
208
|
+
format: "cjs",
|
|
209
|
+
// One output file per source module, instead of hoisting everything into one scope.
|
|
210
|
+
//
|
|
211
|
+
// This is not a preference, it is the only shape that keeps the OpenAPI contract.
|
|
212
|
+
// Merging every module into one scope forces Rollup to rename duplicate class names,
|
|
213
|
+
// and `@nestjs/swagger` keys its schemas on `class.name`, so a renamed class silently
|
|
214
|
+
// becomes a renamed schema in the published API. Measured on `network`, which has two
|
|
215
|
+
// such collisions: single bundle renames `InvalidPermissionError` and
|
|
216
|
+
// `PermissionNotFoundError`; preserving modules renames nothing and reproduces the
|
|
217
|
+
// committed contract byte for byte.
|
|
218
|
+
//
|
|
219
|
+
// The cost is 4.5 MB across 2003 files against 2.9 MB in one, still well under
|
|
220
|
+
// webpack's 6.9 MB for the same service, so nothing regresses against what it replaces.
|
|
221
|
+
preserveModules: true,
|
|
222
|
+
// The image runs `./dist/main.js`, so the entry keeps that name at the root while
|
|
223
|
+
// every other module keeps its own path. Under `preserveModules` a plain string here
|
|
224
|
+
// would be applied to all of them, which numbers them (`main962.js`) and loses the
|
|
225
|
+
// entry.
|
|
226
|
+
entryFileNames: (chunk) => chunk.facadeModuleId === entry ? "main.js" : "[name].js"
|
|
227
|
+
},
|
|
228
|
+
// Nest registers metadata as an IMPORT SIDE EFFECT: a decorator writes into a catalog
|
|
229
|
+
// when its module loads, and nothing references that module afterwards. Rollup may
|
|
230
|
+
// drop such a module; webpack never did.
|
|
231
|
+
//
|
|
232
|
+
// Measured on the first migrated service this changes nothing, so it is insurance
|
|
233
|
+
// rather than a fix, and it is recorded as such rather than credited with the smaller
|
|
234
|
+
// bundle (that comes from barrel re-exports the service does not use).
|
|
235
|
+
treeshake: { moduleSideEffects: true }
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
};
|
|
240
|
+
var nestService = (options) => options.overrides === void 0 ? baseConfig(options) : mergeConfig(baseConfig(options), options.overrides);
|
|
241
|
+
|
|
242
|
+
// src/roles/build/nest/toolchain.ts
|
|
243
|
+
import { defineConfig, loadEnv, mergeConfig as mergeConfig2 } from "vite";
|
|
244
|
+
export {
|
|
245
|
+
decoratorMetadata,
|
|
246
|
+
defineConfig,
|
|
247
|
+
loadEnv,
|
|
248
|
+
mergeConfig2 as mergeConfig,
|
|
249
|
+
nestService,
|
|
250
|
+
nodeManifest,
|
|
251
|
+
tsconfigAliases
|
|
252
|
+
};
|
|
253
|
+
//# sourceMappingURL=toolchain.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default as tailwindcss } from '@tailwindcss/vite';
|
|
2
|
+
export { default as react } from '@vitejs/plugin-react';
|
|
3
|
+
export { nitro } from 'nitro/vite';
|
|
4
|
+
export { default as svgr } from 'vite-plugin-svgr';
|
|
5
|
+
export { Plugin, PluginOption, SassPreprocessorOptions, UserConfig, UserConfigExport, defineConfig, loadEnv, mergeConfig } from 'vite';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// src/roles/build/toolchain.ts
|
|
2
|
+
import { default as default2 } from "@tailwindcss/vite";
|
|
3
|
+
import { default as default3 } from "@vitejs/plugin-react";
|
|
4
|
+
import { nitro } from "nitro/vite";
|
|
5
|
+
import { default as default4 } from "vite-plugin-svgr";
|
|
6
|
+
import { defineConfig, loadEnv, mergeConfig } from "vite";
|
|
7
|
+
export {
|
|
8
|
+
defineConfig,
|
|
9
|
+
loadEnv,
|
|
10
|
+
mergeConfig,
|
|
11
|
+
nitro,
|
|
12
|
+
default3 as react,
|
|
13
|
+
default4 as svgr,
|
|
14
|
+
default2 as tailwindcss
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=toolchain.js.map
|
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.10",
|
|
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",
|
|
@@ -39,8 +39,12 @@
|
|
|
39
39
|
"default": "./dist/tsconfig/node.json"
|
|
40
40
|
},
|
|
41
41
|
"./build/react": {
|
|
42
|
-
"types": "./dist/roles/build/
|
|
43
|
-
"import": "./dist/roles/build/
|
|
42
|
+
"types": "./dist/roles/build/toolchain.d.ts",
|
|
43
|
+
"import": "./dist/roles/build/toolchain.js"
|
|
44
|
+
},
|
|
45
|
+
"./build/nest": {
|
|
46
|
+
"types": "./dist/roles/build/nest/toolchain.d.ts",
|
|
47
|
+
"import": "./dist/roles/build/nest/toolchain.js"
|
|
44
48
|
}
|
|
45
49
|
},
|
|
46
50
|
"files": [
|
|
@@ -62,9 +66,11 @@
|
|
|
62
66
|
"eslint-plugin-styled-components-a11y": "2.2.0",
|
|
63
67
|
"jsonc-parser": "^3.3.1",
|
|
64
68
|
"nitro": "3.0.260415-beta",
|
|
69
|
+
"oxc-parser": "0.147.0",
|
|
65
70
|
"oxfmt": "0.63.0",
|
|
66
71
|
"oxlint": "1.77.0",
|
|
67
72
|
"oxlint-tsgolint": "7.0.2001",
|
|
73
|
+
"typescript": "5.9.3",
|
|
68
74
|
"vite": "8.0.8",
|
|
69
75
|
"vite-plugin-svgr": "5.2.0"
|
|
70
76
|
},
|
|
@@ -95,6 +101,18 @@
|
|
|
95
101
|
}
|
|
96
102
|
}
|
|
97
103
|
},
|
|
104
|
+
"peerDependencies": {
|
|
105
|
+
"nx": ">= 21",
|
|
106
|
+
"@nx/js": ">= 21"
|
|
107
|
+
},
|
|
108
|
+
"peerDependenciesMeta": {
|
|
109
|
+
"nx": {
|
|
110
|
+
"optional": true
|
|
111
|
+
},
|
|
112
|
+
"@nx/js": {
|
|
113
|
+
"optional": true
|
|
114
|
+
}
|
|
115
|
+
},
|
|
98
116
|
"scripts": {
|
|
99
117
|
"build": "tsup && tsx scripts/build-presets.ts",
|
|
100
118
|
"dev": "tsup --watch",
|
package/dist/chunk-XSCDJZDY.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
// src/roles/build/presets/base.json
|
|
2
|
-
var base_default = {
|
|
3
|
-
target: "chrome89",
|
|
4
|
-
allowedHosts: [".hubpreprod.com", ".playground-hublo.com"],
|
|
5
|
-
publicAssets: [
|
|
6
|
-
{
|
|
7
|
-
dir: "public/branding",
|
|
8
|
-
baseURL: "/branding",
|
|
9
|
-
maxAge: 31536e3
|
|
10
|
-
}
|
|
11
|
-
]
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
// src/roles/build/preset-data.ts
|
|
15
|
-
var REACT_APP_DEFAULTS = base_default;
|
|
16
|
-
|
|
17
|
-
// src/shared/deep-merge.ts
|
|
18
|
-
function isPlainObject(value) {
|
|
19
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export {
|
|
23
|
-
REACT_APP_DEFAULTS,
|
|
24
|
-
isPlainObject
|
|
25
|
-
};
|
|
26
|
-
//# sourceMappingURL=chunk-XSCDJZDY.js.map
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { PluginOption, UserConfig } from 'vite';
|
|
2
|
-
export { defineConfig, loadEnv } from 'vite';
|
|
3
|
-
|
|
4
|
-
/** The convention values, in one place so `--inspect` can report what an app departs from. */
|
|
5
|
-
declare const REACT_APP_DEFAULTS: {
|
|
6
|
-
target: string;
|
|
7
|
-
allowedHosts: string[];
|
|
8
|
-
publicAssets: {
|
|
9
|
-
dir: string;
|
|
10
|
-
baseURL: string;
|
|
11
|
-
maxAge: number;
|
|
12
|
-
}[];
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
/** Where the router's routes live and where it writes its generated tree. */
|
|
16
|
-
interface RouterPaths {
|
|
17
|
-
routesDirectory: string;
|
|
18
|
-
generatedRouteTree: string;
|
|
19
|
-
/** Extra lines appended to the generated tree. Two of the three apps use it. */
|
|
20
|
-
routeTreeFileFooter?: string[];
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* The TanStack factories, supplied by the app.
|
|
24
|
-
*
|
|
25
|
-
* Typed loosely on purpose: sentinel decides WHEN to call them and with what, and has no
|
|
26
|
-
* business asserting their return shape. Narrowing this would only couple sentinel to a
|
|
27
|
-
* version of a package it does not own.
|
|
28
|
-
*/
|
|
29
|
-
interface TanstackFactories {
|
|
30
|
-
start: (options: {
|
|
31
|
-
router: RouterPaths;
|
|
32
|
-
}) => PluginOption[];
|
|
33
|
-
router: (options: RouterPaths & {
|
|
34
|
-
target: string;
|
|
35
|
-
autoCodeSplitting: boolean;
|
|
36
|
-
}) => PluginOption;
|
|
37
|
-
}
|
|
38
|
-
/** Everything an app declares. */
|
|
39
|
-
interface ReactAppOptions {
|
|
40
|
-
/** The app's own directory. Always `__dirname`. */
|
|
41
|
-
root: string;
|
|
42
|
-
/** Vite's `mode`, from the config callback. */
|
|
43
|
-
mode: string;
|
|
44
|
-
/** Served under, e.g. `/console/`. */
|
|
45
|
-
base: string;
|
|
46
|
-
/** Dev server port. */
|
|
47
|
-
port: number;
|
|
48
|
-
/** HMR port. Explicit, NOT `port + 1`: career goes DOWN to 9998 where the others go up. */
|
|
49
|
-
hmrPort: number;
|
|
50
|
-
router: RouterPaths;
|
|
51
|
-
tanstack: TanstackFactories;
|
|
52
|
-
/**
|
|
53
|
-
* Sourcemaps: a boolean, however the app decided it.
|
|
54
|
-
*
|
|
55
|
-
* The three apps disagree on how — two use a mode allowlist, one reads an env var. That is
|
|
56
|
-
* an inconsistency in the repo rather than a requirement, and encoding both would make
|
|
57
|
-
* sentinel the owner of a decision it should not hold.
|
|
58
|
-
*/
|
|
59
|
-
sourcemap?: boolean;
|
|
60
|
-
/** Passed through verbatim, never read. See the note above. */
|
|
61
|
-
alias?: unknown[];
|
|
62
|
-
/** The app's own plugins (branding, babel). Appended AFTER the base set. */
|
|
63
|
-
plugins?: PluginOption[];
|
|
64
|
-
/** Conventions the app may override. */
|
|
65
|
-
target?: string;
|
|
66
|
-
allowedHosts?: string[];
|
|
67
|
-
/**
|
|
68
|
-
* Anything else this app needs, merged over the preset LAST.
|
|
69
|
-
*
|
|
70
|
-
* The preset is a starting point, not a cage. Two things an app must be able to do and
|
|
71
|
-
* could not before: override a NESTED attribute without losing its siblings, and add an
|
|
72
|
-
* attribute the preset knows nothing about.
|
|
73
|
-
*
|
|
74
|
-
* Objects merge, everything else replaces — see `merge-config` for why arrays replace
|
|
75
|
-
* rather than concatenate. Whatever lands here is reported by `--inspect --build`, so an
|
|
76
|
-
* override is a visible, countable decision instead of something found by reading a config.
|
|
77
|
-
*
|
|
78
|
-
* Real cases today: console needs `ssr`, `optimizeDeps` and `preview`; career needs
|
|
79
|
-
* `css.preprocessorOptions` for Sass; all three need their own `nitro.publicAssets`.
|
|
80
|
-
*/
|
|
81
|
-
overrides?: Record<string, unknown>;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* The environment, loaded the way all three apps already load it.
|
|
86
|
-
*
|
|
87
|
-
* `production` is normalised to `prd` because that is this repository's deployment tag: it is
|
|
88
|
-
* the `.env.prd` the build reads and the SSM path segment the values come from. Not a
|
|
89
|
-
* preference, a fact about the repo — which is exactly why it belongs in the preset rather
|
|
90
|
-
* than being retyped in every config.
|
|
91
|
-
*/
|
|
92
|
-
declare function appEnv(root: string, mode: string): Record<string, string>;
|
|
93
|
-
/** True when this run is a test rather than a real build. */
|
|
94
|
-
declare function isTestMode(mode: string, env?: NodeJS.ProcessEnv): boolean;
|
|
95
|
-
/**
|
|
96
|
-
* WHICH plugins run, as names, in order.
|
|
97
|
-
*
|
|
98
|
-
* Split out from building them so the rule can be tested without instantiating a single
|
|
99
|
-
* plugin. The rule is the part that must not diverge between apps; the instances are just
|
|
100
|
-
* function calls.
|
|
101
|
-
*
|
|
102
|
-
* Under test the router plugin runs alone: `tanstackStart` and `nitro` bring a server the
|
|
103
|
-
* tests neither need nor can run. All three apps already do this, identically.
|
|
104
|
-
*/
|
|
105
|
-
declare function pluginPlan(isTest: boolean, extras?: number): string[];
|
|
106
|
-
/**
|
|
107
|
-
* The Vite config for a React app, minus the plugins.
|
|
108
|
-
*
|
|
109
|
-
* Pure and separately testable: everything here is data in, data out, so the composition
|
|
110
|
-
* rules can be asserted without a bundler anywhere near the test.
|
|
111
|
-
*/
|
|
112
|
-
declare function reactAppConfig(options: ReactAppOptions): Omit<UserConfig, 'plugins'>;
|
|
113
|
-
/** The config an app spreads into `defineConfig`. */
|
|
114
|
-
declare function reactApp(options: ReactAppOptions): UserConfig;
|
|
115
|
-
|
|
116
|
-
export { REACT_APP_DEFAULTS, type ReactAppOptions, type RouterPaths, type TanstackFactories, appEnv, isTestMode, pluginPlan, reactApp, reactAppConfig };
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
REACT_APP_DEFAULTS,
|
|
3
|
-
isPlainObject
|
|
4
|
-
} from "../../chunk-XSCDJZDY.js";
|
|
5
|
-
|
|
6
|
-
// src/roles/build/react-app.ts
|
|
7
|
-
import tailwindcss from "@tailwindcss/vite";
|
|
8
|
-
import react from "@vitejs/plugin-react";
|
|
9
|
-
import { nitro } from "nitro/vite";
|
|
10
|
-
import { loadEnv } from "vite";
|
|
11
|
-
import svgr from "vite-plugin-svgr";
|
|
12
|
-
|
|
13
|
-
// src/roles/build/merge-config.ts
|
|
14
|
-
function mergeConfig(base, overrides) {
|
|
15
|
-
if (!overrides) return base;
|
|
16
|
-
const out = { ...base };
|
|
17
|
-
for (const [key, value] of Object.entries(overrides)) {
|
|
18
|
-
if (value === void 0) {
|
|
19
|
-
delete out[key];
|
|
20
|
-
continue;
|
|
21
|
-
}
|
|
22
|
-
const current = out[key];
|
|
23
|
-
out[key] = isPlainObject(current) && isPlainObject(value) ? mergeConfig(current, value) : value;
|
|
24
|
-
}
|
|
25
|
-
return out;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// src/roles/build/react-app.ts
|
|
29
|
-
import { defineConfig, loadEnv as loadEnv2 } from "vite";
|
|
30
|
-
function appEnv(root, mode) {
|
|
31
|
-
return loadEnv(mode === "production" ? "prd" : mode, root, "");
|
|
32
|
-
}
|
|
33
|
-
function isTestMode(mode, env = process.env) {
|
|
34
|
-
return mode === "test" || env.VITEST === "true";
|
|
35
|
-
}
|
|
36
|
-
function pluginPlan(isTest, extras = 0) {
|
|
37
|
-
return [
|
|
38
|
-
"tailwindcss",
|
|
39
|
-
...isTest ? ["tanstackRouter"] : ["tanstackStart", "nitro"],
|
|
40
|
-
"react",
|
|
41
|
-
"svgr",
|
|
42
|
-
...Array.from({ length: extras }, (_, at) => `app:${at}`)
|
|
43
|
-
];
|
|
44
|
-
}
|
|
45
|
-
function basePlugins(options, isTest) {
|
|
46
|
-
const { tanstack, router } = options;
|
|
47
|
-
return [
|
|
48
|
-
tailwindcss(),
|
|
49
|
-
...isTest ? [tanstack.router({ ...router, target: "react", autoCodeSplitting: true })] : [...tanstack.start({ router }), nitro()],
|
|
50
|
-
react(),
|
|
51
|
-
svgr(),
|
|
52
|
-
...options.plugins ?? []
|
|
53
|
-
];
|
|
54
|
-
}
|
|
55
|
-
function reactAppConfig(options) {
|
|
56
|
-
const preset = {
|
|
57
|
-
base: options.base,
|
|
58
|
-
root: options.root,
|
|
59
|
-
define: {
|
|
60
|
-
// Development only. In a build this would leak a browser global into SSR output.
|
|
61
|
-
...options.mode === "development" ? { global: "window" } : {}
|
|
62
|
-
},
|
|
63
|
-
server: {
|
|
64
|
-
port: options.port,
|
|
65
|
-
allowedHosts: options.allowedHosts ?? [...REACT_APP_DEFAULTS.allowedHosts],
|
|
66
|
-
hmr: { host: "localhost", protocol: "ws", port: options.hmrPort }
|
|
67
|
-
},
|
|
68
|
-
build: {
|
|
69
|
-
target: options.target ?? REACT_APP_DEFAULTS.target,
|
|
70
|
-
sourcemap: options.sourcemap ?? false
|
|
71
|
-
},
|
|
72
|
-
/*
|
|
73
|
-
* Nitro's two settings are sentinel's, and both were measured across the three apps rather
|
|
74
|
-
* than assumed.
|
|
75
|
-
*
|
|
76
|
-
* `baseURL` is always the app's `base`, without exception: `/console/`, `/` and `/admin/`.
|
|
77
|
-
* It is derived, so an app cannot get them out of step.
|
|
78
|
-
*
|
|
79
|
-
* `publicAssets` is byte-identical in all three — the branding directory, its URL and a
|
|
80
|
-
* one-year max-age. That is a convention about where branding lives, not data about an
|
|
81
|
-
* app, and it was sitting in three `overrides` blocks saying the same thing.
|
|
82
|
-
*
|
|
83
|
-
* An app that needs MORE from nitro overrides the key: career adds `externals` to keep the
|
|
84
|
-
* SSR sanitisation chain out of the server bundle. `mergeConfig` merges objects, so adding
|
|
85
|
-
* one key does not take these two away.
|
|
86
|
-
*/
|
|
87
|
-
nitro: {
|
|
88
|
-
baseURL: options.base,
|
|
89
|
-
publicAssets: [...REACT_APP_DEFAULTS.publicAssets]
|
|
90
|
-
},
|
|
91
|
-
...options.alias ? { resolve: { alias: options.alias } } : {}
|
|
92
|
-
};
|
|
93
|
-
return mergeConfig(preset, options.overrides);
|
|
94
|
-
}
|
|
95
|
-
function reactApp(options) {
|
|
96
|
-
return {
|
|
97
|
-
...reactAppConfig(options),
|
|
98
|
-
plugins: basePlugins(options, isTestMode(options.mode))
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
export {
|
|
102
|
-
REACT_APP_DEFAULTS,
|
|
103
|
-
appEnv,
|
|
104
|
-
defineConfig,
|
|
105
|
-
isTestMode,
|
|
106
|
-
loadEnv2 as loadEnv,
|
|
107
|
-
pluginPlan,
|
|
108
|
-
reactApp,
|
|
109
|
-
reactAppConfig
|
|
110
|
-
};
|
|
111
|
-
//# sourceMappingURL=react-app.js.map
|