@dk/jolly 0.1.5 → 0.1.7

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/src/api/client.ts CHANGED
@@ -10,63 +10,136 @@ export class SaleorCloudClient {
10
10
  }
11
11
 
12
12
  async request<T>(endpoint: string, options?: RequestInit): Promise<T> {
13
- const response = await fetch(`${this.baseUrl}${endpoint}`, {
13
+ const mergedOptions: RequestInit = {
14
+ method: options?.method || 'GET',
14
15
  ...options,
15
16
  headers: {
16
- 'Authorization': `Bearer ${this.token}`,
17
+ 'Authorization': `Token ${this.token}`,
17
18
  'Content-Type': 'application/json',
18
19
  ...options?.headers,
19
20
  },
20
- });
21
+ };
22
+
23
+ const response = await fetch(`${this.baseUrl}${endpoint}`, mergedOptions);
21
24
 
22
25
  if (!response.ok) {
23
- throw new Error(`API error: ${response.status} ${response.statusText}`);
26
+ const body = await response.text();
27
+ throw new Error(`API error: ${response.status} ${response.statusText} - ${body}`);
24
28
  }
25
29
 
26
- return response.json();
30
+ return response.json() as T;
27
31
  }
28
32
 
29
- async getStores() {
30
- return this.request<{ stores: Store[] }>('/stores');
33
+ // Organizations
34
+ async getOrganizations() {
35
+ return this.request<{ organizations: Organization[] }>('/organizations');
31
36
  }
32
37
 
33
- async createStore(name: string, region: string = 'us-east-1') {
34
- return this.request<{ store: Store }>('/stores', {
38
+ // Projects
39
+ async getProjects(organizationSlug: string) {
40
+ return this.request<{ projects: Project[] }>(`/organizations/${organizationSlug}/projects`);
41
+ }
42
+
43
+ async createProject(organizationSlug: string, name: string, region: string) {
44
+ return this.request<{ project: Project }>(`/organizations/${organizationSlug}/projects`, {
35
45
  method: 'POST',
36
46
  body: JSON.stringify({ name, region }),
37
47
  });
38
48
  }
39
49
 
40
- async getEnvironments(storeId: string) {
41
- return this.request<{ environments: Environment[] }>(`/stores/${storeId}/environments`);
50
+ // Environments
51
+ async getEnvironments(organizationSlug: string, projectSlug: string) {
52
+ return this.request<{ environments: Environment[] }>(
53
+ `/organizations/${organizationSlug}/projects/${projectSlug}/environments`
54
+ );
42
55
  }
43
56
 
44
- async createEnvironment(storeId: string, name: string) {
45
- return this.request<{ environment: Environment }>(`/stores/${storeId}/environments`, {
46
- method: 'POST',
47
- body: JSON.stringify({ name }),
48
- });
57
+ async createEnvironment(organizationSlug: string, projectSlug: string, name: string, region: string) {
58
+ return this.request<{ environment: Environment; task: Task }>(
59
+ `/organizations/${organizationSlug}/projects/${projectSlug}/environments`,
60
+ {
61
+ method: 'POST',
62
+ body: JSON.stringify({ name, region }),
63
+ }
64
+ );
49
65
  }
50
66
 
67
+ async getEnvironment(organizationSlug: string, projectSlug: string, environmentSlug: string) {
68
+ return this.request<Environment>(
69
+ `/organizations/${organizationSlug}/projects/${projectSlug}/environments/${environmentSlug}`
70
+ );
71
+ }
72
+
73
+ // Apps
51
74
  async registerApp(environmentId: string, appType: string, name: string) {
52
75
  return this.request<{ app: App }>(`/environments/${environmentId}/apps`, {
53
76
  method: 'POST',
54
77
  body: JSON.stringify({ type: appType, name }),
55
78
  });
56
79
  }
80
+
81
+ // Backward compatibility methods
82
+ async getStores() {
83
+ return this.getOrganizations();
84
+ }
85
+
86
+ async createStore(name: string, region: string = 'us-east-1') {
87
+ // Create organization as "store" - requires org slug
88
+ // For now, create in first available org
89
+ const { organizations } = await this.getOrganizations();
90
+ if (organizations.length === 0) {
91
+ throw new Error('No organizations found. Create one at https://cloud.saleor.io');
92
+ }
93
+ return this.createProject(organizations[0].slug, name, region);
94
+ }
95
+
96
+ async createEnvironmentFromStore(storeId: string, name: string) {
97
+ // storeId is used as organization slug for backward compatibility
98
+ const { environments } = await this.getEnvironments(storeId, 'default');
99
+ // Create sandbox environment
100
+ return this.createEnvironment(storeId, environments.length > 0 ? environments[0].project?.slug || 'default' : 'default', name, 'default');
101
+ }
57
102
  }
58
103
 
59
- export interface Store {
60
- id: string;
104
+ export interface Organization {
105
+ slug: string;
106
+ name: string;
107
+ created: string;
108
+ company_name?: string;
109
+ owner_email: string;
110
+ }
111
+
112
+ export interface Project {
113
+ slug: string;
61
114
  name: string;
62
115
  region: string;
63
- created_at: string;
116
+ created: string;
117
+ billing_period?: { start: string };
118
+ sandboxes: { count: number };
64
119
  }
65
120
 
66
121
  export interface Environment {
122
+ key: string;
123
+ name: string;
124
+ domain: string;
125
+ service: {
126
+ version: string;
127
+ type: string;
128
+ region: string;
129
+ };
130
+ created: string;
131
+ project: { name: string; slug: string };
132
+ }
133
+
134
+ export interface Task {
135
+ id: string;
136
+ status: string;
137
+ }
138
+
139
+ export interface Store {
67
140
  id: string;
68
141
  name: string;
69
- store_id: string;
142
+ region: string;
70
143
  created_at: string;
71
144
  }
72
145
 
@@ -31,27 +31,27 @@ export async function createApp(
31
31
  environmentId?: string,
32
32
  provider?: PaymentProvider
33
33
  ): Promise<void> {
34
- info(`Creating ${type} app: ${name}`);
34
+ console.log(info(`Creating ${type} app: ${name}`));
35
35
 
36
36
  if (type === 'payment') {
37
37
  const paymentProvider = provider || 'dummy';
38
- info(`Payment provider: ${paymentProvider}`);
39
- info(`Using hosted payment app: ${PAYMENT_APP_URLS[paymentProvider]}`);
38
+ console.log(info(`Payment provider: ${paymentProvider}`));
39
+ console.log(info(`Using hosted payment app: ${PAYMENT_APP_URLS[paymentProvider]}`));
40
40
 
41
41
  if (environmentId) {
42
42
  await registerHostedApp(environmentId, name, paymentProvider);
43
43
  } else {
44
- success(`\nPayment app "${name}" configured to use ${PAYMENT_APP_URLS[paymentProvider]}`);
45
- info(`\nTo complete setup:`);
46
- info(`1. Go to your dashboard at https://cloud.saleor.io`);
47
- info(`2. Navigate to Apps > Third party apps`);
48
- info(`3. Add the ${paymentProvider} payment app from ${PAYMENT_APP_URLS[paymentProvider]}`);
44
+ console.log(success(`\nPayment app "${name}" configured to use ${PAYMENT_APP_URLS[paymentProvider]}`));
45
+ console.log(info(`\nTo complete setup:`));
46
+ console.log(info(`1. Go to your dashboard at https://cloud.saleor.io`));
47
+ console.log(info(`2. Navigate to Apps > Third party apps`));
48
+ console.log(info(`3. Add the ${paymentProvider} payment app from ${PAYMENT_APP_URLS[paymentProvider]}`));
49
49
  }
50
50
  return;
51
51
  }
52
52
 
53
53
  const template = APP_TEMPLATES[type];
54
- info(`Cloning template from: ${template.repo}`);
54
+ console.log(info(`Cloning template from: ${template.repo}`));
55
55
 
56
56
  try {
57
57
  const { spawn } = await import('child_process');
@@ -61,23 +61,23 @@ export async function createApp(
61
61
 
62
62
  child.on('close', (code) => {
63
63
  if (code === 0) {
64
- success(`\n${type} app "${name}" created successfully!`);
65
- info(`\nTo get started:`);
66
- info(`cd ${name}`);
67
- info(`npm install`);
68
- info(`npm run dev`);
64
+ console.log(success(`\n${type} app "${name}" created successfully!`));
65
+ console.log(info(`\nTo get started:`));
66
+ console.log(info(`cd ${name}`));
67
+ console.log(info(`npm install`));
68
+ console.log(info(`npm run dev`));
69
69
 
70
70
  if (environmentId) {
71
- info(`\nRegistering app with environment ${environmentId}...`);
71
+ console.log(info(`\nRegistering app with environment ${environmentId}...`));
72
72
  registerLocalApp(environmentId, name, type);
73
73
  }
74
74
  } else {
75
- error(`Failed to clone template (exit code: ${code})`);
75
+ console.log(error(`Failed to clone template (exit code: ${code})`));
76
76
  process.exit(1);
77
77
  }
78
78
  });
79
79
  } catch (err) {
80
- error(`Failed to create app: ${err}`);
80
+ console.log(error(`Failed to create app: ${err}`));
81
81
  process.exit(1);
82
82
  }
83
83
  }
@@ -90,15 +90,15 @@ async function registerHostedApp(
90
90
  const token = requireToken();
91
91
  const client = new SaleorCloudClient(token);
92
92
 
93
- info(`Registering hosted ${provider} payment app with environment...`);
93
+ console.log(info(`Registering hosted ${provider} payment app with environment...`));
94
94
 
95
95
  try {
96
96
  const result = await client.registerApp(environmentId, 'payment', name);
97
- success(`Payment app registered successfully!`);
98
- info(`App ID: ${result.app.id}`);
99
- info(`Payment URL: ${PAYMENT_APP_URLS[provider]}`);
97
+ console.log(success(`Payment app registered successfully!`));
98
+ console.log(info(`App ID: ${result.app.id}`));
99
+ console.log(info(`Payment URL: ${PAYMENT_APP_URLS[provider]}`));
100
100
  } catch (err) {
101
- error(`Failed to register app: ${err}`);
101
+ console.log(error(`Failed to register app: ${err}`));
102
102
  process.exit(1);
103
103
  }
104
104
  }
@@ -113,11 +113,11 @@ async function registerLocalApp(
113
113
 
114
114
  try {
115
115
  const result = await client.registerApp(environmentId, type, name);
116
- success(`App registered with environment!`);
117
- info(`App ID: ${result.app.id}`);
116
+ console.log(success(`App registered with environment!`));
117
+ console.log(info(`App ID: ${result.app.id}`));
118
118
  } catch (err) {
119
- warning(`Could not register app automatically: ${err}`);
120
- info(`You can register manually in the dashboard.`);
119
+ console.log(warning(`Could not register app automatically: ${err}`));
120
+ console.log(info(`You can register manually in the dashboard.`));
121
121
  }
122
122
  }
123
123
 
@@ -1,20 +1,20 @@
1
1
  import { SaleorCloudClient } from '../api/client.js';
2
2
  import { requireToken } from '../api/auth.js';
3
- import { spinner, success, error, info } from '../tui/components.js';
3
+ import { success, error, info } from '../tui/components.js';
4
4
 
5
5
  export async function createStore(name: string, region: string): Promise<void> {
6
6
  const token = requireToken();
7
7
  const client = new SaleorCloudClient(token);
8
8
 
9
- info(`Creating store: ${name} in ${region}...`);
9
+ console.log(info(`Creating store: ${name} in ${region}...`));
10
10
 
11
11
  try {
12
12
  const result = await client.createStore(name, region);
13
- success(`Store created successfully!`);
14
- info(`Store ID: ${result.store.id}`);
15
- info(`Dashboard: https://cloud.saleor.io/stores/${result.store.id}`);
13
+ console.log(success(`Store created successfully!`));
14
+ console.log(info(`Project slug: ${result.project.slug}`));
15
+ console.log(info(`Dashboard: https://cloud.saleor.io/organizations/default/projects/${result.project.slug}`));
16
16
  } catch (err) {
17
- error(`Failed to create store: ${err}`);
17
+ console.log(error(`Failed to create store: ${err}`));
18
18
  process.exit(1);
19
19
  }
20
20
  }
@@ -23,42 +23,42 @@ export async function listStores(): Promise<void> {
23
23
  const token = requireToken();
24
24
  const client = new SaleorCloudClient(token);
25
25
 
26
- info('Fetching stores...');
26
+ console.log(info('Fetching organizations...'));
27
27
 
28
28
  try {
29
- const result = await client.getStores();
29
+ const { organizations } = await client.getOrganizations();
30
30
 
31
- if (result.stores.length === 0) {
32
- info('No stores found. Create one with: jolly store create --name <name>');
31
+ if (organizations.length === 0) {
32
+ console.log(info('No organizations found. Create one at https://cloud.saleor.io'));
33
33
  return;
34
34
  }
35
35
 
36
- success(`Found ${result.stores.length} store(s):\n`);
37
- for (const store of result.stores) {
38
- console.log(` ${store.name} (${store.id})`);
39
- console.log(` Region: ${store.region}`);
40
- console.log(` Created: ${new Date(store.created_at).toLocaleDateString()}`);
36
+ console.log(success(`Found ${organizations.length} organization(s):\n`));
37
+ for (const org of organizations) {
38
+ console.log(` ${org.name} (${org.slug})`);
39
+ console.log(` Email: ${org.owner_email}`);
40
+ console.log(` Created: ${new Date(org.created).toLocaleDateString()}`);
41
41
  console.log();
42
42
  }
43
43
  } catch (err) {
44
- error(`Failed to list stores: ${err}`);
44
+ console.log(error(`Failed to list stores: ${err}`));
45
45
  process.exit(1);
46
46
  }
47
47
  }
48
48
 
49
- export async function createEnvironment(storeId: string, name: string): Promise<void> {
49
+ export async function createEnvironment(organizationSlug: string, name: string): Promise<void> {
50
50
  const token = requireToken();
51
51
  const client = new SaleorCloudClient(token);
52
52
 
53
- info(`Creating environment: ${name} for store ${storeId}...`);
53
+ console.log(info(`Creating environment: ${name} for organization ${organizationSlug}...`));
54
54
 
55
55
  try {
56
- const result = await client.createEnvironment(storeId, name);
57
- success(`Environment created successfully!`);
58
- info(`Environment ID: ${result.environment.id}`);
59
- info(`API URL will be available at: https://${result.environment.id}.saleor.cloud`);
56
+ const { environment } = await client.createEnvironment(organizationSlug, 'default', name, 'us-east-1');
57
+ console.log(success(`Environment created successfully!`));
58
+ console.log(info(`Environment key: ${environment.key}`));
59
+ console.log(info(`Domain: ${environment.domain}`));
60
60
  } catch (err) {
61
- error(`Failed to create environment: ${err}`);
61
+ console.log(error(`Failed to create environment: ${err}`));
62
62
  process.exit(1);
63
63
  }
64
64
  }
@@ -36,67 +36,72 @@ describe('Command Handlers', () => {
36
36
  describe('Store Handlers', () => {
37
37
  Given('a valid token and successful API', () => {
38
38
  When('calling createStore', () => {
39
- Then('it should call POST /stores with name and region', async () => {
39
+ Then('it should call POST /organizations/:slug/projects with name and region', async () => {
40
40
  const fetchMock = mockFetch({
41
- '/stores': { store: fixtures.store },
41
+ '/organizations': { organizations: [fixtures.organization] },
42
+ '/organizations/my-org/projects': { project: fixtures.project },
42
43
  });
43
44
 
44
45
  const { createStore } = await import('../commands/store');
45
46
  await createStore('my-store', 'us-east-1');
46
47
 
47
- expect(fetchMock).toHaveBeenCalledTimes(1);
48
- const [url, opts] = fetchMock.mock.calls[0];
49
- expect(url).toContain('/stores');
50
- expect(opts.method).toBe('POST');
51
- expect(JSON.parse(opts.body)).toEqual({ name: 'my-store', region: 'us-east-1' });
48
+ expect(fetchMock).toHaveBeenCalled();
49
+ const createCall = fetchMock.mock.calls.find(([url]: [string]) =>
50
+ (url as string).includes('/organizations/my-org/projects')
51
+ );
52
+ expect(createCall).toBeDefined();
53
+ expect(createCall?.[1].method).toBe('POST');
54
+ expect(JSON.parse(createCall?.[1].body)).toEqual({ name: 'my-store', region: 'us-east-1' });
52
55
  });
53
56
  });
54
57
 
55
58
  When('calling listStores with results', () => {
56
- Then('it should display store names and IDs', async () => {
59
+ Then('it should display organization names and slugs', async () => {
57
60
  mockFetch({
58
- '/stores': { stores: [fixtures.store, fixtures.store2] },
61
+ '/organizations': { organizations: [fixtures.organization, fixtures.organization2] },
59
62
  });
60
63
 
61
64
  const { listStores } = await import('../commands/store');
62
65
  await listStores();
63
66
 
64
67
  const allOutput = console_.logs.join('\n');
65
- expect(allOutput).toContain('my-store');
66
- expect(allOutput).toContain('store-1');
67
- expect(allOutput).toContain('other-store');
68
- expect(allOutput).toContain('store-2');
68
+ expect(allOutput).toContain('My Organization');
69
+ expect(allOutput).toContain('my-org');
70
+ expect(allOutput).toContain('Other Organization');
71
+ expect(allOutput).toContain('other-org');
69
72
  });
70
73
  });
71
74
 
72
75
  When('calling listStores with empty results', () => {
73
76
  Then('it should display a helpful message', async () => {
74
77
  mockFetch({
75
- '/stores': { stores: [] },
78
+ '/organizations': { organizations: [] },
76
79
  });
77
80
 
78
81
  const { listStores } = await import('../commands/store');
79
82
  await listStores();
80
83
 
81
84
  const infoMessages = tuiCalls.filter(c => c.fn === 'info').map(c => c.msg).join('\n');
82
- expect(infoMessages).toContain('No stores found');
85
+ expect(infoMessages).toContain('No organizations found');
83
86
  });
84
87
  });
85
88
 
86
89
  When('calling createEnvironment', () => {
87
- Then('it should call POST /stores/:id/environments', async () => {
90
+ Then('it should call POST /organizations/:slug/projects/:slug/environments', async () => {
88
91
  const fetchMock = mockFetch({
89
- '/stores/store-1/environments': { environment: fixtures.environment },
92
+ '/organizations/my-org/projects/default/environments': { environment: fixtures.environment },
90
93
  });
91
94
 
92
95
  const { createEnvironment } = await import('../commands/store');
93
- await createEnvironment('store-1', 'staging');
94
-
95
- expect(fetchMock).toHaveBeenCalledTimes(1);
96
- const [url, opts] = fetchMock.mock.calls[0];
97
- expect(url).toContain('/stores/store-1/environments');
98
- expect(opts.method).toBe('POST');
99
- expect(JSON.parse(opts.body)).toEqual({ name: 'staging' });
96
+ await createEnvironment('my-org', 'staging');
97
+
98
+ expect(fetchMock).toHaveBeenCalled();
99
+ const createCall = fetchMock.mock.calls.find(([url]: [string]) =>
100
+ (url as string).includes('/organizations/my-org/projects/default/environments')
101
+ );
102
+ expect(createCall).toBeDefined();
103
+ expect(createCall?.[1].method).toBe('POST');
104
+ expect(JSON.parse(createCall?.[1].body)).toEqual({ name: 'staging', region: 'us-east-1' });
100
105
  });
101
106
  });
102
107
  });
@@ -137,7 +142,7 @@ describe('Command Handlers', () => {
137
142
  const { createEnvironment } = await import('../commands/store');
138
143
 
139
144
  try {
140
- await createEnvironment('bad-store', 'staging');
145
+ await createEnvironment('bad-org', 'staging');
141
146
  } catch {}
142
147
 
143
148
  expect(exitSpy).toHaveBeenCalledWith(1);
@@ -194,15 +199,15 @@ describe('Command Handlers', () => {
194
199
  When('calling createApp', () => {
195
200
  Then('it should register the hosted app via API', async () => {
196
201
  const fetchMock = mockFetch({
197
- '/environments/env-1/apps': { app: fixtures.app },
202
+ '/environments/staging/apps': { app: fixtures.app },
198
203
  });
199
204
 
200
205
  const { createApp } = await import('../commands/app');
201
- await createApp('pay-app', 'payment', 'env-1', 'dummy');
206
+ await createApp('pay-app', 'payment', 'staging', 'dummy');
202
207
 
203
208
  expect(fetchMock).toHaveBeenCalledTimes(1);
204
209
  const [url, opts] = fetchMock.mock.calls[0];
205
- expect(url).toContain('/environments/env-1/apps');
210
+ expect(url).toContain('/environments/staging/apps');
206
211
  expect(opts.method).toBe('POST');
207
212
  });
208
213
  });
@@ -216,7 +221,7 @@ describe('Command Handlers', () => {
216
221
  const { createApp } = await import('../commands/app');
217
222
 
218
223
  try {
219
- await createApp('pay-app', 'payment', 'env-1', 'dummy');
224
+ await createApp('pay-app', 'payment', 'staging', 'dummy');
220
225
  } catch {}
221
226
 
222
227
  expect(exitSpy).toHaveBeenCalledWith(1);
@@ -36,30 +36,31 @@ describe('End-to-End Flows', () => {
36
36
  describe('Store Creation Flow', () => {
37
37
  Given('a valid token and working API', () => {
38
38
  When('creating a store end-to-end', () => {
39
- Then('it should call API and display store ID and dashboard URL', async () => {
39
+ Then('it should call API and display project slug and dashboard URL', async () => {
40
40
  const fetchMock = mockFetch({
41
- '/stores': { store: fixtures.store },
41
+ '/organizations': { organizations: [fixtures.organization] },
42
+ '/organizations/my-org/projects': { project: fixtures.project },
42
43
  });
43
44
 
44
45
  const { createStore } = await import('../commands/store');
45
46
  await createStore('my-store', 'us-east-1');
46
47
 
47
- expect(fetchMock).toHaveBeenCalledTimes(1);
48
+ expect(fetchMock).toHaveBeenCalled();
48
49
 
49
50
  const allTui = tuiCalls.map(c => c.msg).join('\n');
50
- expect(allTui).toContain('store-1');
51
- expect(allTui).toContain('cloud.saleor.io/stores/store-1');
51
+ expect(allTui).toContain('my-store');
52
+ expect(allTui).toContain('cloud.saleor.io');
52
53
  });
53
54
  });
54
55
  });
55
56
  });
56
57
 
57
58
  describe('Store List Flow', () => {
58
- Given('a valid token and multiple stores', () => {
59
+ Given('a valid token and multiple organizations', () => {
59
60
  When('listing stores end-to-end', () => {
60
- Then('it should display all stores with regions and dates', async () => {
61
+ Then('it should display all organizations with emails and dates', async () => {
61
62
  mockFetch({
62
- '/stores': { stores: [fixtures.store, fixtures.store2] },
63
+ '/organizations': { organizations: [fixtures.organization, fixtures.organization2] },
63
64
  });
64
65
 
65
66
  const { listStores } = await import('../commands/store');
@@ -67,31 +68,31 @@ describe('End-to-End Flows', () => {
67
68
 
68
69
  const allTui = tuiCalls.map(c => c.msg).join('\n');
69
70
  const allConsole = console_.logs.join('\n');
70
- expect(allTui).toContain('2 store(s)');
71
- expect(allConsole).toContain('my-store');
72
- expect(allConsole).toContain('us-east-1');
73
- expect(allConsole).toContain('other-store');
74
- expect(allConsole).toContain('eu-west-1');
71
+ expect(allTui).toContain('2 organization(s)');
72
+ expect(allConsole).toContain('My Organization');
73
+ expect(allConsole).toContain('owner@example.com');
74
+ expect(allConsole).toContain('Other Organization');
75
+ expect(allConsole).toContain('other@example.com');
75
76
  });
76
77
  });
77
78
  });
78
79
  });
79
80
 
80
81
  describe('Environment Creation Flow', () => {
81
- Given('a valid token and existing store', () => {
82
+ Given('a valid token and existing organization', () => {
82
83
  When('creating an environment end-to-end', () => {
83
- Then('it should call API and display environment ID and URL', async () => {
84
+ Then('it should call API and display environment key and domain', async () => {
84
85
  const fetchMock = mockFetch({
85
- '/stores/store-1/environments': { environment: fixtures.environment },
86
+ '/organizations/my-org/projects/default/environments': { environment: fixtures.environment },
86
87
  });
87
88
 
88
89
  const { createEnvironment } = await import('../commands/store');
89
- await createEnvironment('store-1', 'staging');
90
+ await createEnvironment('my-org', 'staging');
90
91
 
91
- expect(fetchMock).toHaveBeenCalledTimes(1);
92
+ expect(fetchMock).toHaveBeenCalled();
92
93
 
93
94
  const allTui = tuiCalls.map(c => c.msg).join('\n');
94
- expect(allTui).toContain('env-1');
95
+ expect(allTui).toContain('staging');
95
96
  expect(allTui).toContain('saleor.cloud');
96
97
  });
97
98
  });
@@ -103,11 +104,11 @@ describe('End-to-End Flows', () => {
103
104
  When('creating a payment app with environment', () => {
104
105
  Then('it should register via API and display app ID', async () => {
105
106
  const fetchMock = mockFetch({
106
- '/environments/env-1/apps': { app: fixtures.app },
107
+ '/environments/staging/apps': { app: fixtures.app },
107
108
  });
108
109
 
109
110
  const { createApp } = await import('../commands/app');
110
- await createApp('my-payment', 'payment', 'env-1', 'stripe');
111
+ await createApp('my-payment', 'payment', 'staging', 'stripe');
111
112
 
112
113
  expect(fetchMock).toHaveBeenCalledTimes(1);
113
114
 
@@ -138,7 +139,7 @@ describe('End-to-End Flows', () => {
138
139
  When('attempting any store operation', () => {
139
140
  Then('it should fail fast with auth error before API call', async () => {
140
141
  withoutToken();
141
- const fetchMock = mockFetch({ '/stores': { stores: [] } });
142
+ const fetchMock = mockFetch({ '/organizations': { organizations: [] } });
142
143
 
143
144
  const { listStores } = await import('../commands/store');
144
145
 
@@ -173,37 +174,55 @@ describe('End-to-End Flows', () => {
173
174
 
174
175
  describe('API Client Request Construction', () => {
175
176
  Given('a SaleorCloudClient', () => {
176
- When('creating a store', () => {
177
+ When('listing organizations', () => {
178
+ Then('it should send correct URL, method, and headers', async () => {
179
+ const fetchMock = mockFetch({
180
+ '/organizations': { organizations: [fixtures.organization] },
181
+ });
182
+
183
+ const { SaleorCloudClient } = await import('../api/client');
184
+ const client = new SaleorCloudClient(fixtures.token);
185
+ await client.getOrganizations();
186
+
187
+ expect(fetchMock).toHaveBeenCalledTimes(1);
188
+ const [url, opts] = fetchMock.mock.calls[0];
189
+ expect(url).toBe('https://cloud.saleor.io/api/organizations');
190
+ expect(opts.method).toBe('GET');
191
+ expect(opts.headers.Authorization).toBe(`Token ${fixtures.token}`);
192
+ });
193
+ });
194
+
195
+ When('creating a project', () => {
177
196
  Then('it should send correct URL, method, headers, and body', async () => {
178
197
  const fetchMock = mockFetch({
179
- '/stores': { store: fixtures.store },
198
+ '/organizations/default/projects': { project: fixtures.project },
180
199
  });
181
200
 
182
201
  const { SaleorCloudClient } = await import('../api/client');
183
202
  const client = new SaleorCloudClient(fixtures.token);
184
- await client.createStore('test-store', 'eu-west-1');
203
+ await client.createProject('default', 'test-store', 'eu-west-1');
185
204
 
186
205
  expect(fetchMock).toHaveBeenCalledTimes(1);
187
206
  const [url, opts] = fetchMock.mock.calls[0];
188
- expect(url).toBe('https://cloud.saleor.io/api/stores');
207
+ expect(url).toBe('https://cloud.saleor.io/api/organizations/default/projects');
189
208
  expect(opts.method).toBe('POST');
190
- expect(opts.headers.Authorization).toBe(`Bearer ${fixtures.token}`);
209
+ expect(opts.headers.Authorization).toBe(`Token ${fixtures.token}`);
191
210
  expect(JSON.parse(opts.body)).toEqual({ name: 'test-store', region: 'eu-west-1' });
192
211
  });
193
212
  });
194
213
 
195
214
  When('registering an app', () => {
196
- Then('it should send correct URL with environment ID', async () => {
215
+ Then('it should send correct URL with environment key', async () => {
197
216
  const fetchMock = mockFetch({
198
- '/environments/env-1/apps': { app: fixtures.app },
217
+ '/environments/staging/apps': { app: fixtures.app },
199
218
  });
200
219
 
201
220
  const { SaleorCloudClient } = await import('../api/client');
202
221
  const client = new SaleorCloudClient(fixtures.token);
203
- await client.registerApp('env-1', 'payment', 'my-app');
222
+ await client.registerApp('staging', 'payment', 'my-app');
204
223
 
205
224
  const [url, opts] = fetchMock.mock.calls[0];
206
- expect(url).toBe('https://cloud.saleor.io/api/environments/env-1/apps');
225
+ expect(url).toBe('https://cloud.saleor.io/api/environments/staging/apps');
207
226
  expect(JSON.parse(opts.body)).toEqual({ type: 'payment', name: 'my-app' });
208
227
  });
209
228
  });