@dudousxd/nestjs-codegen 0.27.1 → 0.28.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @dudousxd/nestjs-codegen
2
2
 
3
+ ## 0.28.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#111](https://github.com/DavideCarvalho/nestjs-codegen/pull/111) [`26529df`](https://github.com/DavideCarvalho/nestjs-codegen/commit/26529df5f6fd1f91148217f9149da41de49fc8ca) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - New built-in `genuiSandboxKit()` extension: writes the generative-UI sandbox-kit docs (`.genui/sandbox-kit.json`, the component names and props of your shadcn `components/ui` read from their TypeScript types) for `@dudousxd/nestjs-agent`, on every generate and in watch mode. It calls `writeSandboxKitDocs` from the optional peer `@dudousxd/nestjs-agent-core/genui/kit` (>=0.52.0), or a `generate` function you pass.
8
+
9
+ Watch mode now also watches the files extensions declare with `ctx.trackInput` (so editing an extension's out-of-glob dependency, such as a filter class or a kit component, regenerates without waiting for an unrelated change), and a tracked directory hashes as its file listing, so adding a file to it invalidates the skip-when-unchanged check.
10
+
11
+ ## 0.27.2
12
+
13
+ ### Patch Changes
14
+
15
+ - [#105](https://github.com/DavideCarvalho/nestjs-codegen/pull/105) [`12be710`](https://github.com/DavideCarvalho/nestjs-codegen/commit/12be710f3c21ccc2e058d901a9921a72587ad939) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Follow relative imports inside declaration files to their declarations: `./chunk.cjs` resolves to `chunk.d.cts` (and `.cts`), `./chunk.mjs` to `chunk.d.mts`, `./chunk.js` to `chunk.d.ts` — after the source candidates, as before. A package's bundled `index.d.cts` imports its chunks by runtime name, and those types came out `unknown`.
16
+
3
17
  ## 0.27.1
4
18
 
5
19
  ### Patch Changes
package/README.md CHANGED
@@ -116,6 +116,29 @@ const create = useMutation(api.users.create().mutationOptions());
116
116
  | [`@dudousxd/nestjs-filter-codegen`](https://www.npmjs.com/package/@dudousxd/nestjs-filter-codegen) | Extension — typed `filterQuery` helpers from the [nestjs-filter](https://github.com/DavideCarvalho/nestjs-filter) repo. |
117
117
  | [`@dudousxd/nestjs-inertia-codegen-extension`](https://www.npmjs.com/package/@dudousxd/nestjs-inertia-codegen-extension) | Extension — Inertia `router` / navigate output from the nestjs-inertia repo. |
118
118
 
119
+ ### Generative-UI sandbox kit (built in)
120
+
121
+ `genuiSandboxKit()` writes the sandbox-kit docs that
122
+ [`@dudousxd/nestjs-agent`](https://github.com/DavideCarvalho/nestjs-agent) uses to render
123
+ generative UI with your design-system components: the names and props of your shadcn
124
+ `components/ui` (read from their TypeScript types) go to `.genui/sandbox-kit.json`, on every
125
+ generate and in watch mode. It needs the optional peer `@dudousxd/nestjs-agent-core`
126
+ (>=0.52.0).
127
+
128
+ ```ts
129
+ import { defineConfig, genuiSandboxKit } from '@dudousxd/nestjs-codegen';
130
+
131
+ export default defineConfig({
132
+ contracts: { glob: 'src/**/*.controller.ts' },
133
+ codegen: { outDir: 'src/generated' },
134
+ extensions: [genuiSandboxKit({ include: 'web/components/ui/*.tsx', css: 'web/styles/globals.css' })],
135
+ });
136
+ ```
137
+
138
+ Options: `entry`, `include` (default: the first shadcn `components/ui` dir found), `css`,
139
+ `tsconfig`, `output` (default `.genui/sandbox-kit.json`, relative to the app cwd), and
140
+ `generate` to inject the docs writer.
141
+
119
142
  Write your own against the `@dudousxd/nestjs-codegen/extension` contract:
120
143
 
121
144
  ```ts
package/dist/cli/main.cjs CHANGED
@@ -708,12 +708,18 @@ function findTypeInFile(name, file) {
708
708
  function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
709
709
  if (moduleSpecifier.startsWith(".")) {
710
710
  const dir = (0, import_node_path4.dirname)(sourceFile.getFilePath());
711
- const noExt = moduleSpecifier.replace(/\.(js|ts)$/, "");
712
- return [
713
- (0, import_node_path4.resolve)(dir, `${noExt}.ts`),
711
+ const ext = /\.(c|m)?(j|t)sx?$/.exec(moduleSpecifier)?.[0];
712
+ const base = ext ? moduleSpecifier.slice(0, -ext.length) : moduleSpecifier;
713
+ const flavour = ext?.startsWith(".c") ? "c" : ext?.startsWith(".m") ? "m" : "";
714
+ const candidates = [
715
+ (0, import_node_path4.resolve)(dir, `${base}.${flavour}ts`),
716
+ ...flavour === "" ? [(0, import_node_path4.resolve)(dir, `${base}.tsx`)] : [],
714
717
  (0, import_node_path4.resolve)(dir, `${moduleSpecifier}.ts`),
715
- (0, import_node_path4.resolve)(dir, moduleSpecifier, "index.ts")
718
+ (0, import_node_path4.resolve)(dir, moduleSpecifier, "index.ts"),
719
+ (0, import_node_path4.resolve)(dir, `${base}.d.${flavour}ts`),
720
+ ...ext === void 0 ? [(0, import_node_path4.resolve)(dir, moduleSpecifier, "index.d.ts")] : []
716
721
  ];
722
+ return [...new Set(candidates)];
717
723
  }
718
724
  const ctx = _ctxFor(project);
719
725
  const baseUrl = ctx.pathsBase ?? ctx.projectRoot;
@@ -5657,14 +5663,26 @@ async function computeInputsHash(config, extraInputs = []) {
5657
5663
  hash.update("\n");
5658
5664
  }
5659
5665
  for (const file of extra) {
5660
- const contents = await (0, import_promises11.readFile)((0, import_node_path15.join)(cwd, file), "utf8").catch(() => null);
5661
5666
  hash.update(`extra:${file}
5662
5667
  `);
5663
- hash.update(contents ?? "\0missing");
5668
+ hash.update(await readTrackedInput((0, import_node_path15.join)(cwd, file)));
5664
5669
  hash.update("\n");
5665
5670
  }
5666
5671
  return hash.digest("hex");
5667
5672
  }
5673
+ async function readTrackedInput(path) {
5674
+ try {
5675
+ return await (0, import_promises11.readFile)(path, "utf8");
5676
+ } catch (err) {
5677
+ if (err.code !== "EISDIR") return "\0missing";
5678
+ }
5679
+ try {
5680
+ const entries = await (0, import_promises11.readdir)(path);
5681
+ return `\0dir:${entries.sort().join("\n")}`;
5682
+ } catch {
5683
+ return "\0missing";
5684
+ }
5685
+ }
5668
5686
  async function readManifest(outDir) {
5669
5687
  try {
5670
5688
  const raw = await (0, import_promises11.readFile)((0, import_node_path15.join)(outDir, MANIFEST_FILE), "utf8");
@@ -5902,11 +5920,36 @@ async function watch(config, onChange, options = {}) {
5902
5920
  }
5903
5921
  return discovery;
5904
5922
  }
5923
+ const trackedWatcher = import_chokidar.default.watch([], {
5924
+ ignoreInitial: true,
5925
+ persistent: true,
5926
+ awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
5927
+ });
5928
+ const trackedPaths = /* @__PURE__ */ new Set();
5929
+ async function syncTrackedInputs() {
5930
+ const manifest = await readManifest(config.codegen.outDir);
5931
+ const fresh = [];
5932
+ for (const path of manifest?.extraInputs ?? []) {
5933
+ const abs = (0, import_node_path18.resolve)(config.codegen.cwd, path);
5934
+ if (trackedPaths.has(abs)) continue;
5935
+ trackedPaths.add(abs);
5936
+ fresh.push(abs);
5937
+ }
5938
+ if (fresh.length > 0) trackedWatcher.add(fresh);
5939
+ }
5940
+ async function generateAndTrack(routes) {
5941
+ try {
5942
+ await generate(config, routes, entryPoint);
5943
+ } finally {
5944
+ await syncTrackedInputs().catch(() => {
5945
+ });
5946
+ }
5947
+ }
5905
5948
  async function runInitialPass() {
5906
5949
  try {
5907
5950
  const initialRoutes = (await getDiscovery()).discover();
5908
5951
  lastRoutes = initialRoutes;
5909
- await generate(config, initialRoutes, entryPoint);
5952
+ await generateAndTrack(initialRoutes);
5910
5953
  } catch (err) {
5911
5954
  if (err instanceof DriftGuardError) {
5912
5955
  console.error(err.message);
@@ -5916,7 +5959,7 @@ async function watch(config, onChange, options = {}) {
5916
5959
  `[nestjs-codegen] Initial route discovery failed, falling back to pages-only: ${err instanceof Error ? err.message : String(err)}`
5917
5960
  );
5918
5961
  try {
5919
- await generate(config, lastRoutes, entryPoint);
5962
+ await generateAndTrack(lastRoutes);
5920
5963
  } catch {
5921
5964
  }
5922
5965
  }
@@ -5949,7 +5992,7 @@ async function watch(config, onChange, options = {}) {
5949
5992
  pagesDebounceTimer = setTimeout(async () => {
5950
5993
  pagesDebounceTimer = void 0;
5951
5994
  try {
5952
- await generate(config, lastRoutes, entryPoint);
5995
+ await generateAndTrack(lastRoutes);
5953
5996
  } catch (err) {
5954
5997
  console.error(
5955
5998
  "[nestjs-codegen] Pages generation failed:",
@@ -5981,7 +6024,7 @@ async function watch(config, onChange, options = {}) {
5981
6024
  try {
5982
6025
  const routes = await (await getDiscovery()).rediscover(changed);
5983
6026
  lastRoutes = routes;
5984
- await generate(config, routes, entryPoint);
6027
+ await generateAndTrack(routes);
5985
6028
  } catch (err) {
5986
6029
  console.error(
5987
6030
  "[nestjs-codegen] Contracts generation failed:",
@@ -6002,6 +6045,9 @@ async function watch(config, onChange, options = {}) {
6002
6045
  formsWatcher.on("add", (p) => scheduleContractsRegenerate(p));
6003
6046
  formsWatcher.on("change", (p) => scheduleContractsRegenerate(p));
6004
6047
  formsWatcher.on("unlink", (p) => scheduleContractsRegenerate(p));
6048
+ trackedWatcher.on("add", (p) => scheduleContractsRegenerate(p));
6049
+ trackedWatcher.on("change", (p) => scheduleContractsRegenerate(p));
6050
+ trackedWatcher.on("unlink", (p) => scheduleContractsRegenerate(p));
6005
6051
  return {
6006
6052
  close: async () => {
6007
6053
  if (pagesDebounceTimer !== void 0) {
@@ -6015,13 +6061,14 @@ async function watch(config, onChange, options = {}) {
6015
6061
  await pagesWatcher.close();
6016
6062
  await contractsWatcher.close();
6017
6063
  await formsWatcher.close();
6064
+ await trackedWatcher.close();
6018
6065
  await lock.release();
6019
6066
  }
6020
6067
  };
6021
6068
  }
6022
6069
 
6023
6070
  // src/index.ts
6024
- var VERSION = "0.27.1";
6071
+ var VERSION = "0.28.0";
6025
6072
 
6026
6073
  // src/cli/codegen.ts
6027
6074
  async function runCodegen(opts = {}) {
@@ -6029,9 +6076,9 @@ async function runCodegen(opts = {}) {
6029
6076
  const config = await loadConfig(cwd);
6030
6077
  if (opts.watch) {
6031
6078
  const watcher = await watch(config, void 0, { entryPoint: "cli" });
6032
- await new Promise((resolve5) => {
6079
+ await new Promise((resolve6) => {
6033
6080
  function onSignal() {
6034
- watcher.close().then(resolve5).catch(resolve5);
6081
+ watcher.close().then(resolve6).catch(resolve6);
6035
6082
  }
6036
6083
  process.once("SIGINT", onSignal);
6037
6084
  process.once("SIGTERM", onSignal);
@@ -6161,16 +6208,16 @@ async function detectPackageManager(cwd) {
6161
6208
  }
6162
6209
  async function promptFramework() {
6163
6210
  if (!process.stdin.isTTY) return "react";
6164
- return new Promise((resolve5) => {
6211
+ return new Promise((resolve6) => {
6165
6212
  const rl = (0, import_node_readline.createInterface)({ input: process.stdin, output: process.stdout });
6166
6213
  rl.question(
6167
6214
  "[nestjs-codegen] Which frontend framework? (react/vue/svelte) [react]: ",
6168
6215
  (answer) => {
6169
6216
  rl.close();
6170
6217
  const trimmed = answer.trim().toLowerCase();
6171
- if (trimmed === "vue") resolve5("vue");
6172
- else if (trimmed === "svelte") resolve5("svelte");
6173
- else resolve5("react");
6218
+ if (trimmed === "vue") resolve6("vue");
6219
+ else if (trimmed === "svelte") resolve6("svelte");
6220
+ else resolve6("react");
6174
6221
  }
6175
6222
  );
6176
6223
  });