@workos/oagen-emitters 0.4.0 → 0.5.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.
Files changed (105) hide show
  1. package/.github/workflows/ci.yml +1 -1
  2. package/.github/workflows/lint.yml +1 -1
  3. package/.github/workflows/release-please.yml +2 -2
  4. package/.github/workflows/release.yml +1 -1
  5. package/.husky/pre-push +11 -0
  6. package/.node-version +1 -1
  7. package/.release-please-manifest.json +1 -1
  8. package/CHANGELOG.md +8 -0
  9. package/README.md +35 -224
  10. package/dist/index.d.mts +9 -1
  11. package/dist/index.d.mts.map +1 -1
  12. package/dist/index.mjs +2 -15234
  13. package/dist/plugin-BSop9f9z.mjs +21471 -0
  14. package/dist/plugin-BSop9f9z.mjs.map +1 -0
  15. package/dist/plugin.d.mts +7 -0
  16. package/dist/plugin.d.mts.map +1 -0
  17. package/dist/plugin.mjs +2 -0
  18. package/docs/sdk-architecture/dotnet.md +5 -5
  19. package/oagen.config.ts +5 -373
  20. package/package.json +10 -34
  21. package/src/dotnet/index.ts +6 -4
  22. package/src/dotnet/models.ts +58 -82
  23. package/src/dotnet/naming.ts +44 -6
  24. package/src/dotnet/resources.ts +350 -29
  25. package/src/dotnet/tests.ts +44 -24
  26. package/src/dotnet/type-map.ts +44 -17
  27. package/src/dotnet/wrappers.ts +21 -10
  28. package/src/go/client.ts +35 -3
  29. package/src/go/enums.ts +4 -0
  30. package/src/go/index.ts +10 -5
  31. package/src/go/models.ts +6 -1
  32. package/src/go/resources.ts +534 -73
  33. package/src/go/tests.ts +39 -3
  34. package/src/go/type-map.ts +8 -3
  35. package/src/go/wrappers.ts +79 -21
  36. package/src/index.ts +14 -0
  37. package/src/kotlin/client.ts +7 -2
  38. package/src/kotlin/enums.ts +30 -3
  39. package/src/kotlin/models.ts +97 -6
  40. package/src/kotlin/naming.ts +7 -1
  41. package/src/kotlin/resources.ts +370 -39
  42. package/src/kotlin/tests.ts +120 -6
  43. package/src/node/client.ts +38 -11
  44. package/src/node/field-plan.ts +12 -14
  45. package/src/node/fixtures.ts +39 -3
  46. package/src/node/models.ts +281 -37
  47. package/src/node/resources.ts +156 -52
  48. package/src/node/tests.ts +76 -27
  49. package/src/node/type-map.ts +1 -31
  50. package/src/node/utils.ts +96 -6
  51. package/src/node/wrappers.ts +31 -1
  52. package/src/php/models.ts +0 -33
  53. package/src/php/resources.ts +199 -18
  54. package/src/php/tests.ts +26 -2
  55. package/src/php/type-map.ts +16 -2
  56. package/src/php/wrappers.ts +6 -2
  57. package/src/plugin.ts +50 -0
  58. package/src/python/client.ts +13 -3
  59. package/src/python/enums.ts +28 -3
  60. package/src/python/index.ts +35 -27
  61. package/src/python/models.ts +138 -1
  62. package/src/python/resources.ts +234 -17
  63. package/src/python/tests.ts +260 -16
  64. package/src/python/type-map.ts +16 -2
  65. package/src/ruby/client.ts +238 -0
  66. package/src/ruby/enums.ts +149 -0
  67. package/src/ruby/index.ts +93 -0
  68. package/src/ruby/manifest.ts +35 -0
  69. package/src/ruby/models.ts +360 -0
  70. package/src/ruby/naming.ts +187 -0
  71. package/src/ruby/rbi.ts +313 -0
  72. package/src/ruby/resources.ts +799 -0
  73. package/src/ruby/tests.ts +459 -0
  74. package/src/ruby/type-map.ts +97 -0
  75. package/src/ruby/wrappers.ts +161 -0
  76. package/src/shared/model-utils.ts +131 -7
  77. package/src/shared/naming-utils.ts +36 -0
  78. package/src/shared/non-spec-services.ts +13 -0
  79. package/src/shared/resolved-ops.ts +75 -1
  80. package/test/dotnet/client.test.ts +2 -2
  81. package/test/dotnet/models.test.ts +7 -9
  82. package/test/dotnet/resources.test.ts +135 -3
  83. package/test/dotnet/tests.test.ts +5 -5
  84. package/test/entrypoint.test.ts +89 -0
  85. package/test/go/client.test.ts +6 -6
  86. package/test/go/resources.test.ts +156 -7
  87. package/test/kotlin/models.test.ts +1 -1
  88. package/test/kotlin/resources.test.ts +210 -0
  89. package/test/node/models.test.ts +134 -1
  90. package/test/node/resources.test.ts +134 -26
  91. package/test/node/utils.test.ts +140 -0
  92. package/test/php/models.test.ts +5 -4
  93. package/test/php/resources.test.ts +66 -1
  94. package/test/plugin.test.ts +50 -0
  95. package/test/python/client.test.ts +56 -0
  96. package/test/python/models.test.ts +99 -0
  97. package/test/python/resources.test.ts +294 -0
  98. package/test/python/tests.test.ts +91 -0
  99. package/test/ruby/client.test.ts +81 -0
  100. package/test/ruby/resources.test.ts +386 -0
  101. package/test/shared/resolved-ops.test.ts +122 -0
  102. package/tsdown.config.ts +1 -1
  103. package/dist/index.mjs.map +0 -1
  104. package/scripts/generate-php.js +0 -13
  105. package/scripts/git-push-with-published-oagen.sh +0 -21
