@workos/oagen-emitters 0.2.1 → 0.4.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 (136) hide show
  1. package/.husky/pre-commit +1 -0
  2. package/.release-please-manifest.json +1 -1
  3. package/CHANGELOG.md +15 -0
  4. package/README.md +129 -0
  5. package/dist/index.d.mts +13 -1
  6. package/dist/index.d.mts.map +1 -1
  7. package/dist/index.mjs +14549 -3385
  8. package/dist/index.mjs.map +1 -1
  9. package/docs/sdk-architecture/dotnet.md +336 -0
  10. package/docs/sdk-architecture/go.md +338 -0
  11. package/docs/sdk-architecture/php.md +315 -0
  12. package/docs/sdk-architecture/python.md +511 -0
  13. package/oagen.config.ts +328 -2
  14. package/package.json +9 -5
  15. package/scripts/generate-php.js +13 -0
  16. package/scripts/git-push-with-published-oagen.sh +21 -0
  17. package/smoke/sdk-dotnet.ts +45 -12
  18. package/smoke/sdk-go.ts +116 -42
  19. package/smoke/sdk-php.ts +28 -26
  20. package/smoke/sdk-python.ts +5 -2
  21. package/src/dotnet/client.ts +89 -0
  22. package/src/dotnet/enums.ts +323 -0
  23. package/src/dotnet/fixtures.ts +236 -0
  24. package/src/dotnet/index.ts +246 -0
  25. package/src/dotnet/manifest.ts +36 -0
  26. package/src/dotnet/models.ts +344 -0
  27. package/src/dotnet/naming.ts +330 -0
  28. package/src/dotnet/resources.ts +622 -0
  29. package/src/dotnet/tests.ts +693 -0
  30. package/src/dotnet/type-map.ts +201 -0
  31. package/src/dotnet/wrappers.ts +186 -0
  32. package/src/go/client.ts +141 -0
  33. package/src/go/enums.ts +196 -0
  34. package/src/go/fixtures.ts +212 -0
  35. package/src/go/index.ts +84 -0
  36. package/src/go/manifest.ts +36 -0
  37. package/src/go/models.ts +254 -0
  38. package/src/go/naming.ts +179 -0
  39. package/src/go/resources.ts +827 -0
  40. package/src/go/tests.ts +751 -0
  41. package/src/go/type-map.ts +82 -0
  42. package/src/go/wrappers.ts +261 -0
  43. package/src/index.ts +4 -0
  44. package/src/kotlin/client.ts +53 -0
  45. package/src/kotlin/enums.ts +162 -0
  46. package/src/kotlin/index.ts +92 -0
  47. package/src/kotlin/manifest.ts +55 -0
  48. package/src/kotlin/models.ts +395 -0
  49. package/src/kotlin/naming.ts +223 -0
  50. package/src/kotlin/overrides.ts +25 -0
  51. package/src/kotlin/resources.ts +667 -0
  52. package/src/kotlin/tests.ts +1019 -0
  53. package/src/kotlin/type-map.ts +123 -0
  54. package/src/kotlin/wrappers.ts +168 -0
  55. package/src/node/client.ts +128 -115
  56. package/src/node/enums.ts +9 -0
  57. package/src/node/errors.ts +37 -232
  58. package/src/node/field-plan.ts +726 -0
  59. package/src/node/fixtures.ts +9 -1
  60. package/src/node/index.ts +3 -9
  61. package/src/node/models.ts +178 -21
  62. package/src/node/naming.ts +49 -111
  63. package/src/node/resources.ts +527 -397
  64. package/src/node/sdk-errors.ts +41 -0
  65. package/src/node/tests.ts +69 -19
  66. package/src/node/type-map.ts +4 -2
  67. package/src/node/utils.ts +13 -71
  68. package/src/node/wrappers.ts +151 -0
  69. package/src/php/client.ts +179 -0
  70. package/src/php/enums.ts +67 -0
  71. package/src/php/errors.ts +9 -0
  72. package/src/php/fixtures.ts +181 -0
  73. package/src/php/index.ts +96 -0
  74. package/src/php/manifest.ts +36 -0
  75. package/src/php/models.ts +310 -0
  76. package/src/php/naming.ts +279 -0
  77. package/src/php/resources.ts +636 -0
  78. package/src/php/tests.ts +609 -0
  79. package/src/php/type-map.ts +90 -0
  80. package/src/php/utils.ts +18 -0
  81. package/src/php/wrappers.ts +152 -0
  82. package/src/python/client.ts +345 -0
  83. package/src/python/enums.ts +313 -0
  84. package/src/python/fixtures.ts +196 -0
  85. package/src/python/index.ts +95 -0
  86. package/src/python/manifest.ts +38 -0
  87. package/src/python/models.ts +688 -0
  88. package/src/python/naming.ts +189 -0
  89. package/src/python/resources.ts +1322 -0
  90. package/src/python/tests.ts +1335 -0
  91. package/src/python/type-map.ts +93 -0
  92. package/src/python/wrappers.ts +191 -0
  93. package/src/shared/model-utils.ts +472 -0
  94. package/src/shared/naming-utils.ts +154 -0
  95. package/src/shared/non-spec-services.ts +54 -0
  96. package/src/shared/resolved-ops.ts +109 -0
  97. package/src/shared/wrapper-utils.ts +70 -0
  98. package/test/dotnet/client.test.ts +121 -0
  99. package/test/dotnet/enums.test.ts +193 -0
  100. package/test/dotnet/errors.test.ts +9 -0
  101. package/test/dotnet/manifest.test.ts +82 -0
  102. package/test/dotnet/models.test.ts +260 -0
  103. package/test/dotnet/resources.test.ts +255 -0
  104. package/test/dotnet/tests.test.ts +202 -0
  105. package/test/go/client.test.ts +92 -0
  106. package/test/go/enums.test.ts +132 -0
  107. package/test/go/errors.test.ts +9 -0
  108. package/test/go/models.test.ts +265 -0
  109. package/test/go/resources.test.ts +408 -0
  110. package/test/go/tests.test.ts +143 -0
  111. package/test/kotlin/models.test.ts +135 -0
  112. package/test/kotlin/tests.test.ts +176 -0
  113. package/test/node/client.test.ts +92 -12
  114. package/test/node/enums.test.ts +2 -0
  115. package/test/node/errors.test.ts +2 -41
  116. package/test/node/models.test.ts +2 -0
  117. package/test/node/naming.test.ts +23 -0
  118. package/test/node/resources.test.ts +315 -84
  119. package/test/node/serializers.test.ts +3 -1
  120. package/test/node/type-map.test.ts +11 -0
  121. package/test/php/client.test.ts +95 -0
  122. package/test/php/enums.test.ts +173 -0
  123. package/test/php/errors.test.ts +9 -0
  124. package/test/php/models.test.ts +497 -0
  125. package/test/php/resources.test.ts +682 -0
  126. package/test/php/tests.test.ts +185 -0
  127. package/test/python/client.test.ts +200 -0
  128. package/test/python/enums.test.ts +228 -0
  129. package/test/python/errors.test.ts +16 -0
  130. package/test/python/manifest.test.ts +74 -0
  131. package/test/python/models.test.ts +716 -0
  132. package/test/python/resources.test.ts +617 -0
  133. package/test/python/tests.test.ts +202 -0
  134. package/src/node/common.ts +0 -273
  135. package/src/node/config.ts +0 -71
  136. package/src/node/serializers.ts +0 -746
