590 lines
21 KiB
JavaScript
590 lines
21 KiB
JavaScript
(() => {
|
|
if (window.forgeflow) return;
|
|
|
|
const wait = (ms = 180) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
const clone = (value) => JSON.parse(JSON.stringify(value));
|
|
const iso = (offset = 0) => new Date(Date.now() + offset).toISOString();
|
|
const storage = {
|
|
get(key) {
|
|
try {
|
|
return localStorage.getItem(key);
|
|
} catch {
|
|
return null;
|
|
}
|
|
},
|
|
set(key, value) {
|
|
try {
|
|
localStorage.setItem(key, value);
|
|
} catch {}
|
|
},
|
|
};
|
|
const repositoryListeners = new Set();
|
|
const operationListeners = new Set();
|
|
const updateListeners = new Set();
|
|
const emitRepositories = () =>
|
|
repositoryListeners.forEach((listener) =>
|
|
listener({ reason: "demo-change" }),
|
|
);
|
|
const emitOperations = (operations) =>
|
|
operationListeners.forEach((listener) =>
|
|
listener({ operations: clone(operations) }),
|
|
);
|
|
const randomSha = () =>
|
|
`${Math.random().toString(16).slice(2)}${Date.now().toString(16)}`
|
|
.padEnd(40, "a")
|
|
.slice(0, 40);
|
|
|
|
const makeStatus = ({
|
|
head,
|
|
branch = "main",
|
|
ahead = 0,
|
|
behind = 0,
|
|
upstream = `origin/${branch}`,
|
|
files = [],
|
|
}) => ({
|
|
branch: { oid: head, head: branch, upstream, ahead, behind },
|
|
files,
|
|
counts: {
|
|
changed: files.length,
|
|
staged: files.filter((item) => item.staged).length,
|
|
unstaged: files.filter((item) => item.unstaged).length,
|
|
conflicts: files.filter((item) => item.conflict).length,
|
|
untracked: files.filter((item) => item.untracked).length,
|
|
},
|
|
clean: files.length === 0,
|
|
root: "",
|
|
remoteUrl: "",
|
|
head,
|
|
shortHead: head.slice(0, 7),
|
|
fingerprint: `${head}:${branch}:${ahead}:${behind}:${files.map((item) => `${item.path}:${item.indexCode}${item.worktreeCode}`).join("|")}`,
|
|
});
|
|
|
|
const makeFile = (path, status = "modified", options = {}) => ({
|
|
path,
|
|
originalPath: options.originalPath || null,
|
|
indexCode: options.staged
|
|
? status === "added"
|
|
? "A"
|
|
: status === "deleted"
|
|
? "D"
|
|
: "M"
|
|
: ".",
|
|
worktreeCode: options.staged
|
|
? "."
|
|
: status === "untracked"
|
|
? "?"
|
|
: status === "deleted"
|
|
? "D"
|
|
: status === "conflict"
|
|
? "U"
|
|
: "M",
|
|
staged: Boolean(options.staged),
|
|
unstaged: !options.staged,
|
|
untracked: status === "untracked",
|
|
conflict: status === "conflict",
|
|
status,
|
|
});
|
|
|
|
const profile = (id, name, environment, options = {}) => ({
|
|
id,
|
|
name,
|
|
environment,
|
|
provider: "gitea-actions",
|
|
branch: options.branch || "main",
|
|
workflowFile: options.workflowFile || "deploy.yml",
|
|
rollbackWorkflowFile: options.rollbackWorkflowFile ?? "rollback.yml",
|
|
healthcheckUrl:
|
|
options.healthcheckUrl || `https://${environment}.internal/health`,
|
|
statusUrl:
|
|
options.statusUrl ||
|
|
`https://${environment}.internal/.well-known/forgeflow`,
|
|
confirmationRequired: options.confirmationRequired !== false,
|
|
inputs: {},
|
|
state: {
|
|
liveSha: options.liveSha || null,
|
|
previousSha: options.previousSha || null,
|
|
healthy: options.healthy ?? null,
|
|
healthConfigured: true,
|
|
statusConfigured: true,
|
|
healthStatus: options.healthy === false ? 503 : 200,
|
|
healthLatencyMs: 42,
|
|
checkedAt: options.checkedAt || iso(-120000),
|
|
},
|
|
});
|
|
|
|
const sshProfile = (id, name, environment, options = {}) => ({
|
|
id, name, environment, provider: "ssh-unraid", branch: options.branch || "main",
|
|
serverId: "demo-unraid", remoteFolder: options.remoteFolder || name,
|
|
deploymentMode: "server-git", composeFiles: ["compose.yml"],
|
|
composeProject: options.composeProject || String(options.remoteFolder || name).toLowerCase(),
|
|
composeServices: options.composeServices || [String(options.remoteFolder || name).toLowerCase()],
|
|
containerName: options.containerName || options.remoteFolder || name,
|
|
generatedCompose: false, adoptedFromServer: true, serverSourceOfTruth: true,
|
|
confirmationRequired: true,
|
|
serverGitAccess: { configured: options.accessConfigured !== false, keyFingerprint: "SHA256:demo", hostFingerprint: "SHA256:gitea", configuredAt: iso(-3600000) },
|
|
state: {
|
|
liveSha: options.liveSha || null, giteaSha: options.giteaSha || options.liveSha || null,
|
|
previousSha: options.previousSha || null, healthy: options.healthy ?? true,
|
|
containerRunning: true, runtimeVerification: "verified", matchesGitea: options.matchesGitea ?? true,
|
|
checkedAt: iso(-120000), dockerMan: { templateExists: true, webUi: true, icon: true },
|
|
},
|
|
});
|
|
|
|
const now = iso();
|
|
const defaultPreferences = {
|
|
autoRefresh: true,
|
|
repositoryPollSeconds: 4,
|
|
operationPollSeconds: 5,
|
|
fetchIntervalMinutes: 10,
|
|
preferredCloneProtocol: "https",
|
|
diagnosticsEnabled: true,
|
|
diagnosticLevel: "info",
|
|
logRetentionDays: 14,
|
|
maxLogFileMb: 8,
|
|
};
|
|
|
|
let state = {
|
|
schemaVersion: 8,
|
|
setupComplete: storage.get("forgeflow-demo-setup") !== "false",
|
|
appearance: storage.get("forgeflow-theme") || "dark",
|
|
gitea: {
|
|
baseUrl: "https://gitea.internal",
|
|
user: { login: "jens", full_name: "Jens" },
|
|
hasToken: true,
|
|
},
|
|
workspaceRoots: ["C:\\Development"],
|
|
repositoryMappings: {},
|
|
deploymentProfiles: {},
|
|
deploymentStates: {},
|
|
favorites: [
|
|
"jens/microsoft-cloud-operations-platform",
|
|
"jens/unraid-appops-gateway",
|
|
],
|
|
updates: {
|
|
owner: "Jens",
|
|
repo: "ForgeFlow",
|
|
branch: "main",
|
|
autoCheck: true,
|
|
lastCheckedAt: null,
|
|
},
|
|
servers: [
|
|
{
|
|
id: "server-unraid",
|
|
name: "Unraid",
|
|
host: "192.168.1.10",
|
|
port: 22,
|
|
username: "root",
|
|
authType: "privateKey",
|
|
basePath: "/mnt/user/appdata",
|
|
privateKeyPath: "C:\\Users\\your-name\\.ssh\\id_ed25519",
|
|
hostFingerprint: "SHA256:demo",
|
|
hasPassword: false,
|
|
hasPassphrase: false,
|
|
},
|
|
],
|
|
preferences: { ...defaultPreferences },
|
|
operations: [
|
|
{
|
|
id: "op-success",
|
|
type: "deployment",
|
|
action: "deploy",
|
|
status: "success",
|
|
repository: "jens/microsoft-cloud-operations-platform",
|
|
profileId: "profile-mcop-prod",
|
|
profileName: "Production",
|
|
environment: "production",
|
|
workflowFile: "deploy.yml",
|
|
branch: "main",
|
|
sha: "b82f91ab0173cd4346ca0f0f7dcc3e8182cc8fd0",
|
|
shortSha: "b82f91a",
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
stages: [
|
|
{ id: "requested", label: "Requested", status: "complete" },
|
|
{ id: "verified", label: "Verified", status: "complete" },
|
|
{ id: "queued", label: "Workflow queued", status: "complete" },
|
|
{ id: "runner", label: "Runner execution", status: "complete" },
|
|
{ id: "healthcheck", label: "Healthcheck", status: "complete" },
|
|
{ id: "complete", label: "Complete", status: "complete" },
|
|
],
|
|
logs: [
|
|
"[info] Exact commit verified.",
|
|
"[job] deploy: success",
|
|
"[ok] Server reports b82f91a and healthcheck returned 200.",
|
|
],
|
|
run: {
|
|
id: 48,
|
|
runNumber: 48,
|
|
status: "completed",
|
|
conclusion: "success",
|
|
name: "ForgeFlow deployment",
|
|
},
|
|
runUrl:
|
|
"https://gitea.internal/jens/microsoft-cloud-operations-platform/actions/runs/48",
|
|
},
|
|
{
|
|
id: "op-failed",
|
|
type: "deployment",
|
|
action: "deploy",
|
|
status: "failed",
|
|
repository: "jens/portfolio",
|
|
profileId: "profile-portfolio",
|
|
profileName: "Production",
|
|
environment: "production",
|
|
workflowFile: "deploy.yml",
|
|
branch: "main",
|
|
sha: "a7f2e1c1bb6147fc8b6633d2b08500c93402a719",
|
|
shortSha: "a7f2e1c",
|
|
createdAt: iso(-86400000),
|
|
updatedAt: iso(-86300000),
|
|
failure: { stage: "healthcheck", message: "Healthcheck returned 502." },
|
|
stages: [
|
|
{ id: "requested", label: "Requested", status: "complete" },
|
|
{ id: "verified", label: "Verified", status: "complete" },
|
|
{ id: "queued", label: "Workflow queued", status: "complete" },
|
|
{ id: "runner", label: "Runner execution", status: "complete" },
|
|
{ id: "healthcheck", label: "Healthcheck", status: "failed" },
|
|
{ id: "complete", label: "Complete", status: "failed" },
|
|
],
|
|
logs: ["[job] deploy: success", "[error] Healthcheck returned 502."],
|
|
},
|
|
],
|
|
};
|
|
|
|
let repositories = [
|
|
{
|
|
id: 1,
|
|
name: "microsoft-cloud-operations-platform",
|
|
fullName: "jens/microsoft-cloud-operations-platform",
|
|
owner: { login: "jens" },
|
|
description: "Tenant-aware Microsoft cloud operations console.",
|
|
private: true,
|
|
defaultBranch: "main",
|
|
htmlUrl:
|
|
"https://gitea.internal/jens/microsoft-cloud-operations-platform",
|
|
cloneUrl:
|
|
"https://gitea.internal/jens/microsoft-cloud-operations-platform.git",
|
|
sshUrl: "git@gitea.internal:jens/microsoft-cloud-operations-platform.git",
|
|
updatedAt: now,
|
|
localPath: "C:\\Development\\Microsoft-Cloud-Operations-Platform",
|
|
localStatus: makeStatus({
|
|
head: "b82f91ab0173cd4346ca0f0f7dcc3e8182cc8fd0",
|
|
}),
|
|
linkState: "linked",
|
|
deploymentProfiles: [
|
|
profile("profile-mcop-prod", "Production", "production", {
|
|
liveSha: "72bd10eb0173cd4346ca0f0f7dcc3e8182cc8fd0",
|
|
previousSha: "6ac991ab0173cd4346ca0f0f7dcc3e8182cc8fd0",
|
|
healthy: true,
|
|
}),
|
|
profile("profile-mcop-stage", "Staging", "staging", {
|
|
liveSha: "b82f91ab0173cd4346ca0f0f7dcc3e8182cc8fd0",
|
|
previousSha: "72bd10eb0173cd4346ca0f0f7dcc3e8182cc8fd0",
|
|
healthy: true,
|
|
confirmationRequired: false,
|
|
}),
|
|
],
|
|
},
|
|
{
|
|
id: 2,
|
|
name: "vacancyradar",
|
|
fullName: "jens/vacancyradar",
|
|
owner: { login: "jens" },
|
|
description: "Local-first vacancy intelligence cockpit.",
|
|
private: true,
|
|
defaultBranch: "main",
|
|
htmlUrl: "https://gitea.internal/jens/vacancyradar",
|
|
cloneUrl: "https://gitea.internal/jens/vacancyradar.git",
|
|
sshUrl: "git@gitea.internal:jens/vacancyradar.git",
|
|
updatedAt: now,
|
|
localPath: "C:\\Development\\VacancyRadar",
|
|
localStatus: makeStatus({
|
|
head: "c9182d0d28318c8cf0af109edc054732426aadf1",
|
|
branch: "feature/deployment-api",
|
|
files: [
|
|
makeFile("src/api/deploy.ts", "added", { staged: true }),
|
|
makeFile("src/main.tsx"),
|
|
makeFile("src/components/Sidebar.tsx"),
|
|
],
|
|
}),
|
|
linkState: "linked",
|
|
deploymentProfiles: [
|
|
profile("profile-vr", "Production", "production", {
|
|
liveSha: "c117ab9d28318c8cf0af109edc054732426aadf1",
|
|
previousSha: "b1f57aad28318c8cf0af109edc054732426aadf1",
|
|
healthy: true,
|
|
}),
|
|
],
|
|
},
|
|
{
|
|
id: 3,
|
|
name: "unraid-appops-gateway",
|
|
fullName: "jens/unraid-appops-gateway",
|
|
owner: { login: "jens" },
|
|
description: "Safe operations gateway for Unraid and Portainer.",
|
|
private: true,
|
|
defaultBranch: "main",
|
|
htmlUrl: "https://gitea.internal/jens/unraid-appops-gateway",
|
|
cloneUrl: "https://gitea.internal/jens/unraid-appops-gateway.git",
|
|
sshUrl: "git@gitea.internal:jens/unraid-appops-gateway.git",
|
|
updatedAt: now,
|
|
localPath: "C:\\Development\\Unraid-AppOps-Gateway",
|
|
localStatus: makeStatus({
|
|
head: "f2d1e0a1bb6147fc8b6633d2b08500c93402a719",
|
|
ahead: 2,
|
|
}),
|
|
linkState: "linked",
|
|
deploymentProfiles: [
|
|
profile("profile-appops", "Production", "production", {
|
|
liveSha: "8ac731b1bb6147fc8b6633d2b08500c93402a719",
|
|
previousSha: "7bc198a1bb6147fc8b6633d2b08500c93402a719",
|
|
healthy: true,
|
|
}),
|
|
],
|
|
},
|
|
{
|
|
id: 4,
|
|
name: "support-bundle-collector",
|
|
fullName: "jens/support-bundle-collector",
|
|
owner: { login: "jens" },
|
|
description: "Privacy-aware Windows support bundle collector.",
|
|
private: true,
|
|
defaultBranch: "main",
|
|
htmlUrl: "https://gitea.internal/jens/support-bundle-collector",
|
|
cloneUrl: "https://gitea.internal/jens/support-bundle-collector.git",
|
|
sshUrl: "git@gitea.internal:jens/support-bundle-collector.git",
|
|
updatedAt: now,
|
|
localPath: null,
|
|
localStatus: null,
|
|
linkState: "remote-only",
|
|
deploymentProfiles: [],
|
|
},
|
|
{
|
|
id: 5,
|
|
name: "portfolio",
|
|
fullName: "jens/portfolio",
|
|
owner: { login: "jens" },
|
|
description: "Professional infrastructure and automation portfolio.",
|
|
private: false,
|
|
defaultBranch: "main",
|
|
htmlUrl: "https://gitea.internal/jens/portfolio",
|
|
cloneUrl: "https://gitea.internal/jens/portfolio.git",
|
|
sshUrl: "git@gitea.internal:jens/portfolio.git",
|
|
updatedAt: now,
|
|
localPath: "C:\\Development\\portfolio",
|
|
localStatus: makeStatus({
|
|
head: "a7f2e1c1bb6147fc8b6633d2b08500c93402a719",
|
|
behind: 1,
|
|
}),
|
|
linkState: "linked",
|
|
deploymentProfiles: [
|
|
sshProfile("profile-portfolio", "Production", "production", {
|
|
remoteFolder: "Portfolio",
|
|
containerName: "Portfolio",
|
|
liveSha: "4c20dd11bb6147fc8b6633d2b08500c93402a719",
|
|
giteaSha: "a7f2e1c1bb6147fc8b6633d2b08500c93402a719",
|
|
previousSha: "31adfe11bb6147fc8b6633d2b08500c93402a719",
|
|
healthy: false,
|
|
matchesGitea: false,
|
|
}),
|
|
],
|
|
},
|
|
];
|
|
|
|
const diffs = {
|
|
"src/api/deploy.ts": `diff --git a/src/api/deploy.ts b/src/api/deploy.ts\nnew file mode 100644\n--- /dev/null\n+++ b/src/api/deploy.ts\n@@ -0,0 +1,18 @@\n+export interface DeploymentRequest {\n+ environment: 'staging' | 'production';\n+ commitSha: string;\n+}\n+\n+export async function deploy(request: DeploymentRequest) {\n+ return api.post('/deployments', request);\n+}`,
|
|
"src/main.tsx": `diff --git a/src/main.tsx b/src/main.tsx\nindex 45ad1a2..939fc17 100644\n--- a/src/main.tsx\n+++ b/src/main.tsx\n@@ -24,8 +24,9 @@ import { Router } from './routes';\n-const API_ENDPOINT = 'http://localhost:3000';\n+const API_ENDPOINT = process.env.VITE_API_URL || '/api';\n+const DEPLOY_VERSION = '1.0.4-rc1';`,
|
|
"src/components/Sidebar.tsx": `diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx\nindex a7bbd82..bf21e90 100644\n--- a/src/components/Sidebar.tsx\n+++ b/src/components/Sidebar.tsx\n@@ -31,6 +31,7 @@ export function Sidebar() {\n+ <NavItem to="/deployments">Deployments</NavItem>`,
|
|
};
|
|
|
|
const findRepo = (localPath) =>
|
|
repositories.find((item) => item.localPath === localPath);
|
|
const findProfileRepo = (profileId) =>
|
|
repositories.find((item) =>
|
|
item.deploymentProfiles.some((entry) => entry.id === profileId),
|
|
);
|
|
const syncState = () => {
|
|
state.deploymentProfiles = {};
|
|
state.deploymentStates = {};
|
|
state.repositoryMappings = {};
|
|
for (const repository of repositories) {
|
|
if (repository.localPath)
|
|
state.repositoryMappings[repository.fullName.toLowerCase()] =
|
|
repository.localPath;
|
|
state.deploymentProfiles[repository.fullName.toLowerCase()] =
|
|
repository.deploymentProfiles.map(
|
|
({ state: profileState, ...entry }) => entry,
|
|
);
|
|
for (const entry of repository.deploymentProfiles)
|
|
if (entry.state) state.deploymentStates[entry.id] = clone(entry.state);
|
|
}
|
|
};
|
|
const recompute = (repository) => {
|
|
const status = repository.localStatus;
|
|
if (status) {
|
|
status.counts = {
|
|
changed: status.files.length,
|
|
staged: status.files.filter((item) => item.staged).length,
|
|
unstaged: status.files.filter((item) => item.unstaged).length,
|
|
conflicts: status.files.filter((item) => item.conflict).length,
|
|
untracked: status.files.filter((item) => item.untracked).length,
|
|
};
|
|
status.clean = status.files.length === 0;
|
|
status.shortHead = status.head.slice(0, 7);
|
|
status.branch.oid = status.head;
|
|
}
|
|
repository.favorite = state.favorites.includes(
|
|
repository.fullName.toLowerCase(),
|
|
);
|
|
repository.readyToDeploy = Boolean(
|
|
repository.localPath &&
|
|
status?.clean &&
|
|
status.branch.upstream &&
|
|
status.branch.ahead === 0 &&
|
|
status.branch.behind === 0 &&
|
|
repository.deploymentProfiles.some(
|
|
(entry) => entry.branch === status.branch.head,
|
|
),
|
|
);
|
|
repository.attention =
|
|
!repository.localPath ||
|
|
Boolean(
|
|
status?.counts.conflicts ||
|
|
status?.branch.behind ||
|
|
status?.branch.ahead ||
|
|
status?.counts.changed,
|
|
);
|
|
repository.attentionReason = !repository.localPath
|
|
? "No local folder linked"
|
|
: status?.counts.conflicts
|
|
? `${status.counts.conflicts} conflict(s)`
|
|
: status?.counts.changed
|
|
? `${status.counts.changed} local change(s)`
|
|
: status?.branch.behind
|
|
? `${status.branch.behind} commit(s) behind remote`
|
|
: status?.branch.ahead
|
|
? `${status.branch.ahead} unpushed commit(s)`
|
|
: null;
|
|
repository.preferredCloneUrl =
|
|
state.preferences.preferredCloneProtocol === "ssh"
|
|
? repository.sshUrl
|
|
: repository.cloneUrl;
|
|
};
|
|
const snapshot = () => {
|
|
repositories.forEach(recompute);
|
|
syncState();
|
|
return clone(repositories);
|
|
};
|
|
syncState();
|
|
|
|
const commitHistory = [
|
|
{
|
|
sha: "c9182d0d28318c8cf0af109edc054732426aadf1",
|
|
shortSha: "c9182d0",
|
|
author: "Jens",
|
|
date: now,
|
|
subject: "feat: add deployment provider contract",
|
|
},
|
|
{
|
|
sha: "1fa7399d28318c8cf0af109edc054732426aadf1",
|
|
shortSha: "1fa7399",
|
|
author: "Jens",
|
|
date: iso(-86400000),
|
|
subject: "refactor: consolidate repository state",
|
|
},
|
|
{
|
|
sha: "a251a11d28318c8cf0af109edc054732426aadf1",
|
|
shortSha: "a251a11",
|
|
author: "Jens",
|
|
date: iso(-172800000),
|
|
subject: "docs: define deployment safety gates",
|
|
},
|
|
];
|
|
const branchesByRepo = new Map();
|
|
const stashesByRepo = new Map();
|
|
|
|
function updateOperation(operation) {
|
|
state.operations = [
|
|
clone(operation),
|
|
...state.operations.filter((item) => item.id !== operation.id),
|
|
].slice(0, 250);
|
|
emitOperations([operation]);
|
|
return clone(operation);
|
|
}
|
|
|
|
function advanceOperation(operation) {
|
|
if (
|
|
!operation ||
|
|
["success", "failed", "cancelled", "rolled-back"].includes(
|
|
operation.status,
|
|
)
|
|
)
|
|
return operation;
|
|
operation.demoPolls = (operation.demoPolls || 0) + 1;
|
|
if (operation.demoPolls === 1) {
|
|
operation.status = "running";
|
|
operation.run = {
|
|
id: 81,
|
|
runNumber: 81,
|
|
status: "running",
|
|
conclusion: null,
|
|
name:
|
|
operation.action === "rollback"
|
|
? "ForgeFlow rollback"
|
|
: "ForgeFlow deployment",
|
|
};
|
|
operation.runUrl = `https://gitea.internal/${operation.repository}/actions/runs/81`;
|
|
operation.stages.find((item) => item.id === "queued").status = "complete";
|
|
operation.stages.find((item) => item.id === "runner").status = "active";
|
|
operation.jobs = [
|
|
{
|
|
id: 201,
|
|
name: operation.action === "rollback" ? "rollback" : "deploy",
|
|
status: "running",
|
|
conclusion: null,
|
|
},
|
|
];
|
|
operation.logs.push(`[job] ${operation.jobs[0].name}: running`);
|
|
} else if (operation.demoPolls >= 2) {
|
|
operation.status =
|
|
operation.action === "rollback" ? "rolled-back" : "success";
|
|
operation.stages.forEach((item) => {
|
|
item.status = "complete";
|
|
});
|
|
operation.jobs = [
|
|
{
|
|
id: 201,
|
|
name: operation.action === "rollback" ? "rollback" : "deploy",
|
|
status: "completed",
|
|
conclusion: "success",
|
|
},
|
|
];
|
|
operation.logs.push(
|
|
"[ok] Runner completed successfully.",
|
|
`[ok] Server status endpoint confirms ${operation.shortSha}.`,
|
|
);
|
|
const repository = repositories.find(
|
|
(item) => item.fullName === operation.repository,
|
|
);
|
|
const targetProfile = repository?.deploymentProfiles.find(
|
|
(item) => item.id === operation.profileId,
|
|
);
|
|
if (targetProfile) {
|
|
const oldLive = targetProfile.state.liveSha;
|
|
targetProfile.state.previousSha = oldLive;
|
|
targetProfile.state.liveSha = operation.sha;
|
|
targetProfile.state.healthy = true;
|
|
targetProfile.state.checkedAt = iso();
|
|
}
|
|
}
|
|
operation.updatedAt = iso();
|
|
return operation;
|
|
}
|
|
|
|
const bridgeContext = { wait, clone, iso, storage, repositoryListeners, operationListeners, updateListeners, emitRepositories, emitOperations, randomSha, now, profile, state, repositories, recompute, snapshot, commitHistory, advanceOperation, syncState, diffs, findRepo, findProfileRepo, branchesByRepo, stashesByRepo, updateOperation };
|
|
window.forgeflow = Object.freeze({
|
|
...createMockRepositoryBridge(bridgeContext),
|
|
...createMockDeploymentBridge(bridgeContext),
|
|
});
|
|
})();
|