@prisma/compute-sdk 0.29.0 → 0.31.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.
- package/dist/api-client.d.ts +38 -38
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +44 -43
- package/dist/api-client.js.map +1 -1
- package/dist/callbacks.d.ts +33 -33
- package/dist/callbacks.d.ts.map +1 -1
- package/dist/compute-client.d.ts +66 -66
- package/dist/compute-client.d.ts.map +1 -1
- package/dist/compute-client.js +274 -272
- package/dist/compute-client.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +1 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/normalize.d.ts +3 -1
- package/dist/config/normalize.d.ts.map +1 -1
- package/dist/config/normalize.js +24 -6
- package/dist/config/normalize.js.map +1 -1
- package/dist/config/serialize.d.ts.map +1 -1
- package/dist/config/serialize.js +26 -3
- package/dist/config/serialize.js.map +1 -1
- package/dist/config/types.d.ts +15 -3
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +9 -0
- package/dist/config/types.js.map +1 -1
- package/dist/errors.d.ts +28 -28
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +12 -12
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/log-stream.d.ts +1 -1
- package/dist/log-stream.d.ts.map +1 -1
- package/dist/log-stream.js +1 -1
- package/dist/log-stream.js.map +1 -1
- package/dist/polling.d.ts +4 -4
- package/dist/polling.d.ts.map +1 -1
- package/dist/polling.js +15 -15
- package/dist/polling.js.map +1 -1
- package/dist/types.d.ts +9 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -8
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
- package/src/api-client.ts +57 -63
- package/src/callbacks.ts +34 -34
- package/src/compute-client.ts +433 -413
- package/src/config/index.ts +3 -0
- package/src/config/normalize.ts +58 -5
- package/src/config/serialize.ts +39 -4
- package/src/config/types.ts +27 -2
- package/src/errors.ts +42 -38
- package/src/index.ts +27 -26
- package/src/log-stream.ts +2 -2
- package/src/polling.ts +19 -18
- package/src/types.ts +13 -17
package/dist/compute-client.js
CHANGED
|
@@ -2,8 +2,8 @@ import { Result } from "better-result";
|
|
|
2
2
|
import invariant from "tiny-invariant";
|
|
3
3
|
import { InternalApiClient, readUploadUrl, toDeploymentUrl, } from "./api-client.js";
|
|
4
4
|
import { createArchive } from "./archive.js";
|
|
5
|
-
import { ApiError, ArtifactError, BuildError, CancelledError, DestroyAggregateError, InvalidOptionsError, MissingArgumentError,
|
|
6
|
-
import {
|
|
5
|
+
import { ApiError, ArtifactError, BuildError, CancelledError, DestroyAggregateError, InvalidOptionsError, MissingArgumentError, NoDeploymentsFoundError, NoExistingDeploymentError, } from "./errors.js";
|
|
6
|
+
import { pollDeploymentStatus } from "./polling.js";
|
|
7
7
|
import { REGIONS } from "./types.js";
|
|
8
8
|
import { uploadArtifact } from "./upload-artifact.js";
|
|
9
9
|
const DEFAULT_TIMEOUT_SECONDS = 120;
|
|
@@ -14,9 +14,9 @@ export class ComputeClient {
|
|
|
14
14
|
this.#api = new InternalApiClient(managementApiClient);
|
|
15
15
|
}
|
|
16
16
|
async deploy(options) {
|
|
17
|
-
if (options.skipPromote && options.
|
|
17
|
+
if (options.skipPromote && options.destroyOldDeployment) {
|
|
18
18
|
return Result.err(new InvalidOptionsError({
|
|
19
|
-
message: "
|
|
19
|
+
message: "destroyOldDeployment cannot be combined with skipPromote — the old deployment stays active when promotion is skipped",
|
|
20
20
|
}));
|
|
21
21
|
}
|
|
22
22
|
const self = this;
|
|
@@ -46,16 +46,16 @@ export class ComputeClient {
|
|
|
46
46
|
options.progress?.onArchiveReady?.(archiveBytes.byteLength);
|
|
47
47
|
yield* self.#checkAborted(options.signal);
|
|
48
48
|
const portMapping = options.portMapping ??
|
|
49
|
-
(target.
|
|
49
|
+
(target.isNewApp && artifact.defaultPortMapping
|
|
50
50
|
? artifact.defaultPortMapping
|
|
51
51
|
: undefined);
|
|
52
52
|
yield* Result.await(self.#applyEnvVars(target.projectId, target.branchId, options.envVars, options.signal));
|
|
53
|
-
const createResponse = yield* Result.await(self.#api.
|
|
54
|
-
options.progress?.
|
|
53
|
+
const createResponse = yield* Result.await(self.#api.createAppDeployment(target.appId, portMapping ? { portMapping } : {}, options.signal));
|
|
54
|
+
options.progress?.onDeploymentCreated?.(createResponse.id);
|
|
55
55
|
const uploadUrl = readUploadUrl(createResponse);
|
|
56
56
|
if (!uploadUrl) {
|
|
57
57
|
return Result.err(new ArtifactError({
|
|
58
|
-
message: "
|
|
58
|
+
message: "Deployment creation did not return an upload URL",
|
|
59
59
|
}));
|
|
60
60
|
}
|
|
61
61
|
yield* self.#checkAborted(options.signal);
|
|
@@ -63,55 +63,55 @@ export class ComputeClient {
|
|
|
63
63
|
yield* Result.await(self.#uploadArtifactResult(uploadUrl, archiveBytes, options.signal));
|
|
64
64
|
options.progress?.onUploadComplete?.();
|
|
65
65
|
yield* self.#checkAborted(options.signal);
|
|
66
|
-
const
|
|
67
|
-
const
|
|
66
|
+
const app = yield* Result.await(self.#api.getApp(target.appId, options.signal));
|
|
67
|
+
const previousDeploymentId = app.latestDeploymentId ?? null;
|
|
68
68
|
options.progress?.onStartRequested?.();
|
|
69
|
-
yield* Result.await(self.#api.
|
|
70
|
-
const pollResult = yield* Result.await(
|
|
69
|
+
yield* Result.await(self.#api.startDeployment(createResponse.id, options.signal));
|
|
70
|
+
const pollResult = yield* Result.await(pollDeploymentStatus(self.#api, createResponse.id, {
|
|
71
71
|
targetStatus: "running",
|
|
72
72
|
timeoutSeconds: options.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS,
|
|
73
73
|
pollIntervalMs: options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,
|
|
74
74
|
signal: options.signal,
|
|
75
75
|
onStatusChange: options.progress?.onStatusChange,
|
|
76
76
|
}));
|
|
77
|
-
const
|
|
78
|
-
options.progress?.onRunning?.(
|
|
77
|
+
const deploymentEndpointDomain = toDeploymentUrl(pollResult.previewDomain);
|
|
78
|
+
options.progress?.onRunning?.(deploymentEndpointDomain);
|
|
79
79
|
const resolvedConfig = {
|
|
80
80
|
projectId: target.projectId,
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
appId: target.appId,
|
|
82
|
+
appName: target.appName,
|
|
83
83
|
region: target.region,
|
|
84
|
-
portMapping
|
|
84
|
+
portMapping,
|
|
85
85
|
};
|
|
86
86
|
if (options.skipPromote) {
|
|
87
87
|
return Result.ok({
|
|
88
88
|
projectId: target.projectId,
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
appId: target.appId,
|
|
90
|
+
appName: target.appName,
|
|
91
91
|
region: target.region,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
deploymentId: createResponse.id,
|
|
93
|
+
deploymentEndpointDomain,
|
|
94
|
+
appEndpointDomain: null,
|
|
95
95
|
promoted: false,
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
previousDeploymentId,
|
|
97
|
+
previousDeploymentAction: previousDeploymentId
|
|
98
98
|
? "still-active"
|
|
99
99
|
: null,
|
|
100
100
|
resolvedConfig,
|
|
101
101
|
});
|
|
102
102
|
}
|
|
103
|
-
const switchoverResult = yield* Result.await(self.#promoteAndSwitchover(target.
|
|
103
|
+
const switchoverResult = yield* Result.await(self.#promoteAndSwitchover(target.appId, createResponse.id, previousDeploymentId, options.destroyOldDeployment ?? false, options.signal, options.progress));
|
|
104
104
|
return Result.ok({
|
|
105
105
|
projectId: target.projectId,
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
appId: target.appId,
|
|
107
|
+
appName: target.appName,
|
|
108
108
|
region: target.region,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
deploymentId: createResponse.id,
|
|
110
|
+
deploymentEndpointDomain,
|
|
111
|
+
appEndpointDomain: switchoverResult.appEndpointDomain,
|
|
112
112
|
promoted: true,
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
previousDeploymentId: switchoverResult.previousDeploymentId,
|
|
114
|
+
previousDeploymentAction: switchoverResult.previousDeploymentAction,
|
|
115
115
|
resolvedConfig,
|
|
116
116
|
});
|
|
117
117
|
}
|
|
@@ -125,24 +125,24 @@ export class ComputeClient {
|
|
|
125
125
|
return Result.gen(async function* () {
|
|
126
126
|
yield* self.#checkAborted(options.signal);
|
|
127
127
|
const target = yield* Result.await(self.#resolveDeployTarget(options));
|
|
128
|
-
const
|
|
129
|
-
if (
|
|
130
|
-
return Result.err(new
|
|
128
|
+
const deployments = yield* Result.await(self.#api.listAppDeployments(target.appId, options.signal));
|
|
129
|
+
if (deployments.length === 0) {
|
|
130
|
+
return Result.err(new NoExistingDeploymentError({ appId: target.appId }));
|
|
131
131
|
}
|
|
132
132
|
yield* self.#checkAborted(options.signal);
|
|
133
133
|
yield* Result.await(self.#applyEnvVars(target.projectId, target.branchId, options.envVars, options.signal));
|
|
134
134
|
yield* self.#checkAborted(options.signal);
|
|
135
|
-
const createResponse = yield* Result.await(self.#api.
|
|
135
|
+
const createResponse = yield* Result.await(self.#api.createAppDeployment(target.appId, {
|
|
136
136
|
...(options.portMapping !== undefined
|
|
137
137
|
? { portMapping: options.portMapping }
|
|
138
138
|
: {}),
|
|
139
139
|
skipCodeUpload: true,
|
|
140
140
|
}, options.signal));
|
|
141
|
-
options.progress?.
|
|
141
|
+
options.progress?.onDeploymentCreated?.(createResponse.id);
|
|
142
142
|
yield* self.#checkAborted(options.signal);
|
|
143
143
|
options.progress?.onStartRequested?.();
|
|
144
|
-
yield* Result.await(self.#api.
|
|
145
|
-
const pollResult = yield* Result.await(
|
|
144
|
+
yield* Result.await(self.#api.startDeployment(createResponse.id, options.signal));
|
|
145
|
+
const pollResult = yield* Result.await(pollDeploymentStatus(self.#api, createResponse.id, {
|
|
146
146
|
targetStatus: "running",
|
|
147
147
|
timeoutSeconds: options.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS,
|
|
148
148
|
pollIntervalMs: options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,
|
|
@@ -153,119 +153,120 @@ export class ComputeClient {
|
|
|
153
153
|
options.progress?.onRunning?.(deploymentUrl);
|
|
154
154
|
return Result.ok({
|
|
155
155
|
projectId: target.projectId,
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
appId: target.appId,
|
|
157
|
+
deploymentId: createResponse.id,
|
|
158
158
|
deploymentUrl,
|
|
159
159
|
resolvedConfig: {
|
|
160
160
|
projectId: target.projectId,
|
|
161
|
-
|
|
162
|
-
|
|
161
|
+
appId: target.appId,
|
|
162
|
+
appName: target.appName,
|
|
163
163
|
region: target.region,
|
|
164
164
|
portMapping: options.portMapping,
|
|
165
165
|
},
|
|
166
166
|
});
|
|
167
167
|
});
|
|
168
168
|
}
|
|
169
|
-
async
|
|
169
|
+
async destroyDeployment(options) {
|
|
170
170
|
const self = this;
|
|
171
171
|
return Result.gen(async function* () {
|
|
172
172
|
yield* self.#checkAborted(options.signal);
|
|
173
|
-
let
|
|
174
|
-
let
|
|
175
|
-
if (!
|
|
176
|
-
const
|
|
177
|
-
if (!
|
|
178
|
-
return Result.err(new MissingArgumentError({ field: "
|
|
173
|
+
let deploymentId = options.deploymentId;
|
|
174
|
+
let deploymentDetailsCache;
|
|
175
|
+
if (!deploymentId) {
|
|
176
|
+
const appId = options.appId;
|
|
177
|
+
if (!appId) {
|
|
178
|
+
return Result.err(new MissingArgumentError({ field: "deploymentId" }));
|
|
179
179
|
}
|
|
180
|
-
const
|
|
181
|
-
const
|
|
180
|
+
const deployments = yield* Result.await(self.#api.listAppDeployments(appId, options.signal));
|
|
181
|
+
const deploymentDetailResults = await Promise.all(deployments.map((d) => self.#api.getDeployment(d.id, options.signal)));
|
|
182
182
|
const details = [];
|
|
183
|
-
for (const result of
|
|
183
|
+
for (const result of deploymentDetailResults) {
|
|
184
184
|
if (result.isErr())
|
|
185
185
|
return result;
|
|
186
186
|
details.push(result.value);
|
|
187
187
|
}
|
|
188
|
-
|
|
189
|
-
const
|
|
190
|
-
.map((
|
|
191
|
-
id:
|
|
192
|
-
status:
|
|
193
|
-
createdAt:
|
|
194
|
-
previewDomain:
|
|
188
|
+
deploymentDetailsCache = new Map(details.map((d) => [d.id, d]));
|
|
189
|
+
const deploymentInfos = details
|
|
190
|
+
.map((d) => ({
|
|
191
|
+
id: d.id,
|
|
192
|
+
status: d.status,
|
|
193
|
+
createdAt: d.createdAt,
|
|
194
|
+
previewDomain: d.previewDomain,
|
|
195
195
|
}))
|
|
196
|
-
.sort(
|
|
197
|
-
if (
|
|
198
|
-
return Result.err(new
|
|
196
|
+
.sort(deploymentSortComparator);
|
|
197
|
+
if (deploymentInfos.length === 0) {
|
|
198
|
+
return Result.err(new NoDeploymentsFoundError({ appId }));
|
|
199
199
|
}
|
|
200
|
-
if (!options.interaction?.
|
|
201
|
-
return Result.err(new MissingArgumentError({ field: "
|
|
200
|
+
if (!options.interaction?.selectDeployment) {
|
|
201
|
+
return Result.err(new MissingArgumentError({ field: "deploymentId" }));
|
|
202
202
|
}
|
|
203
|
-
|
|
203
|
+
deploymentId =
|
|
204
|
+
await options.interaction.selectDeployment(deploymentInfos);
|
|
204
205
|
}
|
|
205
|
-
const
|
|
206
|
-
(yield* Result.await(self.#api.
|
|
207
|
-
const previousStatus =
|
|
208
|
-
const shouldStop =
|
|
206
|
+
const deployment = deploymentDetailsCache?.get(deploymentId) ??
|
|
207
|
+
(yield* Result.await(self.#api.getDeployment(deploymentId, options.signal)));
|
|
208
|
+
const previousStatus = deployment.status ?? "unknown";
|
|
209
|
+
const shouldStop = deployment.status === "running" || deployment.status === "provisioning";
|
|
209
210
|
let stopped = false;
|
|
210
211
|
if (shouldStop) {
|
|
211
|
-
options.progress?.onStopRequested?.(
|
|
212
|
-
yield* Result.await(self.#api.
|
|
213
|
-
yield* Result.await(
|
|
212
|
+
options.progress?.onStopRequested?.(deploymentId);
|
|
213
|
+
yield* Result.await(self.#api.stopDeployment(deploymentId, options.signal));
|
|
214
|
+
yield* Result.await(pollDeploymentStatus(self.#api, deploymentId, {
|
|
214
215
|
targetStatus: "stopped",
|
|
215
216
|
timeoutSeconds: options.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS,
|
|
216
217
|
pollIntervalMs: options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,
|
|
217
218
|
signal: options.signal,
|
|
218
219
|
}));
|
|
219
220
|
stopped = true;
|
|
220
|
-
options.progress?.onStopped?.(
|
|
221
|
+
options.progress?.onStopped?.(deploymentId);
|
|
221
222
|
}
|
|
222
|
-
yield* Result.await(self.#api.
|
|
223
|
-
options.progress?.onDeleted?.(
|
|
223
|
+
yield* Result.await(self.#api.deleteDeployment(deploymentId, options.signal));
|
|
224
|
+
options.progress?.onDeleted?.(deploymentId);
|
|
224
225
|
return Result.ok({
|
|
225
|
-
|
|
226
|
+
deploymentId,
|
|
226
227
|
previousStatus,
|
|
227
228
|
stopped,
|
|
228
229
|
deleted: true,
|
|
229
230
|
});
|
|
230
231
|
});
|
|
231
232
|
}
|
|
232
|
-
async
|
|
233
|
+
async destroyApp(options) {
|
|
233
234
|
const self = this;
|
|
234
235
|
return Result.gen(async function* () {
|
|
235
236
|
yield* self.#checkAborted(options.signal);
|
|
236
237
|
const timeoutSeconds = options.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS;
|
|
237
238
|
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
238
|
-
const
|
|
239
|
-
const
|
|
240
|
-
const
|
|
241
|
-
for (const result of
|
|
239
|
+
const deployments = yield* Result.await(self.#api.listAppDeployments(options.appId, options.signal));
|
|
240
|
+
const deploymentDetailResults = await Promise.all(deployments.map((d) => self.#api.getDeployment(d.id, options.signal)));
|
|
241
|
+
const deploymentDetails = [];
|
|
242
|
+
for (const result of deploymentDetailResults) {
|
|
242
243
|
if (result.isErr())
|
|
243
244
|
return result;
|
|
244
|
-
|
|
245
|
+
deploymentDetails.push(result.value);
|
|
245
246
|
}
|
|
246
|
-
const
|
|
247
|
+
const activeDeployments = deploymentDetails.filter((d) => d.status === "running" || d.status === "provisioning");
|
|
247
248
|
const succeededIds = [];
|
|
248
249
|
const failures = [];
|
|
249
|
-
if (
|
|
250
|
-
const activeIds =
|
|
251
|
-
options.progress?.
|
|
252
|
-
const stopResults = await Promise.all(
|
|
250
|
+
if (activeDeployments.length > 0) {
|
|
251
|
+
const activeIds = activeDeployments.map((d) => d.id);
|
|
252
|
+
options.progress?.onStoppingDeployments?.(activeIds);
|
|
253
|
+
const stopResults = await Promise.all(activeDeployments.map((d) => self.#api.stopDeployment(d.id, options.signal)));
|
|
253
254
|
const stoppedCandidates = [];
|
|
254
255
|
for (const [i, result] of stopResults.entries()) {
|
|
255
256
|
// biome-ignore lint/style/noNonNullAssertion: both lists have the same length
|
|
256
|
-
const
|
|
257
|
+
const activeDeployment = activeDeployments[i];
|
|
257
258
|
if (result.isErr()) {
|
|
258
259
|
failures.push({
|
|
259
|
-
|
|
260
|
+
deploymentId: activeDeployment.id,
|
|
260
261
|
error: result.error,
|
|
261
262
|
});
|
|
262
263
|
}
|
|
263
264
|
else {
|
|
264
|
-
stoppedCandidates.push(
|
|
265
|
+
stoppedCandidates.push(activeDeployment);
|
|
265
266
|
}
|
|
266
267
|
}
|
|
267
268
|
if (stoppedCandidates.length > 0) {
|
|
268
|
-
const pollResults = await Promise.all(stoppedCandidates.map((
|
|
269
|
+
const pollResults = await Promise.all(stoppedCandidates.map((d) => pollDeploymentStatus(self.#api, d.id, {
|
|
269
270
|
targetStatus: "stopped",
|
|
270
271
|
timeoutSeconds,
|
|
271
272
|
pollIntervalMs,
|
|
@@ -273,64 +274,64 @@ export class ComputeClient {
|
|
|
273
274
|
})));
|
|
274
275
|
for (const [i, result] of pollResults.entries()) {
|
|
275
276
|
// biome-ignore lint/style/noNonNullAssertion: both lists have the same length
|
|
276
|
-
const
|
|
277
|
+
const stoppedDeployment = stoppedCandidates[i];
|
|
277
278
|
if (result.isErr()) {
|
|
278
279
|
failures.push({
|
|
279
|
-
|
|
280
|
+
deploymentId: stoppedDeployment.id,
|
|
280
281
|
error: result.error,
|
|
281
282
|
});
|
|
282
283
|
}
|
|
283
284
|
else {
|
|
284
|
-
options.progress?.
|
|
285
|
+
options.progress?.onDeploymentStopped?.(stoppedDeployment.id);
|
|
285
286
|
}
|
|
286
287
|
}
|
|
287
288
|
}
|
|
288
289
|
if (failures.length > 0) {
|
|
289
290
|
return Result.err(new DestroyAggregateError({
|
|
290
|
-
|
|
291
|
+
succeededDeploymentIds: succeededIds,
|
|
291
292
|
failures,
|
|
292
|
-
|
|
293
|
+
appDeleted: false,
|
|
293
294
|
}));
|
|
294
295
|
}
|
|
295
|
-
options.progress?.
|
|
296
|
+
options.progress?.onAllDeploymentsStopped?.();
|
|
296
297
|
}
|
|
297
|
-
if (
|
|
298
|
-
const
|
|
299
|
-
options.progress?.
|
|
300
|
-
const deleteResults = await Promise.all(
|
|
298
|
+
if (deployments.length > 0) {
|
|
299
|
+
const deploymentIds = deployments.map((d) => d.id);
|
|
300
|
+
options.progress?.onDeletingDeployments?.(deploymentIds);
|
|
301
|
+
const deleteResults = await Promise.all(deployments.map((d) => self.#api.deleteDeployment(d.id, options.signal)));
|
|
301
302
|
for (const [i, result] of deleteResults.entries()) {
|
|
302
303
|
// biome-ignore lint/style/noNonNullAssertion: both lists have the same length
|
|
303
|
-
const
|
|
304
|
+
const deployment = deployments[i];
|
|
304
305
|
if (result.isErr()) {
|
|
305
306
|
failures.push({
|
|
306
|
-
|
|
307
|
+
deploymentId: deployment.id,
|
|
307
308
|
error: result.error,
|
|
308
309
|
});
|
|
309
310
|
}
|
|
310
311
|
else {
|
|
311
|
-
succeededIds.push(
|
|
312
|
-
options.progress?.
|
|
312
|
+
succeededIds.push(deployment.id);
|
|
313
|
+
options.progress?.onDeploymentDeleted?.(deployment.id);
|
|
313
314
|
}
|
|
314
315
|
}
|
|
315
316
|
if (failures.length > 0) {
|
|
316
317
|
return Result.err(new DestroyAggregateError({
|
|
317
|
-
|
|
318
|
+
succeededDeploymentIds: succeededIds,
|
|
318
319
|
failures,
|
|
319
|
-
|
|
320
|
+
appDeleted: false,
|
|
320
321
|
}));
|
|
321
322
|
}
|
|
322
|
-
options.progress?.
|
|
323
|
+
options.progress?.onAllDeploymentsDeleted?.();
|
|
323
324
|
}
|
|
324
|
-
let
|
|
325
|
-
if (!options.
|
|
326
|
-
yield* Result.await(self.#api.
|
|
327
|
-
|
|
328
|
-
options.progress?.
|
|
325
|
+
let appDeleted = false;
|
|
326
|
+
if (!options.keepApp) {
|
|
327
|
+
yield* Result.await(self.#api.deleteApp(options.appId, options.signal));
|
|
328
|
+
appDeleted = true;
|
|
329
|
+
options.progress?.onAppDeleted?.(options.appId);
|
|
329
330
|
}
|
|
330
331
|
return Result.ok({
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
332
|
+
appId: options.appId,
|
|
333
|
+
deletedDeploymentIds: succeededIds,
|
|
334
|
+
appDeleted,
|
|
334
335
|
});
|
|
335
336
|
});
|
|
336
337
|
}
|
|
@@ -373,25 +374,25 @@ export class ComputeClient {
|
|
|
373
374
|
})));
|
|
374
375
|
});
|
|
375
376
|
}
|
|
376
|
-
async
|
|
377
|
+
async listApps(options) {
|
|
377
378
|
const self = this;
|
|
378
379
|
return Result.gen(async function* () {
|
|
379
380
|
yield* self.#checkAborted(options.signal);
|
|
380
|
-
const
|
|
381
|
-
return Result.ok(
|
|
382
|
-
id:
|
|
383
|
-
name:
|
|
384
|
-
region:
|
|
385
|
-
projectId:
|
|
381
|
+
const apps = yield* Result.await(self.#api.listProjectApps(options.projectId, options.signal));
|
|
382
|
+
return Result.ok(apps.map((a) => ({
|
|
383
|
+
id: a.id,
|
|
384
|
+
name: a.name,
|
|
385
|
+
region: a.region.id,
|
|
386
|
+
projectId: a.projectId,
|
|
386
387
|
})));
|
|
387
388
|
});
|
|
388
389
|
}
|
|
389
|
-
async
|
|
390
|
+
async createApp(options) {
|
|
390
391
|
const self = this;
|
|
391
392
|
return Result.gen(async function* () {
|
|
392
393
|
yield* self.#checkAborted(options.signal);
|
|
393
|
-
const response = yield* Result.await(self.#api.
|
|
394
|
-
displayName: options.
|
|
394
|
+
const response = yield* Result.await(self.#api.createProjectApp(options.projectId, {
|
|
395
|
+
displayName: options.appName,
|
|
395
396
|
regionId: options.region,
|
|
396
397
|
}, options.signal));
|
|
397
398
|
return Result.ok({
|
|
@@ -402,89 +403,89 @@ export class ComputeClient {
|
|
|
402
403
|
});
|
|
403
404
|
});
|
|
404
405
|
}
|
|
405
|
-
async
|
|
406
|
+
async showApp(options) {
|
|
406
407
|
const self = this;
|
|
407
408
|
return Result.gen(async function* () {
|
|
408
409
|
yield* self.#checkAborted(options.signal);
|
|
409
|
-
const
|
|
410
|
+
const a = yield* Result.await(self.#api.getApp(options.appId, options.signal));
|
|
410
411
|
return Result.ok({
|
|
411
|
-
id:
|
|
412
|
-
name:
|
|
413
|
-
region:
|
|
414
|
-
projectId:
|
|
415
|
-
|
|
416
|
-
|
|
412
|
+
id: a.id,
|
|
413
|
+
name: a.name,
|
|
414
|
+
region: a.region.id,
|
|
415
|
+
projectId: a.projectId,
|
|
416
|
+
latestDeploymentId: a.latestDeploymentId,
|
|
417
|
+
appEndpointDomain: a.appEndpointDomain,
|
|
417
418
|
});
|
|
418
419
|
});
|
|
419
420
|
}
|
|
420
|
-
async
|
|
421
|
+
async deleteApp(options) {
|
|
421
422
|
const self = this;
|
|
422
423
|
return Result.gen(async function* () {
|
|
423
424
|
yield* self.#checkAborted(options.signal);
|
|
424
|
-
yield* Result.await(self.#api.
|
|
425
|
+
yield* Result.await(self.#api.deleteApp(options.appId, options.signal));
|
|
425
426
|
return Result.ok();
|
|
426
427
|
});
|
|
427
428
|
}
|
|
428
|
-
async
|
|
429
|
+
async listDeployments(options) {
|
|
429
430
|
const self = this;
|
|
430
431
|
return Result.gen(async function* () {
|
|
431
432
|
yield* self.#checkAborted(options.signal);
|
|
432
|
-
const
|
|
433
|
-
const detailResults = await Promise.all(
|
|
433
|
+
const deployments = yield* Result.await(self.#api.listAppDeployments(options.appId, options.signal));
|
|
434
|
+
const detailResults = await Promise.all(deployments.map((d) => self.#api.getDeployment(d.id, options.signal)));
|
|
434
435
|
const details = [];
|
|
435
436
|
for (const result of detailResults) {
|
|
436
437
|
if (result.isErr()) {
|
|
437
|
-
//
|
|
438
|
-
// and can't be fetched using
|
|
438
|
+
// listAppDeployments returns deployments that have previously been deleted
|
|
439
|
+
// and can't be fetched using getDeployment so we need to filter those out.
|
|
439
440
|
if (ApiError.is(result.error) && result.error.statusCode === 404)
|
|
440
441
|
continue;
|
|
441
442
|
return result;
|
|
442
443
|
}
|
|
443
444
|
details.push(result.value);
|
|
444
445
|
}
|
|
445
|
-
return Result.ok(details.map((
|
|
446
|
-
id:
|
|
447
|
-
status:
|
|
448
|
-
createdAt:
|
|
449
|
-
previewDomain:
|
|
446
|
+
return Result.ok(details.map((d) => ({
|
|
447
|
+
id: d.id,
|
|
448
|
+
status: d.status,
|
|
449
|
+
createdAt: d.createdAt,
|
|
450
|
+
previewDomain: d.previewDomain,
|
|
450
451
|
})));
|
|
451
452
|
});
|
|
452
453
|
}
|
|
453
|
-
async
|
|
454
|
+
async showDeployment(options) {
|
|
454
455
|
const self = this;
|
|
455
456
|
return Result.gen(async function* () {
|
|
456
457
|
yield* self.#checkAborted(options.signal);
|
|
457
|
-
const
|
|
458
|
+
const d = yield* Result.await(self.#api.getDeployment(options.deploymentId, options.signal));
|
|
458
459
|
return Result.ok({
|
|
459
|
-
id:
|
|
460
|
-
status:
|
|
461
|
-
createdAt:
|
|
462
|
-
previewDomain:
|
|
463
|
-
envVars:
|
|
460
|
+
id: d.id,
|
|
461
|
+
status: d.status,
|
|
462
|
+
createdAt: d.createdAt,
|
|
463
|
+
previewDomain: d.previewDomain,
|
|
464
|
+
envVars: d.envVars ?? undefined,
|
|
464
465
|
});
|
|
465
466
|
});
|
|
466
467
|
}
|
|
467
|
-
async
|
|
468
|
+
async startDeployment(options) {
|
|
468
469
|
const self = this;
|
|
469
470
|
return Result.gen(async function* () {
|
|
470
471
|
yield* self.#checkAborted(options.signal);
|
|
471
|
-
yield* Result.await(self.#api.
|
|
472
|
+
yield* Result.await(self.#api.startDeployment(options.deploymentId, options.signal));
|
|
472
473
|
return Result.ok();
|
|
473
474
|
});
|
|
474
475
|
}
|
|
475
|
-
async
|
|
476
|
+
async stopDeployment(options) {
|
|
476
477
|
const self = this;
|
|
477
478
|
return Result.gen(async function* () {
|
|
478
479
|
yield* self.#checkAborted(options.signal);
|
|
479
|
-
yield* Result.await(self.#api.
|
|
480
|
+
yield* Result.await(self.#api.stopDeployment(options.deploymentId, options.signal));
|
|
480
481
|
return Result.ok();
|
|
481
482
|
});
|
|
482
483
|
}
|
|
483
|
-
async
|
|
484
|
+
async deleteDeployment(options) {
|
|
484
485
|
const self = this;
|
|
485
486
|
return Result.gen(async function* () {
|
|
486
487
|
yield* self.#checkAborted(options.signal);
|
|
487
|
-
yield* Result.await(self.#api.
|
|
488
|
+
yield* Result.await(self.#api.deleteDeployment(options.deploymentId, options.signal));
|
|
488
489
|
return Result.ok();
|
|
489
490
|
});
|
|
490
491
|
}
|
|
@@ -492,10 +493,10 @@ export class ComputeClient {
|
|
|
492
493
|
const self = this;
|
|
493
494
|
return Result.gen(async function* () {
|
|
494
495
|
yield* self.#checkAborted(options.signal);
|
|
495
|
-
let
|
|
496
|
-
if (!
|
|
497
|
-
const
|
|
498
|
-
const detailResults = await Promise.all(
|
|
496
|
+
let deploymentId = options.deploymentId;
|
|
497
|
+
if (!deploymentId) {
|
|
498
|
+
const deployments = yield* Result.await(self.#api.listAppDeployments(options.appId, options.signal));
|
|
499
|
+
const detailResults = await Promise.all(deployments.map((d) => self.#api.getDeployment(d.id, options.signal)));
|
|
499
500
|
const details = [];
|
|
500
501
|
for (const result of detailResults) {
|
|
501
502
|
if (result.isErr()) {
|
|
@@ -505,35 +506,36 @@ export class ComputeClient {
|
|
|
505
506
|
}
|
|
506
507
|
details.push(result.value);
|
|
507
508
|
}
|
|
508
|
-
const
|
|
509
|
-
.map((
|
|
510
|
-
id:
|
|
511
|
-
status:
|
|
512
|
-
createdAt:
|
|
513
|
-
previewDomain:
|
|
509
|
+
const deploymentInfos = details
|
|
510
|
+
.map((d) => ({
|
|
511
|
+
id: d.id,
|
|
512
|
+
status: d.status,
|
|
513
|
+
createdAt: d.createdAt,
|
|
514
|
+
previewDomain: d.previewDomain,
|
|
514
515
|
}))
|
|
515
|
-
.sort(
|
|
516
|
-
if (
|
|
517
|
-
return Result.err(new
|
|
516
|
+
.sort(deploymentSortComparator);
|
|
517
|
+
if (deploymentInfos.length === 0) {
|
|
518
|
+
return Result.err(new NoDeploymentsFoundError({ appId: options.appId }));
|
|
518
519
|
}
|
|
519
|
-
if (!options.interaction?.
|
|
520
|
-
return Result.err(new MissingArgumentError({ field: "
|
|
520
|
+
if (!options.interaction?.selectDeployment) {
|
|
521
|
+
return Result.err(new MissingArgumentError({ field: "deploymentId" }));
|
|
521
522
|
}
|
|
522
|
-
|
|
523
|
+
deploymentId =
|
|
524
|
+
await options.interaction.selectDeployment(deploymentInfos);
|
|
523
525
|
}
|
|
524
|
-
const
|
|
525
|
-
let
|
|
526
|
-
if (
|
|
527
|
-
options.progress?.
|
|
528
|
-
if (
|
|
529
|
-
options.progress?.
|
|
530
|
-
const startResult = await self.#api.
|
|
526
|
+
const deployment = yield* Result.await(self.#api.getDeployment(deploymentId, options.signal));
|
|
527
|
+
let deploymentStarted = false;
|
|
528
|
+
if (deployment.status !== "running") {
|
|
529
|
+
options.progress?.onDeploymentStarting?.(deploymentId);
|
|
530
|
+
if (deployment.status !== "provisioning") {
|
|
531
|
+
options.progress?.onDeploymentStartRequested?.();
|
|
532
|
+
const startResult = await self.#api.startDeployment(deploymentId, options.signal);
|
|
531
533
|
if (startResult.isErr()) {
|
|
532
534
|
options.progress?.onPromoteFailed?.(startResult.error);
|
|
533
535
|
return startResult;
|
|
534
536
|
}
|
|
535
537
|
}
|
|
536
|
-
const pollResult = await
|
|
538
|
+
const pollResult = await pollDeploymentStatus(self.#api, deploymentId, {
|
|
537
539
|
targetStatus: "running",
|
|
538
540
|
timeoutSeconds: options.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS,
|
|
539
541
|
pollIntervalMs: options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,
|
|
@@ -544,97 +546,97 @@ export class ComputeClient {
|
|
|
544
546
|
options.progress?.onPromoteFailed?.(pollResult.error);
|
|
545
547
|
return pollResult;
|
|
546
548
|
}
|
|
547
|
-
|
|
548
|
-
options.progress?.
|
|
549
|
+
deploymentStarted = true;
|
|
550
|
+
options.progress?.onDeploymentRunning?.();
|
|
549
551
|
}
|
|
550
552
|
options.progress?.onPromoteStart?.();
|
|
551
|
-
const promoteResult = await self.#api.
|
|
553
|
+
const promoteResult = await self.#api.promoteApp(options.appId, { deploymentId }, options.signal);
|
|
552
554
|
if (promoteResult.isErr()) {
|
|
553
555
|
options.progress?.onPromoteFailed?.(promoteResult.error);
|
|
554
556
|
return promoteResult;
|
|
555
557
|
}
|
|
556
|
-
options.progress?.onPromoted?.(promoteResult.value.
|
|
558
|
+
options.progress?.onPromoted?.(promoteResult.value.appEndpointDomain);
|
|
557
559
|
return Result.ok({
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
560
|
+
appId: options.appId,
|
|
561
|
+
deploymentId,
|
|
562
|
+
appEndpointDomain: promoteResult.value.appEndpointDomain,
|
|
563
|
+
deploymentStarted,
|
|
562
564
|
});
|
|
563
565
|
});
|
|
564
566
|
}
|
|
565
|
-
async #promoteAndSwitchover(
|
|
567
|
+
async #promoteAndSwitchover(appId, newDeploymentId, previousDeploymentId, destroyOldDeployment, signal, progress) {
|
|
566
568
|
progress?.onPromoteStart?.();
|
|
567
|
-
const promoteResult = await this.#api.
|
|
569
|
+
const promoteResult = await this.#api.promoteApp(appId, { deploymentId: newDeploymentId }, signal);
|
|
568
570
|
if (promoteResult.isErr()) {
|
|
569
571
|
progress?.onPromoteFailed?.(promoteResult.error);
|
|
570
|
-
progress?.
|
|
571
|
-
await this.#
|
|
572
|
+
progress?.onCleanupDanglingDeployment?.(newDeploymentId);
|
|
573
|
+
await this.#cleanupDanglingDeployment(newDeploymentId, undefined, progress);
|
|
572
574
|
return promoteResult;
|
|
573
575
|
}
|
|
574
|
-
const
|
|
575
|
-
progress?.onPromoted?.(
|
|
576
|
-
let
|
|
577
|
-
if (
|
|
578
|
-
progress?.
|
|
579
|
-
const stopResult = await this.#api.
|
|
576
|
+
const appEndpointDomain = promoteResult.value.appEndpointDomain;
|
|
577
|
+
progress?.onPromoted?.(appEndpointDomain);
|
|
578
|
+
let previousDeploymentAction = null;
|
|
579
|
+
if (previousDeploymentId && previousDeploymentId !== newDeploymentId) {
|
|
580
|
+
progress?.onOldDeploymentStopping?.(previousDeploymentId);
|
|
581
|
+
const stopResult = await this.#api.stopDeployment(previousDeploymentId, signal);
|
|
580
582
|
if (stopResult.isOk()) {
|
|
581
|
-
const pollResult = await
|
|
583
|
+
const pollResult = await pollDeploymentStatus(this.#api, previousDeploymentId, {
|
|
582
584
|
targetStatus: "stopped",
|
|
583
585
|
timeoutSeconds: 30,
|
|
584
586
|
pollIntervalMs: 1000,
|
|
585
587
|
signal,
|
|
586
588
|
});
|
|
587
589
|
if (pollResult.isOk()) {
|
|
588
|
-
|
|
589
|
-
progress?.
|
|
590
|
-
if (
|
|
591
|
-
progress?.
|
|
592
|
-
const deleteResult = await this.#api.
|
|
590
|
+
previousDeploymentAction = "stopped";
|
|
591
|
+
progress?.onOldDeploymentStopped?.(previousDeploymentId);
|
|
592
|
+
if (destroyOldDeployment) {
|
|
593
|
+
progress?.onOldDeploymentDeleting?.(previousDeploymentId);
|
|
594
|
+
const deleteResult = await this.#api.deleteDeployment(previousDeploymentId, signal);
|
|
593
595
|
if (deleteResult.isOk()) {
|
|
594
|
-
|
|
595
|
-
progress?.
|
|
596
|
+
previousDeploymentAction = "destroyed";
|
|
597
|
+
progress?.onOldDeploymentDeleted?.(previousDeploymentId);
|
|
596
598
|
}
|
|
597
599
|
else {
|
|
598
|
-
progress?.
|
|
600
|
+
progress?.onOldDeploymentDeleteFailed?.(previousDeploymentId);
|
|
599
601
|
}
|
|
600
602
|
}
|
|
601
603
|
}
|
|
602
604
|
else {
|
|
603
|
-
progress?.
|
|
605
|
+
progress?.onOldDeploymentStopFailed?.(previousDeploymentId);
|
|
604
606
|
}
|
|
605
607
|
}
|
|
606
608
|
else {
|
|
607
|
-
progress?.
|
|
609
|
+
progress?.onOldDeploymentStopFailed?.(previousDeploymentId);
|
|
608
610
|
}
|
|
609
611
|
}
|
|
610
612
|
return Result.ok({
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
613
|
+
appEndpointDomain,
|
|
614
|
+
previousDeploymentId,
|
|
615
|
+
previousDeploymentAction,
|
|
614
616
|
});
|
|
615
617
|
}
|
|
616
|
-
async #
|
|
617
|
-
const stopResult = await this.#api.
|
|
618
|
+
async #cleanupDanglingDeployment(deploymentId, signal, progress) {
|
|
619
|
+
const stopResult = await this.#api.stopDeployment(deploymentId, signal);
|
|
618
620
|
if (stopResult.isErr()) {
|
|
619
|
-
progress?.
|
|
621
|
+
progress?.onCleanupDanglingDeploymentFailed?.(deploymentId);
|
|
620
622
|
return;
|
|
621
623
|
}
|
|
622
|
-
const pollResult = await
|
|
624
|
+
const pollResult = await pollDeploymentStatus(this.#api, deploymentId, {
|
|
623
625
|
targetStatus: "stopped",
|
|
624
626
|
timeoutSeconds: 30,
|
|
625
627
|
pollIntervalMs: 1000,
|
|
626
628
|
signal,
|
|
627
629
|
});
|
|
628
630
|
if (pollResult.isErr()) {
|
|
629
|
-
progress?.
|
|
631
|
+
progress?.onCleanupDanglingDeploymentFailed?.(deploymentId);
|
|
630
632
|
return;
|
|
631
633
|
}
|
|
632
|
-
const deleteResult = await this.#api.
|
|
634
|
+
const deleteResult = await this.#api.deleteDeployment(deploymentId, signal);
|
|
633
635
|
if (deleteResult.isErr()) {
|
|
634
|
-
progress?.
|
|
636
|
+
progress?.onCleanupDanglingDeploymentFailed?.(deploymentId);
|
|
635
637
|
return;
|
|
636
638
|
}
|
|
637
|
-
progress?.
|
|
639
|
+
progress?.onCleanupDanglingDeploymentComplete?.(deploymentId);
|
|
638
640
|
}
|
|
639
641
|
#checkAborted(signal) {
|
|
640
642
|
try {
|
|
@@ -648,26 +650,26 @@ export class ComputeClient {
|
|
|
648
650
|
async #resolveDeployTarget(options) {
|
|
649
651
|
const self = this;
|
|
650
652
|
return Result.gen(async function* () {
|
|
651
|
-
if (options.
|
|
652
|
-
const
|
|
653
|
-
if (!
|
|
653
|
+
if (options.appId) {
|
|
654
|
+
const app = yield* Result.await(self.#api.getApp(options.appId, options.signal));
|
|
655
|
+
if (!app.projectId) {
|
|
654
656
|
return Result.err(new MissingArgumentError({ field: "projectId" }));
|
|
655
657
|
}
|
|
656
658
|
const requestedProjectId = options.projectId?.replace(/^proj_/, "");
|
|
657
|
-
const
|
|
659
|
+
const appProjectId = app.projectId.replace(/^proj_/, "");
|
|
658
660
|
if (requestedProjectId !== undefined &&
|
|
659
|
-
requestedProjectId !==
|
|
661
|
+
requestedProjectId !== appProjectId) {
|
|
660
662
|
return Result.err(new InvalidOptionsError({
|
|
661
|
-
message: `
|
|
663
|
+
message: `App ${app.id} belongs to project ${app.projectId}, but project ${options.projectId} was provided`,
|
|
662
664
|
}));
|
|
663
665
|
}
|
|
664
666
|
return Result.ok({
|
|
665
|
-
projectId:
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
region:
|
|
669
|
-
branchId:
|
|
670
|
-
|
|
667
|
+
projectId: app.projectId,
|
|
668
|
+
appId: app.id,
|
|
669
|
+
appName: app.name ?? app.id,
|
|
670
|
+
region: app.region.id,
|
|
671
|
+
branchId: app.branchId ?? null,
|
|
672
|
+
isNewApp: false,
|
|
671
673
|
});
|
|
672
674
|
}
|
|
673
675
|
let projectId = options.projectId;
|
|
@@ -683,50 +685,50 @@ export class ComputeClient {
|
|
|
683
685
|
}
|
|
684
686
|
projectId = await options.interaction.selectProject(projectInfos);
|
|
685
687
|
}
|
|
686
|
-
if (options.
|
|
687
|
-
const response = yield* Result.await(self.#api.
|
|
688
|
-
displayName: options.
|
|
688
|
+
if (options.appName && options.region) {
|
|
689
|
+
const response = yield* Result.await(self.#api.createProjectApp(projectId, {
|
|
690
|
+
displayName: options.appName,
|
|
689
691
|
regionId: options.region,
|
|
690
692
|
}, options.signal));
|
|
691
693
|
return Result.ok({
|
|
692
694
|
projectId,
|
|
693
|
-
|
|
694
|
-
|
|
695
|
+
appId: response.id,
|
|
696
|
+
appName: response.name,
|
|
695
697
|
region: response.region.id,
|
|
696
698
|
branchId: response.branchId ?? null,
|
|
697
|
-
|
|
699
|
+
isNewApp: true,
|
|
698
700
|
});
|
|
699
701
|
}
|
|
700
|
-
const
|
|
701
|
-
const
|
|
702
|
-
id:
|
|
703
|
-
name:
|
|
704
|
-
region:
|
|
705
|
-
projectId:
|
|
702
|
+
const apps = yield* Result.await(self.#api.listProjectApps(projectId, options.signal));
|
|
703
|
+
const appInfos = apps.map((a) => ({
|
|
704
|
+
id: a.id,
|
|
705
|
+
name: a.name,
|
|
706
|
+
region: a.region.id,
|
|
707
|
+
projectId: a.projectId,
|
|
706
708
|
}));
|
|
707
|
-
if (!options.interaction?.
|
|
708
|
-
return Result.err(new MissingArgumentError({ field: "
|
|
709
|
+
if (!options.interaction?.selectApp) {
|
|
710
|
+
return Result.err(new MissingArgumentError({ field: "appId" }));
|
|
709
711
|
}
|
|
710
|
-
const
|
|
711
|
-
if (
|
|
712
|
-
const selected =
|
|
713
|
-
invariant(selected, "
|
|
714
|
-
const
|
|
712
|
+
const selectedAppId = await options.interaction.selectApp(appInfos);
|
|
713
|
+
if (selectedAppId !== null) {
|
|
714
|
+
const selected = apps.find((a) => a.id === selectedAppId);
|
|
715
|
+
invariant(selected, "selectApp returned app ID not in the list");
|
|
716
|
+
const app = yield* Result.await(self.#api.getApp(selectedAppId, options.signal));
|
|
715
717
|
return Result.ok({
|
|
716
718
|
projectId,
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
region:
|
|
720
|
-
branchId:
|
|
721
|
-
|
|
719
|
+
appId: selectedAppId,
|
|
720
|
+
appName: app.name ?? selected.name,
|
|
721
|
+
region: app.region.id,
|
|
722
|
+
branchId: app.branchId ?? null,
|
|
723
|
+
isNewApp: false,
|
|
722
724
|
});
|
|
723
725
|
}
|
|
724
|
-
let
|
|
725
|
-
if (!
|
|
726
|
-
if (!options.interaction?.
|
|
727
|
-
return Result.err(new MissingArgumentError({ field: "
|
|
726
|
+
let appName = options.appName;
|
|
727
|
+
if (!appName) {
|
|
728
|
+
if (!options.interaction?.provideAppName) {
|
|
729
|
+
return Result.err(new MissingArgumentError({ field: "appName" }));
|
|
728
730
|
}
|
|
729
|
-
|
|
731
|
+
appName = await options.interaction.provideAppName();
|
|
730
732
|
}
|
|
731
733
|
let region = options.region;
|
|
732
734
|
if (!region) {
|
|
@@ -735,17 +737,17 @@ export class ComputeClient {
|
|
|
735
737
|
}
|
|
736
738
|
region = await options.interaction.selectRegion(REGIONS);
|
|
737
739
|
}
|
|
738
|
-
const response = yield* Result.await(self.#api.
|
|
739
|
-
displayName:
|
|
740
|
+
const response = yield* Result.await(self.#api.createProjectApp(projectId, {
|
|
741
|
+
displayName: appName,
|
|
740
742
|
regionId: region,
|
|
741
743
|
}, options.signal));
|
|
742
744
|
return Result.ok({
|
|
743
745
|
projectId,
|
|
744
|
-
|
|
745
|
-
|
|
746
|
+
appId: response.id,
|
|
747
|
+
appName: response.name,
|
|
746
748
|
region: response.region.id,
|
|
747
749
|
branchId: response.branchId ?? null,
|
|
748
|
-
|
|
750
|
+
isNewApp: true,
|
|
749
751
|
});
|
|
750
752
|
});
|
|
751
753
|
}
|
|
@@ -810,9 +812,9 @@ export class ComputeClient {
|
|
|
810
812
|
}
|
|
811
813
|
}
|
|
812
814
|
/**
|
|
813
|
-
* Sort
|
|
815
|
+
* Sort deployments: running/provisioning first, then by createdAt descending.
|
|
814
816
|
*/
|
|
815
|
-
function
|
|
817
|
+
function deploymentSortComparator(a, b) {
|
|
816
818
|
const statusPriority = (s) => s === "running" ? 0 : s === "provisioning" ? 1 : 2;
|
|
817
819
|
const aPriority = statusPriority(a.status);
|
|
818
820
|
const bPriority = statusPriority(b.status);
|