@dk/jolly 0.1.6 → 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/dist/bootstrap.js +38 -15
- package/dist/index.js +52 -29
- package/package.json +1 -1
- package/src/api/client.ts +93 -20
- package/src/commands/store.ts +16 -16
- package/src/test/command-handlers.test.ts +34 -29
- package/src/test/e2e-flows.test.ts +50 -31
- package/src/test/entry-points.test.ts +4 -1
- package/src/test/error-handling.test.ts +8 -8
- package/src/test/mocks.ts +55 -15
package/dist/bootstrap.js
CHANGED
|
@@ -11,43 +11,66 @@ class SaleorCloudClient {
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
async request(endpoint, options) {
|
|
14
|
-
const
|
|
14
|
+
const mergedOptions = {
|
|
15
|
+
method: options?.method || "GET",
|
|
15
16
|
...options,
|
|
16
17
|
headers: {
|
|
17
|
-
Authorization: `
|
|
18
|
+
Authorization: `Token ${this.token}`,
|
|
18
19
|
"Content-Type": "application/json",
|
|
19
20
|
...options?.headers
|
|
20
21
|
}
|
|
21
|
-
}
|
|
22
|
+
};
|
|
23
|
+
const response = await fetch(`${this.baseUrl}${endpoint}`, mergedOptions);
|
|
22
24
|
if (!response.ok) {
|
|
23
|
-
|
|
25
|
+
const body = await response.text();
|
|
26
|
+
throw new Error(`API error: ${response.status} ${response.statusText} - ${body}`);
|
|
24
27
|
}
|
|
25
28
|
return response.json();
|
|
26
29
|
}
|
|
27
|
-
async
|
|
28
|
-
return this.request("/
|
|
30
|
+
async getOrganizations() {
|
|
31
|
+
return this.request("/organizations");
|
|
29
32
|
}
|
|
30
|
-
async
|
|
31
|
-
return this.request(
|
|
33
|
+
async getProjects(organizationSlug) {
|
|
34
|
+
return this.request(`/organizations/${organizationSlug}/projects`);
|
|
35
|
+
}
|
|
36
|
+
async createProject(organizationSlug, name, region) {
|
|
37
|
+
return this.request(`/organizations/${organizationSlug}/projects`, {
|
|
32
38
|
method: "POST",
|
|
33
39
|
body: JSON.stringify({ name, region })
|
|
34
40
|
});
|
|
35
41
|
}
|
|
36
|
-
async getEnvironments(
|
|
37
|
-
return this.request(`/
|
|
42
|
+
async getEnvironments(organizationSlug, projectSlug) {
|
|
43
|
+
return this.request(`/organizations/${organizationSlug}/projects/${projectSlug}/environments`);
|
|
38
44
|
}
|
|
39
|
-
async createEnvironment(
|
|
40
|
-
return this.request(`/
|
|
45
|
+
async createEnvironment(organizationSlug, projectSlug, name, region) {
|
|
46
|
+
return this.request(`/organizations/${organizationSlug}/projects/${projectSlug}/environments`, {
|
|
41
47
|
method: "POST",
|
|
42
|
-
body: JSON.stringify({ name })
|
|
48
|
+
body: JSON.stringify({ name, region })
|
|
43
49
|
});
|
|
44
50
|
}
|
|
51
|
+
async getEnvironment(organizationSlug, projectSlug, environmentSlug) {
|
|
52
|
+
return this.request(`/organizations/${organizationSlug}/projects/${projectSlug}/environments/${environmentSlug}`);
|
|
53
|
+
}
|
|
45
54
|
async registerApp(environmentId, appType, name) {
|
|
46
55
|
return this.request(`/environments/${environmentId}/apps`, {
|
|
47
56
|
method: "POST",
|
|
48
57
|
body: JSON.stringify({ type: appType, name })
|
|
49
58
|
});
|
|
50
59
|
}
|
|
60
|
+
async getStores() {
|
|
61
|
+
return this.getOrganizations();
|
|
62
|
+
}
|
|
63
|
+
async createStore(name, region = "us-east-1") {
|
|
64
|
+
const { organizations } = await this.getOrganizations();
|
|
65
|
+
if (organizations.length === 0) {
|
|
66
|
+
throw new Error("No organizations found. Create one at https://cloud.saleor.io");
|
|
67
|
+
}
|
|
68
|
+
return this.createProject(organizations[0].slug, name, region);
|
|
69
|
+
}
|
|
70
|
+
async createEnvironmentFromStore(storeId, name) {
|
|
71
|
+
const { environments } = await this.getEnvironments(storeId, "default");
|
|
72
|
+
return this.createEnvironment(storeId, environments.length > 0 ? environments[0].project?.slug || "default" : "default", name, "default");
|
|
73
|
+
}
|
|
51
74
|
}
|
|
52
75
|
|
|
53
76
|
// src/api/auth.ts
|
|
@@ -129,8 +152,8 @@ async function createStore(name, region) {
|
|
|
129
152
|
try {
|
|
130
153
|
const result = await client.createStore(name, region);
|
|
131
154
|
console.log(success(`Store created successfully!`));
|
|
132
|
-
console.log(info(`
|
|
133
|
-
console.log(info(`Dashboard: https://cloud.saleor.io/
|
|
155
|
+
console.log(info(`Project slug: ${result.project.slug}`));
|
|
156
|
+
console.log(info(`Dashboard: https://cloud.saleor.io/organizations/default/projects/${result.project.slug}`));
|
|
134
157
|
} catch (err) {
|
|
135
158
|
console.log(error(`Failed to create store: ${err}`));
|
|
136
159
|
process.exit(1);
|
package/dist/index.js
CHANGED
|
@@ -33,43 +33,66 @@ class SaleorCloudClient {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
async request(endpoint, options) {
|
|
36
|
-
const
|
|
36
|
+
const mergedOptions = {
|
|
37
|
+
method: options?.method || "GET",
|
|
37
38
|
...options,
|
|
38
39
|
headers: {
|
|
39
|
-
Authorization: `
|
|
40
|
+
Authorization: `Token ${this.token}`,
|
|
40
41
|
"Content-Type": "application/json",
|
|
41
42
|
...options?.headers
|
|
42
43
|
}
|
|
43
|
-
}
|
|
44
|
+
};
|
|
45
|
+
const response = await fetch(`${this.baseUrl}${endpoint}`, mergedOptions);
|
|
44
46
|
if (!response.ok) {
|
|
45
|
-
|
|
47
|
+
const body = await response.text();
|
|
48
|
+
throw new Error(`API error: ${response.status} ${response.statusText} - ${body}`);
|
|
46
49
|
}
|
|
47
50
|
return response.json();
|
|
48
51
|
}
|
|
49
|
-
async
|
|
50
|
-
return this.request("/
|
|
52
|
+
async getOrganizations() {
|
|
53
|
+
return this.request("/organizations");
|
|
51
54
|
}
|
|
52
|
-
async
|
|
53
|
-
return this.request(
|
|
55
|
+
async getProjects(organizationSlug) {
|
|
56
|
+
return this.request(`/organizations/${organizationSlug}/projects`);
|
|
57
|
+
}
|
|
58
|
+
async createProject(organizationSlug, name, region) {
|
|
59
|
+
return this.request(`/organizations/${organizationSlug}/projects`, {
|
|
54
60
|
method: "POST",
|
|
55
61
|
body: JSON.stringify({ name, region })
|
|
56
62
|
});
|
|
57
63
|
}
|
|
58
|
-
async getEnvironments(
|
|
59
|
-
return this.request(`/
|
|
64
|
+
async getEnvironments(organizationSlug, projectSlug) {
|
|
65
|
+
return this.request(`/organizations/${organizationSlug}/projects/${projectSlug}/environments`);
|
|
60
66
|
}
|
|
61
|
-
async createEnvironment(
|
|
62
|
-
return this.request(`/
|
|
67
|
+
async createEnvironment(organizationSlug, projectSlug, name, region) {
|
|
68
|
+
return this.request(`/organizations/${organizationSlug}/projects/${projectSlug}/environments`, {
|
|
63
69
|
method: "POST",
|
|
64
|
-
body: JSON.stringify({ name })
|
|
70
|
+
body: JSON.stringify({ name, region })
|
|
65
71
|
});
|
|
66
72
|
}
|
|
73
|
+
async getEnvironment(organizationSlug, projectSlug, environmentSlug) {
|
|
74
|
+
return this.request(`/organizations/${organizationSlug}/projects/${projectSlug}/environments/${environmentSlug}`);
|
|
75
|
+
}
|
|
67
76
|
async registerApp(environmentId, appType, name) {
|
|
68
77
|
return this.request(`/environments/${environmentId}/apps`, {
|
|
69
78
|
method: "POST",
|
|
70
79
|
body: JSON.stringify({ type: appType, name })
|
|
71
80
|
});
|
|
72
81
|
}
|
|
82
|
+
async getStores() {
|
|
83
|
+
return this.getOrganizations();
|
|
84
|
+
}
|
|
85
|
+
async createStore(name, region = "us-east-1") {
|
|
86
|
+
const { organizations } = await this.getOrganizations();
|
|
87
|
+
if (organizations.length === 0) {
|
|
88
|
+
throw new Error("No organizations found. Create one at https://cloud.saleor.io");
|
|
89
|
+
}
|
|
90
|
+
return this.createProject(organizations[0].slug, name, region);
|
|
91
|
+
}
|
|
92
|
+
async createEnvironmentFromStore(storeId, name) {
|
|
93
|
+
const { environments } = await this.getEnvironments(storeId, "default");
|
|
94
|
+
return this.createEnvironment(storeId, environments.length > 0 ? environments[0].project?.slug || "default" : "default", name, "default");
|
|
95
|
+
}
|
|
73
96
|
}
|
|
74
97
|
|
|
75
98
|
// src/api/auth.ts
|
|
@@ -198,8 +221,8 @@ async function createStore(name, region) {
|
|
|
198
221
|
try {
|
|
199
222
|
const result = await client.createStore(name, region);
|
|
200
223
|
console.log(success(`Store created successfully!`));
|
|
201
|
-
console.log(info(`
|
|
202
|
-
console.log(info(`Dashboard: https://cloud.saleor.io/
|
|
224
|
+
console.log(info(`Project slug: ${result.project.slug}`));
|
|
225
|
+
console.log(info(`Dashboard: https://cloud.saleor.io/organizations/default/projects/${result.project.slug}`));
|
|
203
226
|
} catch (err) {
|
|
204
227
|
console.log(error(`Failed to create store: ${err}`));
|
|
205
228
|
process.exit(1);
|
|
@@ -208,19 +231,19 @@ async function createStore(name, region) {
|
|
|
208
231
|
async function listStores() {
|
|
209
232
|
const token = requireToken();
|
|
210
233
|
const client = new SaleorCloudClient(token);
|
|
211
|
-
console.log(info("Fetching
|
|
234
|
+
console.log(info("Fetching organizations..."));
|
|
212
235
|
try {
|
|
213
|
-
const
|
|
214
|
-
if (
|
|
215
|
-
console.log(info("No
|
|
236
|
+
const { organizations } = await client.getOrganizations();
|
|
237
|
+
if (organizations.length === 0) {
|
|
238
|
+
console.log(info("No organizations found. Create one at https://cloud.saleor.io"));
|
|
216
239
|
return;
|
|
217
240
|
}
|
|
218
|
-
console.log(success(`Found ${
|
|
241
|
+
console.log(success(`Found ${organizations.length} organization(s):
|
|
219
242
|
`));
|
|
220
|
-
for (const
|
|
221
|
-
console.log(` ${
|
|
222
|
-
console.log(`
|
|
223
|
-
console.log(` Created: ${new Date(
|
|
243
|
+
for (const org of organizations) {
|
|
244
|
+
console.log(` ${org.name} (${org.slug})`);
|
|
245
|
+
console.log(` Email: ${org.owner_email}`);
|
|
246
|
+
console.log(` Created: ${new Date(org.created).toLocaleDateString()}`);
|
|
224
247
|
console.log();
|
|
225
248
|
}
|
|
226
249
|
} catch (err) {
|
|
@@ -228,15 +251,15 @@ async function listStores() {
|
|
|
228
251
|
process.exit(1);
|
|
229
252
|
}
|
|
230
253
|
}
|
|
231
|
-
async function createEnvironment(
|
|
254
|
+
async function createEnvironment(organizationSlug, name) {
|
|
232
255
|
const token = requireToken();
|
|
233
256
|
const client = new SaleorCloudClient(token);
|
|
234
|
-
console.log(info(`Creating environment: ${name} for
|
|
257
|
+
console.log(info(`Creating environment: ${name} for organization ${organizationSlug}...`));
|
|
235
258
|
try {
|
|
236
|
-
const
|
|
259
|
+
const { environment } = await client.createEnvironment(organizationSlug, "default", name, "us-east-1");
|
|
237
260
|
console.log(success(`Environment created successfully!`));
|
|
238
|
-
console.log(info(`Environment
|
|
239
|
-
console.log(info(`
|
|
261
|
+
console.log(info(`Environment key: ${environment.key}`));
|
|
262
|
+
console.log(info(`Domain: ${environment.domain}`));
|
|
240
263
|
} catch (err) {
|
|
241
264
|
console.log(error(`Failed to create environment: ${err}`));
|
|
242
265
|
process.exit(1);
|
package/package.json
CHANGED
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
|
|
13
|
+
const mergedOptions: RequestInit = {
|
|
14
|
+
method: options?.method || 'GET',
|
|
14
15
|
...options,
|
|
15
16
|
headers: {
|
|
16
|
-
'Authorization': `
|
|
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
|
-
|
|
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
|
-
|
|
30
|
-
|
|
33
|
+
// Organizations
|
|
34
|
+
async getOrganizations() {
|
|
35
|
+
return this.request<{ organizations: Organization[] }>('/organizations');
|
|
31
36
|
}
|
|
32
37
|
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
41
|
-
|
|
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(
|
|
45
|
-
return this.request<{ environment: Environment }>(
|
|
46
|
-
|
|
47
|
-
|
|
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
|
|
60
|
-
|
|
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
|
-
|
|
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
|
-
|
|
142
|
+
region: string;
|
|
70
143
|
created_at: string;
|
|
71
144
|
}
|
|
72
145
|
|
package/src/commands/store.ts
CHANGED
|
@@ -11,8 +11,8 @@ export async function createStore(name: string, region: string): Promise<void> {
|
|
|
11
11
|
try {
|
|
12
12
|
const result = await client.createStore(name, region);
|
|
13
13
|
console.log(success(`Store created successfully!`));
|
|
14
|
-
console.log(info(`
|
|
15
|
-
console.log(info(`Dashboard: https://cloud.saleor.io/
|
|
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
17
|
console.log(error(`Failed to create store: ${err}`));
|
|
18
18
|
process.exit(1);
|
|
@@ -23,21 +23,21 @@ export async function listStores(): Promise<void> {
|
|
|
23
23
|
const token = requireToken();
|
|
24
24
|
const client = new SaleorCloudClient(token);
|
|
25
25
|
|
|
26
|
-
console.log(info('Fetching
|
|
26
|
+
console.log(info('Fetching organizations...'));
|
|
27
27
|
|
|
28
28
|
try {
|
|
29
|
-
const
|
|
29
|
+
const { organizations } = await client.getOrganizations();
|
|
30
30
|
|
|
31
|
-
if (
|
|
32
|
-
console.log(info('No
|
|
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
|
-
console.log(success(`Found ${
|
|
37
|
-
for (const
|
|
38
|
-
console.log(` ${
|
|
39
|
-
console.log(`
|
|
40
|
-
console.log(` Created: ${new Date(
|
|
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) {
|
|
@@ -46,17 +46,17 @@ export async function listStores(): Promise<void> {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
export async function createEnvironment(
|
|
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
|
-
console.log(info(`Creating environment: ${name} for
|
|
53
|
+
console.log(info(`Creating environment: ${name} for organization ${organizationSlug}...`));
|
|
54
54
|
|
|
55
55
|
try {
|
|
56
|
-
const
|
|
56
|
+
const { environment } = await client.createEnvironment(organizationSlug, 'default', name, 'us-east-1');
|
|
57
57
|
console.log(success(`Environment created successfully!`));
|
|
58
|
-
console.log(info(`Environment
|
|
59
|
-
console.log(info(`
|
|
58
|
+
console.log(info(`Environment key: ${environment.key}`));
|
|
59
|
+
console.log(info(`Domain: ${environment.domain}`));
|
|
60
60
|
} catch (err) {
|
|
61
61
|
console.log(error(`Failed to create environment: ${err}`));
|
|
62
62
|
process.exit(1);
|
|
@@ -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 /
|
|
39
|
+
Then('it should call POST /organizations/:slug/projects with name and region', async () => {
|
|
40
40
|
const fetchMock = mockFetch({
|
|
41
|
-
'/
|
|
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).
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
expect(
|
|
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
|
|
59
|
+
Then('it should display organization names and slugs', async () => {
|
|
57
60
|
mockFetch({
|
|
58
|
-
'/
|
|
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('
|
|
66
|
-
expect(allOutput).toContain('
|
|
67
|
-
expect(allOutput).toContain('
|
|
68
|
-
expect(allOutput).toContain('
|
|
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
|
-
'/
|
|
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
|
|
85
|
+
expect(infoMessages).toContain('No organizations found');
|
|
83
86
|
});
|
|
84
87
|
});
|
|
85
88
|
|
|
86
89
|
When('calling createEnvironment', () => {
|
|
87
|
-
Then('it should call POST /
|
|
90
|
+
Then('it should call POST /organizations/:slug/projects/:slug/environments', async () => {
|
|
88
91
|
const fetchMock = mockFetch({
|
|
89
|
-
'/
|
|
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('
|
|
94
|
-
|
|
95
|
-
expect(fetchMock).
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
expect(
|
|
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-
|
|
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/
|
|
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', '
|
|
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/
|
|
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', '
|
|
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
|
|
39
|
+
Then('it should call API and display project slug and dashboard URL', async () => {
|
|
40
40
|
const fetchMock = mockFetch({
|
|
41
|
-
'/
|
|
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).
|
|
48
|
+
expect(fetchMock).toHaveBeenCalled();
|
|
48
49
|
|
|
49
50
|
const allTui = tuiCalls.map(c => c.msg).join('\n');
|
|
50
|
-
expect(allTui).toContain('store
|
|
51
|
-
expect(allTui).toContain('cloud.saleor.io
|
|
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
|
|
59
|
+
Given('a valid token and multiple organizations', () => {
|
|
59
60
|
When('listing stores end-to-end', () => {
|
|
60
|
-
Then('it should display all
|
|
61
|
+
Then('it should display all organizations with emails and dates', async () => {
|
|
61
62
|
mockFetch({
|
|
62
|
-
'/
|
|
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
|
|
71
|
-
expect(allConsole).toContain('
|
|
72
|
-
expect(allConsole).toContain('
|
|
73
|
-
expect(allConsole).toContain('
|
|
74
|
-
expect(allConsole).toContain('
|
|
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
|
|
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
|
|
84
|
+
Then('it should call API and display environment key and domain', async () => {
|
|
84
85
|
const fetchMock = mockFetch({
|
|
85
|
-
'/
|
|
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('
|
|
90
|
+
await createEnvironment('my-org', 'staging');
|
|
90
91
|
|
|
91
|
-
expect(fetchMock).
|
|
92
|
+
expect(fetchMock).toHaveBeenCalled();
|
|
92
93
|
|
|
93
94
|
const allTui = tuiCalls.map(c => c.msg).join('\n');
|
|
94
|
-
expect(allTui).toContain('
|
|
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/
|
|
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', '
|
|
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({ '/
|
|
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('
|
|
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
|
-
'/
|
|
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.
|
|
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/
|
|
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(`
|
|
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
|
|
215
|
+
Then('it should send correct URL with environment key', async () => {
|
|
197
216
|
const fetchMock = mockFetch({
|
|
198
|
-
'/environments/
|
|
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('
|
|
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/
|
|
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
|
});
|
|
@@ -50,7 +50,10 @@ describe('Entry Points', () => {
|
|
|
50
50
|
When('bootstrap calls createStore', () => {
|
|
51
51
|
Then('it should pass project name and default region', async () => {
|
|
52
52
|
withToken();
|
|
53
|
-
mockFetch({
|
|
53
|
+
mockFetch({
|
|
54
|
+
'/organizations': { organizations: [fixtures.organization] },
|
|
55
|
+
'/organizations/my-org/projects': { project: fixtures.project },
|
|
56
|
+
});
|
|
54
57
|
|
|
55
58
|
const { createStore } = await import('../commands/store');
|
|
56
59
|
await createStore('my-project', 'us-east-1');
|
|
@@ -76,7 +76,7 @@ describe('Error Handling', () => {
|
|
|
76
76
|
const { SaleorCloudClient } = await import('../api/client');
|
|
77
77
|
const client = new SaleorCloudClient(fixtures.token);
|
|
78
78
|
|
|
79
|
-
await expect(client.
|
|
79
|
+
await expect(client.getOrganizations()).rejects.toThrow('401');
|
|
80
80
|
});
|
|
81
81
|
});
|
|
82
82
|
|
|
@@ -87,7 +87,7 @@ describe('Error Handling', () => {
|
|
|
87
87
|
const { SaleorCloudClient } = await import('../api/client');
|
|
88
88
|
const client = new SaleorCloudClient(fixtures.token);
|
|
89
89
|
|
|
90
|
-
await expect(client.
|
|
90
|
+
await expect(client.createProject('test-org', 'test', 'us-east-1')).rejects.toThrow('403');
|
|
91
91
|
});
|
|
92
92
|
});
|
|
93
93
|
|
|
@@ -98,7 +98,7 @@ describe('Error Handling', () => {
|
|
|
98
98
|
const { SaleorCloudClient } = await import('../api/client');
|
|
99
99
|
const client = new SaleorCloudClient(fixtures.token);
|
|
100
100
|
|
|
101
|
-
await expect(client.getEnvironments('
|
|
101
|
+
await expect(client.getEnvironments('test-org', 'test-project')).rejects.toThrow('500');
|
|
102
102
|
});
|
|
103
103
|
});
|
|
104
104
|
|
|
@@ -109,7 +109,7 @@ describe('Error Handling', () => {
|
|
|
109
109
|
const { SaleorCloudClient } = await import('../api/client');
|
|
110
110
|
const client = new SaleorCloudClient(fixtures.token);
|
|
111
111
|
|
|
112
|
-
await expect(client.createEnvironment('bad-
|
|
112
|
+
await expect(client.createEnvironment('bad-org', 'bad-project', 'staging', 'us-east-1')).rejects.toThrow('404');
|
|
113
113
|
});
|
|
114
114
|
});
|
|
115
115
|
});
|
|
@@ -118,17 +118,17 @@ describe('Error Handling', () => {
|
|
|
118
118
|
describe('API Request Headers', () => {
|
|
119
119
|
Given('a SaleorCloudClient with a token', () => {
|
|
120
120
|
When('making any request', () => {
|
|
121
|
-
Then('it should include Authorization
|
|
121
|
+
Then('it should include Authorization Token header', async () => {
|
|
122
122
|
const fetchMock = mockFetch({
|
|
123
|
-
'/
|
|
123
|
+
'/organizations': { organizations: [] },
|
|
124
124
|
});
|
|
125
125
|
|
|
126
126
|
const { SaleorCloudClient } = await import('../api/client');
|
|
127
127
|
const client = new SaleorCloudClient(fixtures.token);
|
|
128
|
-
await client.
|
|
128
|
+
await client.getOrganizations();
|
|
129
129
|
|
|
130
130
|
const [, opts] = fetchMock.mock.calls[0];
|
|
131
|
-
expect(opts.headers.Authorization).toBe(`
|
|
131
|
+
expect(opts.headers.Authorization).toBe(`Token ${fixtures.token}`);
|
|
132
132
|
expect(opts.headers['Content-Type']).toBe('application/json');
|
|
133
133
|
});
|
|
134
134
|
});
|
package/src/test/mocks.ts
CHANGED
|
@@ -1,44 +1,84 @@
|
|
|
1
1
|
import { mock, spyOn } from 'bun:test';
|
|
2
|
-
import type {
|
|
2
|
+
import type { Organization, Project, Environment, App } from '../api/client';
|
|
3
3
|
|
|
4
4
|
export const fixtures = {
|
|
5
5
|
token: 'test-token-abc123',
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
// New API structure: Organizations → Projects → Environments
|
|
8
|
+
organization: {
|
|
9
|
+
slug: 'my-org',
|
|
10
|
+
name: 'My Organization',
|
|
11
|
+
created: '2025-01-01T00:00:00Z',
|
|
12
|
+
owner_email: 'owner@example.com',
|
|
13
|
+
company_name: 'My Company',
|
|
14
|
+
} satisfies Organization,
|
|
15
|
+
|
|
16
|
+
organization2: {
|
|
17
|
+
slug: 'other-org',
|
|
18
|
+
name: 'Other Organization',
|
|
19
|
+
created: '2025-06-15T00:00:00Z',
|
|
20
|
+
owner_email: 'other@example.com',
|
|
21
|
+
} satisfies Organization,
|
|
22
|
+
|
|
23
|
+
project: {
|
|
24
|
+
slug: 'my-store',
|
|
9
25
|
name: 'my-store',
|
|
10
26
|
region: 'us-east-1',
|
|
11
|
-
|
|
12
|
-
|
|
27
|
+
created: '2025-01-01T00:00:00Z',
|
|
28
|
+
sandboxes: { count: 2 },
|
|
29
|
+
} satisfies Project,
|
|
13
30
|
|
|
14
|
-
|
|
15
|
-
|
|
31
|
+
project2: {
|
|
32
|
+
slug: 'other-store',
|
|
16
33
|
name: 'other-store',
|
|
17
34
|
region: 'eu-west-1',
|
|
18
|
-
|
|
19
|
-
|
|
35
|
+
created: '2025-06-15T00:00:00Z',
|
|
36
|
+
sandboxes: { count: 1 },
|
|
37
|
+
} satisfies Project,
|
|
20
38
|
|
|
21
39
|
environment: {
|
|
22
|
-
|
|
40
|
+
key: 'staging',
|
|
23
41
|
name: 'staging',
|
|
24
|
-
|
|
25
|
-
|
|
42
|
+
domain: 'staging-my-store.saleor.cloud',
|
|
43
|
+
service: {
|
|
44
|
+
version: '3.15.0',
|
|
45
|
+
type: 'main',
|
|
46
|
+
region: 'us-east-1',
|
|
47
|
+
},
|
|
48
|
+
created: '2025-01-02T00:00:00Z',
|
|
49
|
+
project: { name: 'my-store', slug: 'my-store' },
|
|
26
50
|
} satisfies Environment,
|
|
27
51
|
|
|
28
52
|
app: {
|
|
29
53
|
id: 'app-1',
|
|
30
54
|
name: 'my-app',
|
|
31
55
|
type: 'payment',
|
|
32
|
-
environment_id: '
|
|
56
|
+
environment_id: 'staging',
|
|
33
57
|
} satisfies App,
|
|
58
|
+
|
|
59
|
+
// Backward compat aliases (for tests that haven't been migrated yet)
|
|
60
|
+
store: {
|
|
61
|
+
id: 'my-org',
|
|
62
|
+
name: 'My Organization',
|
|
63
|
+
region: 'us-east-1',
|
|
64
|
+
created_at: '2025-01-01T00:00:00Z',
|
|
65
|
+
},
|
|
66
|
+
store2: {
|
|
67
|
+
id: 'other-org',
|
|
68
|
+
name: 'Other Organization',
|
|
69
|
+
region: 'eu-west-1',
|
|
70
|
+
created_at: '2025-06-15T00:00:00Z',
|
|
71
|
+
},
|
|
34
72
|
};
|
|
35
73
|
|
|
36
74
|
export function mockFetch(routes: Record<string, unknown>): ReturnType<typeof mock> {
|
|
37
75
|
const handler = mock((url: string | URL | Request, init?: RequestInit) => {
|
|
38
76
|
const urlStr = typeof url === 'string' ? url : url instanceof URL ? url.toString() : url.url;
|
|
39
77
|
|
|
40
|
-
|
|
41
|
-
|
|
78
|
+
const sortedEntries = Object.entries(routes).sort((a, b) => b[0].length - a[0].length);
|
|
79
|
+
|
|
80
|
+
for (const [pattern, body] of sortedEntries) {
|
|
81
|
+
if (urlStr.endsWith(pattern)) {
|
|
42
82
|
return Promise.resolve(new Response(JSON.stringify(body), {
|
|
43
83
|
status: 200,
|
|
44
84
|
headers: { 'Content-Type': 'application/json' },
|