@@ -0,0 +1,386 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import type { EmitterContext, ApiSpec, Service, Operation, Model, ResolvedOperation } from '@workos/oagen';
3
+ import { defaultSdkBehavior, toSnakeCase, toPascalCase } from '@workos/oagen';
4
+ import { generateResources } from '../../src/ruby/resources.js';
5
+
6
+ function makeSpec(services: Service[], models: Model[] = []): ApiSpec {
7
+ return {
8
+ name: 'Test',
9
+ version: '1.0.0',
10
+ baseUrl: '',
11
+ services,
12
+ models,
13
+ enums: [],
14
+ sdk: defaultSdkBehavior(),
15
+ };
16
+ }
17
+
18
+ /** Build resolvedOperations from services so groupByMount works. */
19
+ function buildResolvedOps(services: Service[]): ResolvedOperation[] {
20
+ const ops: ResolvedOperation[] = [];
21
+ for (const service of services) {
22
+ const mountOn = toPascalCase(service.name);
23
+ for (const op of service.operations) {
24
+ ops.push({
25
+ operation: op,
26
+ service,
27
+ methodName: toSnakeCase(op.name),
28
+ mountOn,
29
+ defaults: {},
30
+ inferFromClient: [],
31
+ urlBuilder: false,
32
+ });
33
+ }
34
+ }
35
+ return ops;
36
+ }
37
+
38
+ function makeCtx(spec: ApiSpec): EmitterContext {
39
+ return {
40
+ namespace: 'workos',
41
+ namespacePascal: 'WorkOS',
42
+ spec,
43
+ resolvedOperations: buildResolvedOps(spec.services),
44
+ };
45
+ }
46
+
47
+ function makeOp(overrides: Partial<Operation>): Operation {
48
+ return {
49
+ name: 'listOrganizations',
50
+ httpMethod: 'get',
51
+ path: '/organizations',
52
+ pathParams: [],
53
+ queryParams: [],
54
+ headerParams: [],
55
+ requestBody: undefined,
56
+ response: { kind: 'model', name: 'Organization' },
57
+ errors: [],
58
+ injectIdempotencyKey: false,
59
+ ...overrides,
60
+ };
61
+ }
62
+
63
+ describe('ruby/resources', () => {
64
+ it('returns empty for no services', () => {
65
+ const spec = makeSpec([]);
66
+ expect(generateResources([], makeCtx(spec))).toEqual([]);
67
+ });
68
+
69
+ // ── P0-1: request_options forwarding ────────────────────────────────────
70
+
71
+ it('forwards request_options to the unified request helper', () => {
72
+ const services: Service[] = [
73
+ {
74
+ name: 'Organizations',
75
+ operations: [
76
+ makeOp({
77
+ name: 'getOrganization',
78
+ httpMethod: 'get',
79
+ path: '/organizations/{id}',
80
+ pathParams: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
81
+ }),
82
+ ],
83
+ },
84
+ ];
85
+ const spec = makeSpec(services);
86
+ const files = generateResources(services, makeCtx(spec));
87
+ const content = files[0].content;
88
+
89
+ // Uses the unified @client.request helper
90
+ expect(content).toContain('@client.request(');
91
+ expect(content).toContain('method: :get');
92
+ expect(content).toContain('request_options: request_options');
93
+ // Should NOT use the two-layer execute_request(X_request(...)) pattern
94
+ expect(content).not.toContain('execute_request(');
95
+ expect(content).not.toContain('get_request(');
96
+ });
97
+
98
+ it('forwards request_options in POST methods', () => {
99
+ const services: Service[] = [
100
+ {
101
+ name: 'Organizations',
102
+ operations: [
103
+ makeOp({
104
+ name: 'createOrganization',
105
+ httpMethod: 'post',
106
+ path: '/organizations',
107
+ requestBody: { kind: 'model', name: 'CreateOrganizationRequest' },
108
+ }),
109
+ ],
110
+ },
111
+ ];
112
+ const spec = makeSpec(services, [
113
+ {
114
+ name: 'CreateOrganizationRequest',
115
+ fields: [{ name: 'name', type: { kind: 'primitive', type: 'string' }, required: true }],
116
+ },
117
+ ]);
118
+ const files = generateResources(services, makeCtx(spec));
119
+ const content = files[0].content;
120
+
121
+ expect(content).toContain('@client.request(');
122
+ expect(content).toContain('method: :post');
123
+ expect(content).toContain('body: body');
124
+ expect(content).toContain('request_options: request_options');
125
+ });
126
+
127
+ // ── P0-2: pagination cursor direction ──────────────────────────────────
128
+
129
+ it('uses after cursor for fetch_next lambda (not before)', () => {
130
+ const listModel: Model = {
131
+ name: 'OrganizationList',
132
+ fields: [
133
+ {
134
+ name: 'data',
135
+ type: { kind: 'array', items: { kind: 'model', name: 'Organization' } },
136
+ required: true,
137
+ },
138
+ {
139
+ name: 'list_metadata',
140
+ type: { kind: 'model', name: 'ListMetadata' },
141
+ required: true,
142
+ },
143
+ ],
144
+ };
145
+ const services: Service[] = [
146
+ {
147
+ name: 'Organizations',
148
+ operations: [
149
+ makeOp({
150
+ name: 'listOrganizations',
151
+ httpMethod: 'get',
152
+ path: '/organizations',
153
+ queryParams: [
154
+ { name: 'before', type: { kind: 'primitive', type: 'string' }, required: false },
155
+ { name: 'after', type: { kind: 'primitive', type: 'string' }, required: false },
156
+ { name: 'limit', type: { kind: 'primitive', type: 'integer' }, required: false },
157
+ ],
158
+ response: { kind: 'model', name: 'OrganizationList' },
159
+ pagination: {
160
+ strategy: 'cursor',
161
+ param: 'before',
162
+ dataPath: 'data',
163
+ itemType: { kind: 'model', name: 'Organization' },
164
+ },
165
+ }),
166
+ ],
167
+ },
168
+ ];
169
+ const spec = makeSpec(services, [
170
+ listModel,
171
+ {
172
+ name: 'ListMetadata',
173
+ fields: [
174
+ { name: 'before', type: { kind: 'nullable', inner: { kind: 'primitive', type: 'string' } }, required: false },
175
+ { name: 'after', type: { kind: 'nullable', inner: { kind: 'primitive', type: 'string' } }, required: false },
176
+ ],
177
+ },
178
+ ]);
179
+ const files = generateResources(services, makeCtx(spec));
180
+ const content = files[0].content;
181
+
182
+ // fetch_next lambda receives cursor string; the recursive call passes after: cursor
183
+ expect(content).toContain('after: cursor');
184
+ // Must NOT pass before: cursor in the recursive call
185
+ expect(content).not.toMatch(/before: cursor/);
186
+ // Should use ListStruct.from_response
187
+ expect(content).toContain('ListStruct.from_response(');
188
+ });
189
+
190
+ // ── P0-3: paginated response shape detection ──────────────────────────
191
+
192
+ it('generates ListStruct for paginated endpoints with array response type', () => {
193
+ const services: Service[] = [
194
+ {
195
+ name: 'UserManagement',
196
+ operations: [
197
+ makeOp({
198
+ name: 'listSessions',
199
+ httpMethod: 'get',
200
+ path: '/user_management/users/{id}/sessions',
201
+ pathParams: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
202
+ queryParams: [
203
+ { name: 'before', type: { kind: 'primitive', type: 'string' }, required: false },
204
+ { name: 'after', type: { kind: 'primitive', type: 'string' }, required: false },
205
+ { name: 'limit', type: { kind: 'primitive', type: 'integer' }, required: false },
206
+ ],
207
+ // Response is typed as array in IR, but endpoint is actually paginated
208
+ response: { kind: 'array', items: { kind: 'model', name: 'Session' } },
209
+ pagination: {
210
+ strategy: 'cursor',
211
+ param: 'after',
212
+ dataPath: 'data',
213
+ itemType: { kind: 'model', name: 'Session' },
214
+ },
215
+ }),
216
+ ],
217
+ },
218
+ ];
219
+ const spec = makeSpec(services);
220
+ const files = generateResources(services, makeCtx(spec));
221
+ const content = files[0].content;
222
+
223
+ // Should generate ListStruct.from_response, not bare array mapping
224
+ expect(content).toContain('ListStruct.from_response(');
225
+ // Should NOT be treating response as bare array
226
+ expect(content).not.toContain('(parsed || []).map');
227
+ });
228
+
229
+ it('preserves bare array handling for non-paginated array endpoints', () => {
230
+ const services: Service[] = [
231
+ {
232
+ name: 'UserManagement',
233
+ operations: [
234
+ makeOp({
235
+ name: 'getUserIdentities',
236
+ httpMethod: 'get',
237
+ path: '/user_management/users/{id}/identities',
238
+ pathParams: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
239
+ // Array response, no pagination
240
+ response: { kind: 'array', items: { kind: 'model', name: 'Identity' } },
241
+ }),
242
+ ],
243
+ },
244
+ ];
245
+ const spec = makeSpec(services, [
246
+ { name: 'Identity', fields: [{ name: 'type', type: { kind: 'primitive', type: 'string' }, required: true }] },
247
+ ]);
248
+ const files = generateResources(services, makeCtx(spec));
249
+ const content = files[0].content;
250
+
251
+ // Should be bare array mapping (no pagination)
252
+ expect(content).toContain('(parsed || []).map');
253
+ expect(content).not.toContain('ListStruct');
254
+ });
255
+
256
+ // ── P0-4: DELETE with body ─────────────────────────────────────────────
257
+
258
+ it('generates body for DELETE endpoints with requestBody', () => {
259
+ const services: Service[] = [
260
+ {
261
+ name: 'Authorization',
262
+ operations: [
263
+ makeOp({
264
+ name: 'removeRole',
265
+ httpMethod: 'delete',
266
+ path: '/authorization/memberships/{id}/role_assignments',
267
+ pathParams: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
268
+ requestBody: { kind: 'model', name: 'RemoveRoleRequest' },
269
+ response: { kind: 'primitive', type: 'unknown' },
270
+ }),
271
+ ],
272
+ },
273
+ ];
274
+ const spec = makeSpec(services, [
275
+ {
276
+ name: 'RemoveRoleRequest',
277
+ fields: [
278
+ { name: 'role_slug', type: { kind: 'primitive', type: 'string' }, required: true },
279
+ { name: 'resource_id', type: { kind: 'primitive', type: 'string' }, required: false },
280
+ ],
281
+ },
282
+ ]);
283
+ const files = generateResources(services, makeCtx(spec));
284
+ const content = files[0].content;
285
+
286
+ // Should construct a body hash
287
+ expect(content).toContain('body = {');
288
+ expect(content).toContain("'role_slug' => role_slug");
289
+ // Should pass body to the request helper
290
+ expect(content).toContain('@client.request(');
291
+ expect(content).toContain('body: body');
292
+ });
293
+
294
+ it('does not generate body for DELETE without requestBody', () => {
295
+ const services: Service[] = [
296
+ {
297
+ name: 'Items',
298
+ operations: [
299
+ makeOp({
300
+ name: 'deleteItem',
301
+ httpMethod: 'delete',
302
+ path: '/items/{id}',
303
+ pathParams: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
304
+ response: { kind: 'primitive', type: 'unknown' },
305
+ }),
306
+ ],
307
+ },
308
+ ];
309
+ const spec = makeSpec(services);
310
+ const files = generateResources(services, makeCtx(spec));
311
+ const content = files[0].content;
312
+
313
+ // No body construction
314
+ expect(content).not.toContain('body = {');
315
+ expect(content).toContain('method: :delete');
316
+ });
317
+
318
+ // ── P0-5: path/body parameter name collision ───────────────────────────
319
+
320
+ it('disambiguates colliding path and body parameter names', () => {
321
+ const services: Service[] = [
322
+ {
323
+ name: 'Authorization',
324
+ operations: [
325
+ makeOp({
326
+ name: 'createRolePermission',
327
+ httpMethod: 'post',
328
+ path: '/authorization/roles/{slug}/permissions',
329
+ pathParams: [{ name: 'slug', type: { kind: 'primitive', type: 'string' }, required: true }],
330
+ requestBody: { kind: 'model', name: 'CreateRolePermissionRequest' },
331
+ }),
332
+ ],
333
+ },
334
+ ];
335
+ const spec = makeSpec(services, [
336
+ {
337
+ name: 'CreateRolePermissionRequest',
338
+ fields: [{ name: 'slug', type: { kind: 'primitive', type: 'string' }, required: true }],
339
+ },
340
+ ]);
341
+ const files = generateResources(services, makeCtx(spec));
342
+ const content = files[0].content;
343
+
344
+ // Should have both slug: (path) and body_slug: (body) in signature
345
+ expect(content).toContain('slug:');
346
+ expect(content).toContain('body_slug:');
347
+
348
+ // Path interpolation uses slug (the path param) with Util.encode_path
349
+ expect(content).toContain('WorkOS::Util.encode_path(slug)');
350
+
351
+ // Body hash uses body_slug for the wire name "slug"
352
+ expect(content).toContain("'slug' => body_slug");
353
+
354
+ // YARD doc should mention both params
355
+ expect(content).toContain('@param slug');
356
+ expect(content).toContain('@param body_slug');
357
+ });
358
+
359
+ it('does not rename body fields that do not collide with path params', () => {
360
+ const services: Service[] = [
361
+ {
362
+ name: 'Organizations',
363
+ operations: [
364
+ makeOp({
365
+ name: 'createOrganization',
366
+ httpMethod: 'post',
367
+ path: '/organizations',
368
+ requestBody: { kind: 'model', name: 'CreateOrganizationRequest' },
369
+ }),
370
+ ],
371
+ },
372
+ ];
373
+ const spec = makeSpec(services, [
374
+ {
375
+ name: 'CreateOrganizationRequest',
376
+ fields: [{ name: 'name', type: { kind: 'primitive', type: 'string' }, required: true }],
377
+ },
378
+ ]);
379
+ const files = generateResources(services, makeCtx(spec));
380
+ const content = files[0].content;
381
+
382
+ // name: should be used directly (no body_ prefix)
383
+ expect(content).toContain('name:');
384
+ expect(content).not.toContain('body_name');
385
+ });
386
+ });
@@ -0,0 +1,122 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { EmitterContext, ResolvedOperation, Model, Operation } from '@workos/oagen';
3
+ import {
4
+ assertUniqueResolvedMethods,
5
+ buildResolvedLookup,
6
+ collectBodyFieldTypes,
7
+ } from '../../src/shared/resolved-ops.js';
8
+
9
+ function makeResolvedOperation(
10
+ httpMethod: string,
11
+ path: string,
12
+ methodName: string,
13
+ mountOn = 'Authorization',
14
+ ): ResolvedOperation {
15
+ return {
16
+ operation: {
17
+ name: methodName,
18
+ httpMethod: httpMethod as any,
19
+ path,
20
+ pathParams: [],
21
+ queryParams: [],
22
+ headerParams: [],
23
+ response: { kind: 'primitive', type: 'string' },
24
+ errors: [],
25
+ injectIdempotencyKey: false,
26
+ },
27
+ service: {
28
+ name: mountOn,
29
+ operations: [],
30
+ },
31
+ methodName,
32
+ mountOn,
33
+ defaults: {},
34
+ inferFromClient: [],
35
+ urlBuilder: false,
36
+ } as unknown as ResolvedOperation;
37
+ }
38
+
39
+ function makeCtx(resolvedOperations: ResolvedOperation[]): EmitterContext {
40
+ return {
41
+ namespace: 'workos',
42
+ namespacePascal: 'WorkOS',
43
+ spec: {
44
+ name: 'Test',
45
+ version: '1.0.0',
46
+ baseUrl: 'https://api.example.com',
47
+ services: [],
48
+ models: [],
49
+ enums: [],
50
+ sdk: {} as any,
51
+ },
52
+ resolvedOperations,
53
+ } as EmitterContext;
54
+ }
55
+
56
+ describe('shared/resolved-ops', () => {
57
+ it('allows duplicate method names when they target the same path', () => {
58
+ const ctx = makeCtx([
59
+ makeResolvedOperation('put', '/organizations/{id}', 'update_organization'),
60
+ makeResolvedOperation('patch', '/organizations/{id}', 'update_organization'),
61
+ ]);
62
+
63
+ expect(() => assertUniqueResolvedMethods(ctx)).not.toThrow();
64
+ expect(buildResolvedLookup(ctx).size).toBe(2);
65
+ });
66
+
67
+ it('throws when two different paths in the same mount resolve to the same method', () => {
68
+ const ctx = makeCtx([
69
+ makeResolvedOperation(
70
+ 'get',
71
+ '/authorization/organization_memberships/{organization_membership_id}/resources/{resource_id}/permissions',
72
+ 'list_resource_permissions',
73
+ ),
74
+ makeResolvedOperation(
75
+ 'get',
76
+ '/authorization/organization_memberships/{organization_membership_id}/resources/{resource_type_slug}/{external_id}/permissions',
77
+ 'list_resource_permissions',
78
+ ),
79
+ ]);
80
+
81
+ expect(() => assertUniqueResolvedMethods(ctx)).toThrow(
82
+ /Resolved operation name collision for Authorization\.list_resource_permissions/,
83
+ );
84
+ });
85
+
86
+ it('collects body field types for grouped body params', () => {
87
+ const op: Operation = {
88
+ name: 'createOrganizationMembership',
89
+ httpMethod: 'post',
90
+ path: '/user_management/organization_memberships',
91
+ pathParams: [],
92
+ queryParams: [],
93
+ headerParams: [],
94
+ requestBody: { kind: 'model', name: 'CreateOrganizationMembershipRequest' },
95
+ response: { kind: 'primitive', type: 'string' },
96
+ errors: [],
97
+ injectIdempotencyKey: false,
98
+ };
99
+
100
+ const models: Model[] = [
101
+ {
102
+ name: 'CreateOrganizationMembershipRequest',
103
+ fields: [
104
+ { name: 'role_slug', type: { kind: 'primitive', type: 'string' }, required: false },
105
+ {
106
+ name: 'role_slugs',
107
+ type: { kind: 'array', items: { kind: 'primitive', type: 'string' } },
108
+ required: false,
109
+ },
110
+ ],
111
+ },
112
+ ];
113
+
114
+ const fieldTypes = collectBodyFieldTypes(op, models);
115
+
116
+ expect(fieldTypes.get('role_slug')).toEqual({ kind: 'primitive', type: 'string' });
117
+ expect(fieldTypes.get('role_slugs')).toEqual({
118
+ kind: 'array',
119
+ items: { kind: 'primitive', type: 'string' },
120
+ });
121
+ });
122
+ });
package/tsdown.config.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { defineConfig } from 'tsdown';
2
2
  export default defineConfig({
3
- entry: { index: 'src/index.ts' },
3
+ entry: { index: 'src/index.ts', plugin: 'src/plugin.ts' },
4
4
  format: ['esm'],
5
5
  dts: true,
6
6
  clean: true,