package/oagen.config.ts CHANGED
@@ -1,6 +1,11 @@
1
- import type { OagenConfig } from '@workos/oagen';
1
+ import type { OagenConfig, OperationHint } from '@workos/oagen';
2
2
  import { toCamelCase } from '@workos/oagen';
3
3
  import { nodeEmitter } from './src/node/index.js';
4
+ import { pythonEmitter } from './src/python/index.js';
5
+ import { phpEmitter } from './src/php/index.js';
6
+ import { goEmitter } from './src/go/index.js';
7
+ import { dotnetEmitter } from './src/dotnet/index.js';
8
+ import { kotlinEmitter } from './src/kotlin/index.js';
4
9
  import { nodeExtractor } from './src/compat/extractors/node.js';
5
10
  import { rubyExtractor } from './src/compat/extractors/ruby.js';
6
11
  import { pythonExtractor } from './src/compat/extractors/python.js';
@@ -21,8 +26,310 @@ function nestjsOperationIdTransform(id: string): string {
21
26
  return idx !== -1 ? toCamelCase(stripped.slice(idx + 1)) : toCamelCase(stripped);
22
27
  }
23
28
 
29
+ // ---------------------------------------------------------------------------
30
+ // Operation hints — per-operation overrides for the operation resolver.
31
+ // Keyed by "METHOD /path". Only operations that need overrides are listed;
32
+ // the algorithm handles the rest.
33
+ // ---------------------------------------------------------------------------
34
+ const operationHints: Record<string, OperationHint> = {
35
+ // ── Radar ────────────────────────────────────────────────────────────────
36
+ 'POST /radar/lists/{type}/{action}': { name: 'add_list_entry' },
37
+ 'DELETE /radar/lists/{type}/{action}': { name: 'remove_list_entry' },
38
+
39
+ // ── SSO ──────────────────────────────────────────────────────────────────
40
+ 'GET /sso/authorize': {
41
+ name: 'get_authorization_url',
42
+ defaults: { response_type: 'code' },
43
+ inferFromClient: ['client_id'],
44
+ urlBuilder: true,
45
+ },
46
+ 'GET /sso/logout': { name: 'get_logout_url', urlBuilder: true },
47
+ 'GET /sso/profile': { name: 'get_profile' },
48
+ 'POST /sso/token': {
49
+ name: 'get_profile_and_token',
50
+ defaults: { grant_type: 'authorization_code' },
51
+ inferFromClient: ['client_id', 'client_secret'],
52
+ },
53
+
54
+ // ── SSO / JWKS (mounted on UserManagement via mountRules) ────────────────
55
+ 'GET /sso/jwks/{clientId}': { name: 'get_jwks' },
56
+
57
+ // ── User Management — auth ──────────────────────────────────────────────
58
+ 'GET /user_management/authorize': {
59
+ name: 'get_authorization_url',
60
+ defaults: { response_type: 'code' },
61
+ inferFromClient: ['client_id'],
62
+ urlBuilder: true,
63
+ },
64
+ 'GET /user_management/sessions/logout': { name: 'get_logout_url', urlBuilder: true },
65
+
66
+ // ── User Management — org membership actions ────────────────────────────
67
+ 'PUT /user_management/organization_memberships/{id}/deactivate': {
68
+ name: 'deactivate_organization_membership',
69
+ },
70
+ 'PUT /user_management/organization_memberships/{id}/reactivate': {
71
+ name: 'reactivate_organization_membership',
72
+ },
73
+
74
+ // ── Admin Portal ────────────────────────────────────────────────────────
75
+ 'POST /portal/generate_link': { name: 'generate_link' },
76
+
77
+ // ── Feature Flags — disambiguate co-mounted list operations ─────────────
78
+ 'GET /organizations/{organizationId}/feature-flags': { name: 'list_organization_feature_flags' },
79
+ 'GET /user_management/users/{userId}/feature-flags': { name: 'list_user_feature_flags' },
80
+
81
+ // ── External ID lookups (not derivable from path) ──────────────────────
82
+ 'GET /organizations/external_id/{external_id}': { name: 'get_organization_by_external_id' },
83
+ 'GET /user_management/users/external_id/{external_id}': { name: 'get_user_by_external_id' },
84
+
85
+ // ── Authorization — environment-scoped roles ─────────────────────────────
86
+ 'GET /authorization/roles': { name: 'list_environment_roles' },
87
+ 'POST /authorization/roles': { name: 'create_environment_role' },
88
+ 'GET /authorization/roles/{slug}': { name: 'get_environment_role' },
89
+ 'PATCH /authorization/roles/{slug}': { name: 'update_environment_role' },
90
+ 'PUT /authorization/roles/{slug}/permissions': {
91
+ name: 'set_environment_role_permissions',
92
+ },
93
+ 'POST /authorization/roles/{slug}/permissions': {
94
+ name: 'add_environment_role_permission',
95
+ },
96
+
97
+ // ── Authorization — singularized/shortened names ────────────────────────
98
+ 'POST /authorization/permissions': { name: 'create_permission' },
99
+ 'POST /authorization/resources': { name: 'create_resource' },
100
+ 'POST /authorization/organization_memberships/{organization_membership_id}/check': {
101
+ name: 'check',
102
+ },
103
+ 'POST /authorization/organization_memberships/{organization_membership_id}/role_assignments': {
104
+ name: 'assign_role',
105
+ },
106
+ 'DELETE /authorization/organization_memberships/{organization_membership_id}/role_assignments': {
107
+ name: 'remove_role',
108
+ },
109
+ 'POST /authorization/organizations/{organizationId}/roles': {
110
+ name: 'create_organization_role',
111
+ },
112
+
113
+ // ── Authorization — env-scoped resource memberships ────────────────────
114
+ 'GET /authorization/resources/{resource_id}/organization_memberships': { name: 'list_memberships_for_resource' },
115
+
116
+ // ── User Management — singularized/shortened names ─────────────────────
117
+ 'POST /user_management/users': { name: 'create_user' },
118
+ 'POST /user_management/organization_memberships': {
119
+ name: 'create_organization_membership',
120
+ },
121
+ 'POST /user_management/invitations': { name: 'send_invitation' },
122
+ 'GET /user_management/invitations/by_token/{token}': {
123
+ name: 'find_invitation_by_token',
124
+ },
125
+ 'POST /user_management/users/{id}/email_verification/send': {
126
+ name: 'send_verification_email',
127
+ },
128
+ 'POST /user_management/users/{id}/email_verification/confirm': {
129
+ name: 'verify_email',
130
+ },
131
+ 'POST /user_management/password_reset': { name: 'reset_password' },
132
+ 'POST /user_management/password_reset/confirm': {
133
+ name: 'confirm_password_reset',
134
+ },
135
+ 'GET /user_management/users/{id}/sessions': { name: 'list_sessions' },
136
+ 'GET /user_management/users/{id}/identities': { name: 'get_user_identities' },
137
+ 'POST /user_management/cors_origins': { name: 'create_cors_origin' },
138
+ 'POST /user_management/redirect_uris': { name: 'create_redirect_uri' },
139
+
140
+ // ── Organizations — singularized names ─────────────────────────────────
141
+ 'POST /organizations': { name: 'create_organization' },
142
+
143
+ // ── Directory Sync — shortened names ───────────────────────────────────
144
+ 'GET /directory_groups': { name: 'list_groups' },
145
+ 'GET /directory_groups/{id}': { name: 'get_group' },
146
+ 'GET /directory_users': { name: 'list_users' },
147
+ 'GET /directory_users/{id}': { name: 'get_user' },
148
+
149
+ // ── Audit Logs — singularized names ────────────────────────────────────
150
+ 'POST /audit_logs/events': { name: 'create_event' },
151
+ 'POST /audit_logs/exports': { name: 'create_export' },
152
+ 'POST /audit_logs/actions/{actionName}/schemas': { name: 'create_schema' },
153
+
154
+ // ── Feature Flags — match SDK conventions ──────────────────────────────
155
+ 'POST /feature-flags/{slug}/targets/{resourceId}': { name: 'add_flag_target' },
156
+ 'DELETE /feature-flags/{slug}/targets/{resourceId}': {
157
+ name: 'remove_flag_target',
158
+ },
159
+
160
+ // ── Organizations — audit log config (singular fetch, not a list) ───────
161
+ 'GET /organizations/{id}/audit_log_configuration': {
162
+ name: 'get_audit_log_configuration',
163
+ },
164
+
165
+ // ── Organizations — audit logs retention (mounted on AuditLogs) ─────────
166
+ 'GET /organizations/{id}/audit_logs_retention': {
167
+ name: 'get_organization_audit_logs_retention',
168
+ mountOn: 'AuditLogs',
169
+ },
170
+ 'PUT /organizations/{id}/audit_logs_retention': { mountOn: 'AuditLogs' },
171
+
172
+ // ── Union split: POST /user_management/authenticate (8 variants) ────────
173
+ // Common optional fields appended to every variant's exposedParams so the
174
+ // generated wrappers cover the full spec body shape (fraud/audit context the
175
+ // server consumes when present).
176
+ 'POST /user_management/authenticate': {
177
+ split: [
178
+ {
179
+ name: 'authenticate_with_password',
180
+ targetVariant: 'PasswordSessionAuthenticateRequest',
181
+ defaults: { grant_type: 'password' },
182
+ inferFromClient: ['client_id', 'client_secret'],
183
+ exposedParams: ['email', 'password', 'invitation_token', 'ip_address', 'device_id', 'user_agent'],
184
+ optionalParams: ['invitation_token', 'ip_address', 'device_id', 'user_agent'],
185
+ },
186
+ {
187
+ name: 'authenticate_with_code',
188
+ targetVariant: 'CodeSessionAuthenticateRequest',
189
+ defaults: { grant_type: 'authorization_code' },
190
+ inferFromClient: ['client_id', 'client_secret'],
191
+ exposedParams: ['code', 'ip_address', 'device_id', 'user_agent'],
192
+ optionalParams: ['ip_address', 'device_id', 'user_agent'],
193
+ },
194
+ {
195
+ name: 'authenticate_with_refresh_token',
196
+ targetVariant: 'RefreshTokenSessionAuthenticateRequest',
197
+ defaults: { grant_type: 'refresh_token' },
198
+ inferFromClient: ['client_id', 'client_secret'],
199
+ exposedParams: ['refresh_token', 'organization_id', 'ip_address', 'device_id', 'user_agent'],
200
+ optionalParams: ['organization_id', 'ip_address', 'device_id', 'user_agent'],
201
+ },
202
+ {
203
+ name: 'authenticate_with_magic_auth',
204
+ targetVariant: 'MagicAuthSessionAuthenticateRequest',
205
+ defaults: { grant_type: 'urn:workos:oauth:grant-type:magic-auth:code' },
206
+ inferFromClient: ['client_id', 'client_secret'],
207
+ exposedParams: ['code', 'email', 'invitation_token', 'ip_address', 'device_id', 'user_agent'],
208
+ optionalParams: ['invitation_token', 'ip_address', 'device_id', 'user_agent'],
209
+ },
210
+ {
211
+ name: 'authenticate_with_email_verification',
212
+ targetVariant: 'EmailVerificationSessionAuthenticateRequest',
213
+ defaults: { grant_type: 'urn:workos:oauth:grant-type:email-verification:code' },
214
+ inferFromClient: ['client_id', 'client_secret'],
215
+ exposedParams: ['code', 'pending_authentication_token', 'ip_address', 'device_id', 'user_agent'],
216
+ optionalParams: ['pending_authentication_token', 'ip_address', 'device_id', 'user_agent'],
217
+ },
218
+ {
219
+ name: 'authenticate_with_totp',
220
+ targetVariant: 'TotpSessionAuthenticateRequest',
221
+ defaults: { grant_type: 'urn:workos:oauth:grant-type:mfa-totp' },
222
+ inferFromClient: ['client_id', 'client_secret'],
223
+ exposedParams: [
224
+ 'code',
225
+ 'pending_authentication_token',
226
+ 'authentication_challenge_id',
227
+ 'ip_address',
228
+ 'device_id',
229
+ 'user_agent',
230
+ ],
231
+ optionalParams: ['ip_address', 'device_id', 'user_agent'],
232
+ },
233
+ {
234
+ name: 'authenticate_with_organization_selection',
235
+ targetVariant: 'OrganizationSelectionSessionAuthenticateRequest',
236
+ defaults: { grant_type: 'urn:workos:oauth:grant-type:organization-selection' },
237
+ inferFromClient: ['client_id', 'client_secret'],
238
+ exposedParams: ['pending_authentication_token', 'organization_id', 'ip_address', 'device_id', 'user_agent'],
239
+ optionalParams: ['ip_address', 'device_id', 'user_agent'],
240
+ },
241
+ {
242
+ name: 'authenticate_with_device_code',
243
+ targetVariant: 'DeviceCodeSessionAuthenticateRequest',
244
+ defaults: { grant_type: 'urn:ietf:params:oauth:grant-type:device_code' },
245
+ inferFromClient: ['client_id'],
246
+ exposedParams: ['device_code', 'ip_address', 'device_id', 'user_agent'],
247
+ optionalParams: ['ip_address', 'device_id', 'user_agent'],
248
+ },
249
+ ],
250
+ },
251
+
252
+ // ── Union split: POST /connect/applications (2 variants) ────────────────
253
+ 'POST /connect/applications': {
254
+ split: [
255
+ {
256
+ name: 'create_oauth_application',
257
+ targetVariant: 'CreateOAuthApplication',
258
+ defaults: { application_type: 'oauth' },
259
+ exposedParams: [
260
+ 'name',
261
+ 'is_first_party',
262
+ 'description',
263
+ 'scopes',
264
+ 'redirect_uris',
265
+ 'uses_pkce',
266
+ 'organization_id',
267
+ ],
268
+ },
269
+ {
270
+ name: 'create_m2m_application',
271
+ targetVariant: 'CreateM2MApplication',
272
+ defaults: { application_type: 'm2m' },
273
+ exposedParams: ['name', 'organization_id', 'description', 'scopes'],
274
+ },
275
+ ],
276
+ },
277
+ };
278
+
279
+ // ---------------------------------------------------------------------------
280
+ // Mount rules — service-level remounting. Maps IR service name → target
281
+ // service/namespace (PascalCase). All operations in the source service are
282
+ // mounted on the target unless overridden per-operation in operationHints.
283
+ // ---------------------------------------------------------------------------
284
+ const mountRules: Record<string, string> = {
285
+ // MFA sub-services → MultiFactorAuth
286
+ MultiFactorAuthChallenges: 'MultiFactorAuth',
287
+
288
+ // RBAC permissions → Authorization
289
+ Permissions: 'Authorization',
290
+
291
+ // Connect sub-services → Connect
292
+ WorkosConnect: 'Connect',
293
+ Applications: 'Connect',
294
+ ApplicationClientSecrets: 'Connect',
295
+
296
+ // SSO connections → SSO
297
+ Connections: 'SSO',
298
+
299
+ // Directory Sync sub-services → DirectorySync
300
+ Directories: 'DirectorySync',
301
+ DirectoryGroups: 'DirectorySync',
302
+ DirectoryUsers: 'DirectorySync',
303
+
304
+ // Feature flag sub-services → FeatureFlags
305
+ FeatureFlagsTargets: 'FeatureFlags',
306
+ OrganizationsFeatureFlags: 'FeatureFlags',
307
+ UserManagementUsersFeatureFlags: 'FeatureFlags',
308
+
309
+ // Org API keys → ApiKeys
310
+ OrganizationsApiKeys: 'ApiKeys',
311
+
312
+ // User Management sub-services → UserManagement
313
+ UserManagementSessionTokens: 'UserManagement',
314
+ UserManagementAuthentication: 'UserManagement',
315
+ UserManagementCorsOrigins: 'UserManagement',
316
+ UserManagementUsers: 'UserManagement',
317
+ UserManagementInvitations: 'UserManagement',
318
+ UserManagementJWTTemplate: 'UserManagement',
319
+ UserManagementMagicAuth: 'UserManagement',
320
+ UserManagementOrganizationMembership: 'UserManagement',
321
+ UserManagementRedirectUris: 'UserManagement',
322
+ UserManagementUsersAuthorizedApplications: 'UserManagement',
323
+
324
+ // Pipes / Data Providers → Pipes
325
+ UserManagementDataProviders: 'Pipes',
326
+
327
+ // User Management MFA → MultiFactorAuth
328
+ UserManagementMultiFactorAuthentication: 'MultiFactorAuth',
329
+ };
330
+
24
331
  const config: OagenConfig = {
25
- emitters: [nodeEmitter],
332
+ emitters: [nodeEmitter, pythonEmitter, phpEmitter, goEmitter, dotnetEmitter, kotlinEmitter],
26
333
  extractors: [
27
334
  nodeExtractor,
28
335
  rubyExtractor,
@@ -47,5 +354,24 @@ const config: OagenConfig = {
47
354
  },
48
355
  docUrl: 'https://workos.com/docs',
49
356
  operationIdTransform: nestjsOperationIdTransform,
357
+ schemaNameTransform: (name: string) => {
358
+ // Explicit renames for Dto models that collide with response models
359
+ const COLLISION_RENAMES: Record<string, string> = {
360
+ OrganizationDto: 'OrganizationInput',
361
+ RedirectUriDto: 'RedirectUriInput',
362
+ // Generic list-derived names that need domain-specific identifiers
363
+ ListData: 'Role',
364
+ ListModel: 'RoleList',
365
+ // Double-List naming artifact
366
+ EventListListMetadata: 'EventListMetadata',
367
+ };
368
+ if (COLLISION_RENAMES[name]) return COLLISION_RENAMES[name];
369
+ return name
370
+ .replace(/Dto/g, '')
371
+ .replace(/DTO/g, '')
372
+ .replace(/^Urn(?:IetfParams|Workos)O[Aa]uthGrantType/, '');
373
+ },
374
+ operationHints,
375
+ mountRules,
50
376
  };
51
377
  export default config;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workos/oagen-emitters",
3
- "version": "0.2.1",
3
+ "version": "0.4.0",
4
4
  "description": "WorkOS' oagen emitters",
5
5
  "license": "MIT",
6
6
  "author": "WorkOS",
@@ -28,6 +28,10 @@
28
28
  "test": "vitest run",
29
29
  "test:watch": "vitest",
30
30
  "typecheck": "tsc --noEmit",
31
+ "git:push": "sh ./scripts/git-push-with-published-oagen.sh",
32
+ "oagen:build:local": "npm --prefix ../oagen run build",
33
+ "oagen:use:local": "npm run oagen:build:local && npm link ../oagen",
34
+ "oagen:use:published": "sh -c 'npm unlink @workos/oagen >/dev/null 2>&1 || true; npm install --ignore-scripts @workos/oagen@^0.4.0'",
31
35
  "prepare": "husky",
32
36
  "sdk:generate:dotnet": "oagen generate --lang dotnet --output ./sdk-dotnet --namespace workos --api-surface ./sdk-dotnet-surface.json",
33
37
  "sdk:verify:dotnet": "oagen verify --lang dotnet --output ./sdk-dotnet --api-surface ./sdk-dotnet-surface.json",
@@ -44,7 +48,7 @@
44
48
  "sdk:generate:node": "oagen generate --lang node --output ./sdk-node --namespace workos --api-surface ./sdk-node-surface.json",
45
49
  "sdk:verify:node": "oagen verify --lang node --output ./sdk-node --api-surface ./sdk-node-surface.json",
46
50
  "sdk:extract:node": "oagen extract --lang node --sdk-path ../backend/workos-node --output ./sdk-node-surface.json",
47
- "sdk:generate:php": "oagen generate --lang php --output ./sdk-php --namespace workos --api-surface ./sdk-php-surface.json",
51
+ "sdk:generate:php": "node scripts/generate-php.js",
48
52
  "sdk:verify:php": "oagen verify --lang php --output ./sdk-php --api-surface ./sdk-php-surface.json",
49
53
  "sdk:extract:php": "oagen extract --lang php --sdk-path ../backend/workos-php --output ./sdk-php-surface.json",
50
54
  "sdk:generate:python": "oagen generate --lang python --output ./sdk-python --namespace workos --api-surface ./sdk-python-surface.json",
@@ -57,9 +61,6 @@
57
61
  "sdk:verify:rust": "oagen verify --lang rust --output ./sdk-rust --api-surface ./sdk-rust-surface.json",
58
62
  "sdk:extract:rust": "oagen extract --lang rust --sdk-path ../backend/workos-rust --output ./sdk-rust-surface.json"
59
63
  },
