@hublo/sentinel 1.2.0-alpha.4 → 1.2.0-alpha.5
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
|
@@ -467,7 +467,11 @@ var BUILD_CONFIG_FILES = [
|
|
|
467
467
|
"vite.config.js",
|
|
468
468
|
"vite.config.mjs"
|
|
469
469
|
];
|
|
470
|
-
var
|
|
470
|
+
var BUILD_PRESET_SPECIFIERS = {
|
|
471
|
+
react: "@hublo/sentinel/build/react",
|
|
472
|
+
nest: "@hublo/sentinel/build/nest"
|
|
473
|
+
};
|
|
474
|
+
var BUILD_PRESET_SPECIFIER = BUILD_PRESET_SPECIFIERS.react;
|
|
471
475
|
var BUILD_SCRIPT_NAME = "build";
|
|
472
476
|
var SENTINEL_BUILD_COMMAND = "sentinel --run --build --";
|
|
473
477
|
var DEV_SCRIPT_NAME = "serve";
|
|
@@ -524,12 +528,15 @@ function importsSpecifier(source, specifier) {
|
|
|
524
528
|
return new RegExp(`(?:from|import)\\s*\\(?\\s*['"\`]${escaped}(?:/[^'"\`]*)?['"\`]`).test(source);
|
|
525
529
|
}
|
|
526
530
|
function importsPreset(source) {
|
|
527
|
-
return
|
|
531
|
+
return Object.values(BUILD_PRESET_SPECIFIERS).some(
|
|
532
|
+
(specifier) => importsSpecifier(source, specifier)
|
|
533
|
+
);
|
|
528
534
|
}
|
|
529
535
|
var REACT_PLUGIN_SPECIFIERS = ["@vitejs/plugin-react", "@tanstack/react-start"];
|
|
530
536
|
function declaredBuildPreset(cwd) {
|
|
531
537
|
const source = readBuildConfigSource(cwd);
|
|
532
538
|
if (source === void 0) return void 0;
|
|
539
|
+
if (importsSpecifier(source, BUILD_PRESET_SPECIFIERS.nest)) return "nest";
|
|
533
540
|
if (importsPreset(source)) return "react";
|
|
534
541
|
return REACT_PLUGIN_SPECIFIERS.some((specifier) => importsSpecifier(source, specifier)) ? "react" : void 0;
|
|
535
542
|
}
|
|
@@ -559,7 +566,9 @@ function readBuildAdoption(cwd) {
|
|
|
559
566
|
);
|
|
560
567
|
return {
|
|
561
568
|
configFile,
|
|
562
|
-
|
|
569
|
+
// Read from the config rather than assumed: the two presets are adopted the same way and
|
|
570
|
+
// reporting the wrong one would make `--inspect` lie about what a module builds.
|
|
571
|
+
preset: declaredBuildPreset(cwd) ?? "react",
|
|
563
572
|
adopted: true,
|
|
564
573
|
conformant: ownDeclarations.length === 0,
|
|
565
574
|
drift: ownDeclarations.map(
|
|
@@ -842,11 +851,17 @@ var ViteRoleAdapter = class extends BaseAdapter {
|
|
|
842
851
|
* resolution let it run at all — filtering here would give "no adapter for preset svelte",
|
|
843
852
|
* which tells nobody what to do about it.
|
|
844
853
|
*
|
|
845
|
-
* Nest
|
|
846
|
-
*
|
|
854
|
+
* Nest it now serves too, through `@hublo/sentinel/build/nest`. It used to be declined on the
|
|
855
|
+
* grounds that "there is nothing to bundle", which was never true: those 38 services bundle
|
|
856
|
+
* with webpack. What changed is that Nx v24 removes the `@nx/webpack:webpack` executor and
|
|
857
|
+
* the `composePlugins` / `withNx` helpers their shared config is built on, and nx's own
|
|
858
|
+
* migration generator refuses every one of them because they use `@nx/js:node`
|
|
859
|
+
* (nrwl/nx#36389). So the way out runs through here.
|
|
860
|
+
*
|
|
861
|
+
* `node` and `tools` remain genuinely declined: nothing under them bundles at all.
|
|
847
862
|
*/
|
|
848
863
|
appliesTo(preset) {
|
|
849
|
-
return preset === "react" || preset === "svelte";
|
|
864
|
+
return preset === "react" || preset === "svelte" || preset === "nest";
|
|
850
865
|
}
|
|
851
866
|
/**
|
|
852
867
|
* `react`, read from the module's Vite config rather than from its dependencies.
|
|
@@ -6265,4 +6280,4 @@ export {
|
|
|
6265
6280
|
detectFramework,
|
|
6266
6281
|
dispatch
|
|
6267
6282
|
};
|
|
6268
|
-
//# sourceMappingURL=chunk-
|
|
6283
|
+
//# sourceMappingURL=chunk-XPWM77PB.js.map
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,80 @@
|
|
|
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
|
+
/**
|
|
55
|
+
* Writes the manifest and lockfile once the bundle is on disk.
|
|
56
|
+
*
|
|
57
|
+
* `closeBundle` rather than `writeBundle`, so the files land after Vite has finished with the
|
|
58
|
+
* directory and cannot be cleared by `emptyOutDir`.
|
|
59
|
+
*
|
|
60
|
+
* `closeBundle` also runs after a FAILED build, where there is no directory to write into. Left
|
|
61
|
+
* alone this hook then threw `ENOENT` on the manifest, and since it is the last error raised it
|
|
62
|
+
* became the one Vite printed, hiding the failure that actually stopped the build. It cost two
|
|
63
|
+
* diagnoses before being recognised, so the missing directory is now read as what it is: the
|
|
64
|
+
* bundle was never written, and this plugin has nothing to say about why.
|
|
65
|
+
*/
|
|
66
|
+
declare const nodeManifest: (options: NodeManifestOptions) => Plugin;
|
|
67
|
+
|
|
68
|
+
interface Alias {
|
|
69
|
+
find: RegExp;
|
|
70
|
+
replacement: string;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Read the mappings from the workspace's base tsconfig.
|
|
74
|
+
*
|
|
75
|
+
* Longest pattern first, because Vite takes the first alias that matches and the mappings
|
|
76
|
+
* overlap by design: `@front/theme/node` must not be swallowed by `@front/theme`.
|
|
77
|
+
*/
|
|
78
|
+
declare const tsconfigAliases: (workspaceRoot: string, file?: string) => Alias[];
|
|
79
|
+
|
|
80
|
+
export { type Alias, type DecoratorMetadataOptions, type NestServiceOptions, type NodeManifestOptions, decoratorMetadata, nestService, nodeManifest, tsconfigAliases };
|
|
@@ -0,0 +1,198 @@
|
|
|
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
|
+
transform(code, id) {
|
|
71
|
+
if (!TYPESCRIPT_SOURCE.test(id) || id.includes("node_modules")) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
compilerOptions ??= readCompilerOptions(options);
|
|
75
|
+
const output = ts.transpileModule(code, { fileName: id, compilerOptions });
|
|
76
|
+
return { code: output.outputText, map: output.sourceMapText ?? null };
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// src/roles/build/nest/node-manifest.ts
|
|
82
|
+
import { existsSync as existsSync2, writeFileSync } from "fs";
|
|
83
|
+
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
|
+
);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// src/roles/build/nest/tsconfig-aliases.ts
|
|
107
|
+
import { readFileSync } from "fs";
|
|
108
|
+
import { join as join2 } from "path";
|
|
109
|
+
var stripLineComments = (json) => json.replace(/^\s*\/\/.*$/gm, "");
|
|
110
|
+
var toAlias = (workspaceRoot, pattern, target) => {
|
|
111
|
+
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, "(.*)");
|
|
112
|
+
return {
|
|
113
|
+
find: new RegExp(`^${escaped}$`),
|
|
114
|
+
replacement: join2(workspaceRoot, target.replace(/\*/g, "$1"))
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
var tsconfigAliases = (workspaceRoot, file = "tsconfig.base.json") => {
|
|
118
|
+
const raw = readFileSync(join2(workspaceRoot, file), "utf8");
|
|
119
|
+
const { compilerOptions } = JSON.parse(stripLineComments(raw));
|
|
120
|
+
return Object.entries(compilerOptions?.paths ?? {}).flatMap(([pattern, targets]) => {
|
|
121
|
+
const [target] = targets;
|
|
122
|
+
return target === void 0 ? [] : [toAlias(workspaceRoot, pattern, target)];
|
|
123
|
+
}).sort((a, b) => b.find.source.length - a.find.source.length);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// src/roles/build/nest/nest-service.ts
|
|
127
|
+
var baseConfig = (options) => {
|
|
128
|
+
const outDir = join3(options.workspaceRoot, options.outDir ?? `dist/${options.project}`);
|
|
129
|
+
const entry = join3(options.root, options.entry ?? "src/main.ts");
|
|
130
|
+
return {
|
|
131
|
+
plugins: [
|
|
132
|
+
decoratorMetadata({ root: options.root, tsconfig: options.tsconfig }),
|
|
133
|
+
nodeManifest({
|
|
134
|
+
project: options.project,
|
|
135
|
+
workspaceRoot: options.workspaceRoot,
|
|
136
|
+
outDir
|
|
137
|
+
})
|
|
138
|
+
],
|
|
139
|
+
resolve: { alias: tsconfigAliases(options.workspaceRoot) },
|
|
140
|
+
build: {
|
|
141
|
+
// An SSR build targets Node and leaves real packages external, which is what
|
|
142
|
+
// `webpack-node-externals` did: the image installs them from the generated manifest.
|
|
143
|
+
ssr: entry,
|
|
144
|
+
outDir,
|
|
145
|
+
emptyOutDir: true,
|
|
146
|
+
sourcemap: true,
|
|
147
|
+
target: "node20",
|
|
148
|
+
// The bundle is read by humans when a stack trace points into it, and minifying a
|
|
149
|
+
// server bundle buys nothing: it is never downloaded.
|
|
150
|
+
minify: false,
|
|
151
|
+
rollupOptions: {
|
|
152
|
+
output: {
|
|
153
|
+
format: "cjs",
|
|
154
|
+
// One output file per source module, instead of hoisting everything into one scope.
|
|
155
|
+
//
|
|
156
|
+
// This is not a preference, it is the only shape that keeps the OpenAPI contract.
|
|
157
|
+
// Merging every module into one scope forces Rollup to rename duplicate class names,
|
|
158
|
+
// and `@nestjs/swagger` keys its schemas on `class.name`, so a renamed class silently
|
|
159
|
+
// becomes a renamed schema in the published API. Measured on `network`, which has two
|
|
160
|
+
// such collisions: single bundle renames `InvalidPermissionError` and
|
|
161
|
+
// `PermissionNotFoundError`; preserving modules renames nothing and reproduces the
|
|
162
|
+
// committed contract byte for byte.
|
|
163
|
+
//
|
|
164
|
+
// The cost is 4.5 MB across 2003 files against 2.9 MB in one, still well under
|
|
165
|
+
// webpack's 6.9 MB for the same service, so nothing regresses against what it replaces.
|
|
166
|
+
preserveModules: true,
|
|
167
|
+
// The image runs `./dist/main.js`, so the entry keeps that name at the root while
|
|
168
|
+
// every other module keeps its own path. Under `preserveModules` a plain string here
|
|
169
|
+
// would be applied to all of them, which numbers them (`main962.js`) and loses the
|
|
170
|
+
// entry.
|
|
171
|
+
entryFileNames: (chunk) => chunk.facadeModuleId === entry ? "main.js" : "[name].js"
|
|
172
|
+
},
|
|
173
|
+
// Nest registers metadata as an IMPORT SIDE EFFECT: a decorator writes into a catalog
|
|
174
|
+
// when its module loads, and nothing references that module afterwards. Rollup may
|
|
175
|
+
// drop such a module; webpack never did.
|
|
176
|
+
//
|
|
177
|
+
// Measured on the first migrated service this changes nothing, so it is insurance
|
|
178
|
+
// rather than a fix, and it is recorded as such rather than credited with the smaller
|
|
179
|
+
// bundle (that comes from barrel re-exports the service does not use).
|
|
180
|
+
treeshake: { moduleSideEffects: true }
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
var nestService = (options) => options.overrides === void 0 ? baseConfig(options) : mergeConfig(baseConfig(options), options.overrides);
|
|
186
|
+
|
|
187
|
+
// src/roles/build/nest/toolchain.ts
|
|
188
|
+
import { defineConfig, loadEnv, mergeConfig as mergeConfig2 } from "vite";
|
|
189
|
+
export {
|
|
190
|
+
decoratorMetadata,
|
|
191
|
+
defineConfig,
|
|
192
|
+
loadEnv,
|
|
193
|
+
mergeConfig2 as mergeConfig,
|
|
194
|
+
nestService,
|
|
195
|
+
nodeManifest,
|
|
196
|
+
tsconfigAliases
|
|
197
|
+
};
|
|
198
|
+
//# 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.5",
|
|
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",
|
|
@@ -41,6 +41,10 @@
|
|
|
41
41
|
"./build/react": {
|
|
42
42
|
"types": "./dist/roles/build/toolchain.d.ts",
|
|
43
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": [
|
|
@@ -66,6 +70,7 @@
|
|
|
66
70
|
"oxfmt": "0.63.0",
|
|
67
71
|
"oxlint": "1.77.0",
|
|
68
72
|
"oxlint-tsgolint": "7.0.2001",
|
|
73
|
+
"typescript": "5.9.3",
|
|
69
74
|
"vite": "8.0.8",
|
|
70
75
|
"vite-plugin-svgr": "5.2.0"
|
|
71
76
|
},
|
|
@@ -96,6 +101,18 @@
|
|
|
96
101
|
}
|
|
97
102
|
}
|
|
98
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
|
+
},
|
|
99
116
|
"scripts": {
|
|
100
117
|
"build": "tsup && tsx scripts/build-presets.ts",
|
|
101
118
|
"dev": "tsup --watch",
|