@contractkit/plugin-typescript 0.19.1 → 0.21.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/.turbo/turbo-build$colon$ci.log +6 -6
- package/.turbo/turbo-test$colon$ci.log +15 -15
- package/CHANGELOG.md +27 -0
- package/coverage/clover.xml +432 -394
- package/coverage/coverage-final.json +3 -3
- package/coverage/index.html +17 -17
- package/coverage/src/codegen-contract.ts.html +1 -1
- package/coverage/src/codegen-operation.ts.html +1 -1
- package/coverage/src/codegen-plain-types.ts.html +1 -1
- package/coverage/src/codegen-sdk.ts.html +291 -180
- package/coverage/src/index.html +36 -36
- package/coverage/src/index.ts.html +291 -81
- package/coverage/src/path-utils.ts.html +109 -13
- package/coverage/src/ts-render.ts.html +1 -1
- package/coverage/tests/helpers.ts.html +1 -1
- package/coverage/tests/index.html +1 -1
- package/dist/codegen-sdk.d.ts +40 -15
- package/dist/codegen-sdk.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +190 -99
- package/dist/index.js.map +1 -1
- package/dist/path-utils.d.ts +11 -0
- package/dist/path-utils.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/codegen-sdk.ts +133 -96
- package/src/index.ts +114 -44
- package/src/path-utils.ts +32 -0
- package/tests/codegen-sdk.test.ts +73 -78
- package/tests/codegen-server.test.ts +29 -16
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
generateSdk,
|
|
4
4
|
generateSdkOptions,
|
|
5
5
|
generateSdkAggregator,
|
|
6
|
+
generateAreaClient,
|
|
6
7
|
deriveClientClassName,
|
|
7
8
|
deriveClientPropertyName,
|
|
8
9
|
deriveAreaClientClassName,
|
|
@@ -1536,60 +1537,83 @@ describe('area + subarea name derivation', () => {
|
|
|
1536
1537
|
// ─── generateSdkAggregator — area + subarea wiring ────────────────────────
|
|
1537
1538
|
|
|
1538
1539
|
describe('generateSdkAggregator — area + subarea', () => {
|
|
1539
|
-
it('
|
|
1540
|
+
it('imports each area client and exposes it on Sdk', () => {
|
|
1540
1541
|
const out = generateSdkAggregator({
|
|
1541
1542
|
topLevelClients: [],
|
|
1542
1543
|
areas: [
|
|
1543
1544
|
{
|
|
1544
1545
|
area: 'identity',
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1546
|
+
client: { className: 'IdentityClient', propertyName: 'identity', importPath: './identity/identity.client.js' },
|
|
1547
|
+
},
|
|
1548
|
+
],
|
|
1549
|
+
});
|
|
1550
|
+
expect(out).toContain("import { IdentityClient } from './identity/identity.client.js'");
|
|
1551
|
+
expect(out).toContain('export class Sdk {');
|
|
1552
|
+
expect(out).toContain('readonly identity: IdentityClient');
|
|
1553
|
+
expect(out).toContain('this.identity = new IdentityClient(sdkFetch)');
|
|
1554
|
+
// The aggregator no longer declares the IdentityClient itself.
|
|
1555
|
+
expect(out).not.toContain('class IdentityClient {');
|
|
1556
|
+
});
|
|
1557
|
+
|
|
1558
|
+
it('mixes area clients with top-level (no-area) clients', () => {
|
|
1559
|
+
const out = generateSdkAggregator({
|
|
1560
|
+
topLevelClients: [{ className: 'WebhooksClient', propertyName: 'webhooks', importPath: './webhooks.client.js' }],
|
|
1561
|
+
areas: [
|
|
1562
|
+
{
|
|
1563
|
+
area: 'payments',
|
|
1564
|
+
client: { className: 'PaymentsClient', propertyName: 'payments', importPath: './payments/payments.client.js' },
|
|
1556
1565
|
},
|
|
1557
1566
|
],
|
|
1558
1567
|
});
|
|
1559
|
-
expect(out).toContain(
|
|
1560
|
-
expect(out).toContain(
|
|
1561
|
-
expect(out).toContain('
|
|
1568
|
+
expect(out).toContain('readonly payments: PaymentsClient');
|
|
1569
|
+
expect(out).toContain('readonly webhooks: WebhooksClient');
|
|
1570
|
+
expect(out).toContain('this.payments = new PaymentsClient(sdkFetch)');
|
|
1571
|
+
expect(out).toContain('this.webhooks = new WebhooksClient(sdkFetch)');
|
|
1572
|
+
});
|
|
1573
|
+
});
|
|
1574
|
+
|
|
1575
|
+
describe('generateAreaClient — <Area>Client emission', () => {
|
|
1576
|
+
it('emits one <Area>Client class with subarea property wiring', () => {
|
|
1577
|
+
const out = generateAreaClient({
|
|
1578
|
+
area: 'identity',
|
|
1579
|
+
outPath: '/out/identity/identity.client.ts',
|
|
1580
|
+
inlineFiles: [],
|
|
1581
|
+
subareaClients: [
|
|
1582
|
+
{
|
|
1583
|
+
propertyName: 'invitations',
|
|
1584
|
+
client: { className: 'IdentityInvitationsClient', propertyName: 'invitations', importPath: './invitations.client.js' },
|
|
1585
|
+
},
|
|
1586
|
+
{
|
|
1587
|
+
propertyName: 'users',
|
|
1588
|
+
client: { className: 'IdentityUsersClient', propertyName: 'users', importPath: './users.client.js' },
|
|
1589
|
+
},
|
|
1590
|
+
],
|
|
1591
|
+
sdkOptionsPath: '/out/sdk-options.ts',
|
|
1592
|
+
});
|
|
1593
|
+
expect(out).toContain("import { IdentityInvitationsClient } from './invitations.client.js'");
|
|
1594
|
+
expect(out).toContain("import { IdentityUsersClient } from './users.client.js'");
|
|
1595
|
+
expect(out).toContain('export class IdentityClient {');
|
|
1562
1596
|
expect(out).toContain('readonly invitations: IdentityInvitationsClient');
|
|
1563
1597
|
expect(out).toContain('readonly users: IdentityUsersClient');
|
|
1564
1598
|
expect(out).toContain('this.invitations = new IdentityInvitationsClient(fetch)');
|
|
1565
1599
|
expect(out).toContain('this.users = new IdentityUsersClient(fetch)');
|
|
1566
|
-
// Sdk wires the area property
|
|
1567
|
-
expect(out).toContain('export class Sdk {');
|
|
1568
|
-
expect(out).toContain('readonly identity: IdentityClient');
|
|
1569
|
-
expect(out).toContain('this.identity = new IdentityClient(sdkFetch)');
|
|
1570
1600
|
});
|
|
1571
1601
|
|
|
1572
|
-
it('inlines methods from area-level (no-subarea) files into
|
|
1602
|
+
it('inlines methods from area-level (no-subarea) files into the area client', () => {
|
|
1573
1603
|
const root = opRoot(
|
|
1574
1604
|
[opRoute('/me', [opOperation('get', { sdk: 'getCurrentUser', responses: [opResponse(200, 'User', 'application/json')] })])],
|
|
1575
1605
|
'identity.ck',
|
|
1576
1606
|
{ area: 'identity' },
|
|
1577
1607
|
);
|
|
1578
|
-
const out =
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
subareaClients: [],
|
|
1585
|
-
},
|
|
1586
|
-
],
|
|
1608
|
+
const out = generateAreaClient({
|
|
1609
|
+
area: 'identity',
|
|
1610
|
+
outPath: '/out/identity/identity.client.ts',
|
|
1611
|
+
inlineFiles: [{ root, codegenOptions: { outPath: '/out/identity/identity.client.ts', sdkOptionsPath: '/out/sdk-options.ts' } }],
|
|
1612
|
+
subareaClients: [],
|
|
1613
|
+
sdkOptionsPath: '/out/sdk-options.ts',
|
|
1587
1614
|
});
|
|
1588
|
-
expect(out).toContain('class IdentityClient {');
|
|
1615
|
+
expect(out).toContain('export class IdentityClient {');
|
|
1589
1616
|
expect(out).toContain('async getCurrentUser(');
|
|
1590
|
-
// No subarea property declarations on this client
|
|
1591
|
-
expect(out).not.toMatch(/readonly \w+: \w+;[\s\S]*async getCurrentUser/);
|
|
1592
|
-
// The fetch arg is captured as `private fetch` since methods reference `this.fetch`
|
|
1593
1617
|
expect(out).toContain('constructor(private fetch: SdkFetch)');
|
|
1594
1618
|
});
|
|
1595
1619
|
|
|
@@ -1599,20 +1623,17 @@ describe('generateSdkAggregator — area + subarea', () => {
|
|
|
1599
1623
|
'identity.ck',
|
|
1600
1624
|
{ area: 'identity' },
|
|
1601
1625
|
);
|
|
1602
|
-
const out =
|
|
1603
|
-
|
|
1604
|
-
|
|
1626
|
+
const out = generateAreaClient({
|
|
1627
|
+
area: 'identity',
|
|
1628
|
+
outPath: '/out/identity/identity.client.ts',
|
|
1629
|
+
inlineFiles: [{ root: inlineRoot, codegenOptions: { outPath: '/out/identity/identity.client.ts', sdkOptionsPath: '/out/sdk-options.ts' } }],
|
|
1630
|
+
subareaClients: [
|
|
1605
1631
|
{
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
subareaClients: [
|
|
1609
|
-
{
|
|
1610
|
-
propertyName: 'invitations',
|
|
1611
|
-
client: { className: 'IdentityInvitationsClient', propertyName: 'invitations', importPath: './identity/invitations.client.js' },
|
|
1612
|
-
},
|
|
1613
|
-
],
|
|
1632
|
+
propertyName: 'invitations',
|
|
1633
|
+
client: { className: 'IdentityInvitationsClient', propertyName: 'invitations', importPath: './invitations.client.js' },
|
|
1614
1634
|
},
|
|
1615
1635
|
],
|
|
1636
|
+
sdkOptionsPath: '/out/sdk-options.ts',
|
|
1616
1637
|
});
|
|
1617
1638
|
expect(out).toContain('readonly invitations: IdentityInvitationsClient');
|
|
1618
1639
|
expect(out).toContain('async getCurrentUser(');
|
|
@@ -1631,42 +1652,16 @@ describe('generateSdkAggregator — area + subarea', () => {
|
|
|
1631
1652
|
{ area: 'identity' },
|
|
1632
1653
|
);
|
|
1633
1654
|
expect(() =>
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
{ root: a, codegenOptions: { outPath: '/out/sdk.ts', sdkOptionsPath: '/out/sdk-options.ts' } },
|
|
1641
|
-
{ root: b, codegenOptions: { outPath: '/out/sdk.ts', sdkOptionsPath: '/out/sdk-options.ts' } },
|
|
1642
|
-
],
|
|
1643
|
-
subareaClients: [],
|
|
1644
|
-
},
|
|
1655
|
+
generateAreaClient({
|
|
1656
|
+
area: 'identity',
|
|
1657
|
+
outPath: '/out/identity/identity.client.ts',
|
|
1658
|
+
inlineFiles: [
|
|
1659
|
+
{ root: a, codegenOptions: { outPath: '/out/identity/identity.client.ts', sdkOptionsPath: '/out/sdk-options.ts' } },
|
|
1660
|
+
{ root: b, codegenOptions: { outPath: '/out/identity/identity.client.ts', sdkOptionsPath: '/out/sdk-options.ts' } },
|
|
1645
1661
|
],
|
|
1662
|
+
subareaClients: [],
|
|
1663
|
+
sdkOptionsPath: '/out/sdk-options.ts',
|
|
1646
1664
|
}),
|
|
1647
1665
|
).toThrow(/duplicate method 'getCurrentUser' in area 'identity'/);
|
|
1648
1666
|
});
|
|
1649
|
-
|
|
1650
|
-
it('mixes area clients with top-level (no-area) clients', () => {
|
|
1651
|
-
const out = generateSdkAggregator({
|
|
1652
|
-
topLevelClients: [{ className: 'WebhooksClient', propertyName: 'webhooks', importPath: './webhooks.client.js' }],
|
|
1653
|
-
areas: [
|
|
1654
|
-
{
|
|
1655
|
-
area: 'payments',
|
|
1656
|
-
inlineFiles: [],
|
|
1657
|
-
subareaClients: [
|
|
1658
|
-
{
|
|
1659
|
-
propertyName: 'transfers',
|
|
1660
|
-
client: { className: 'PaymentsTransfersClient', propertyName: 'transfers', importPath: './payments/transfers.client.js' },
|
|
1661
|
-
},
|
|
1662
|
-
],
|
|
1663
|
-
},
|
|
1664
|
-
],
|
|
1665
|
-
});
|
|
1666
|
-
// Sdk has both an area property AND a top-level property
|
|
1667
|
-
expect(out).toContain('readonly payments: PaymentsClient');
|
|
1668
|
-
expect(out).toContain('readonly webhooks: WebhooksClient');
|
|
1669
|
-
expect(out).toContain('this.payments = new PaymentsClient(sdkFetch)');
|
|
1670
|
-
expect(out).toContain('this.webhooks = new WebhooksClient(sdkFetch)');
|
|
1671
|
-
});
|
|
1672
1667
|
});
|
|
@@ -11,6 +11,7 @@ function makeCtx(rootDir = '/project', options: Record<string, unknown> = {}): P
|
|
|
11
11
|
rootDir,
|
|
12
12
|
options,
|
|
13
13
|
cacheEnabled: true,
|
|
14
|
+
cacheDir: `${rootDir}/.contractkit/cache`,
|
|
14
15
|
emitFile: (outPath: string, content: string) => {
|
|
15
16
|
emitted.set(outPath, content);
|
|
16
17
|
},
|
|
@@ -203,7 +204,7 @@ describe('createTypescriptPlugin (sdk) — area / subarea grouping', () => {
|
|
|
203
204
|
return undefined;
|
|
204
205
|
}
|
|
205
206
|
|
|
206
|
-
it('emits a leaf <Area><Subarea>Client and
|
|
207
|
+
it('emits a leaf <Area><Subarea>Client and a separate <Area>Client wrapper file', async () => {
|
|
207
208
|
const plugin = createTypescriptPlugin({ sdk: { output: { sdk: 'sdk.ts', clients: '{area}/{subarea}.client.ts' } } }, '/project');
|
|
208
209
|
const ctx = makeCtx('/project');
|
|
209
210
|
const root = opRoot(
|
|
@@ -219,18 +220,25 @@ describe('createTypescriptPlugin (sdk) — area / subarea grouping', () => {
|
|
|
219
220
|
expect(leaf!).toContain('export class IdentityInvitationsClient');
|
|
220
221
|
expect(leaf!).toContain('async createInvitation');
|
|
221
222
|
|
|
222
|
-
//
|
|
223
|
+
// Separate <area>.client.ts holds the IdentityClient wrapper
|
|
224
|
+
const areaClient = findEmitted(ctx.emitted, 'identity/identity.client.ts');
|
|
225
|
+
expect(areaClient).toBeDefined();
|
|
226
|
+
expect(areaClient!).toContain('export class IdentityClient {');
|
|
227
|
+
expect(areaClient!).toContain('readonly invitations: IdentityInvitationsClient');
|
|
228
|
+
expect(areaClient!).toContain('this.invitations = new IdentityInvitationsClient(fetch)');
|
|
229
|
+
|
|
230
|
+
// sdk.ts just imports + wires the area client onto Sdk
|
|
223
231
|
const sdk = findEmitted(ctx.emitted, '/sdk.ts');
|
|
224
232
|
expect(sdk).toBeDefined();
|
|
225
|
-
expect(sdk!).toContain(
|
|
226
|
-
expect(sdk!).toContain('readonly invitations: IdentityInvitationsClient');
|
|
227
|
-
expect(sdk!).toContain('this.invitations = new IdentityInvitationsClient(fetch)');
|
|
233
|
+
expect(sdk!).toContain("import { IdentityClient } from './identity/identity.client.js'");
|
|
228
234
|
expect(sdk!).toContain('export class Sdk {');
|
|
229
235
|
expect(sdk!).toContain('readonly identity: IdentityClient');
|
|
230
236
|
expect(sdk!).toContain('this.identity = new IdentityClient(sdkFetch)');
|
|
237
|
+
// The class declaration itself is no longer in sdk.ts.
|
|
238
|
+
expect(sdk!).not.toContain('class IdentityClient {');
|
|
231
239
|
});
|
|
232
240
|
|
|
233
|
-
it('
|
|
241
|
+
it('emits a synthesized <area>.client.ts for area-level (no-subarea) inputs instead of inlining into sdk.ts', async () => {
|
|
234
242
|
const plugin = createTypescriptPlugin({ sdk: { output: { sdk: 'sdk.ts', clients: '{area}/{filename}.client.ts' } } }, '/project');
|
|
235
243
|
const ctx = makeCtx('/project');
|
|
236
244
|
const root = opRoot(
|
|
@@ -240,19 +248,24 @@ describe('createTypescriptPlugin (sdk) — area / subarea grouping', () => {
|
|
|
240
248
|
);
|
|
241
249
|
await plugin.generateTargets!(inputs([root]), ctx);
|
|
242
250
|
|
|
243
|
-
//
|
|
251
|
+
// The contributing file does NOT get its own *.client.ts (per the previous design).
|
|
244
252
|
for (const path of ctx.emitted.keys()) {
|
|
245
253
|
expect(path).not.toMatch(/identity[/-]me\.client\.ts$/);
|
|
246
254
|
}
|
|
247
255
|
|
|
248
|
-
//
|
|
256
|
+
// The area client lives in <area>/<area>.client.ts and holds the merged methods.
|
|
257
|
+
const areaClient = findEmitted(ctx.emitted, 'identity/identity.client.ts');
|
|
258
|
+
expect(areaClient).toBeDefined();
|
|
259
|
+
expect(areaClient!).toContain('export class IdentityClient {');
|
|
260
|
+
expect(areaClient!).toContain('async getCurrentUser');
|
|
261
|
+
|
|
262
|
+
// sdk.ts just imports it.
|
|
249
263
|
const sdk = findEmitted(ctx.emitted, '/sdk.ts');
|
|
250
|
-
expect(sdk).
|
|
251
|
-
expect(sdk!).toContain('class IdentityClient {');
|
|
252
|
-
expect(sdk!).toContain('async getCurrentUser');
|
|
264
|
+
expect(sdk!).toContain("import { IdentityClient } from './identity/identity.client.js'");
|
|
265
|
+
expect(sdk!).not.toContain('class IdentityClient {');
|
|
253
266
|
});
|
|
254
267
|
|
|
255
|
-
it('mixes area-level inline methods with subarea property wiring on the same area
|
|
268
|
+
it('mixes area-level inline methods with subarea property wiring on the same <area>.client.ts', async () => {
|
|
256
269
|
const plugin = createTypescriptPlugin({ sdk: { output: { sdk: 'sdk.ts', clients: '{area}/{filename}.client.ts' } } }, '/project');
|
|
257
270
|
const ctx = makeCtx('/project');
|
|
258
271
|
const me = opRoot(
|
|
@@ -267,10 +280,10 @@ describe('createTypescriptPlugin (sdk) — area / subarea grouping', () => {
|
|
|
267
280
|
);
|
|
268
281
|
await plugin.generateTargets!(inputs([me, invitations]), ctx);
|
|
269
282
|
|
|
270
|
-
const
|
|
271
|
-
expect(
|
|
272
|
-
expect(
|
|
273
|
-
expect(
|
|
283
|
+
const areaClient = findEmitted(ctx.emitted, 'identity/identity.client.ts')!;
|
|
284
|
+
expect(areaClient).toContain('export class IdentityClient {');
|
|
285
|
+
expect(areaClient).toContain('readonly invitations: IdentityInvitationsClient');
|
|
286
|
+
expect(areaClient).toContain('async getCurrentUser');
|
|
274
287
|
});
|
|
275
288
|
|
|
276
289
|
it('preserves legacy flat wiring for files with no area', async () => {
|