60
- "dependencies": {
61
- "@workos/oagen": "^0.3.0"
62
- },
63
64
  "devDependencies": {
64
65
  "@commitlint/cli": "^20.5.0",
65
66
  "@commitlint/config-conventional": "^20.5.0",
@@ -75,5 +76,8 @@
75
76
  },
76
77
  "engines": {
77
78
  "node": ">=24.10.0"
79
+ },
80
+ "dependencies": {
81
+ "@workos/oagen": "^0.6.0"
78
82
  }
79
83
  }
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * PHP SDK generation wrapper.
4
+ */
5
+ import { execSync } from 'node:child_process';
6
+
7
+ // Build oagen command with all passed arguments
8
+ const extraArgs = process.argv.slice(2).join(' ');
9
+ const baseCmd = 'oagen generate --lang php --output ./sdk-php --namespace WorkOS --api-surface ./sdk-php-surface.json';
10
+ const fullCmd = extraArgs ? `${baseCmd} ${extraArgs}` : baseCmd;
11
+
12
+ // Run oagen generate
13
+ execSync(fullCmd, { stdio: 'inherit' });
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env sh
2
+ set -eu
3
+
4
+ repo_root="$(git rev-parse --show-toplevel)"
5
+ cd "$repo_root"
6
+
7
+ restore_local() {
8
+ echo "push wrapper: restoring local ../oagen link"
9
+ npm run oagen:use:local
10
+ }
11
+
12
+ cleanup() {
13
+ restore_local
14
+ }
15
+
16
+ trap cleanup EXIT INT TERM HUP
17
+
18
+ echo "push wrapper: switching @workos/oagen to the published package"
19
+ npm run oagen:use:published
20
+
21
+ git push "$@"
@@ -405,14 +405,33 @@ function buildBatchedCSharpScript(port: number, ns: string, calls: PlannedCall[]
405
405
  // ---------------------------------------------------------------------------
406
406
 
407
407
  /**
408
- * Find the .csproj file in the SDK directory. Returns the full resolved path.
408
+ * Find the .csproj file in the SDK directory. Searches the root first, then
409
+ * common subdirectory patterns (src/{Name}/) used by the generated SDK layout.
409
410
  */
410
411
  function findCsproj(sdkPath: string): string {
411
- const files = readdirSync(sdkPath).filter((f) => f.endsWith('.csproj'));
412
- if (files.length === 0) {
413
- throw new Error(`No .csproj file found in ${sdkPath}`);
412
+ // Check root directory first
413
+ const rootFiles = readdirSync(sdkPath).filter((f) => f.endsWith('.csproj'));
414
+ if (rootFiles.length > 0) {
415
+ return resolve(sdkPath, rootFiles[0]);
414
416
  }
415
- return resolve(sdkPath, files[0]);
417
+
418
+ // Check src/ subdirectories (generated SDK layout: src/WorkOS.net/WorkOS.net.csproj)
419
+ const srcDir = resolve(sdkPath, 'src');
420
+ if (existsSync(srcDir)) {
421
+ for (const subdir of readdirSync(srcDir)) {
422
+ const subdirPath = resolve(srcDir, subdir);
423
+ try {
424
+ const subFiles = readdirSync(subdirPath).filter((f) => f.endsWith('.csproj'));
425
+ if (subFiles.length > 0) {
426
+ return resolve(subdirPath, subFiles[0]);
427
+ }
428
+ } catch {
429
+ // Not a directory, skip
430
+ }
431
+ }
432
+ }
433
+
434
+ throw new Error(`No .csproj file found in ${sdkPath} or ${sdkPath}/src/*/`);
416
435
  }
417
436
 
418
437
  /**
@@ -429,6 +448,18 @@ function detectNamespace(sdkPath: string): string {
429
448
  return base.replace('.csproj', '');
430
449
  }
431
450
 
451
+ /**
452
+ * Detect the assembly name from the .csproj file's AssemblyName property.
453
+ * Falls back to namespace if not found.
454
+ */
455
+ function detectAssemblyName(sdkPath: string): string {
456
+ const csprojPath = findCsproj(sdkPath);
457
+ const content = readFileSync(csprojPath, 'utf-8');
458
+ const match = content.match(/<AssemblyName>([^<]+)<\/AssemblyName>/);
459
+ if (match) return match[1];
460
+ return detectNamespace(sdkPath);
461
+ }
462
+
432
463
  // ---------------------------------------------------------------------------
433
464
  // .NET project generation
434
465
  // ---------------------------------------------------------------------------
@@ -590,9 +621,10 @@ async function main(): Promise<void> {
590
621
  const spec = await parseSpec(specPath);
591
622
  console.log(`Spec: ${spec.name} v${spec.version}`);
592
623
 
593
- // Detect SDK namespace
624
+ // Detect SDK namespace and assembly name
594
625
  const ns = detectNamespace(sdkPath);
595
- console.log(`SDK namespace: ${ns}`);
626
+ const assemblyName = detectAssemblyName(sdkPath);
627
+ console.log(`SDK namespace: ${ns}, assembly: ${assemblyName}`);
596
628
 
597
629
  // Load manifest
598
630
  const manifest = loadManifest(sdkPath);
@@ -620,10 +652,12 @@ async function main(): Promise<void> {
620
652
 
621
653
  // Step 1: Build the SDK project to a DLL
622
654
  const sdkCsprojPath = findCsproj(sdkPath);
655
+ const sdkCsprojDir = sdkCsprojPath.substring(0, sdkCsprojPath.lastIndexOf('/'));
656
+ const sdkDllDir = resolve(sdkPath, 'bin/Release');
623
657
  console.log('Building SDK...');
624
658
  try {
625
- execSync(`dotnet build "${sdkCsprojPath}" -c Release -o "${resolve(sdkPath, 'bin/Release/net8.0')}"`, {
626
- cwd: sdkPath,
659
+ execSync(`dotnet build "${sdkCsprojPath}" -c Release -o "${sdkDllDir}"`, {
660
+ cwd: sdkCsprojDir,
627
661
  timeout: 120000,
628
662
  stdio: ['pipe', 'pipe', 'pipe'],
629
663
  env: { ...process.env, DOTNET_NOLOGO: '1' },
@@ -635,9 +669,8 @@ async function main(): Promise<void> {
635
669
  process.exit(1);
636
670
  }
637
671
 
638
- // Find the SDK DLL
639
- const sdkDllDir = resolve(sdkPath, 'bin/Release/net8.0');
640
- const sdkDll = resolve(sdkDllDir, `${ns}.dll`);
672
+ // Find the SDK DLL (use AssemblyName, not namespace)
673
+ const sdkDll = resolve(sdkDllDir, `${assemblyName}.dll`);
641
674
 
642
675
  // Step 2: Bootstrap the driver project referencing the built DLL
643
676
  mkdirSync(tmpDir, { recursive: true });