@contractkit/plugin-typescript
Advanced tools
| > @contractkit/plugin-typescript@0.20.0 build:ci /home/runner/work/ContractKit/ContractKit/packages/plugin-typescript | ||
| > @contractkit/plugin-typescript@0.21.0 build:ci /home/runner/work/ContractKit/ContractKit/packages/plugin-typescript | ||
| > eslint --max-warnings=0 && pnpm run build | ||
| > @contractkit/plugin-typescript@0.20.0 build /home/runner/work/ContractKit/ContractKit/packages/plugin-typescript | ||
| > @contractkit/plugin-typescript@0.21.0 build /home/runner/work/ContractKit/ContractKit/packages/plugin-typescript | ||
| > tsup src/index.ts --format esm --sourcemap --dts && tsc --emitDeclarationOnly --declaration | ||
@@ -14,7 +14,7 @@ | ||
| [34mESM[39m Build start | ||
| [32mESM[39m [1mdist/index.js [22m[32m146.20 KB[39m | ||
| [32mESM[39m [1mdist/index.js.map [22m[32m317.95 KB[39m | ||
| [32mESM[39m ⚡️ Build success in 371ms | ||
| [32mESM[39m [1mdist/index.js [22m[32m149.46 KB[39m | ||
| [32mESM[39m [1mdist/index.js.map [22m[32m325.97 KB[39m | ||
| [32mESM[39m ⚡️ Build success in 327ms | ||
| [34mDTS[39m Build start | ||
| [32mDTS[39m ⚡️ Build success in 6464ms | ||
| [32mDTS[39m ⚡️ Build success in 6545ms | ||
| [32mDTS[39m [1mdist/index.d.ts [22m[32m1.82 KB[39m |
| > @contractkit/plugin-typescript@0.20.0 test:ci /home/runner/work/ContractKit/ContractKit/packages/plugin-typescript | ||
| > @contractkit/plugin-typescript@0.21.0 test:ci /home/runner/work/ContractKit/ContractKit/packages/plugin-typescript | ||
| > vitest run --coverage | ||
@@ -9,13 +9,13 @@ | ||
| [32m✓[39m tests/codegen-operation.test.ts [2m([22m[2m88 tests[22m[2m)[22m[32m 265[2mms[22m[39m | ||
| [32m✓[39m tests/codegen-sdk.test.ts [2m([22m[2m131 tests[22m[2m)[22m[33m 333[2mms[22m[39m | ||
| [32m✓[39m tests/codegen-contract.test.ts [2m([22m[2m124 tests[22m[2m)[22m[32m 184[2mms[22m[39m | ||
| [32m✓[39m tests/codegen-plain-types.test.ts [2m([22m[2m61 tests[22m[2m)[22m[32m 56[2mms[22m[39m | ||
| [32m✓[39m tests/pipeline.test.ts [2m([22m[2m25 tests[22m[2m)[22m[32m 281[2mms[22m[39m | ||
| [32m✓[39m tests/codegen-server.test.ts [2m([22m[2m19 tests[22m[2m)[22m[32m 37[2mms[22m[39m | ||
| [32m✓[39m tests/codegen-contract.test.ts [2m([22m[2m124 tests[22m[2m)[22m[32m 299[2mms[22m[39m | ||
| [32m✓[39m tests/codegen-operation.test.ts [2m([22m[2m88 tests[22m[2m)[22m[32m 281[2mms[22m[39m | ||
| [32m✓[39m tests/codegen-sdk.test.ts [2m([22m[2m132 tests[22m[2m)[22m[33m 366[2mms[22m[39m | ||
| [32m✓[39m tests/codegen-plain-types.test.ts [2m([22m[2m61 tests[22m[2m)[22m[32m 46[2mms[22m[39m | ||
| [32m✓[39m tests/pipeline.test.ts [2m([22m[2m25 tests[22m[2m)[22m[32m 127[2mms[22m[39m | ||
| [32m✓[39m tests/codegen-server.test.ts [2m([22m[2m19 tests[22m[2m)[22m[32m 44[2mms[22m[39m | ||
| [2m Test Files [22m [1m[32m6 passed[39m[22m[90m (6)[39m | ||
| [2m Tests [22m [1m[32m448 passed[39m[22m[90m (448)[39m | ||
| [2m Start at [22m 11:38:17 | ||
| [2m Duration [22m 6.86s[2m (transform 3.00s, setup 0ms, import 12.86s, tests 1.16s, environment 9ms)[22m | ||
| [2m Tests [22m [1m[32m449 passed[39m[22m[90m (449)[39m | ||
| [2m Start at [22m 11:53:46 | ||
| [2m Duration [22m 7.18s[2m (transform 4.25s, setup 0ms, import 14.68s, tests 1.16s, environment 1ms)[22m | ||
@@ -26,10 +26,10 @@ [34m % [39m[2mCoverage report from [22m[33mv8[39m | ||
| -------------------|---------|----------|---------|---------|------------------- | ||
| All files | 80 | 75.35 | 83.45 | 82.47 | | ||
| src | 79.77 | 75.03 | 83.01 | 82.23 | | ||
| All files | 79.96 | 75.1 | 83.54 | 82.56 | | ||
| src | 79.75 | 74.78 | 83.1 | 82.32 | | ||
| ...n-contract.ts | 87.83 | 82.12 | 90.08 | 88.96 | ...1069,1074-1075 | ||
| ...-operation.ts | 78.18 | 74.26 | 78.08 | 79.69 | ...68-679,684-685 | ||
| ...lain-types.ts | 89.08 | 78.37 | 96.66 | 92.3 | ...31,243,249,301 | ||
| codegen-sdk.ts | 88.26 | 83.01 | 87.14 | 91.22 | ...1087-1088,1091 | ||
| index.ts | 58.69 | 47.22 | 63.82 | 63.33 | ...51-754,770-789 | ||
| path-utils.ts | 36.52 | 27.53 | 75 | 38.14 | ...32-136,147-187 | ||
| codegen-sdk.ts | 88.07 | 82.56 | 87.32 | 91.3 | ...1106-1107,1110 | ||
| index.ts | 60.23 | 47.91 | 63.04 | 65.37 | ...08-811,827-846 | ||
| path-utils.ts | 41.04 | 31.32 | 78.57 | 42.47 | ...64-168,179-219 | ||
| ts-render.ts | 82.19 | 85.39 | 72.22 | 87.09 | 56,90,126,157-165 | ||
@@ -36,0 +36,0 @@ tests | 90.38 | 90.9 | 89.28 | 93.47 | |
+12
-0
| # @contractkit/contractkit-plugin-typescript | ||
| ## 0.21.0 | ||
| ### Minor Changes | ||
| - 2aad136: Move `<Area>Client` classes out of `sdk.ts` and into their own `<area>.client.ts` files. Previously the SDK aggregator declared the `<Area>Client` class inline and merged area-level methods into it; now the merged class is emitted to a synthesized `<area>.client.ts` next to its leaf subarea clients, and `sdk.ts` only imports it. The aggregator is now a thin file: imports + a `Sdk` class with property wiring. | ||
| The area-client output path is derived from the same `output.clients` template as leaf clients via the new `computeSdkAreaClientOutPath` helper — `{filename}` and `{area}` substitute to the area name, `{subarea}` to empty (with double-slashes collapsed and any hidden `.client.ts` segment fixed up). For typical templates like `src/{area}/{subarea}.client.ts` or `src/{area}/{filename}.client.ts`, this produces `src/<area>/<area>.client.ts`. | ||
| `generateSdkAggregator`'s `SdkAreaInfo` shape changed: `inlineFiles` and `subareaClients` are gone — the aggregator now takes a single `client: SdkClientInfo` per area pointing at the new file. Plugins / tooling consuming `generateSdkAggregator` directly need to update. The new `generateAreaClient` function takes the inline-file list + subarea clients and returns the `<area>.client.ts` content. Per-area cache units mean a change to one file's ops only re-renders that area's client. | ||
| Consumers who imported an `<Area>Client` type directly from `sdk.ts` need to import from `./<area>/<area>.client.ts` (or `./<area>/<area>.js` after compile) instead — `Sdk` and `SdkOptions` continue to come from `sdk.ts`. | ||
| ## 0.20.0 | ||
@@ -4,0 +16,0 @@ |
+19
-19
@@ -26,5 +26,5 @@ | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">80% </span> | ||
| <span class="strong">79.96% </span> | ||
| <span class="quiet">Statements</span> | ||
| <span class='fraction'>1996/2495</span> | ||
| <span class='fraction'>2028/2536</span> | ||
| </div> | ||
@@ -34,5 +34,5 @@ | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">75.35% </span> | ||
| <span class="strong">75.1% </span> | ||
| <span class="quiet">Branches</span> | ||
| <span class='fraction'>1220/1619</span> | ||
| <span class='fraction'>1228/1635</span> | ||
| </div> | ||
@@ -42,5 +42,5 @@ | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">83.45% </span> | ||
| <span class="strong">83.54% </span> | ||
| <span class="quiet">Functions</span> | ||
| <span class='fraction'>333/399</span> | ||
| <span class='fraction'>335/401</span> | ||
| </div> | ||
@@ -50,5 +50,5 @@ | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">82.47% </span> | ||
| <span class="strong">82.56% </span> | ||
| <span class="quiet">Lines</span> | ||
| <span class='fraction'>1746/2117</span> | ||
| <span class='fraction'>1776/2151</span> | ||
| </div> | ||
@@ -68,3 +68,3 @@ | ||
| </div> | ||
| <div class='status-line high'></div> | ||
| <div class='status-line medium'></div> | ||
| <div class="pad1"> | ||
@@ -88,13 +88,13 @@ <table class="coverage-summary"> | ||
| <td class="file medium" data-value="src"><a href="src/index.html">src</a></td> | ||
| <td data-value="79.77" class="pic medium"> | ||
| <td data-value="79.75" class="pic medium"> | ||
| <div class="chart"><div class="cover-fill" style="width: 79%"></div><div class="cover-empty" style="width: 21%"></div></div> | ||
| </td> | ||
| <td data-value="79.77" class="pct medium">79.77%</td> | ||
| <td data-value="2443" class="abs medium">1949/2443</td> | ||
| <td data-value="75.03" class="pct medium">75.03%</td> | ||
| <td data-value="1586" class="abs medium">1190/1586</td> | ||
| <td data-value="83.01" class="pct high">83.01%</td> | ||
| <td data-value="371" class="abs high">308/371</td> | ||
| <td data-value="82.23" class="pct high">82.23%</td> | ||
| <td data-value="2071" class="abs high">1703/2071</td> | ||
| <td data-value="79.75" class="pct medium">79.75%</td> | ||
| <td data-value="2484" class="abs medium">1981/2484</td> | ||
| <td data-value="74.78" class="pct medium">74.78%</td> | ||
| <td data-value="1602" class="abs medium">1198/1602</td> | ||
| <td data-value="83.1" class="pct high">83.1%</td> | ||
| <td data-value="373" class="abs high">310/373</td> | ||
| <td data-value="82.32" class="pct high">82.32%</td> | ||
| <td data-value="2105" class="abs high">1733/2105</td> | ||
| </tr> | ||
@@ -125,3 +125,3 @@ | ||
| <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||
| at 2026-05-07T11:38:24.644Z | ||
| at 2026-05-07T11:53:53.335Z | ||
| </div> | ||
@@ -128,0 +128,0 @@ <script src="prettify.js"></script> |
@@ -985,3 +985,3 @@ | ||
| <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||
| at 2026-05-07T11:38:24.644Z | ||
| at 2026-05-07T11:53:53.335Z | ||
| </div> | ||
@@ -988,0 +988,0 @@ <script src="../prettify.js"></script> |
+38
-38
@@ -26,5 +26,5 @@ | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">79.77% </span> | ||
| <span class="strong">79.75% </span> | ||
| <span class="quiet">Statements</span> | ||
| <span class='fraction'>1949/2443</span> | ||
| <span class='fraction'>1981/2484</span> | ||
| </div> | ||
@@ -34,5 +34,5 @@ | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">75.03% </span> | ||
| <span class="strong">74.78% </span> | ||
| <span class="quiet">Branches</span> | ||
| <span class='fraction'>1190/1586</span> | ||
| <span class='fraction'>1198/1602</span> | ||
| </div> | ||
@@ -42,5 +42,5 @@ | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">83.01% </span> | ||
| <span class="strong">83.1% </span> | ||
| <span class="quiet">Functions</span> | ||
| <span class='fraction'>308/371</span> | ||
| <span class='fraction'>310/373</span> | ||
| </div> | ||
@@ -50,5 +50,5 @@ | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">82.23% </span> | ||
| <span class="strong">82.32% </span> | ||
| <span class="quiet">Lines</span> | ||
| <span class='fraction'>1703/2071</span> | ||
| <span class='fraction'>1733/2105</span> | ||
| </div> | ||
@@ -132,13 +132,13 @@ | ||
| <td class="file high" data-value="codegen-sdk.ts"><a href="codegen-sdk.ts.html">codegen-sdk.ts</a></td> | ||
| <td data-value="88.26" class="pic high"> | ||
| <td data-value="88.07" class="pic high"> | ||
| <div class="chart"><div class="cover-fill" style="width: 88%"></div><div class="cover-empty" style="width: 12%"></div></div> | ||
| </td> | ||
| <td data-value="88.26" class="pct high">88.26%</td> | ||
| <td data-value="622" class="abs high">549/622</td> | ||
| <td data-value="83.01" class="pct high">83.01%</td> | ||
| <td data-value="365" class="abs high">303/365</td> | ||
| <td data-value="87.14" class="pct high">87.14%</td> | ||
| <td data-value="70" class="abs high">61/70</td> | ||
| <td data-value="91.22" class="pct high">91.22%</td> | ||
| <td data-value="547" class="abs high">499/547</td> | ||
| <td data-value="88.07" class="pct high">88.07%</td> | ||
| <td data-value="629" class="abs high">554/629</td> | ||
| <td data-value="82.56" class="pct high">82.56%</td> | ||
| <td data-value="367" class="abs high">303/367</td> | ||
| <td data-value="87.32" class="pct high">87.32%</td> | ||
| <td data-value="71" class="abs high">62/71</td> | ||
| <td data-value="91.3" class="pct high">91.3%</td> | ||
| <td data-value="552" class="abs high">504/552</td> | ||
| </tr> | ||
@@ -148,13 +148,13 @@ | ||
| <td class="file medium" data-value="index.ts"><a href="index.ts.html">index.ts</a></td> | ||
| <td data-value="58.69" class="pic medium"> | ||
| <div class="chart"><div class="cover-fill" style="width: 58%"></div><div class="cover-empty" style="width: 42%"></div></div> | ||
| <td data-value="60.23" class="pic medium"> | ||
| <div class="chart"><div class="cover-fill" style="width: 60%"></div><div class="cover-empty" style="width: 40%"></div></div> | ||
| </td> | ||
| <td data-value="58.69" class="pct medium">58.69%</td> | ||
| <td data-value="322" class="abs medium">189/322</td> | ||
| <td data-value="47.22" class="pct low">47.22%</td> | ||
| <td data-value="144" class="abs low">68/144</td> | ||
| <td data-value="63.82" class="pct medium">63.82%</td> | ||
| <td data-value="47" class="abs medium">30/47</td> | ||
| <td data-value="63.33" class="pct medium">63.33%</td> | ||
| <td data-value="270" class="abs medium">171/270</td> | ||
| <td data-value="60.23" class="pct medium">60.23%</td> | ||
| <td data-value="337" class="abs medium">203/337</td> | ||
| <td data-value="47.91" class="pct low">47.91%</td> | ||
| <td data-value="144" class="abs low">69/144</td> | ||
| <td data-value="63.04" class="pct medium">63.04%</td> | ||
| <td data-value="46" class="abs medium">29/46</td> | ||
| <td data-value="65.37" class="pct medium">65.37%</td> | ||
| <td data-value="283" class="abs medium">185/283</td> | ||
| </tr> | ||
@@ -164,13 +164,13 @@ | ||
| <td class="file low" data-value="path-utils.ts"><a href="path-utils.ts.html">path-utils.ts</a></td> | ||
| <td data-value="36.52" class="pic low"> | ||
| <div class="chart"><div class="cover-fill" style="width: 36%"></div><div class="cover-empty" style="width: 64%"></div></div> | ||
| <td data-value="41.04" class="pic low"> | ||
| <div class="chart"><div class="cover-fill" style="width: 41%"></div><div class="cover-empty" style="width: 59%"></div></div> | ||
| </td> | ||
| <td data-value="36.52" class="pct low">36.52%</td> | ||
| <td data-value="115" class="abs low">42/115</td> | ||
| <td data-value="27.53" class="pct low">27.53%</td> | ||
| <td data-value="69" class="abs low">19/69</td> | ||
| <td data-value="75" class="pct medium">75%</td> | ||
| <td data-value="12" class="abs medium">9/12</td> | ||
| <td data-value="38.14" class="pct low">38.14%</td> | ||
| <td data-value="97" class="abs low">37/97</td> | ||
| <td data-value="41.04" class="pct low">41.04%</td> | ||
| <td data-value="134" class="abs low">55/134</td> | ||
| <td data-value="31.32" class="pct low">31.32%</td> | ||
| <td data-value="83" class="abs low">26/83</td> | ||
| <td data-value="78.57" class="pct medium">78.57%</td> | ||
| <td data-value="14" class="abs medium">11/14</td> | ||
| <td data-value="42.47" class="pct low">42.47%</td> | ||
| <td data-value="113" class="abs low">48/113</td> | ||
| </tr> | ||
@@ -201,3 +201,3 @@ | ||
| <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||
| at 2026-05-07T11:38:24.644Z | ||
| at 2026-05-07T11:53:53.335Z | ||
| </div> | ||
@@ -204,0 +204,0 @@ <script src="../prettify.js"></script> |
@@ -26,5 +26,5 @@ | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">36.52% </span> | ||
| <span class="strong">41.04% </span> | ||
| <span class="quiet">Statements</span> | ||
| <span class='fraction'>42/115</span> | ||
| <span class='fraction'>55/134</span> | ||
| </div> | ||
@@ -34,5 +34,5 @@ | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">27.53% </span> | ||
| <span class="strong">31.32% </span> | ||
| <span class="quiet">Branches</span> | ||
| <span class='fraction'>19/69</span> | ||
| <span class='fraction'>26/83</span> | ||
| </div> | ||
@@ -42,5 +42,5 @@ | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">75% </span> | ||
| <span class="strong">78.57% </span> | ||
| <span class="quiet">Functions</span> | ||
| <span class='fraction'>9/12</span> | ||
| <span class='fraction'>11/14</span> | ||
| </div> | ||
@@ -50,5 +50,5 @@ | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">38.14% </span> | ||
| <span class="strong">42.47% </span> | ||
| <span class="quiet">Lines</span> | ||
| <span class='fraction'>37/97</span> | ||
| <span class='fraction'>48/113</span> | ||
| </div> | ||
@@ -258,3 +258,35 @@ | ||
| <a name='L188'></a><a href='#L188'>188</a> | ||
| <a name='L189'></a><a href='#L189'>189</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span> | ||
| <a name='L189'></a><a href='#L189'>189</a> | ||
| <a name='L190'></a><a href='#L190'>190</a> | ||
| <a name='L191'></a><a href='#L191'>191</a> | ||
| <a name='L192'></a><a href='#L192'>192</a> | ||
| <a name='L193'></a><a href='#L193'>193</a> | ||
| <a name='L194'></a><a href='#L194'>194</a> | ||
| <a name='L195'></a><a href='#L195'>195</a> | ||
| <a name='L196'></a><a href='#L196'>196</a> | ||
| <a name='L197'></a><a href='#L197'>197</a> | ||
| <a name='L198'></a><a href='#L198'>198</a> | ||
| <a name='L199'></a><a href='#L199'>199</a> | ||
| <a name='L200'></a><a href='#L200'>200</a> | ||
| <a name='L201'></a><a href='#L201'>201</a> | ||
| <a name='L202'></a><a href='#L202'>202</a> | ||
| <a name='L203'></a><a href='#L203'>203</a> | ||
| <a name='L204'></a><a href='#L204'>204</a> | ||
| <a name='L205'></a><a href='#L205'>205</a> | ||
| <a name='L206'></a><a href='#L206'>206</a> | ||
| <a name='L207'></a><a href='#L207'>207</a> | ||
| <a name='L208'></a><a href='#L208'>208</a> | ||
| <a name='L209'></a><a href='#L209'>209</a> | ||
| <a name='L210'></a><a href='#L210'>210</a> | ||
| <a name='L211'></a><a href='#L211'>211</a> | ||
| <a name='L212'></a><a href='#L212'>212</a> | ||
| <a name='L213'></a><a href='#L213'>213</a> | ||
| <a name='L214'></a><a href='#L214'>214</a> | ||
| <a name='L215'></a><a href='#L215'>215</a> | ||
| <a name='L216'></a><a href='#L216'>216</a> | ||
| <a name='L217'></a><a href='#L217'>217</a> | ||
| <a name='L218'></a><a href='#L218'>218</a> | ||
| <a name='L219'></a><a href='#L219'>219</a> | ||
| <a name='L220'></a><a href='#L220'>220</a> | ||
| <a name='L221'></a><a href='#L221'>221</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
@@ -266,8 +298,8 @@ <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-yes">16x</span> | ||
| <span class="cline-any cline-yes">22x</span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-yes">12x</span> | ||
| <span class="cline-any cline-yes">12x</span> | ||
| <span class="cline-any cline-yes">15x</span> | ||
| <span class="cline-any cline-yes">15x</span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
@@ -365,8 +397,40 @@ <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-no"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-no"> </span> | ||
| <span class="cline-any cline-no"> </span> | ||
| <span class="cline-any cline-no"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-no"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-no"> </span> | ||
| <span class="cline-any cline-no"> </span> | ||
| <span class="cline-any cline-no"> </span> | ||
| <span class="cline-any cline-no"> </span> | ||
| <span class="cline-any cline-no"> </span> | ||
| <span class="cline-any cline-no"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
@@ -547,2 +611,34 @@ <span class="cline-any cline-no"> </span> | ||
| | ||
| /** | ||
| * Resolve the output path for a synthesized `<area>.client.ts` — the file holding the | ||
| * `<Area>Client` class that aggregates an area's inlined methods and subarea wiring. | ||
| * | ||
| * Uses the same `output.clients` template as leaf clients, with `{filename}` and `{area}` | ||
| * substituted to the area name and `{subarea}` substituted to the empty string. Resulting | ||
| * double-slashes from the empty substitution are collapsed, and a final segment that | ||
| * would otherwise begin with a dot (e.g. `.client.ts` from `{subarea}.client.ts`) is | ||
| * prefixed with the area so the file isn't hidden. | ||
| */ | ||
| export function computeSdkAreaClientOutPath(area: string, rootDir: string, clientOutput: string | undefined): string { | ||
| const filename = area; | ||
| const baseOutDir = resolve(rootDir); | ||
| const fixHiddenSegment = (path: string): string => { | ||
| const segments = path.split('/'); | ||
| const last = segments[segments.length - 1] ?? <span class="branch-1 cbranch-no" title="branch not covered" >'';</span> | ||
| if (last.startsWith('.')) segments[segments.length - 1] = `${filename}${last}`; | ||
| return segments.join('/'); | ||
| }; | ||
| <span class="missing-if-branch" title="else path not taken" >E</span>if (clientOutput && TEMPLATE_VAR_RE.test(clientOutput)) { | ||
| const resolved = resolveTemplate(clientOutput, { filename, dir: '', ext: 'ck', area, subarea: '' }); | ||
| const cleaned = fixHiddenSegment(resolved.replace(/\/+/g, '/').replace(/^\//, '')); | ||
| <span class="missing-if-branch" title="else path not taken" >E</span>if (includesFilename(cleaned)) return join(baseOutDir, cleaned); | ||
| <span class="cstat-no" title="statement not covered" > return join(baseOutDir, cleaned, `${filename}.client.ts`);</span> | ||
| } | ||
| <span class="cstat-no" title="statement not covered" > if (clientOutput) {</span> | ||
| <span class="cstat-no" title="statement not covered" > if (includesFilename(clientOutput)) <span class="cstat-no" title="statement not covered" >return join(baseOutDir, clientOutput);</span></span> | ||
| <span class="cstat-no" title="statement not covered" > return join(baseOutDir, clientOutput, `${filename}.client.ts`);</span> | ||
| } | ||
| <span class="cstat-no" title="statement not covered" > return join(baseOutDir, `${filename}.client.ts`);</span> | ||
| } | ||
| | ||
| export function <span class="fstat-no" title="function not covered" >computeSdkTypeOutPath(</span> | ||
@@ -645,3 +741,3 @@ filePath: string, | ||
| <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||
| at 2026-05-07T11:38:24.644Z | ||
| at 2026-05-07T11:53:53.335Z | ||
| </div> | ||
@@ -648,0 +744,0 @@ <script src="../prettify.js"></script> |
@@ -580,3 +580,3 @@ | ||
| <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||
| at 2026-05-07T11:38:24.644Z | ||
| at 2026-05-07T11:53:53.335Z | ||
| </div> | ||
@@ -583,0 +583,0 @@ <script src="../prettify.js"></script> |
@@ -814,3 +814,3 @@ | ||
| <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||
| at 2026-05-07T11:38:24.644Z | ||
| at 2026-05-07T11:53:53.335Z | ||
| </div> | ||
@@ -817,0 +817,0 @@ <script src="../prettify.js"></script> |
@@ -104,3 +104,3 @@ | ||
| <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||
| at 2026-05-07T11:38:24.644Z | ||
| at 2026-05-07T11:53:53.335Z | ||
| </div> | ||
@@ -107,0 +107,0 @@ <script src="../prettify.js"></script> |
+40
-15
@@ -92,4 +92,4 @@ import type { OpRootNode } from '@contractkit/core'; | ||
| /** | ||
| * One area-level (no-subarea) `.ck` file whose methods are inlined directly into the | ||
| * generated `<Area>Client` class instead of getting a standalone `*.client.ts`. | ||
| * One area-level (no-subarea) `.ck` file whose methods are merged into the area's | ||
| * `<Area>Client` (emitted to its own `<area>.client.ts`). | ||
| */ | ||
@@ -99,3 +99,3 @@ export interface SdkAreaInlineFile { | ||
| root: OpRootNode; | ||
| /** Codegen options for this file (must have `outPath` pointing at the SDK aggregator file so type-import paths resolve correctly). */ | ||
| /** Codegen options for this file (must have `outPath` pointing at the area client file so type-import paths resolve correctly). */ | ||
| codegenOptions: SdkCodegenOptions; | ||
@@ -106,9 +106,8 @@ } | ||
| area: string; | ||
| /** Files for which all methods are inlined into the area client (no subarea). */ | ||
| inlineFiles: SdkAreaInlineFile[]; | ||
| /** Per-file leaf clients exposed as named properties on the area client. */ | ||
| subareaClients: { | ||
| propertyName: string; | ||
| client: SdkClientInfo; | ||
| }[]; | ||
| /** | ||
| * Reference to the `<Area>Client` class — the file that holds it lives at | ||
| * `client.importPath` (relative to `sdk.ts`) and is generated separately by | ||
| * {@link generateAreaClient}. The aggregator just imports it. | ||
| */ | ||
| client: SdkClientInfo; | ||
| } | ||
@@ -125,12 +124,38 @@ export interface SdkAggregatorInput { | ||
| } | ||
| /** Inputs to {@link generateAreaClient}. */ | ||
| export interface AreaClientInput { | ||
| /** Area name (e.g. `payments`). Drives the generated class name (`PaymentsClient`). */ | ||
| area: string; | ||
| /** Output path of the generated `<area>.client.ts` file. Used to resolve relative type / leaf-client / sdk-options imports. */ | ||
| outPath: string; | ||
| /** Files contributing inlined methods to the area client (typically area-level files with no subarea). */ | ||
| inlineFiles: SdkAreaInlineFile[]; | ||
| /** Subarea leaf clients exposed as named properties on the area client. */ | ||
| subareaClients: { | ||
| propertyName: string; | ||
| client: SdkClientInfo; | ||
| }[]; | ||
| /** Path to `sdk-options.ts`, used for `SdkFetch` and runtime helpers. */ | ||
| sdkOptionsPath: string; | ||
| } | ||
| /** | ||
| * Generate the SDK aggregator (`sdk.ts`) — the entry-point file consumers import. | ||
| * Generate a complete `<area>.client.ts` file: the `<Area>Client` class with | ||
| * subarea property fields, a constructor that wires them, and inlined methods | ||
| * merged from every area-level file in `inlineFiles`. | ||
| * | ||
| * Emits one `<Area>Client` class per area (combining inlined area-level methods with | ||
| * subarea property wiring), one `class Sdk` exposing area properties + flat top-level | ||
| * properties, plus all imports needed by the inlined methods. | ||
| * Emitted by the plugin alongside the per-leaf `*.client.ts` files. The SDK | ||
| * aggregator just imports the resulting class — see {@link generateSdkAggregator}. | ||
| * | ||
| * @throws if two area-level files in the same area produce the same method name. | ||
| * @throws if two area-level files contribute the same method name to the area — | ||
| * disambiguate via `sdk:` on the operation, or move one into a subarea. | ||
| */ | ||
| export declare function generateAreaClient(input: AreaClientInput): string; | ||
| /** | ||
| * Generate the SDK aggregator (`sdk.ts`) — the entry-point file consumers import. | ||
| * | ||
| * Imports every `<Area>Client` (one per area, generated by {@link generateAreaClient} | ||
| * to its own `<area>.client.ts`) and every leaf top-level client, then emits a | ||
| * `class Sdk` that exposes them as properties. | ||
| */ | ||
| export declare function generateSdkAggregator(input: SdkAggregatorInput): string; | ||
| //# sourceMappingURL=codegen-sdk.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"codegen-sdk.d.ts","sourceRoot":"","sources":["../src/codegen-sdk.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"codegen-sdk.d.ts","sourceRoot":"","sources":["../src/codegen-sdk.ts"],"names":[],"mappings":"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"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACR,iBAAiB,EAQpB,MAAM,mBAAmB,CAAC;AAsC3B,MAAM,WAAW,YAAY;IACzB,wFAAwF;IACxF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6HAA6H;IAC7H,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE;QACL,8EAA8E;QAC9E,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,+EAA+E;QAC/E,KAAK,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,wDAAwD;IACxD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,wEAAwE;IACxE,eAAe,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,SAAS;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE;QACL,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,eAAe,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,SAAS;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IACnC,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,KAAK,CAAC,EAAE,WAAW,CAAC;CACvB;AAID,yGAAyG;AACzG,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAO9C,QAAA,MAAM,MAAM,EAAE,iBAMb,CAAC;AAEF,eAAe,MAAM,CAAC;AAEtB,2GAA2G;AAC3G,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,sBAAsB,EAAE,OAAO,EAAE,MAAM,GAAG,iBAAiB,CAOzG"} | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACR,iBAAiB,EAQpB,MAAM,mBAAmB,CAAC;AA0C3B,MAAM,WAAW,YAAY;IACzB,wFAAwF;IACxF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6HAA6H;IAC7H,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE;QACL,8EAA8E;QAC9E,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,+EAA+E;QAC/E,KAAK,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,wDAAwD;IACxD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,wEAAwE;IACxE,eAAe,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,SAAS;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE;QACL,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,eAAe,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,SAAS;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IACnC,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,KAAK,CAAC,EAAE,WAAW,CAAC;CACvB;AAID,yGAAyG;AACzG,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAO9C,QAAA,MAAM,MAAM,EAAE,iBAMb,CAAC;AAEF,eAAe,MAAM,CAAC;AAEtB,2GAA2G;AAC3G,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,sBAAsB,EAAE,OAAO,EAAE,MAAM,GAAG,iBAAiB,CAOzG"} |
+11
-0
@@ -9,2 +9,13 @@ import type { ContractRootNode, OpRootNode } from '@contractkit/core'; | ||
| export declare function computeSdkOutPath(filePath: string, rootDir: string, clientOutput: string | undefined, commonRoot: string, meta?: Record<string, string>): string | null; | ||
| /** | ||
| * Resolve the output path for a synthesized `<area>.client.ts` — the file holding the | ||
| * `<Area>Client` class that aggregates an area's inlined methods and subarea wiring. | ||
| * | ||
| * Uses the same `output.clients` template as leaf clients, with `{filename}` and `{area}` | ||
| * substituted to the area name and `{subarea}` substituted to the empty string. Resulting | ||
| * double-slashes from the empty substitution are collapsed, and a final segment that | ||
| * would otherwise begin with a dot (e.g. `.client.ts` from `{subarea}.client.ts`) is | ||
| * prefixed with the area so the file isn't hidden. | ||
| */ | ||
| export declare function computeSdkAreaClientOutPath(area: string, rootDir: string, clientOutput: string | undefined): string; | ||
| export declare function computeSdkTypeOutPath(filePath: string, rootDir: string, typeOutput: string, commonRoot: string, meta?: Record<string, string>): string | null; | ||
@@ -11,0 +22,0 @@ export declare function generateBarrelFiles(contractPaths: string[]): { |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"path-utils.d.ts","sourceRoot":"","sources":["../src/path-utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAGtE,eAAO,MAAM,eAAe,QAAY,CAAC;AAEzC,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAEtF;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAGnD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAclE;AAID,wBAAgB,gBAAgB,CAC5B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAClC,MAAM,CAiBR;AAED,wBAAgB,sBAAsB,CAClC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAClC,MAAM,CAER;AAID,wBAAgB,iBAAiB,CAC7B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAClC,MAAM,GAAG,IAAI,CAkBf;AAED,wBAAgB,qBAAqB,CACjC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAClC,MAAM,GAAG,IAAI,CAef;AAED,wBAAgB,mBAAmB,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAAE,CAiBnG;AAED,wBAAgB,6BAA6B,CACzC,MAAM,EAAE,UAAU,EAAE,EACpB,YAAY,EAAE,gBAAgB,EAAE,EAChC,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,EAC5B,gBAAgB,GAAE,GAAG,CAAC,MAAM,CAAa,GAC1C,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CA0CpB"} | ||
| {"version":3,"file":"path-utils.d.ts","sourceRoot":"","sources":["../src/path-utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAGtE,eAAO,MAAM,eAAe,QAAY,CAAC;AAEzC,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAEtF;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAGnD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAclE;AAID,wBAAgB,gBAAgB,CAC5B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAClC,MAAM,CAiBR;AAED,wBAAgB,sBAAsB,CAClC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAClC,MAAM,CAER;AAID,wBAAgB,iBAAiB,CAC7B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAClC,MAAM,GAAG,IAAI,CAkBf;AAED;;;;;;;;;GASG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAoBnH;AAED,wBAAgB,qBAAqB,CACjC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAClC,MAAM,GAAG,IAAI,CAef;AAED,wBAAgB,mBAAmB,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAAE,CAiBnG;AAED,wBAAgB,6BAA6B,CACzC,MAAM,EAAE,UAAU,EAAE,EACpB,YAAY,EAAE,gBAAgB,EAAE,EAChC,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,EAC5B,gBAAgB,GAAE,GAAG,CAAC,MAAM,CAAa,GAC1C,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CA0CpB"} |
+3
-3
| { | ||
| "name": "@contractkit/plugin-typescript", | ||
| "version": "0.20.0", | ||
| "version": "0.21.0", | ||
| "description": "ContractKit built-in plugin: TypeScript codegen (SDK clients, Koa routers, Zod schemas, plain types)", | ||
@@ -32,4 +32,4 @@ "author": { | ||
| "devDependencies": { | ||
| "@repo/config-typescript": "0.1.0", | ||
| "@repo/config-eslint": "0.3.1" | ||
| "@repo/config-eslint": "0.3.1", | ||
| "@repo/config-typescript": "0.1.0" | ||
| }, | ||
@@ -36,0 +36,0 @@ "scripts": { |
+132
-95
@@ -975,4 +975,4 @@ import type { OpRootNode, OpRouteNode, OpOperationNode, OpRequestBodyNode, ContractTypeNode, ParamSource } from '@contractkit/core'; | ||
| /** | ||
| * One area-level (no-subarea) `.ck` file whose methods are inlined directly into the | ||
| * generated `<Area>Client` class instead of getting a standalone `*.client.ts`. | ||
| * One area-level (no-subarea) `.ck` file whose methods are merged into the area's | ||
| * `<Area>Client` (emitted to its own `<area>.client.ts`). | ||
| */ | ||
@@ -982,3 +982,3 @@ export interface SdkAreaInlineFile { | ||
| root: OpRootNode; | ||
| /** Codegen options for this file (must have `outPath` pointing at the SDK aggregator file so type-import paths resolve correctly). */ | ||
| /** Codegen options for this file (must have `outPath` pointing at the area client file so type-import paths resolve correctly). */ | ||
| codegenOptions: SdkCodegenOptions; | ||
@@ -990,6 +990,8 @@ } | ||
| area: string; | ||
| /** Files for which all methods are inlined into the area client (no subarea). */ | ||
| inlineFiles: SdkAreaInlineFile[]; | ||
| /** Per-file leaf clients exposed as named properties on the area client. */ | ||
| subareaClients: { propertyName: string; client: SdkClientInfo }[]; | ||
| /** | ||
| * Reference to the `<Area>Client` class — the file that holds it lives at | ||
| * `client.importPath` (relative to `sdk.ts`) and is generated separately by | ||
| * {@link generateAreaClient}. The aggregator just imports it. | ||
| */ | ||
| client: SdkClientInfo; | ||
| } | ||
@@ -1008,18 +1010,35 @@ | ||
| /** Inputs to {@link generateAreaClient}. */ | ||
| export interface AreaClientInput { | ||
| /** Area name (e.g. `payments`). Drives the generated class name (`PaymentsClient`). */ | ||
| area: string; | ||
| /** Output path of the generated `<area>.client.ts` file. Used to resolve relative type / leaf-client / sdk-options imports. */ | ||
| outPath: string; | ||
| /** Files contributing inlined methods to the area client (typically area-level files with no subarea). */ | ||
| inlineFiles: SdkAreaInlineFile[]; | ||
| /** Subarea leaf clients exposed as named properties on the area client. */ | ||
| subareaClients: { propertyName: string; client: SdkClientInfo }[]; | ||
| /** Path to `sdk-options.ts`, used for `SdkFetch` and runtime helpers. */ | ||
| sdkOptionsPath: string; | ||
| } | ||
| /** | ||
| * Generate the SDK aggregator (`sdk.ts`) — the entry-point file consumers import. | ||
| * Generate a complete `<area>.client.ts` file: the `<Area>Client` class with | ||
| * subarea property fields, a constructor that wires them, and inlined methods | ||
| * merged from every area-level file in `inlineFiles`. | ||
| * | ||
| * Emits one `<Area>Client` class per area (combining inlined area-level methods with | ||
| * subarea property wiring), one `class Sdk` exposing area properties + flat top-level | ||
| * properties, plus all imports needed by the inlined methods. | ||
| * Emitted by the plugin alongside the per-leaf `*.client.ts` files. The SDK | ||
| * aggregator just imports the resulting class — see {@link generateSdkAggregator}. | ||
| * | ||
| * @throws if two area-level files in the same area produce the same method name. | ||
| * @throws if two area-level files contribute the same method name to the area — | ||
| * disambiguate via `sdk:` on the operation, or move one into a subarea. | ||
| */ | ||
| export function generateSdkAggregator(input: SdkAggregatorInput): string { | ||
| const sdkOptionsImportPath = input.sdkOptionsImportPath ?? './sdk-options.js'; | ||
| const sdkClassName = input.sdkClassName ?? 'Sdk'; | ||
| export function generateAreaClient(input: AreaClientInput): string { | ||
| const { area, outPath, inlineFiles, subareaClients, sdkOptionsPath } = input; | ||
| const className = deriveAreaClientClassName(area); | ||
| // ── Pre-render inline method blocks per area (also collects type/runtime needs) ── | ||
| const inlinedByArea = new Map<string, { lines: string[]; methodNames: Set<string> }>(); | ||
| const typesByImportPath = new Map<string, Set<string>>(); // path → set of type names | ||
| // ── Merge inputs across all inline files ──────────────────────────────── | ||
| const collectedMethodLines: string[] = []; | ||
| const seenMethods = new Set<string>(); | ||
| const typesByImportPath = new Map<string, Set<string>>(); | ||
| const unresolvedTypes = new Set<string>(); | ||
@@ -1031,49 +1050,53 @@ let needsJson = false; | ||
| for (const area of input.areas) { | ||
| const collected: string[] = []; | ||
| const seenMethods = new Set<string>(); | ||
| for (const inline of area.inlineFiles) { | ||
| const includeInternal = inline.codegenOptions.includeInternal ?? false; | ||
| // Collect method names + source lines | ||
| const { lines: methodLines, methodNames } = generateClientMethods(inline.root, inline.codegenOptions); | ||
| for (const name of methodNames) { | ||
| if (seenMethods.has(name)) { | ||
| throw new Error( | ||
| `[sdk] duplicate method '${name}' in area '${area.area}': two area-level files contribute the same method. Disambiguate via 'sdk:' or move one into a subarea.`, | ||
| ); | ||
| } | ||
| seenMethods.add(name); | ||
| for (const inline of inlineFiles) { | ||
| const includeInternal = inline.codegenOptions.includeInternal ?? false; | ||
| const { lines: methodLines, methodNames } = generateClientMethods(inline.root, inline.codegenOptions); | ||
| for (const name of methodNames) { | ||
| if (seenMethods.has(name)) { | ||
| throw new Error( | ||
| `[sdk] duplicate method '${name}' in area '${area}': two area-level files contribute the same method. Disambiguate via 'sdk:' or move one into a subarea.`, | ||
| ); | ||
| } | ||
| collected.push(...methodLines); | ||
| // Side info for imports | ||
| if (sdkNeedsJson(inline.root, includeInternal)) needsJson = true; | ||
| if (sdkNeedsBigIntReplacer(inline.root, includeInternal)) needsBigIntReplacer = true; | ||
| if (sdkNeedsBigIntReviver(inline.root, includeInternal)) needsBigIntReviver = true; | ||
| if (sdkNeedsQueryString(inline.root, includeInternal)) needsQueryString = true; | ||
| // Type imports: rebuild what generateTypeImports would produce, but key by import path so multiple files merge | ||
| const typesForFile = collectTypes(inline.root, inline.codegenOptions.modelsWithInput, inline.codegenOptions.modelsWithOutput, includeInternal); | ||
| const { modelOutPaths, outPath } = inline.codegenOptions; | ||
| if (modelOutPaths && outPath) { | ||
| const fromDir = dirname(outPath); | ||
| for (const t of typesForFile) { | ||
| const typeOutPath = modelOutPaths.get(t); | ||
| if (typeOutPath) { | ||
| let rel = relative(fromDir, typeOutPath).replace(/\.ts$/, '.js'); | ||
| if (!rel.startsWith('.')) rel = './' + rel; | ||
| const set = typesByImportPath.get(rel) ?? new Set(); | ||
| set.add(t); | ||
| typesByImportPath.set(rel, set); | ||
| } else { | ||
| unresolvedTypes.add(t); | ||
| } | ||
| seenMethods.add(name); | ||
| } | ||
| collectedMethodLines.push(...methodLines); | ||
| if (sdkNeedsJson(inline.root, includeInternal)) needsJson = true; | ||
| if (sdkNeedsBigIntReplacer(inline.root, includeInternal)) needsBigIntReplacer = true; | ||
| if (sdkNeedsBigIntReviver(inline.root, includeInternal)) needsBigIntReviver = true; | ||
| if (sdkNeedsQueryString(inline.root, includeInternal)) needsQueryString = true; | ||
| // Resolve each file's type refs against THIS file's modelOutPaths, but | ||
| // produce import paths relative to the area client's outPath (not the | ||
| // contributing file's outPath, which pointed at the now-defunct sdk.ts). | ||
| const typesForFile = collectTypes( | ||
| inline.root, | ||
| inline.codegenOptions.modelsWithInput, | ||
| inline.codegenOptions.modelsWithOutput, | ||
| includeInternal, | ||
| ); | ||
| const { modelOutPaths } = inline.codegenOptions; | ||
| if (modelOutPaths) { | ||
| const fromDir = dirname(outPath); | ||
| for (const t of typesForFile) { | ||
| const typeOutPath = modelOutPaths.get(t); | ||
| if (typeOutPath) { | ||
| let rel = relative(fromDir, typeOutPath).replace(/\.ts$/, '.js'); | ||
| if (!rel.startsWith('.')) rel = './' + rel; | ||
| const set = typesByImportPath.get(rel) ?? new Set(); | ||
| set.add(t); | ||
| typesByImportPath.set(rel, set); | ||
| } else { | ||
| unresolvedTypes.add(t); | ||
| } | ||
| } | ||
| } | ||
| inlinedByArea.set(area.area, { lines: collected, methodNames: seenMethods }); | ||
| } | ||
| // ── Imports ── | ||
| // ── Imports ───────────────────────────────────────────────────────────── | ||
| let sdkOptionsRel = relative(dirname(outPath), sdkOptionsPath).replace(/\.ts$/, '.js'); | ||
| if (!sdkOptionsRel.startsWith('.')) sdkOptionsRel = './' + sdkOptionsRel; | ||
| const lines: string[] = []; | ||
| const jsonImport = needsJson ? ', JsonValue' : ''; | ||
| lines.push(`import type { SdkFetch${jsonImport} } from '${sdkOptionsImportPath}';`); | ||
| lines.push(`import type { SdkFetch${jsonImport} } from '${sdkOptionsRel}';`); | ||
| const valueImports: string[] = []; | ||
@@ -1084,10 +1107,6 @@ if (needsBigIntReplacer) valueImports.push('bigIntReplacer'); | ||
| if (valueImports.length > 0) { | ||
| lines.push(`import { ${valueImports.join(', ')} } from '${sdkOptionsImportPath}';`); | ||
| lines.push(`import { ${valueImports.join(', ')} } from '${sdkOptionsRel}';`); | ||
| } | ||
| lines.push(`import type { SdkOptions } from '${sdkOptionsImportPath}';`); | ||
| lines.push(`import { createSdkFetch } from '${sdkOptionsImportPath}';`); | ||
| // Type imports inlined from area-level files | ||
| const typeImportPaths = [...typesByImportPath.keys()].sort(); | ||
| for (const path of typeImportPaths) { | ||
| for (const path of [...typesByImportPath.keys()].sort()) { | ||
| const names = [...typesByImportPath.get(path)!].sort(); | ||
@@ -1100,4 +1119,49 @@ lines.push(`import type { ${names.join(', ')} } from '${path}';`); | ||
| // Leaf client imports (top-level + subarea) | ||
| // Leaf client imports (subareas only — top-level clients live next to sdk.ts). | ||
| const importedClients = new Set<string>(); | ||
| for (const sc of subareaClients) { | ||
| const key = `${sc.client.className}|${sc.client.importPath}`; | ||
| if (importedClients.has(key)) continue; | ||
| importedClients.add(key); | ||
| lines.push(`import { ${sc.client.className} } from '${sc.client.importPath}';`); | ||
| } | ||
| lines.push(''); | ||
| // ── <Area>Client class ────────────────────────────────────────────────── | ||
| lines.push(`export class ${className} {`); | ||
| for (const sc of subareaClients) { | ||
| lines.push(` readonly ${sc.propertyName}: ${sc.client.className};`); | ||
| } | ||
| if (subareaClients.length > 0) lines.push(''); | ||
| if (collectedMethodLines.length > 0 || subareaClients.length > 0) { | ||
| const fetchModifier = collectedMethodLines.length > 0 ? 'private ' : ''; | ||
| lines.push(` constructor(${fetchModifier}fetch: SdkFetch) {`); | ||
| for (const sc of subareaClients) { | ||
| lines.push(` this.${sc.propertyName} = new ${sc.client.className}(fetch);`); | ||
| } | ||
| lines.push(' }'); | ||
| } | ||
| for (const ln of collectedMethodLines) lines.push(ln); | ||
| lines.push('}'); | ||
| lines.push(''); | ||
| return lines.join('\n'); | ||
| } | ||
| /** | ||
| * Generate the SDK aggregator (`sdk.ts`) — the entry-point file consumers import. | ||
| * | ||
| * Imports every `<Area>Client` (one per area, generated by {@link generateAreaClient} | ||
| * to its own `<area>.client.ts`) and every leaf top-level client, then emits a | ||
| * `class Sdk` that exposes them as properties. | ||
| */ | ||
| export function generateSdkAggregator(input: SdkAggregatorInput): string { | ||
| const sdkOptionsImportPath = input.sdkOptionsImportPath ?? './sdk-options.js'; | ||
| const sdkClassName = input.sdkClassName ?? 'Sdk'; | ||
| const lines: string[] = []; | ||
| lines.push(`import type { SdkOptions } from '${sdkOptionsImportPath}';`); | ||
| lines.push(`import { createSdkFetch } from '${sdkOptionsImportPath}';`); | ||
| const importedClients = new Set<string>(); | ||
| const pushClientImport = (c: SdkClientInfo): void => { | ||
@@ -1109,37 +1173,10 @@ const key = `${c.className}|${c.importPath}`; | ||
| }; | ||
| // Areas first, then top-level — keeps the aggregator's import order stable. | ||
| for (const area of input.areas) pushClientImport(area.client); | ||
| for (const c of input.topLevelClients) pushClientImport(c); | ||
| for (const area of input.areas) { | ||
| for (const sc of area.subareaClients) pushClientImport(sc.client); | ||
| } | ||
| lines.push(''); | ||
| // ── <Area>Client classes ── | ||
| for (const area of input.areas) { | ||
| const areaClassName = deriveAreaClientClassName(area.area); | ||
| const inlined = inlinedByArea.get(area.area)!; | ||
| lines.push(`class ${areaClassName} {`); | ||
| // Subarea property declarations | ||
| for (const sc of area.subareaClients) { | ||
| lines.push(` readonly ${sc.propertyName}: ${sc.client.className};`); | ||
| } | ||
| if (area.subareaClients.length > 0) lines.push(''); | ||
| // Constructor | ||
| if (inlined.lines.length > 0 || area.subareaClients.length > 0) { | ||
| const fetchModifier = inlined.lines.length > 0 ? 'private ' : ''; | ||
| lines.push(` constructor(${fetchModifier}fetch: SdkFetch) {`); | ||
| for (const sc of area.subareaClients) { | ||
| lines.push(` this.${sc.propertyName} = new ${sc.client.className}(fetch);`); | ||
| } | ||
| lines.push(' }'); | ||
| } | ||
| // Inlined methods (already class-body indented) | ||
| for (const ln of inlined.lines) lines.push(ln); | ||
| lines.push('}'); | ||
| lines.push(''); | ||
| } | ||
| // ── Sdk aggregator ── | ||
| lines.push(`export class ${sdkClassName} {`); | ||
| for (const area of input.areas) { | ||
| lines.push(` readonly ${deriveAreaPropertyName(area.area)}: ${deriveAreaClientClassName(area.area)};`); | ||
| lines.push(` readonly ${deriveAreaPropertyName(area.area)}: ${area.client.className};`); | ||
| } | ||
@@ -1153,3 +1190,3 @@ for (const c of input.topLevelClients) { | ||
| for (const area of input.areas) { | ||
| lines.push(` this.${deriveAreaPropertyName(area.area)} = new ${deriveAreaClientClassName(area.area)}(sdkFetch);`); | ||
| lines.push(` this.${deriveAreaPropertyName(area.area)} = new ${area.client.className}(sdkFetch);`); | ||
| } | ||
@@ -1156,0 +1193,0 @@ for (const c of input.topLevelClients) { |
+96
-39
@@ -28,4 +28,7 @@ import { resolve, join, relative, dirname, basename } from 'node:path'; | ||
| generateSdkAggregator, | ||
| generateAreaClient, | ||
| deriveClientClassName, | ||
| deriveClientPropertyName, | ||
| deriveAreaClientClassName, | ||
| deriveAreaPropertyName, | ||
| deriveSubareaClientClassName, | ||
@@ -46,2 +49,3 @@ deriveSubareaPropertyName, | ||
| computeSdkOutPath, | ||
| computeSdkAreaClientOutPath, | ||
| computeSdkTypeOutPath, | ||
@@ -553,8 +557,9 @@ generateBarrelFiles, | ||
| // ── Global files: sdk-options, aggregator, barrels, root index ── | ||
| // The aggregator inlines area-only op roots, so its content depends on each inline root's | ||
| // full AST. Caching it gains little — the codegen is fast and any inline-root change rebuilds | ||
| // the file anyway. Same for barrels (one line per imported file). Always regenerate. | ||
| // sdk-options.ts is a constant; the aggregator is small (just imports + a wrapper class) | ||
| // and depends on the cross-cutting client list, so it's cheap to regenerate every run. | ||
| // Per-area `<area>.client.ts` files are cached as their own units below. | ||
| globalFiles.push({ relativePath: sdkOptionsPath, content: generateSdkOptions() }); | ||
| const hasAnything = sdkClientInfos.length > 0 || areaBuckets.size > 0; | ||
| const areaClientOutPaths = new Map<string, string>(); // area → absolute outPath of <area>.client.ts | ||
| if (hasAnything) { | ||
@@ -571,4 +576,4 @@ const sdkEntryDir = dirname(sdkEntryPath); | ||
| const toClientImport = (info: { outPath: string; className: string; propertyName: string }): SdkClientInfo => { | ||
| let rel = relative(sdkEntryDir, info.outPath).replace(/\.ts$/, '.js'); | ||
| const toClientImport = (sourceDir: string, info: { outPath: string; className: string; propertyName: string }): SdkClientInfo => { | ||
| let rel = relative(sourceDir, info.outPath).replace(/\.ts$/, '.js'); | ||
| if (!rel.startsWith('.')) rel = './' + rel; | ||
@@ -578,42 +583,94 @@ return { className: info.className, propertyName: info.propertyName, importPath: rel }; | ||
| const topLevelClients: SdkClientInfo[] = topLevelEntries.map(e => ({ | ||
| className: deriveClientClassName(e.ast.file), | ||
| propertyName: deriveClientPropertyName(e.ast.file), | ||
| importPath: (() => { | ||
| const rel = relative(sdkEntryDir, e.outPath).replace(/\.ts$/, '.js'); | ||
| return rel.startsWith('.') ? rel : './' + rel; | ||
| })(), | ||
| })); | ||
| const topLevelClients: SdkClientInfo[] = topLevelEntries.map(e => | ||
| toClientImport(sdkEntryDir, { | ||
| outPath: e.outPath, | ||
| className: deriveClientClassName(e.ast.file), | ||
| propertyName: deriveClientPropertyName(e.ast.file), | ||
| }), | ||
| ); | ||
| const areas: SdkAreaInfo[] = [...areaBuckets.entries()] | ||
| .sort(([a], [b]) => a.localeCompare(b)) | ||
| .map(([area, bucket]) => ({ | ||
| // ── Per-area `<area>.client.ts` units ── | ||
| const areaInfos: SdkAreaInfo[] = []; | ||
| const sortedAreas = [...areaBuckets.entries()].sort(([a], [b]) => a.localeCompare(b)); | ||
| for (const [area, bucket] of sortedAreas) { | ||
| const areaClientOutPath = computeSdkAreaClientOutPath(area, sdkBase, config.output!.clients); | ||
| areaClientOutPaths.set(area, areaClientOutPath); | ||
| const areaClassName = deriveAreaClientClassName(area); | ||
| const areaPropertyName = deriveAreaPropertyName(area); | ||
| sdkClientInfos.push({ outPath: areaClientOutPath, className: areaClassName, propertyName: areaPropertyName }); | ||
| const subareaClients = bucket.leaves | ||
| .sort((a, b) => a.subarea.localeCompare(b.subarea)) | ||
| .map(l => ({ | ||
| propertyName: deriveSubareaPropertyName(l.subarea), | ||
| client: toClientImport(dirname(areaClientOutPath), { | ||
| outPath: l.outPath, | ||
| className: deriveSubareaClientClassName(area, l.subarea), | ||
| propertyName: deriveSubareaPropertyName(l.subarea), | ||
| }), | ||
| })); | ||
| // Fingerprint covers every input the area client depends on: | ||
| // - all inline roots (full AST) | ||
| // - subarea client metadata (className / propertyName / import path) | ||
| // - the modelOutPaths slice for refs across all inline roots | ||
| // - modelsWithInput/Output slices | ||
| const allInlineRefs = new Set<string>(); | ||
| for (const r of bucket.inlineRoots) { | ||
| for (const ref of collectOpRootRefs(r, modelMap)) allInlineRefs.add(ref); | ||
| } | ||
| const fingerprint = hashFingerprint({ | ||
| kind: 'sdk-area-client', | ||
| v: TYPESCRIPT_CODEGEN_VERSION, | ||
| outPath: areaClientOutPath, | ||
| area, | ||
| inlineFiles: bucket.inlineRoots.map(root => ({ | ||
| root, | ||
| codegenOptions: { | ||
| typeImportPathTemplate: undefined, | ||
| outPath: sdkEntryPath, | ||
| modelOutPaths: sdkModelOutPaths, | ||
| sdkOptionsPath, | ||
| modelsWithInput, | ||
| modelsWithOutput, | ||
| includeInternal: config.includeInternal, | ||
| }, | ||
| })), | ||
| subareaClients: bucket.leaves | ||
| .sort((a, b) => a.subarea.localeCompare(b.subarea)) | ||
| .map(l => ({ | ||
| propertyName: deriveSubareaPropertyName(l.subarea), | ||
| client: toClientImport({ | ||
| outPath: l.outPath, | ||
| className: deriveSubareaClientClassName(area, l.subarea), | ||
| propertyName: deriveSubareaPropertyName(l.subarea), | ||
| }), | ||
| })), | ||
| inlineRoots: bucket.inlineRoots, | ||
| subareaClients, | ||
| outPathSlice: sliceOutPathMap(allInlineRefs, sdkModelOutPaths, modelsWithInput, modelsWithOutput), | ||
| modelsWithInput: sliceModelSet(allInlineRefs, new Set(), modelsWithInput), | ||
| modelsWithOutput: sliceModelSet(allInlineRefs, new Set(), modelsWithOutput), | ||
| sdkOptionsPath, | ||
| includeInternal: config.includeInternal ?? false, | ||
| sub: subConfigKey, | ||
| }); | ||
| const inlineFilesForGen = bucket.inlineRoots.map(root => ({ | ||
| root, | ||
| codegenOptions: { | ||
| typeImportPathTemplate: undefined, | ||
| outPath: areaClientOutPath, | ||
| modelOutPaths: sdkModelOutPaths, | ||
| sdkOptionsPath, | ||
| modelsWithInput, | ||
| modelsWithOutput, | ||
| includeInternal: config.includeInternal, | ||
| }, | ||
| })); | ||
| units.push({ | ||
| key: `sdk-area-client::${areaClientOutPath}`, | ||
| fingerprint, | ||
| render: () => [ | ||
| { | ||
| relativePath: areaClientOutPath, | ||
| content: generateAreaClient({ | ||
| area, | ||
| outPath: areaClientOutPath, | ||
| inlineFiles: inlineFilesForGen, | ||
| subareaClients, | ||
| sdkOptionsPath, | ||
| }), | ||
| }, | ||
| ], | ||
| }); | ||
| areaInfos.push({ | ||
| area, | ||
| client: toClientImport(sdkEntryDir, { outPath: areaClientOutPath, className: areaClassName, propertyName: areaPropertyName }), | ||
| }); | ||
| } | ||
| globalFiles.push({ | ||
| relativePath: sdkEntryPath, | ||
| content: generateSdkAggregator({ topLevelClients, areas, sdkOptionsImportPath, sdkClassName }), | ||
| content: generateSdkAggregator({ topLevelClients, areas: areaInfos, sdkOptionsImportPath, sdkClassName }), | ||
| }); | ||
@@ -620,0 +677,0 @@ } |
+32
-0
@@ -99,2 +99,34 @@ import { resolve, join, relative, dirname } from 'node:path'; | ||
| /** | ||
| * Resolve the output path for a synthesized `<area>.client.ts` — the file holding the | ||
| * `<Area>Client` class that aggregates an area's inlined methods and subarea wiring. | ||
| * | ||
| * Uses the same `output.clients` template as leaf clients, with `{filename}` and `{area}` | ||
| * substituted to the area name and `{subarea}` substituted to the empty string. Resulting | ||
| * double-slashes from the empty substitution are collapsed, and a final segment that | ||
| * would otherwise begin with a dot (e.g. `.client.ts` from `{subarea}.client.ts`) is | ||
| * prefixed with the area so the file isn't hidden. | ||
| */ | ||
| export function computeSdkAreaClientOutPath(area: string, rootDir: string, clientOutput: string | undefined): string { | ||
| const filename = area; | ||
| const baseOutDir = resolve(rootDir); | ||
| const fixHiddenSegment = (path: string): string => { | ||
| const segments = path.split('/'); | ||
| const last = segments[segments.length - 1] ?? ''; | ||
| if (last.startsWith('.')) segments[segments.length - 1] = `${filename}${last}`; | ||
| return segments.join('/'); | ||
| }; | ||
| if (clientOutput && TEMPLATE_VAR_RE.test(clientOutput)) { | ||
| const resolved = resolveTemplate(clientOutput, { filename, dir: '', ext: 'ck', area, subarea: '' }); | ||
| const cleaned = fixHiddenSegment(resolved.replace(/\/+/g, '/').replace(/^\//, '')); | ||
| if (includesFilename(cleaned)) return join(baseOutDir, cleaned); | ||
| return join(baseOutDir, cleaned, `${filename}.client.ts`); | ||
| } | ||
| if (clientOutput) { | ||
| if (includesFilename(clientOutput)) return join(baseOutDir, clientOutput); | ||
| return join(baseOutDir, clientOutput, `${filename}.client.ts`); | ||
| } | ||
| return join(baseOutDir, `${filename}.client.ts`); | ||
| } | ||
| export function computeSdkTypeOutPath( | ||
@@ -101,0 +133,0 @@ filePath: string, |
@@ -207,3 +207,3 @@ import { describe, it, expect } from 'vitest'; | ||
| it('emits a leaf <Area><Subarea>Client and nests it under <Area>Client in sdk.ts', async () => { | ||
| it('emits a leaf <Area><Subarea>Client and a separate <Area>Client wrapper file', async () => { | ||
| const plugin = createTypescriptPlugin({ sdk: { output: { sdk: 'sdk.ts', clients: '{area}/{subarea}.client.ts' } } }, '/project'); | ||
@@ -224,14 +224,21 @@ const ctx = makeCtx('/project'); | ||
| // Aggregator declares IdentityClient + Sdk wiring | ||
| // Separate <area>.client.ts holds the IdentityClient wrapper | ||
| const areaClient = findEmitted(ctx.emitted, 'identity/identity.client.ts'); | ||
| expect(areaClient).toBeDefined(); | ||
| expect(areaClient!).toContain('export class IdentityClient {'); | ||
| expect(areaClient!).toContain('readonly invitations: IdentityInvitationsClient'); | ||
| expect(areaClient!).toContain('this.invitations = new IdentityInvitationsClient(fetch)'); | ||
| // sdk.ts just imports + wires the area client onto Sdk | ||
| const sdk = findEmitted(ctx.emitted, '/sdk.ts'); | ||
| expect(sdk).toBeDefined(); | ||
| expect(sdk!).toContain('class IdentityClient {'); | ||
| expect(sdk!).toContain('readonly invitations: IdentityInvitationsClient'); | ||
| expect(sdk!).toContain('this.invitations = new IdentityInvitationsClient(fetch)'); | ||
| expect(sdk!).toContain("import { IdentityClient } from './identity/identity.client.js'"); | ||
| expect(sdk!).toContain('export class Sdk {'); | ||
| expect(sdk!).toContain('readonly identity: IdentityClient'); | ||
| expect(sdk!).toContain('this.identity = new IdentityClient(sdkFetch)'); | ||
| // The class declaration itself is no longer in sdk.ts. | ||
| expect(sdk!).not.toContain('class IdentityClient {'); | ||
| }); | ||
| it('does NOT emit a standalone client file for an area-level (no-subarea) input — methods inline into sdk.ts', async () => { | ||
| it('emits a synthesized <area>.client.ts for area-level (no-subarea) inputs instead of inlining into sdk.ts', async () => { | ||
| const plugin = createTypescriptPlugin({ sdk: { output: { sdk: 'sdk.ts', clients: '{area}/{filename}.client.ts' } } }, '/project'); | ||
@@ -246,3 +253,3 @@ const ctx = makeCtx('/project'); | ||
| // No per-file leaf for the area-level input | ||
| // The contributing file does NOT get its own *.client.ts (per the previous design). | ||
| for (const path of ctx.emitted.keys()) { | ||
@@ -252,10 +259,15 @@ expect(path).not.toMatch(/identity[/-]me\.client\.ts$/); | ||
| // Methods land directly on IdentityClient inside sdk.ts | ||
| // The area client lives in <area>/<area>.client.ts and holds the merged methods. | ||
| const areaClient = findEmitted(ctx.emitted, 'identity/identity.client.ts'); | ||
| expect(areaClient).toBeDefined(); | ||
| expect(areaClient!).toContain('export class IdentityClient {'); | ||
| expect(areaClient!).toContain('async getCurrentUser'); | ||
| // sdk.ts just imports it. | ||
| const sdk = findEmitted(ctx.emitted, '/sdk.ts'); | ||
| expect(sdk).toBeDefined(); | ||
| expect(sdk!).toContain('class IdentityClient {'); | ||
| expect(sdk!).toContain('async getCurrentUser'); | ||
| expect(sdk!).toContain("import { IdentityClient } from './identity/identity.client.js'"); | ||
| expect(sdk!).not.toContain('class IdentityClient {'); | ||
| }); | ||
| it('mixes area-level inline methods with subarea property wiring on the same area client', async () => { | ||
| it('mixes area-level inline methods with subarea property wiring on the same <area>.client.ts', async () => { | ||
| const plugin = createTypescriptPlugin({ sdk: { output: { sdk: 'sdk.ts', clients: '{area}/{filename}.client.ts' } } }, '/project'); | ||
@@ -275,6 +287,6 @@ const ctx = makeCtx('/project'); | ||
| const sdk = findEmitted(ctx.emitted, '/sdk.ts')!; | ||
| expect(sdk).toContain('class IdentityClient {'); | ||
| expect(sdk).toContain('readonly invitations: IdentityInvitationsClient'); | ||
| expect(sdk).toContain('async getCurrentUser'); | ||
| const areaClient = findEmitted(ctx.emitted, 'identity/identity.client.ts')!; | ||
| expect(areaClient).toContain('export class IdentityClient {'); | ||
| expect(areaClient).toContain('readonly invitations: IdentityInvitationsClient'); | ||
| expect(areaClient).toContain('async getCurrentUser'); | ||
| }); | ||
@@ -281,0 +293,0 @@ |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
2527708
2.03%16200
1.64%