@fluid-app/fluid-cli-mist 0.1.49 → 0.1.51
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/index.mjs +122 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -235,8 +235,7 @@ async function listBranchEnvironments(client, slug) {
|
|
|
235
235
|
* the key before calling and retries with it.
|
|
236
236
|
*/
|
|
237
237
|
async function requestStaging(client, slug, request) {
|
|
238
|
-
|
|
239
|
-
if (!OPERATION_KEY.test(request.operationKey)) throw new Error("Operation key must be 1-128 of A-Z a-z 0-9 . _ : -");
|
|
238
|
+
checkStagingRequest(request.sourceRevision, request.operationKey);
|
|
240
239
|
const id = await resolveMistId(client, slug);
|
|
241
240
|
return parseOperationEnvelope(await client.post(`${base(id)}/branch-environments/staging`, { source_revision: request.sourceRevision }, { "X-Operation-Key": request.operationKey }));
|
|
242
241
|
}
|
|
@@ -245,6 +244,20 @@ async function fetchBranchOperation(client, slug, operationId) {
|
|
|
245
244
|
const id = await resolveMistId(client, slug);
|
|
246
245
|
return parseOperationEnvelope(await client.get(`${base(id)}/branch-operations/${encodeURIComponent(operationId)}`));
|
|
247
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* Refuse a revision or operation key the server would reject, before
|
|
249
|
+
* anything is sent or saved.
|
|
250
|
+
*
|
|
251
|
+
* Raises Error naming the malformed value.
|
|
252
|
+
*/
|
|
253
|
+
function checkStagingRequest(sourceRevision, operationKey) {
|
|
254
|
+
if (!SOURCE_REVISION.test(sourceRevision)) throw new Error("Source revision must be a full 40-character commit SHA");
|
|
255
|
+
if (operationKey !== void 0 && !isOperationKey(operationKey)) throw new Error("Operation key must be 1-128 of A-Z a-z 0-9 . _ : -");
|
|
256
|
+
}
|
|
257
|
+
/** Whether the server would accept `key` as an operation key. */
|
|
258
|
+
function isOperationKey(key) {
|
|
259
|
+
return OPERATION_KEY.test(key);
|
|
260
|
+
}
|
|
248
261
|
/** A fresh key for a new staging request. */
|
|
249
262
|
function newOperationKey() {
|
|
250
263
|
return `staging-${randomUUID()}`;
|
|
@@ -4224,6 +4237,110 @@ const stagingStatusCommand = new Command("status").description("Show the staging
|
|
|
4224
4237
|
console.log(capability.staging.available ? "Staging can be requested with `fluid mist staging request --revision <sha>`." : chalk.yellow(describeUnavailable(capability.staging)));
|
|
4225
4238
|
}));
|
|
4226
4239
|
//#endregion
|
|
4240
|
+
//#region src/lib/staging-request-store.ts
|
|
4241
|
+
/**
|
|
4242
|
+
* Staging requests the server hasn't confirmed yet.
|
|
4243
|
+
*
|
|
4244
|
+
* `fluid mist staging request` saves its operation key here before it sends
|
|
4245
|
+
* the request and removes it once the server answers with an operation. If
|
|
4246
|
+
* the CLI dies in between (crash, lost connection, closed terminal), the
|
|
4247
|
+
* next request for the same revision reuses the saved key, so the server
|
|
4248
|
+
* resumes the same operation instead of starting a second one. After the
|
|
4249
|
+
* server has confirmed, `fluid mist staging wait` follows the operation.
|
|
4250
|
+
*
|
|
4251
|
+
* Records live under the Fluid config dir (`~/.fluid/mist/staging-requests`)
|
|
4252
|
+
* and hold only ids, a commit SHA and a timestamp, no tokens or secrets.
|
|
4253
|
+
*/
|
|
4254
|
+
function createFileStagingRequestStore(dir = join(getConfigDir(), "mist", "staging-requests")) {
|
|
4255
|
+
const fileFor = (mistId) => join(dir, `${encodeURIComponent(mistId)}.json`);
|
|
4256
|
+
return {
|
|
4257
|
+
save(request) {
|
|
4258
|
+
mkdirSync(dir, {
|
|
4259
|
+
recursive: true,
|
|
4260
|
+
mode: 448
|
|
4261
|
+
});
|
|
4262
|
+
writeFileSync(fileFor(request.mistId), JSON.stringify(request, null, 2) + "\n", {
|
|
4263
|
+
encoding: "utf8",
|
|
4264
|
+
mode: 384
|
|
4265
|
+
});
|
|
4266
|
+
},
|
|
4267
|
+
load(mistId) {
|
|
4268
|
+
try {
|
|
4269
|
+
const parsed = JSON.parse(readFileSync(fileFor(mistId), "utf8"));
|
|
4270
|
+
return isPendingRequest(parsed) && parsed.mistId === mistId ? parsed : null;
|
|
4271
|
+
} catch {
|
|
4272
|
+
return null;
|
|
4273
|
+
}
|
|
4274
|
+
},
|
|
4275
|
+
remove(mistId) {
|
|
4276
|
+
rmSync(fileFor(mistId), { force: true });
|
|
4277
|
+
}
|
|
4278
|
+
};
|
|
4279
|
+
}
|
|
4280
|
+
/**
|
|
4281
|
+
* The key to send for `sourceRevision`: the saved one when an unconfirmed
|
|
4282
|
+
* request for the same revision is pending, otherwise none. A pending
|
|
4283
|
+
* request for another revision, or with a key the server would refuse, is
|
|
4284
|
+
* dropped; if the server did admit it, the capability check reports it as
|
|
4285
|
+
* the run in progress.
|
|
4286
|
+
*/
|
|
4287
|
+
function pendingKeyFor(store, mistId, sourceRevision) {
|
|
4288
|
+
const pending = store.load(mistId);
|
|
4289
|
+
if (!pending) return null;
|
|
4290
|
+
if (pending.sourceRevision === sourceRevision && isOperationKey(pending.operationKey)) return pending.operationKey;
|
|
4291
|
+
store.remove(mistId);
|
|
4292
|
+
return null;
|
|
4293
|
+
}
|
|
4294
|
+
function isPendingRequest(value) {
|
|
4295
|
+
if (typeof value !== "object" || value === null) return false;
|
|
4296
|
+
const record = value;
|
|
4297
|
+
return typeof record["mistId"] === "string" && typeof record["operationKey"] === "string" && typeof record["sourceRevision"] === "string" && typeof record["requestedAt"] === "string";
|
|
4298
|
+
}
|
|
4299
|
+
/**
|
|
4300
|
+
* Request staging, remembering the key until the server answers. A saved
|
|
4301
|
+
* key for the same revision is reused, so a request the CLI never heard
|
|
4302
|
+
* back about resumes the same operation. The record is removed once the
|
|
4303
|
+
* server answers, whether it admitted the request or refused it, and kept
|
|
4304
|
+
* when the request may never have reached it.
|
|
4305
|
+
*/
|
|
4306
|
+
async function requestStagingResumably(client, slug, request) {
|
|
4307
|
+
const { store, sourceRevision } = request;
|
|
4308
|
+
checkStagingRequest(sourceRevision, request.operationKey);
|
|
4309
|
+
const mistId = await resolveMistId(client, slug);
|
|
4310
|
+
const savedKey = request.operationKey ? null : pendingKeyFor(store, mistId, sourceRevision);
|
|
4311
|
+
if (savedKey) request.onResume?.(savedKey);
|
|
4312
|
+
try {
|
|
4313
|
+
const operation = await admitStaging(client, slug, {
|
|
4314
|
+
sourceRevision,
|
|
4315
|
+
operationKey: request.operationKey ?? savedKey ?? void 0,
|
|
4316
|
+
onOperationKey: (key) => {
|
|
4317
|
+
store.save({
|
|
4318
|
+
mistId,
|
|
4319
|
+
operationKey: key,
|
|
4320
|
+
sourceRevision,
|
|
4321
|
+
requestedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4322
|
+
});
|
|
4323
|
+
request.onOperationKey?.(key);
|
|
4324
|
+
}
|
|
4325
|
+
});
|
|
4326
|
+
store.remove(mistId);
|
|
4327
|
+
return operation;
|
|
4328
|
+
} catch (err) {
|
|
4329
|
+
if (serverAnswered(err)) store.remove(mistId);
|
|
4330
|
+
throw err;
|
|
4331
|
+
}
|
|
4332
|
+
}
|
|
4333
|
+
/**
|
|
4334
|
+
* Whether the server's answer settled the request. A network failure, a
|
|
4335
|
+
* rate limit, a server error or a response the CLI can't read may come
|
|
4336
|
+
* after the operation was recorded, so the key is kept for those and the
|
|
4337
|
+
* next request replays it.
|
|
4338
|
+
*/
|
|
4339
|
+
function serverAnswered(err) {
|
|
4340
|
+
if (err instanceof MistApiError) return err.status !== 0 && err.status !== 429 && err.status < 500;
|
|
4341
|
+
return err instanceof StagingOperationFailedError;
|
|
4342
|
+
}
|
|
4343
|
+
//#endregion
|
|
4227
4344
|
//#region src/commands/staging/wait.ts
|
|
4228
4345
|
/** `fluid mist staging wait` — follow a staging operation to the end. */
|
|
4229
4346
|
const POLL_INTERVAL_MS = 5e3;
|
|
@@ -4256,9 +4373,11 @@ const stagingRequestCommand = new Command("request").description("Request the st
|
|
|
4256
4373
|
const resolved = resolveSlug(slug);
|
|
4257
4374
|
const client = createMistClient();
|
|
4258
4375
|
const sourceRevision = opts.revision ?? await localRevision(client, resolved);
|
|
4259
|
-
let operation = await
|
|
4376
|
+
let operation = await requestStagingResumably(client, resolved, {
|
|
4260
4377
|
sourceRevision,
|
|
4261
4378
|
operationKey: opts.operationKey,
|
|
4379
|
+
store: createFileStagingRequestStore(),
|
|
4380
|
+
onResume: (key) => process.stderr.write(chalk.dim(`Resuming the unconfirmed request for ${sourceRevision.slice(0, 7)} (key ${key}).\n`)),
|
|
4262
4381
|
onOperationKey: (key) => process.stderr.write(chalk.dim(`Operation key ${key}. If this is interrupted, rerun with --operation-key ${key} --revision ${sourceRevision}.\n`))
|
|
4263
4382
|
});
|
|
4264
4383
|
process.stderr.write(`Staging operation ${operation.id}: ${describeProgress(operation)}\n`);
|