diff --git a/bun.lock b/bun.lock index 3ba6bd1a..ffc6b664 100644 --- a/bun.lock +++ b/bun.lock @@ -9,11 +9,11 @@ "@anthropic-ai/sdk": "^0.124.0", "@inquirer/prompts": "^8.5.2", "@napi-rs/keyring": "^1.2.0", - "@workos-inc/node": "^8.7.0", - "@workos/emulate": "^0.12.0", - "@workos/migrations": "^2.4.0", - "@workos/openapi-spec": "^0.83.0", - "@workos/skills": "0.7.1", + "@workos-inc/node": "^10.13.0", + "@workos/emulate": "^0.13.1", + "@workos/migrations": "^2.6.0", + "@workos/openapi-spec": "^0.100.0", + "@workos/skills": "0.7.3", "chalk": "^5.6.2", "diff": "^8.0.3", "fast-glob": "^3.3.3", @@ -120,7 +120,7 @@ "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], - "@hono/node-server": ["@hono/node-server@1.19.14", "", { "peerDependencies": { "hono": "^4" } }, "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw=="], + "@hono/node-server": ["@hono/node-server@2.1.1", "", { "peerDependencies": { "hono": "^4" } }, "sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg=="], "@inquirer/ansi": ["@inquirer/ansi@2.0.7", "", {}, "sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q=="], @@ -366,15 +366,15 @@ "@vitest/utils": ["@vitest/utils@4.1.10", "", { "dependencies": { "@vitest/pretty-format": "4.1.10", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA=="], - "@workos-inc/node": ["@workos-inc/node@8.13.0", "", { "dependencies": { "eventemitter3": "^5.0.4" } }, "sha512-NgQKHpwh8AbT4KvAsW91Y+4f4jja2IvFPQ5atcy5NUxUMVRgXzRFEee3erawfXrTmiCVqJjd9PljHySKBXmHKQ=="], + "@workos-inc/node": ["@workos-inc/node@10.13.0", "", {}, "sha512-xPtcziTjN1irPiZ4niTaulYvRMs6D+Fz5E54GGHVOxNi0PV2/Ws1g/cx3q9oMvMhnINkva5iZ2T5aS6yLzdCWA=="], - "@workos/emulate": ["@workos/emulate@0.12.0", "", { "dependencies": { "@hono/node-server": "^1", "chalk": "^5.6.2", "hono": "^4", "semver": "^7.7.4", "yaml": "^2.8.2" }, "bin": { "workos-emulate": "dist/cli.js" } }, "sha512-J7kVy1riKg6eyIZ7L4QVQPF8IKzD/k+ZWImcIt9kzh0yGkBYnNPkNK0FrVxAbyFby/9Nwmt6VApawJQo9HWgig=="], + "@workos/emulate": ["@workos/emulate@0.13.1", "", { "dependencies": { "@hono/node-server": "^2.1.1", "chalk": "^5.6.2", "hono": "^4", "semver": "^7.7.4", "yaml": "^2.8.2" }, "bin": { "workos-emulate": "dist/cli.js" } }, "sha512-5/wRpFQNMKvnp1+lwwOEZlXFGARKBMnJyGvPyD1PO28zIXGDgtITIWtNBhRtUHPPzs0tD6vhbhyr/NGhsyQuag=="], - "@workos/migrations": ["@workos/migrations@2.5.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity-provider": "^3.1041.0", "@workos-inc/node": "^10.0.0", "chalk": "^5.6.2", "cli-progress": "^3.12.0", "commander": "^15.0.0", "csv-parse": "^6.2.1", "csv-stringify": "^6.4.5", "dotenv": "^17.4.2", "fast-xml-parser": "^5.7.2", "google-auth-library": "^10.6.2", "prompts": "^2.4.2" }, "bin": { "workos-migrate": "dist/index.js" } }, "sha512-2kBiKOf0djjQIThpi43POWy76Dkvx8fFtKvdKtMNiOk5rJqyjsX3Y3oU7JjSx+gZJTfW2YfdDXrQhPnWzdyiUw=="], + "@workos/migrations": ["@workos/migrations@2.6.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity-provider": "^3.1041.0", "@workos-inc/node": "^10.0.0", "chalk": "^5.6.2", "cli-progress": "^3.12.0", "commander": "^15.0.0", "csv-parse": "^6.2.1", "csv-stringify": "^6.4.5", "dotenv": "^17.4.2", "fast-xml-parser": "^5.7.2", "google-auth-library": "^10.6.2", "prompts": "^2.4.2" }, "bin": { "workos-migrate": "dist/index.js" } }, "sha512-A7pqwyzo/XWpvlwWTpTbf+DMw5XpHGSYr3/v/dWm1LmYFf5QZgqQy0nyC3O4+NQ2I7cpqpvLVP7AWDd94yBOjg=="], - "@workos/openapi-spec": ["@workos/openapi-spec@0.83.0", "", {}, "sha512-nYlp4IK7a4P0NFkvDpKJf3nYs9wA/CXtj4EruEJYzPPv0lwt7lgSKjAD0mskRxVKOgCIeA+i+6HrI1SqaJmqjQ=="], + "@workos/openapi-spec": ["@workos/openapi-spec@0.100.0", "", {}, "sha512-/DuUtAnHh1Tc2y2vMyHwydzGNlcEUzeUGCZ69tbEdEiw3N3pYSFCILBlP0jUAk+/Zx/whtqfRiMT1/tU+vdMqg=="], - "@workos/skills": ["@workos/skills@0.7.1", "", { "dependencies": { "yaml": "^2.8.2" } }, "sha512-s0iVaQ0Y2vo6ViBMxO5v675fLJXQt8w31z5sSuJ3eTV7WUtsWTGzIIPAaChP3keiBkSrMhyLNGmAz8wlEldIYQ=="], + "@workos/skills": ["@workos/skills@0.7.3", "", { "dependencies": { "yaml": "^2.8.2" } }, "sha512-xg+TqR52bBj6nNfhzzUuLhBwaeaFs/913H5z7jsq4LF/mxm6TzQkfecFh7z15wrbbf1unpFXSY3V0HGDyRdbWA=="], "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], @@ -516,8 +516,6 @@ "event-target-shim": ["event-target-shim@6.0.2", "", {}, "sha512-8q3LsZjRezbFZ2PN+uP+Q7pnHUMmAOziU2vA2OwoFaKIXxlxl38IylhSSgUorWu/rf4er67w0ikBqjBFk/pomA=="], - "eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="], - "eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="], "eventsource-parser": ["eventsource-parser@3.1.0", "", {}, "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg=="], @@ -954,7 +952,7 @@ "@inquirer/core/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - "@workos/migrations/@workos-inc/node": ["@workos-inc/node@10.7.0", "", {}, "sha512-rffa9znZuIv4yo+9JRxGOLTTSxh0XhOT6jlsktgqEi9MuB9V0VFHRzLd3bTpkq+J9sgrPZNGpvX4aWNfMqt5cQ=="], + "@modelcontextprotocol/sdk/@hono/node-server": ["@hono/node-server@1.19.14", "", { "peerDependencies": { "hono": "^4" } }, "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw=="], "body-parser/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="], diff --git a/package.json b/package.json index d4594b07..58b40a5b 100644 --- a/package.json +++ b/package.json @@ -26,11 +26,11 @@ "@anthropic-ai/sdk": "^0.124.0", "@inquirer/prompts": "^8.5.2", "@napi-rs/keyring": "^1.2.0", - "@workos-inc/node": "^8.7.0", - "@workos/emulate": "^0.12.0", - "@workos/migrations": "^2.4.0", - "@workos/openapi-spec": "^0.83.0", - "@workos/skills": "0.7.1", + "@workos-inc/node": "^10.13.0", + "@workos/emulate": "^0.13.1", + "@workos/migrations": "^2.6.0", + "@workos/openapi-spec": "^0.100.0", + "@workos/skills": "0.7.3", "chalk": "^5.6.2", "diff": "^8.0.3", "fast-glob": "^3.3.3", diff --git a/src/commands/api-key-mgmt.spec.ts b/src/commands/api-key-mgmt.spec.ts index d69bbce0..79e6770d 100644 --- a/src/commands/api-key-mgmt.spec.ts +++ b/src/commands/api-key-mgmt.spec.ts @@ -1,15 +1,14 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { WorkOS } from '@workos-inc/node'; const mockSdk = { - organizations: { + apiKeys: { listOrganizationApiKeys: vi.fn(), createOrganizationApiKey: vi.fn(), - }, - apiKeys: { - validateApiKey: vi.fn(), + createValidation: vi.fn(), deleteApiKey: vi.fn(), }, -}; +} satisfies { apiKeys: Partial }; vi.mock('../lib/workos-client.js', () => ({ createWorkOSClient: () => ({ sdk: mockSdk }), @@ -45,14 +44,21 @@ describe('api-key-mgmt commands', () => { vi.restoreAllMocks(); }); + it('mocks methods that exist on the installed SDK', () => { + const sdk = new WorkOS('sk_test'); + for (const method of Object.keys(mockSdk.apiKeys)) { + expect(sdk.apiKeys).toHaveProperty(method, expect.any(Function)); + } + }); + describe('runApiKeyList', () => { it('lists keys in table', async () => { - mockSdk.organizations.listOrganizationApiKeys.mockResolvedValue({ + mockSdk.apiKeys.listOrganizationApiKeys.mockResolvedValue({ data: [mockApiKey], listMetadata: { before: null, after: null }, }); await runApiKeyList({ organizationId: 'org_456' }, 'sk_test'); - expect(mockSdk.organizations.listOrganizationApiKeys).toHaveBeenCalledWith( + expect(mockSdk.apiKeys.listOrganizationApiKeys).toHaveBeenCalledWith( expect.objectContaining({ organizationId: 'org_456' }), ); expect(consoleOutput.some((l) => l.includes('key_123'))).toBe(true); @@ -60,7 +66,7 @@ describe('api-key-mgmt commands', () => { }); it('handles empty results', async () => { - mockSdk.organizations.listOrganizationApiKeys.mockResolvedValue({ + mockSdk.apiKeys.listOrganizationApiKeys.mockResolvedValue({ data: [], listMetadata: { before: null, after: null }, }); @@ -69,12 +75,12 @@ describe('api-key-mgmt commands', () => { }); it('passes pagination params', async () => { - mockSdk.organizations.listOrganizationApiKeys.mockResolvedValue({ + mockSdk.apiKeys.listOrganizationApiKeys.mockResolvedValue({ data: [], listMetadata: { before: null, after: null }, }); await runApiKeyList({ organizationId: 'org_456', limit: 5, order: 'desc' }, 'sk_test'); - expect(mockSdk.organizations.listOrganizationApiKeys).toHaveBeenCalledWith( + expect(mockSdk.apiKeys.listOrganizationApiKeys).toHaveBeenCalledWith( expect.objectContaining({ limit: 5, order: 'desc' }), ); }); @@ -82,16 +88,16 @@ describe('api-key-mgmt commands', () => { describe('runApiKeyCreate', () => { it('creates API key with org and name', async () => { - mockSdk.organizations.createOrganizationApiKey.mockResolvedValue({ ...mockApiKey, value: 'sk_test_full_key' }); + mockSdk.apiKeys.createOrganizationApiKey.mockResolvedValue({ ...mockApiKey, value: 'sk_test_full_key' }); await runApiKeyCreate({ organizationId: 'org_456', name: 'My Key' }, 'sk_test'); - expect(mockSdk.organizations.createOrganizationApiKey).toHaveBeenCalledWith({ + expect(mockSdk.apiKeys.createOrganizationApiKey).toHaveBeenCalledWith({ organizationId: 'org_456', name: 'My Key', }); }); it('displays key value warning in human mode', async () => { - mockSdk.organizations.createOrganizationApiKey.mockResolvedValue({ ...mockApiKey, value: 'sk_test_full_key' }); + mockSdk.apiKeys.createOrganizationApiKey.mockResolvedValue({ ...mockApiKey, value: 'sk_test_full_key' }); await runApiKeyCreate({ organizationId: 'org_456', name: 'My Key' }, 'sk_test'); expect(consoleOutput.some((l) => l.includes('Created API key'))).toBe(true); expect(consoleOutput.some((l) => l.includes('sk_test_full_key'))).toBe(true); @@ -99,9 +105,9 @@ describe('api-key-mgmt commands', () => { }); it('passes permissions when provided', async () => { - mockSdk.organizations.createOrganizationApiKey.mockResolvedValue({ ...mockApiKey, value: 'sk_test_full_key' }); + mockSdk.apiKeys.createOrganizationApiKey.mockResolvedValue({ ...mockApiKey, value: 'sk_test_full_key' }); await runApiKeyCreate({ organizationId: 'org_456', name: 'My Key', permissions: ['read', 'write'] }, 'sk_test'); - expect(mockSdk.organizations.createOrganizationApiKey).toHaveBeenCalledWith({ + expect(mockSdk.apiKeys.createOrganizationApiKey).toHaveBeenCalledWith({ organizationId: 'org_456', name: 'My Key', permissions: ['read', 'write'], @@ -111,14 +117,14 @@ describe('api-key-mgmt commands', () => { describe('runApiKeyValidate', () => { it('validates API key', async () => { - mockSdk.apiKeys.validateApiKey.mockResolvedValue({ apiKey: mockApiKey }); + mockSdk.apiKeys.createValidation.mockResolvedValue({ apiKey: mockApiKey }); await runApiKeyValidate('sk_test_value', 'sk_test'); - expect(mockSdk.apiKeys.validateApiKey).toHaveBeenCalledWith({ value: 'sk_test_value' }); + expect(mockSdk.apiKeys.createValidation).toHaveBeenCalledWith({ value: 'sk_test_value' }); expect(consoleOutput.some((l) => l.includes('valid'))).toBe(true); }); it('handles invalid key (null result)', async () => { - mockSdk.apiKeys.validateApiKey.mockResolvedValue({ apiKey: null }); + mockSdk.apiKeys.createValidation.mockResolvedValue({ apiKey: null }); await runApiKeyValidate('sk_test_invalid', 'sk_test'); expect(consoleOutput.some((l) => l.includes('invalid'))).toBe(true); }); @@ -138,7 +144,7 @@ describe('api-key-mgmt commands', () => { afterEach(() => setOutputMode('human')); it('list outputs { data, listMetadata }', async () => { - mockSdk.organizations.listOrganizationApiKeys.mockResolvedValue({ + mockSdk.apiKeys.listOrganizationApiKeys.mockResolvedValue({ data: [mockApiKey], listMetadata: { before: null, after: 'cursor_a' }, }); @@ -150,7 +156,7 @@ describe('api-key-mgmt commands', () => { }); it('create includes key value in output', async () => { - mockSdk.organizations.createOrganizationApiKey.mockResolvedValue({ ...mockApiKey, value: 'sk_test_full_key' }); + mockSdk.apiKeys.createOrganizationApiKey.mockResolvedValue({ ...mockApiKey, value: 'sk_test_full_key' }); await runApiKeyCreate({ organizationId: 'org_456', name: 'My Key' }, 'sk_test'); const output = JSON.parse(consoleOutput[0]); expect(output.status).toBe('ok'); @@ -158,7 +164,7 @@ describe('api-key-mgmt commands', () => { }); it('validate outputs raw JSON', async () => { - mockSdk.apiKeys.validateApiKey.mockResolvedValue({ apiKey: mockApiKey }); + mockSdk.apiKeys.createValidation.mockResolvedValue({ apiKey: mockApiKey }); await runApiKeyValidate('sk_test_value', 'sk_test'); const output = JSON.parse(consoleOutput[0]); expect(output.apiKey.id).toBe('key_123'); diff --git a/src/commands/api-key-mgmt.ts b/src/commands/api-key-mgmt.ts index bd287e00..cbd98518 100644 --- a/src/commands/api-key-mgmt.ts +++ b/src/commands/api-key-mgmt.ts @@ -19,7 +19,7 @@ export async function runApiKeyList(options: ApiKeyListOptions, apiKey: string, const client = createWorkOSClient(apiKey, baseUrl); try { - const result = await client.sdk.organizations.listOrganizationApiKeys({ + const result = await client.sdk.apiKeys.listOrganizationApiKeys({ organizationId: options.organizationId, limit: options.limit, before: options.before, @@ -59,7 +59,7 @@ export async function runApiKeyCreate(options: ApiKeyCreateOptions, apiKey: stri const client = createWorkOSClient(apiKey, baseUrl); try { - const result = await client.sdk.organizations.createOrganizationApiKey({ + const result = await client.sdk.apiKeys.createOrganizationApiKey({ organizationId: options.organizationId, name: options.name, ...(options.permissions && { permissions: options.permissions }), @@ -86,7 +86,7 @@ export async function runApiKeyValidate(value: string, apiKey: string, baseUrl?: const client = createWorkOSClient(apiKey, baseUrl); try { - const result = await client.sdk.apiKeys.validateApiKey({ value }); + const result = await client.sdk.apiKeys.createValidation({ value }); if (isJsonMode()) { outputJson(result); diff --git a/src/commands/setup-org.spec.ts b/src/commands/setup-org.spec.ts index 802c8614..8771a4b3 100644 --- a/src/commands/setup-org.spec.ts +++ b/src/commands/setup-org.spec.ts @@ -1,10 +1,13 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { WorkOS } from '@workos-inc/node'; const mockSdk = { organizations: { createOrganization: vi.fn() }, - organizationDomains: { create: vi.fn(), verify: vi.fn() }, + organizationDomains: { createOrganizationDomain: vi.fn(), verifyOrganizationDomain: vi.fn() }, authorization: { createOrganizationRole: vi.fn() }, - portal: { generateLink: vi.fn() }, + adminPortal: { generateLink: vi.fn() }, +} satisfies { + [Resource in 'organizations' | 'organizationDomains' | 'authorization' | 'adminPortal']: Partial; }; vi.mock('../lib/workos-client.js', () => ({ @@ -27,6 +30,15 @@ describe('setup-org command', () => { afterEach(() => vi.restoreAllMocks()); + it('mocks methods that exist on the installed SDK', () => { + const sdk = new WorkOS('sk_test'); + for (const [resource, methods] of Object.entries(mockSdk)) { + for (const method of Object.keys(methods)) { + expect(sdk).toHaveProperty(`${resource}.${method}`, expect.any(Function)); + } + } + }); + it('creates org with name only', async () => { mockSdk.organizations.createOrganization.mockResolvedValue({ id: 'org_123', name: 'Acme' }); await runSetupOrg({ name: 'Acme' }, 'sk_test'); @@ -37,26 +49,29 @@ describe('setup-org command', () => { it('does not call domain or role APIs when not provided', async () => { mockSdk.organizations.createOrganization.mockResolvedValue({ id: 'org_123', name: 'Acme' }); await runSetupOrg({ name: 'Acme' }, 'sk_test'); - expect(mockSdk.organizationDomains.create).not.toHaveBeenCalled(); + expect(mockSdk.organizationDomains.createOrganizationDomain).not.toHaveBeenCalled(); expect(mockSdk.authorization.createOrganizationRole).not.toHaveBeenCalled(); }); it('adds and verifies domain when provided', async () => { mockSdk.organizations.createOrganization.mockResolvedValue({ id: 'org_123', name: 'Acme' }); - mockSdk.organizationDomains.create.mockResolvedValue({ id: 'dom_1' }); - mockSdk.organizationDomains.verify.mockResolvedValue({ id: 'dom_1', state: 'verified' }); + mockSdk.organizationDomains.createOrganizationDomain.mockResolvedValue({ id: 'dom_1' }); + mockSdk.organizationDomains.verifyOrganizationDomain.mockResolvedValue({ id: 'dom_1', state: 'verified' }); await runSetupOrg({ name: 'Acme', domain: 'acme.com' }, 'sk_test'); - expect(mockSdk.organizationDomains.create).toHaveBeenCalledWith({ domain: 'acme.com', organizationId: 'org_123' }); - expect(mockSdk.organizationDomains.verify).toHaveBeenCalledWith('dom_1'); + expect(mockSdk.organizationDomains.createOrganizationDomain).toHaveBeenCalledWith({ + domain: 'acme.com', + organizationId: 'org_123', + }); + expect(mockSdk.organizationDomains.verifyOrganizationDomain).toHaveBeenCalledWith('dom_1'); expect(consoleOutput.some((l) => l.includes('Verified domain'))).toBe(true); }); it('handles domain verification failure gracefully', async () => { mockSdk.organizations.createOrganization.mockResolvedValue({ id: 'org_123', name: 'Acme' }); - mockSdk.organizationDomains.create.mockResolvedValue({ id: 'dom_1' }); - mockSdk.organizationDomains.verify.mockRejectedValue(new Error('Verification pending')); + mockSdk.organizationDomains.createOrganizationDomain.mockResolvedValue({ id: 'dom_1' }); + mockSdk.organizationDomains.verifyOrganizationDomain.mockRejectedValue(new Error('Verification pending')); await runSetupOrg({ name: 'Acme', domain: 'acme.com' }, 'sk_test'); @@ -100,17 +115,17 @@ describe('setup-org command', () => { it('generates portal link', async () => { mockSdk.organizations.createOrganization.mockResolvedValue({ id: 'org_123', name: 'Acme' }); - mockSdk.portal.generateLink.mockResolvedValue({ link: 'https://portal.workos.com/xxx' }); + mockSdk.adminPortal.generateLink.mockResolvedValue({ link: 'https://portal.workos.com/xxx' }); await runSetupOrg({ name: 'Acme' }, 'sk_test'); - expect(mockSdk.portal.generateLink).toHaveBeenCalledWith(expect.objectContaining({ organization: 'org_123' })); + expect(mockSdk.adminPortal.generateLink).toHaveBeenCalledWith(expect.objectContaining({ organization: 'org_123' })); expect(consoleOutput.some((l) => l.includes('portal.workos.com'))).toBe(true); }); it('handles portal link failure gracefully', async () => { mockSdk.organizations.createOrganization.mockResolvedValue({ id: 'org_123', name: 'Acme' }); - mockSdk.portal.generateLink.mockRejectedValue(new Error('Plan upgrade required')); + mockSdk.adminPortal.generateLink.mockRejectedValue(new Error('Plan upgrade required')); await runSetupOrg({ name: 'Acme' }, 'sk_test'); @@ -119,9 +134,9 @@ describe('setup-org command', () => { it('prints human-mode summary with all components', async () => { mockSdk.organizations.createOrganization.mockResolvedValue({ id: 'org_123', name: 'Acme' }); - mockSdk.organizationDomains.create.mockResolvedValue({ id: 'dom_1' }); - mockSdk.organizationDomains.verify.mockResolvedValue({}); - mockSdk.portal.generateLink.mockResolvedValue({ link: 'https://portal.workos.com/xxx' }); + mockSdk.organizationDomains.createOrganizationDomain.mockResolvedValue({ id: 'dom_1' }); + mockSdk.organizationDomains.verifyOrganizationDomain.mockResolvedValue({}); + mockSdk.adminPortal.generateLink.mockResolvedValue({ link: 'https://portal.workos.com/xxx' }); await runSetupOrg({ name: 'Acme', domain: 'acme.com' }, 'sk_test'); @@ -136,7 +151,7 @@ describe('setup-org command', () => { it('outputs JSON summary with org ID', async () => { mockSdk.organizations.createOrganization.mockResolvedValue({ id: 'org_123', name: 'Acme' }); - mockSdk.portal.generateLink.mockResolvedValue({ link: 'https://portal.workos.com/xxx' }); + mockSdk.adminPortal.generateLink.mockResolvedValue({ link: 'https://portal.workos.com/xxx' }); await runSetupOrg({ name: 'Acme' }, 'sk_test'); @@ -148,8 +163,8 @@ describe('setup-org command', () => { it('includes domain verification status in JSON', async () => { mockSdk.organizations.createOrganization.mockResolvedValue({ id: 'org_123', name: 'Acme' }); - mockSdk.organizationDomains.create.mockResolvedValue({ id: 'dom_1' }); - mockSdk.organizationDomains.verify.mockResolvedValue({}); + mockSdk.organizationDomains.createOrganizationDomain.mockResolvedValue({ id: 'dom_1' }); + mockSdk.organizationDomains.verifyOrganizationDomain.mockResolvedValue({}); await runSetupOrg({ name: 'Acme', domain: 'acme.com' }, 'sk_test'); diff --git a/src/commands/setup-org.ts b/src/commands/setup-org.ts index 70583f75..b3bda706 100644 --- a/src/commands/setup-org.ts +++ b/src/commands/setup-org.ts @@ -27,7 +27,7 @@ export async function runSetupOrg(options: SetupOrgOptions, apiKey: string, base // 2. Add domain if (options.domain) { - const domainResult = await client.sdk.organizationDomains.create({ + const domainResult = await client.sdk.organizationDomains.createOrganizationDomain({ domain: options.domain, organizationId: org.id, }); @@ -36,7 +36,7 @@ export async function runSetupOrg(options: SetupOrgOptions, apiKey: string, base // 3. Verify domain try { - await client.sdk.organizationDomains.verify(domainResult.id); + await client.sdk.organizationDomains.verifyOrganizationDomain(domainResult.id); summary.domainVerified = true; if (!isJsonMode()) console.log(chalk.green(` Verified domain: ${options.domain}`)); } catch { @@ -68,8 +68,8 @@ export async function runSetupOrg(options: SetupOrgOptions, apiKey: string, base // 5. Generate Admin Portal link try { - const portal = await client.sdk.portal.generateLink({ - intent: 'sso' as Parameters[0]['intent'], + const portal = await client.sdk.adminPortal.generateLink({ + intent: 'sso' as Parameters[0]['intent'], organization: org.id, }); summary.portalLink = portal.link; diff --git a/src/commands/vault.spec.ts b/src/commands/vault.spec.ts index b7fe7655..d2fb4c19 100644 --- a/src/commands/vault.spec.ts +++ b/src/commands/vault.spec.ts @@ -1,4 +1,5 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import type { WorkOS } from '@workos-inc/node'; const mockSdk = { vault: { @@ -9,7 +10,7 @@ const mockSdk = { updateObject: vi.fn(), deleteObject: vi.fn(), describeObject: vi.fn(), - listObjectVersions: vi.fn(), + listObjectVersions: vi.fn(), }, }; @@ -181,7 +182,9 @@ describe('vault commands', () => { describe('runVaultListVersions', () => { it('lists versions by object ID', async () => { - const versions = [{ id: 'v1', createdAt: '2024-01-01', currentVersion: true }]; + const versions = [ + { id: 'v1', createdAt: new Date('2024-01-01'), currentVersion: true, etag: 'etag_1', size: 12 }, + ]; mockSdk.vault.listObjectVersions.mockResolvedValue(versions); await runVaultListVersions('obj_123', 'sk_test'); expect(mockSdk.vault.listObjectVersions).toHaveBeenCalledWith({ id: 'obj_123' }); @@ -203,6 +206,16 @@ describe('vault commands', () => { expect(output.listMetadata.after).toBe('cursor_a'); }); + it('versions preserves the JSON shape without SDK-added fields', async () => { + mockSdk.vault.listObjectVersions.mockResolvedValue([ + { id: 'v1', createdAt: new Date('2024-01-01'), currentVersion: true, etag: 'etag_1', size: 12 }, + ]); + await runVaultListVersions('obj_123', 'sk_test'); + expect(JSON.parse(consoleOutput[0])).toEqual([ + { id: 'v1', createdAt: '2024-01-01T00:00:00.000Z', currentVersion: true }, + ]); + }); + it('get --decrypt outputs value in JSON', async () => { mockSdk.vault.readObject.mockResolvedValue(mockObject); await runVaultGet('obj_123', true, 'sk_test'); diff --git a/src/commands/vault.ts b/src/commands/vault.ts index 6116919c..5d35c63d 100644 --- a/src/commands/vault.ts +++ b/src/commands/vault.ts @@ -158,7 +158,7 @@ export async function runVaultListVersions(id: string, apiKey: string, baseUrl?: try { const result = await client.sdk.vault.listObjectVersions({ id }); - outputJson(result); + outputJson(result.map(({ createdAt, currentVersion, id }) => ({ createdAt, currentVersion, id }))); } catch (error) { handleApiError(error); } diff --git a/src/lib/skills-assets.spec.ts b/src/lib/skills-assets.spec.ts index f76971b1..b270ca96 100644 --- a/src/lib/skills-assets.spec.ts +++ b/src/lib/skills-assets.spec.ts @@ -34,6 +34,13 @@ describe('embedded skills assets', () => { expect(reference).toBe(readFileSync(join(skillsDir, 'workos', 'references', 'workos-authkit-base.md'), 'utf8')); }); + it('bundles the shared AuthKit application setup reference', async () => { + const reference = await getReference('workos-authkit-setup'); + expect(reference).toContain('# AuthKit application setup'); + expect(reference).toContain('Sign-out URI'); + expect(reference).toContain('Initiate login URI'); + }); + it('reaps stale extraction roots from other versions, keeping fresh ones', async () => { const suffix = extractionSuffix(); const staleRoot = join(tmpdir(), `workos-skills-0.0.1-spec-stale${suffix}`);