@dk/jolly 0.1.11 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,9 @@
1
1
  // Saleor Cloud API client (feature 012 — existing Saleor store connection).
2
2
  //
3
3
  // Pinned by the feature 012 Rule "Existing-store automation principles":
4
- // - The Cloud API is at https://cloud.saleor.io/platform/api.
4
+ // - The Cloud API is at https://cloud.saleor.io/platform/api, optionally
5
+ // overridden by JOLLY_SALEOR_CLOUD_API_URL (feature 018 Rule); every Cloud
6
+ // API request honors the override.
5
7
  // Authenticate with `Authorization: Token <token>`.
6
8
  // - Organizations: GET /platform/api/organizations/ returns a list with slug
7
9
  // and environments URL.
@@ -25,7 +27,20 @@
25
27
  // with the Cloud token (Bearer), select an existing local app or create one,
26
28
  // and create an app token via the Saleor GraphQL API.
27
29
 
28
- export const CLOUD_API_BASE = "https://cloud.saleor.io/platform/api";
30
+ const DEFAULT_CLOUD_API_BASE = "https://cloud.saleor.io/platform/api";
31
+
32
+ /**
33
+ * The Cloud API base URL for this request: the JOLLY_SALEOR_CLOUD_API_URL
34
+ * override when set (feature 018 Rule — pointing it elsewhere is the
35
+ * customer's explicit choice), otherwise the first-party default.
36
+ */
37
+ export function cloudApiBase(): string {
38
+ const override = process.env["JOLLY_SALEOR_CLOUD_API_URL"];
39
+ if (override && override.trim().length > 0) {
40
+ return override.trim().replace(/\/+$/, "");
41
+ }
42
+ return DEFAULT_CLOUD_API_BASE;
43
+ }
29
44
 
30
45
  const POLL_INTERVAL_MS = 5_000;
31
46
  const POLL_TIMEOUT_MS = 480_000; // stay under the harness's CLI timeout
@@ -70,7 +85,7 @@ export interface CloudOrganization {
70
85
  export async function listOrganizations(
71
86
  token: string,
72
87
  ): Promise<CloudOrganization[]> {
73
- const response = await cloudFetch(`${CLOUD_API_BASE}/organizations/`, token);
88
+ const response = await cloudFetch(`${cloudApiBase()}/organizations/`, token);
74
89
  if (!response.ok) {
75
90
  throw new CloudApiError(
76
91
  `Failed to list organizations: HTTP ${response.status} ${await response.text()}`,
@@ -97,7 +112,7 @@ export async function listProjects(
97
112
  organizationSlug: string,
98
113
  ): Promise<CloudProject[]> {
99
114
  const response = await cloudFetch(
100
- `${CLOUD_API_BASE}/organizations/${organizationSlug}/projects/`,
115
+ `${cloudApiBase()}/organizations/${organizationSlug}/projects/`,
101
116
  token,
102
117
  );
103
118
  if (!response.ok) {
@@ -117,7 +132,7 @@ export async function createProject(
117
132
  body: { name: string; plan: string; region: string },
118
133
  ): Promise<CloudProject> {
119
134
  const response = await cloudFetch(
120
- `${CLOUD_API_BASE}/organizations/${organizationSlug}/projects/`,
135
+ `${cloudApiBase()}/organizations/${organizationSlug}/projects/`,
121
136
  token,
122
137
  { method: "POST", body: JSON.stringify(body) },
123
138
  );
@@ -147,7 +162,7 @@ export async function listProjectServices(
147
162
  projectSlug: string,
148
163
  ): Promise<CloudService[]> {
149
164
  const response = await cloudFetch(
150
- `${CLOUD_API_BASE}/organizations/${organizationSlug}/projects/${projectSlug}/services/`,
165
+ `${cloudApiBase()}/organizations/${organizationSlug}/projects/${projectSlug}/services/`,
151
166
  token,
152
167
  );
153
168
  if (!response.ok) return [];
@@ -202,12 +217,25 @@ export async function createEnvironment(
202
217
  },
203
218
  ): Promise<CloudEnvironment> {
204
219
  const response = await cloudFetch(
205
- `${CLOUD_API_BASE}/organizations/${organizationSlug}/environments/`,
220
+ `${cloudApiBase()}/organizations/${organizationSlug}/environments/`,
206
221
  token,
207
222
  { method: "POST", body: JSON.stringify(body) },
208
223
  );
209
224
  if (!response.ok) {
210
225
  const text = await response.text();
226
+ if (
227
+ response.status >= 400 &&
228
+ response.status < 500 &&
229
+ /domain/i.test(text) &&
230
+ /taken|exists|already|unique|in use|duplicate/i.test(text)
231
+ ) {
232
+ throw new CloudApiError(
233
+ `The Cloud API rejected the environment creation: the domain label ` +
234
+ `"${body.domain_label}" is already taken (HTTP ${response.status}).`,
235
+ "DOMAIN_LABEL_TAKEN",
236
+ response.status,
237
+ );
238
+ }
211
239
  if (
212
240
  response.status >= 400 &&
213
241
  response.status < 500 &&
@@ -236,7 +264,7 @@ export async function listEnvironments(
236
264
  organizationSlug: string,
237
265
  ): Promise<CloudEnvironment[]> {
238
266
  const response = await cloudFetch(
239
- `${CLOUD_API_BASE}/organizations/${organizationSlug}/environments/`,
267
+ `${cloudApiBase()}/organizations/${organizationSlug}/environments/`,
240
268
  token,
241
269
  );
242
270
  if (!response.ok) return [];
@@ -250,7 +278,7 @@ export async function getEnvironment(
250
278
  environmentKey: string,
251
279
  ): Promise<CloudEnvironment | undefined> {
252
280
  const response = await cloudFetch(
253
- `${CLOUD_API_BASE}/organizations/${organizationSlug}/environments/${environmentKey}/`,
281
+ `${cloudApiBase()}/organizations/${organizationSlug}/environments/${environmentKey}/`,
254
282
  token,
255
283
  );
256
284
  if (!response.ok) return undefined;
@@ -266,7 +294,7 @@ export interface TaskStatus {
266
294
 
267
295
  /** The poll URL for a task: GET /platform/api/service/task-status/{task_id}. */
268
296
  export function taskStatusUrl(taskId: string): string {
269
- return `${CLOUD_API_BASE}/service/task-status/${taskId}/`;
297
+ return `${cloudApiBase()}/service/task-status/${taskId}/`;
270
298
  }
271
299
 
272
300
  /**