@ebay-dt/bonfire-node 0.1.0 → 0.2.0-alpha.10
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/README.md +10 -4
- package/dist/agent-service.d.ts +4 -1
- package/dist/agent-service.d.ts.map +1 -1
- package/dist/agent-service.js +18 -2
- package/dist/agent-service.js.map +1 -1
- package/dist/claude.d.ts +12 -11
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +166 -11
- package/dist/claude.js.map +1 -1
- package/dist/deployment-provider.d.ts +1 -0
- package/dist/deployment-provider.d.ts.map +1 -1
- package/dist/deployment-provider.js +1 -0
- package/dist/deployment-provider.js.map +1 -1
- package/dist/git-repository.d.ts +4 -2
- package/dist/git-repository.d.ts.map +1 -1
- package/dist/git-repository.js +35 -2
- package/dist/git-repository.js.map +1 -1
- package/dist/github-cli.d.ts.map +1 -1
- package/dist/github-cli.js +3 -1
- package/dist/github-cli.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lifecycle/deployment-worktree.d.ts +35 -0
- package/dist/lifecycle/deployment-worktree.d.ts.map +1 -0
- package/dist/lifecycle/deployment-worktree.js +302 -0
- package/dist/lifecycle/deployment-worktree.js.map +1 -0
- package/dist/lifecycle/local-prototype-lifecycle.d.ts +11 -1
- package/dist/lifecycle/local-prototype-lifecycle.d.ts.map +1 -1
- package/dist/lifecycle/local-prototype-lifecycle.js +272 -76
- package/dist/lifecycle/local-prototype-lifecycle.js.map +1 -1
- package/dist/lifecycle/repository-summary.d.ts +19 -0
- package/dist/lifecycle/repository-summary.d.ts.map +1 -0
- package/dist/lifecycle/repository-summary.js +191 -0
- package/dist/lifecycle/repository-summary.js.map +1 -0
- package/dist/lifecycle/repository-synchronization.d.ts +36 -0
- package/dist/lifecycle/repository-synchronization.d.ts.map +1 -0
- package/dist/lifecycle/repository-synchronization.js +314 -0
- package/dist/lifecycle/repository-synchronization.js.map +1 -0
- package/dist/lifecycle/runtime-context.d.ts +5 -1
- package/dist/lifecycle/runtime-context.d.ts.map +1 -1
- package/dist/lifecycle/runtime-context.js +7 -0
- package/dist/lifecycle/runtime-context.js.map +1 -1
- package/package.json +2 -2
|
@@ -2,8 +2,8 @@ import { constants } from "node:fs";
|
|
|
2
2
|
import { lstat, open, rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { join, relative, sep } from "node:path";
|
|
5
|
-
import { definePrototypeRef, getPrototypeUrl, LifecycleInputError, lifecycleFailure, lifecycleSuccess, parseCreatePrototypeInput, parseDuplicatePrototypeInput, parseEditPrototypeInput, parseBonfireIdentity, parsePrototypeCommandInput, parsePrototypeMetadata, slugifyPrototypeTitle, PrototypeRefError, } from "@ebay-dt/bonfire-core";
|
|
6
|
-
import { commitPath, discardPath, GitRepositoryError,
|
|
5
|
+
import { definePrototypeRef, getPrototypeUrl, LifecycleInputError, lifecycleFailure, lifecycleSuccess, parseCreatePrototypeInput, parseDuplicatePrototypeInput, parseEditPrototypeInput, parseBonfireIdentity, parsePrototypeCommandInput, parsePrototypeMetadata, slugifyPrototypeTitle, PrototypeRefError, parseGitModeSetting, } from "@ebay-dt/bonfire-core";
|
|
6
|
+
import { commitPath, discardPath, GitRepositoryError, pushBranch, pushCommitToBranch, } from "../git-repository.js";
|
|
7
7
|
import { duplicatePrototype, PrototypeDuplicateError } from "../duplicate.js";
|
|
8
8
|
import { PrototypeCatalog, } from "../prototype-catalog.js";
|
|
9
9
|
import { PrototypeScaffoldError, scaffoldPrototype } from "../scaffold.js";
|
|
@@ -11,6 +11,9 @@ import { createPrototypeLifecycleRuntime, } from "./runtime-context.js";
|
|
|
11
11
|
import { cleanupOwnedDestination, validateOwnedDestination, } from "../internal/owned-destination.js";
|
|
12
12
|
import { RepositoryBusyError, withRepositoryOperation, } from "./operation-gate.js";
|
|
13
13
|
import { DeploymentProviderError, } from "../deployment-provider.js";
|
|
14
|
+
import { assertIdentityBranchReady, synchronizeIdentityBranch, } from "./repository-synchronization.js";
|
|
15
|
+
import { getRepositorySummary } from "./repository-summary.js";
|
|
16
|
+
import { DeploymentWorktreeError, preflightDeploymentSource, publishDeploymentSource, reportDeploymentIssue, } from "./deployment-worktree.js";
|
|
14
17
|
function repositoryRelativePath(runtime, ref) {
|
|
15
18
|
const absolutePath = join(runtime.prototypeRoot, ref.login, ref.slug);
|
|
16
19
|
return relative(runtime.repositoryRoot, absolutePath).split(sep).join("/");
|
|
@@ -29,6 +32,10 @@ class PrototypeMetadataFileError extends Error {
|
|
|
29
32
|
}
|
|
30
33
|
class DeploymentUnavailableError extends Error {
|
|
31
34
|
}
|
|
35
|
+
class DeploymentSourcePushError extends Error {
|
|
36
|
+
}
|
|
37
|
+
class DeploymentNotAcceptedError extends Error {
|
|
38
|
+
}
|
|
32
39
|
function metadataPath(runtime, ref) {
|
|
33
40
|
return join(runtime.prototypeRoot, ref.login, ref.slug, "metadata.json");
|
|
34
41
|
}
|
|
@@ -112,26 +119,6 @@ async function writeMetadataDocument(runtime, ref, document) {
|
|
|
112
119
|
await rm(temporaryPath, { force: true }).catch(() => undefined);
|
|
113
120
|
}
|
|
114
121
|
}
|
|
115
|
-
async function mergeIdentityBranchToPrimary(repository, options) {
|
|
116
|
-
if (options.branch === options.primaryBranch)
|
|
117
|
-
return;
|
|
118
|
-
const hasRemote = (await repository.remotes()).some((remote) => remote.name === options.remote);
|
|
119
|
-
if (!hasRemote)
|
|
120
|
-
return;
|
|
121
|
-
await repository.checkout([options.primaryBranch]);
|
|
122
|
-
try {
|
|
123
|
-
await repository.merge([
|
|
124
|
-
options.branch,
|
|
125
|
-
"--no-ff",
|
|
126
|
-
"-m",
|
|
127
|
-
`merge: ${options.branch}`,
|
|
128
|
-
]);
|
|
129
|
-
await repository.push(options.remote, options.primaryBranch);
|
|
130
|
-
}
|
|
131
|
-
finally {
|
|
132
|
-
await repository.checkout([options.branch]);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
122
|
function mapError(error) {
|
|
136
123
|
if (error instanceof PrototypeNotFoundError)
|
|
137
124
|
return "not-found";
|
|
@@ -173,6 +160,11 @@ function mapError(error) {
|
|
|
173
160
|
if (error instanceof DeploymentUnavailableError) {
|
|
174
161
|
return "deployment-unavailable";
|
|
175
162
|
}
|
|
163
|
+
if (error instanceof DeploymentSourcePushError ||
|
|
164
|
+
error instanceof DeploymentNotAcceptedError ||
|
|
165
|
+
error instanceof DeploymentWorktreeError) {
|
|
166
|
+
return "deployment-failure";
|
|
167
|
+
}
|
|
176
168
|
if (error instanceof DeploymentProviderError) {
|
|
177
169
|
return error.kind === "timeout" ? "timeout" : "deployment-failure";
|
|
178
170
|
}
|
|
@@ -183,19 +175,35 @@ function resultFrom(operation) {
|
|
|
183
175
|
.then(lifecycleSuccess)
|
|
184
176
|
.catch((error) => lifecycleFailure(mapError(error)));
|
|
185
177
|
}
|
|
178
|
+
async function diagnoseMetadataStep(step, operation) {
|
|
179
|
+
try {
|
|
180
|
+
return await operation();
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
reportDeploymentIssue("publication", step, error);
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
186
187
|
export class LocalPrototypeLifecycle {
|
|
187
188
|
#runtime;
|
|
189
|
+
gitMode;
|
|
188
190
|
#catalog;
|
|
189
191
|
#repository;
|
|
190
192
|
#preparePrototypeFiles;
|
|
191
193
|
#deploymentProvider;
|
|
192
194
|
#applicationId;
|
|
193
195
|
#themeRegistry;
|
|
196
|
+
#restartRequired = false;
|
|
197
|
+
#pendingPublication;
|
|
194
198
|
constructor(options) {
|
|
195
199
|
const internalOptions = options;
|
|
196
|
-
|
|
197
|
-
internalOptions
|
|
198
|
-
|
|
200
|
+
const runtime = internalOptions.runtime ??
|
|
201
|
+
createPrototypeLifecycleRuntime(internalOptions);
|
|
202
|
+
this.#runtime = {
|
|
203
|
+
...runtime,
|
|
204
|
+
gitMode: parseGitModeSetting(runtime.gitMode),
|
|
205
|
+
};
|
|
206
|
+
this.gitMode = this.#runtime.gitMode;
|
|
199
207
|
this.#themeRegistry = options.themeRegistry;
|
|
200
208
|
this.#catalog =
|
|
201
209
|
internalOptions.catalog ??
|
|
@@ -241,14 +249,7 @@ export class LocalPrototypeLifecycle {
|
|
|
241
249
|
};
|
|
242
250
|
}
|
|
243
251
|
async #prepareMutationBranch(login) {
|
|
244
|
-
|
|
245
|
-
return this.#currentBranch();
|
|
246
|
-
}
|
|
247
|
-
await prepareIdentityBranch(this.#repository, {
|
|
248
|
-
identityBranch: login,
|
|
249
|
-
primaryBranch: this.#runtime.primaryBranch,
|
|
250
|
-
remote: this.#runtime.remote,
|
|
251
|
-
});
|
|
252
|
+
await assertIdentityBranchReady(this.#repository, login);
|
|
252
253
|
return login;
|
|
253
254
|
}
|
|
254
255
|
async #prepareDestination(receipt, expectedFiles, request) {
|
|
@@ -278,16 +279,35 @@ export class LocalPrototypeLifecycle {
|
|
|
278
279
|
return resultFrom(async () => {
|
|
279
280
|
const verifiedIdentity = await this.#verifiedIdentity(identity);
|
|
280
281
|
return withRepositoryOperation(this.#runtime.repositoryRealPath, async () => {
|
|
281
|
-
if (this.#runtime.
|
|
282
|
+
if (this.#runtime.gitMode === "manual" ||
|
|
283
|
+
this.#runtime.automaticBranchSwitch === "disabled") {
|
|
282
284
|
await this.#currentBranch();
|
|
283
285
|
return { status: "disabled" };
|
|
284
286
|
}
|
|
285
|
-
await
|
|
287
|
+
const synchronization = await synchronizeIdentityBranch(this.#repository, {
|
|
286
288
|
identityBranch: verifiedIdentity.login,
|
|
287
289
|
primaryBranch: this.#runtime.primaryBranch,
|
|
288
290
|
remote: this.#runtime.remote,
|
|
291
|
+
managedPaths: this.#runtime.managedRepositoryPaths,
|
|
289
292
|
});
|
|
290
|
-
|
|
293
|
+
if (synchronization.status !== "synchronized") {
|
|
294
|
+
return {
|
|
295
|
+
status: synchronization.status,
|
|
296
|
+
reason: synchronization.reason,
|
|
297
|
+
...(synchronization.affectedPaths.length > 0
|
|
298
|
+
? { affectedPaths: synchronization.affectedPaths }
|
|
299
|
+
: {}),
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
if (synchronization.affectedPaths.some((path) => this.#runtime.managedRepositoryPaths.includes(path))) {
|
|
303
|
+
this.#restartRequired = true;
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
status: "ready",
|
|
307
|
+
...(synchronization.affectedPaths.length > 0
|
|
308
|
+
? { affectedPaths: synchronization.affectedPaths }
|
|
309
|
+
: {}),
|
|
310
|
+
};
|
|
291
311
|
});
|
|
292
312
|
});
|
|
293
313
|
}
|
|
@@ -300,7 +320,9 @@ export class LocalPrototypeLifecycle {
|
|
|
300
320
|
if (await this.#catalog.getSummary(ref)) {
|
|
301
321
|
throw new PrototypeScaffoldError("collision");
|
|
302
322
|
}
|
|
303
|
-
const branch =
|
|
323
|
+
const branch = this.#runtime.gitMode === "managed"
|
|
324
|
+
? await this.#prepareMutationBranch(verifiedIdentity.login)
|
|
325
|
+
: undefined;
|
|
304
326
|
const prepared = await scaffoldPrototype({
|
|
305
327
|
paths: this.#runtime,
|
|
306
328
|
ref,
|
|
@@ -320,6 +342,13 @@ export class LocalPrototypeLifecycle {
|
|
|
320
342
|
title: normalizedInput.title,
|
|
321
343
|
description: normalizedInput.description,
|
|
322
344
|
});
|
|
345
|
+
if (branch === undefined) {
|
|
346
|
+
return {
|
|
347
|
+
ref,
|
|
348
|
+
url: getPrototypeUrl(ref),
|
|
349
|
+
remoteStatus: "manual",
|
|
350
|
+
};
|
|
351
|
+
}
|
|
323
352
|
await commitPath(this.#repository, repositoryRelativePath(this.#runtime, ref), `init: ${ref.slug}`);
|
|
324
353
|
const push = await pushBranch(this.#repository, this.#runtime.remote, branch);
|
|
325
354
|
return {
|
|
@@ -338,7 +367,9 @@ export class LocalPrototypeLifecycle {
|
|
|
338
367
|
const { ref, path } = await this.#ownedPrototype(verifiedIdentity, {
|
|
339
368
|
ref: normalizedInput.ref,
|
|
340
369
|
});
|
|
341
|
-
const branch =
|
|
370
|
+
const branch = this.#runtime.gitMode === "managed"
|
|
371
|
+
? await this.#prepareMutationBranch(verifiedIdentity.login)
|
|
372
|
+
: undefined;
|
|
342
373
|
await assertPrototypeDirectory(this.#runtime, ref);
|
|
343
374
|
const document = await readMetadataDocument(this.#runtime, ref, this.#themeRegistry);
|
|
344
375
|
const metadata = parsePrototypeMetadata(document, {
|
|
@@ -356,8 +387,10 @@ export class LocalPrototypeLifecycle {
|
|
|
356
387
|
? {}
|
|
357
388
|
: { theme: normalizedInput.theme }),
|
|
358
389
|
});
|
|
359
|
-
|
|
360
|
-
|
|
390
|
+
if (branch !== undefined) {
|
|
391
|
+
await commitPath(this.#repository, path, `edit: ${ref.slug}`);
|
|
392
|
+
await pushBranch(this.#repository, this.#runtime.remote, branch);
|
|
393
|
+
}
|
|
361
394
|
return { edited: true, ref };
|
|
362
395
|
});
|
|
363
396
|
});
|
|
@@ -368,10 +401,14 @@ export class LocalPrototypeLifecycle {
|
|
|
368
401
|
const normalizedInput = parsePrototypeCommandInput(input);
|
|
369
402
|
return withRepositoryOperation(this.#runtime.repositoryRealPath, async () => {
|
|
370
403
|
const { ref, path } = await this.#ownedPrototype(verifiedIdentity, normalizedInput);
|
|
371
|
-
const branch =
|
|
404
|
+
const branch = this.#runtime.gitMode === "managed"
|
|
405
|
+
? await this.#prepareMutationBranch(verifiedIdentity.login)
|
|
406
|
+
: undefined;
|
|
372
407
|
await removePrototypeDirectory(this.#runtime, ref);
|
|
373
|
-
|
|
374
|
-
|
|
408
|
+
if (branch !== undefined) {
|
|
409
|
+
await commitPath(this.#repository, path, `delete: ${ref.slug}`);
|
|
410
|
+
await pushBranch(this.#repository, this.#runtime.remote, branch);
|
|
411
|
+
}
|
|
375
412
|
return { deleted: true, ref };
|
|
376
413
|
});
|
|
377
414
|
});
|
|
@@ -388,7 +425,113 @@ export class LocalPrototypeLifecycle {
|
|
|
388
425
|
};
|
|
389
426
|
});
|
|
390
427
|
}
|
|
428
|
+
async getRepositorySummary(identity) {
|
|
429
|
+
return resultFrom(async () => {
|
|
430
|
+
const verifiedIdentity = await this.#verifiedIdentity(identity);
|
|
431
|
+
return getRepositorySummary({
|
|
432
|
+
repository: this.#repository,
|
|
433
|
+
catalog: this.#catalog,
|
|
434
|
+
repositoryRoot: this.#runtime.repositoryRoot,
|
|
435
|
+
prototypeRoot: this.#runtime.prototypeRoot,
|
|
436
|
+
identity: verifiedIdentity,
|
|
437
|
+
mode: this.#runtime.gitMode,
|
|
438
|
+
remote: this.#runtime.remote,
|
|
439
|
+
primaryBranch: this.#runtime.primaryBranch,
|
|
440
|
+
restartRequired: this.#restartRequired,
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
async checkRepositorySummary(identity) {
|
|
445
|
+
return resultFrom(async () => {
|
|
446
|
+
const verifiedIdentity = await this.#verifiedIdentity(identity);
|
|
447
|
+
return getRepositorySummary({
|
|
448
|
+
repository: this.#repository,
|
|
449
|
+
catalog: this.#catalog,
|
|
450
|
+
repositoryRoot: this.#runtime.repositoryRoot,
|
|
451
|
+
prototypeRoot: this.#runtime.prototypeRoot,
|
|
452
|
+
identity: verifiedIdentity,
|
|
453
|
+
mode: this.#runtime.gitMode,
|
|
454
|
+
remote: this.#runtime.remote,
|
|
455
|
+
primaryBranch: this.#runtime.primaryBranch,
|
|
456
|
+
restartRequired: this.#restartRequired,
|
|
457
|
+
refresh: true,
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
async #headCommit() {
|
|
462
|
+
const commit = (await this.#repository.raw(["rev-parse", "HEAD"])).trim();
|
|
463
|
+
if (!/^[0-9a-f]{40,64}$/iu.test(commit)) {
|
|
464
|
+
throw new GitRepositoryError("malformed-output", "head-commit");
|
|
465
|
+
}
|
|
466
|
+
return commit.toLowerCase();
|
|
467
|
+
}
|
|
468
|
+
async #prepareDeploymentBranch(login) {
|
|
469
|
+
if (this.#runtime.automaticBranchSwitch === "disabled") {
|
|
470
|
+
await assertIdentityBranchReady(this.#repository, login);
|
|
471
|
+
return "updates-not-synchronized";
|
|
472
|
+
}
|
|
473
|
+
const synchronization = await synchronizeIdentityBranch(this.#repository, {
|
|
474
|
+
identityBranch: login,
|
|
475
|
+
primaryBranch: this.#runtime.primaryBranch,
|
|
476
|
+
remote: this.#runtime.remote,
|
|
477
|
+
managedPaths: this.#runtime.managedRepositoryPaths,
|
|
478
|
+
});
|
|
479
|
+
if (synchronization.status === "unsafe") {
|
|
480
|
+
throw new GitRepositoryError("conflict", "deployment-preparation");
|
|
481
|
+
}
|
|
482
|
+
await assertIdentityBranchReady(this.#repository, login);
|
|
483
|
+
return synchronization.status === "deferred"
|
|
484
|
+
? "updates-not-synchronized"
|
|
485
|
+
: undefined;
|
|
486
|
+
}
|
|
487
|
+
#workspaceRuntime(workspacePath, workspaceRepository) {
|
|
488
|
+
return {
|
|
489
|
+
...this.#runtime,
|
|
490
|
+
repositoryRoot: workspacePath,
|
|
491
|
+
repositoryRealPath: workspacePath,
|
|
492
|
+
prototypeRoot: join(workspacePath, relative(this.#runtime.repositoryRoot, this.#runtime.prototypeRoot)),
|
|
493
|
+
gitRepository: workspaceRepository,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
async #publishDeploymentMetadata(pending) {
|
|
497
|
+
return publishDeploymentSource(this.#repository, {
|
|
498
|
+
remote: this.#runtime.remote,
|
|
499
|
+
primaryBranch: this.#runtime.primaryBranch,
|
|
500
|
+
sourceSha: pending.sourceSha,
|
|
501
|
+
commitIdentity: this.#runtime.commitIdentity,
|
|
502
|
+
}, async ({ path, repository }) => {
|
|
503
|
+
const workspaceRuntime = this.#workspaceRuntime(path, repository);
|
|
504
|
+
const mergedDocument = await diagnoseMetadataStep("metadata-read", () => readMetadataDocument(workspaceRuntime, pending.ref, this.#themeRegistry));
|
|
505
|
+
await diagnoseMetadataStep("metadata-write", () => writeMetadataDocument(workspaceRuntime, pending.ref, {
|
|
506
|
+
...mergedDocument,
|
|
507
|
+
previewUrl: pending.previewUrl,
|
|
508
|
+
updatedAt: new Date().toISOString(),
|
|
509
|
+
}));
|
|
510
|
+
await diagnoseMetadataStep("metadata-commit", () => commitPath(repository, pending.path, `deploy: ${pending.ref.slug} (metadata)`));
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
async #deploymentValue(pending, publicationWarnings = []) {
|
|
514
|
+
const warnings = [
|
|
515
|
+
...(pending.synchronizationWarning === undefined
|
|
516
|
+
? []
|
|
517
|
+
: [pending.synchronizationWarning]),
|
|
518
|
+
...publicationWarnings,
|
|
519
|
+
];
|
|
520
|
+
return {
|
|
521
|
+
deployed: true,
|
|
522
|
+
ref: pending.ref,
|
|
523
|
+
previewUrl: pending.previewUrl,
|
|
524
|
+
deploymentId: pending.deploymentId,
|
|
525
|
+
sourceSha: pending.sourceSha,
|
|
526
|
+
readyState: pending.readyState,
|
|
527
|
+
warning: publicationWarnings[0] ?? pending.synchronizationWarning,
|
|
528
|
+
...(warnings.length === 0 ? {} : { warnings }),
|
|
529
|
+
};
|
|
530
|
+
}
|
|
391
531
|
async deploy(identity, input) {
|
|
532
|
+
if (this.#runtime.gitMode === "manual") {
|
|
533
|
+
return lifecycleFailure("forbidden");
|
|
534
|
+
}
|
|
392
535
|
return resultFrom(async () => {
|
|
393
536
|
const verifiedIdentity = await this.#verifiedIdentity(identity);
|
|
394
537
|
const normalizedInput = parsePrototypeCommandInput(input);
|
|
@@ -397,7 +540,7 @@ export class LocalPrototypeLifecycle {
|
|
|
397
540
|
}
|
|
398
541
|
return withRepositoryOperation(this.#runtime.repositoryRealPath, async () => {
|
|
399
542
|
const { ref, path } = await this.#ownedPrototype(verifiedIdentity, normalizedInput);
|
|
400
|
-
const
|
|
543
|
+
const synchronizationWarning = await this.#prepareDeploymentBranch(verifiedIdentity.login);
|
|
401
544
|
await assertPrototypeDirectory(this.#runtime, ref);
|
|
402
545
|
const document = await readMetadataDocument(this.#runtime, ref, this.#themeRegistry);
|
|
403
546
|
const metadata = parsePrototypeMetadata(document, {
|
|
@@ -407,60 +550,104 @@ export class LocalPrototypeLifecycle {
|
|
|
407
550
|
throw new PrototypeNotFoundError();
|
|
408
551
|
}
|
|
409
552
|
await commitPath(this.#repository, path, `deploy: ${ref.slug}`);
|
|
410
|
-
await
|
|
553
|
+
const sourceSha = await this.#headCommit();
|
|
554
|
+
const sourcePush = await pushCommitToBranch(this.#repository, this.#runtime.remote, sourceSha, verifiedIdentity.login);
|
|
555
|
+
if (sourcePush.remoteStatus !== "pushed") {
|
|
556
|
+
throw new DeploymentSourcePushError();
|
|
557
|
+
}
|
|
558
|
+
await preflightDeploymentSource(this.#repository, {
|
|
559
|
+
remote: this.#runtime.remote,
|
|
560
|
+
primaryBranch: this.#runtime.primaryBranch,
|
|
561
|
+
sourceSha,
|
|
562
|
+
commitIdentity: this.#runtime.commitIdentity,
|
|
563
|
+
});
|
|
411
564
|
const deployment = await this.#deploymentProvider.deploy({
|
|
412
565
|
applicationId: this.#applicationId,
|
|
413
566
|
login: verifiedIdentity.login,
|
|
414
567
|
});
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
await writeMetadataDocument(this.#runtime, ref, {
|
|
419
|
-
...document,
|
|
420
|
-
previewUrl,
|
|
421
|
-
updatedAt: new Date().toISOString(),
|
|
422
|
-
});
|
|
423
|
-
await commitPath(this.#repository, path, `deploy: ${ref.slug} (metadata)`);
|
|
424
|
-
await pushBranch(this.#repository, this.#runtime.remote, branch);
|
|
425
|
-
}
|
|
426
|
-
catch {
|
|
427
|
-
warning = "metadata-persistence-failed";
|
|
568
|
+
if (deployment.commitSha.toLowerCase() !== sourceSha ||
|
|
569
|
+
!["READY", "QUEUED", "INITIALIZING", "BUILDING"].includes(deployment.readyState)) {
|
|
570
|
+
throw new DeploymentNotAcceptedError();
|
|
428
571
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
primaryBranch: this.#runtime.primaryBranch,
|
|
434
|
-
remote: this.#runtime.remote,
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
catch {
|
|
438
|
-
warning = "primary-merge-failed";
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
return {
|
|
442
|
-
deployed: true,
|
|
572
|
+
const previewUrl = new URL(getPrototypeUrl(ref).slice(1), `${deployment.url.replace(/\/+$/, "")}/`).toString();
|
|
573
|
+
const pending = {
|
|
574
|
+
identity: verifiedIdentity,
|
|
575
|
+
input: normalizedInput,
|
|
443
576
|
ref,
|
|
577
|
+
path,
|
|
444
578
|
previewUrl,
|
|
445
579
|
deploymentId: deployment.deploymentId,
|
|
580
|
+
sourceSha,
|
|
446
581
|
readyState: deployment.readyState,
|
|
447
|
-
|
|
582
|
+
synchronizationWarning,
|
|
448
583
|
};
|
|
584
|
+
try {
|
|
585
|
+
const publication = await this.#publishDeploymentMetadata(pending);
|
|
586
|
+
this.#pendingPublication = undefined;
|
|
587
|
+
return this.#deploymentValue(pending, publication.cleanupFailed ? ["publication-cleanup-failed"] : []);
|
|
588
|
+
}
|
|
589
|
+
catch (error) {
|
|
590
|
+
// Keep the accepted deployment retryable for every publication
|
|
591
|
+
// failure, including metadata preparation failures.
|
|
592
|
+
this.#pendingPublication = pending;
|
|
593
|
+
return this.#deploymentValue(pending, [
|
|
594
|
+
error instanceof DeploymentWorktreeError &&
|
|
595
|
+
error.kind !== "cleanup-failure"
|
|
596
|
+
? "primary-merge-failed"
|
|
597
|
+
: "metadata-persistence-failed",
|
|
598
|
+
...(error instanceof DeploymentWorktreeError &&
|
|
599
|
+
error.cleanupFailed
|
|
600
|
+
? ["publication-cleanup-failed"]
|
|
601
|
+
: []),
|
|
602
|
+
]);
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
async retryPublish(identity, input) {
|
|
608
|
+
if (this.#runtime.gitMode === "manual") {
|
|
609
|
+
return lifecycleFailure("forbidden");
|
|
610
|
+
}
|
|
611
|
+
return resultFrom(async () => {
|
|
612
|
+
const verifiedIdentity = await this.#verifiedIdentity(identity);
|
|
613
|
+
const normalizedInput = parsePrototypeCommandInput(input);
|
|
614
|
+
const pending = this.#pendingPublication;
|
|
615
|
+
if (pending === undefined ||
|
|
616
|
+
pending.identity.login !== verifiedIdentity.login ||
|
|
617
|
+
pending.ref.login !== normalizedInput.ref.login ||
|
|
618
|
+
pending.ref.slug !== normalizedInput.ref.slug) {
|
|
619
|
+
throw new DeploymentUnavailableError();
|
|
620
|
+
}
|
|
621
|
+
return withRepositoryOperation(this.#runtime.repositoryRealPath, async () => {
|
|
622
|
+
await assertIdentityBranchReady(this.#repository, verifiedIdentity.login);
|
|
623
|
+
const publication = await this.#publishDeploymentMetadata(pending);
|
|
624
|
+
this.#pendingPublication = undefined;
|
|
625
|
+
return this.#deploymentValue(pending, publication.cleanupFailed ? ["publication-cleanup-failed"] : []);
|
|
449
626
|
});
|
|
450
627
|
});
|
|
451
628
|
}
|
|
452
629
|
async save(identity, input) {
|
|
630
|
+
if (this.#runtime.gitMode === "manual") {
|
|
631
|
+
return lifecycleFailure("forbidden");
|
|
632
|
+
}
|
|
453
633
|
return resultFrom(async () => {
|
|
454
|
-
const
|
|
634
|
+
const verifiedIdentity = await this.#verifiedIdentity(identity);
|
|
635
|
+
const { ref, path } = await this.#ownedPrototype(verifiedIdentity, input);
|
|
455
636
|
return withRepositoryOperation(this.#runtime.repositoryRealPath, async () => {
|
|
637
|
+
await this.#prepareMutationBranch(verifiedIdentity.login);
|
|
456
638
|
await commitPath(this.#repository, path, `save: ${ref.slug}`);
|
|
639
|
+
await pushBranch(this.#repository, this.#runtime.remote, verifiedIdentity.login);
|
|
457
640
|
return { saved: true };
|
|
458
641
|
});
|
|
459
642
|
});
|
|
460
643
|
}
|
|
461
644
|
async discard(identity, input) {
|
|
645
|
+
if (this.#runtime.gitMode === "manual") {
|
|
646
|
+
return lifecycleFailure("forbidden");
|
|
647
|
+
}
|
|
462
648
|
return resultFrom(async () => {
|
|
463
|
-
const
|
|
649
|
+
const verifiedIdentity = await this.#verifiedIdentity(identity);
|
|
650
|
+
const { path } = await this.#ownedPrototype(verifiedIdentity, input);
|
|
464
651
|
return withRepositoryOperation(this.#runtime.repositoryRealPath, async () => {
|
|
465
652
|
await discardPath(this.#repository, path);
|
|
466
653
|
return { discarded: true };
|
|
@@ -479,7 +666,9 @@ export class LocalPrototypeLifecycle {
|
|
|
479
666
|
if (await this.#catalog.getSummary(ref)) {
|
|
480
667
|
throw new PrototypeDuplicateError("collision");
|
|
481
668
|
}
|
|
482
|
-
const branch =
|
|
669
|
+
const branch = this.#runtime.gitMode === "managed"
|
|
670
|
+
? await this.#prepareMutationBranch(verifiedIdentity.login)
|
|
671
|
+
: undefined;
|
|
483
672
|
const prepared = await duplicatePrototype({
|
|
484
673
|
paths: this.#runtime,
|
|
485
674
|
source: normalizedInput.source,
|
|
@@ -500,6 +689,13 @@ export class LocalPrototypeLifecycle {
|
|
|
500
689
|
title: normalizedInput.title,
|
|
501
690
|
description: normalizedInput.description,
|
|
502
691
|
});
|
|
692
|
+
if (branch === undefined) {
|
|
693
|
+
return {
|
|
694
|
+
ref,
|
|
695
|
+
url: getPrototypeUrl(ref),
|
|
696
|
+
remoteStatus: "manual",
|
|
697
|
+
};
|
|
698
|
+
}
|
|
503
699
|
await commitPath(this.#repository, repositoryRelativePath(this.#runtime, ref), `init: ${ref.slug}`);
|
|
504
700
|
const push = await pushBranch(this.#repository, this.#runtime.remote, branch);
|
|
505
701
|
return {
|