mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2026-04-02 11:08:13 +03:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c87513b648 | ||
|
|
4f3cbc866e | ||
|
|
60548f2062 | ||
|
|
74dab43e89 | ||
|
|
01a8025140 | ||
|
|
8346748f5a | ||
|
|
38002019ea |
7
.github/workflows/nix-build.yml
vendored
7
.github/workflows/nix-build.yml
vendored
@@ -9,6 +9,8 @@ on:
|
||||
- 'flake.nix'
|
||||
- 'flake.lock'
|
||||
- 'bun.nix'
|
||||
- 'bun.lock'
|
||||
- 'package.json'
|
||||
- '.github/workflows/nix-build.yml'
|
||||
pull_request:
|
||||
branches: [main]
|
||||
@@ -16,6 +18,8 @@ on:
|
||||
- 'flake.nix'
|
||||
- 'flake.lock'
|
||||
- 'bun.nix'
|
||||
- 'bun.lock'
|
||||
- 'package.json'
|
||||
- '.github/workflows/nix-build.yml'
|
||||
|
||||
permissions:
|
||||
@@ -39,6 +43,9 @@ jobs:
|
||||
- name: Setup Nix Cache
|
||||
uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
|
||||
- name: Regenerate bun.nix from bun.lock
|
||||
run: nix run --accept-flake-config github:nix-community/bun2nix -- -o bun.nix
|
||||
|
||||
- name: Check flake
|
||||
run: nix flake check --accept-flake-config
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
# Build the application
|
||||
gitea-mirror = pkgs.stdenv.mkDerivation {
|
||||
pname = "gitea-mirror";
|
||||
version = "3.9.6";
|
||||
version = "3.14.1";
|
||||
|
||||
src = ./.;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "gitea-mirror",
|
||||
"type": "module",
|
||||
"version": "3.14.1",
|
||||
"version": "3.14.2",
|
||||
"engines": {
|
||||
"bun": ">=1.2.9"
|
||||
},
|
||||
|
||||
@@ -555,6 +555,63 @@ describe("Enhanced Gitea Operations", () => {
|
||||
expect(releaseCall.octokit).toBeDefined();
|
||||
});
|
||||
|
||||
test("prefers recorded mirroredLocation when owner resolution changes", async () => {
|
||||
mockGetGiteaRepoOwnerAsync.mockImplementation(() => Promise.resolve("ceph"));
|
||||
|
||||
const config: Partial<Config> = {
|
||||
userId: "user123",
|
||||
githubConfig: {
|
||||
username: "testuser",
|
||||
token: "github-token",
|
||||
privateRepositories: false,
|
||||
mirrorStarred: true,
|
||||
},
|
||||
giteaConfig: {
|
||||
url: "https://gitea.example.com",
|
||||
token: "encrypted-token",
|
||||
defaultOwner: "testuser",
|
||||
mirrorReleases: true,
|
||||
},
|
||||
};
|
||||
|
||||
const repository: Repository = {
|
||||
id: "repo789",
|
||||
name: "test-repo",
|
||||
fullName: "ceph/test-repo",
|
||||
owner: "ceph",
|
||||
cloneUrl: "https://github.com/ceph/test-repo.git",
|
||||
isPrivate: false,
|
||||
isStarred: true,
|
||||
status: repoStatusEnum.parse("mirrored"),
|
||||
visibility: "public",
|
||||
userId: "user123",
|
||||
mirroredLocation: "starred/test-repo",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
const result = await syncGiteaRepoEnhanced(
|
||||
{ config, repository },
|
||||
{
|
||||
getGiteaRepoOwnerAsync: mockGetGiteaRepoOwnerAsync,
|
||||
mirrorGitHubReleasesToGitea: mockMirrorGitHubReleasesToGitea,
|
||||
mirrorGitRepoIssuesToGitea: mockMirrorGitRepoIssuesToGitea,
|
||||
mirrorGitRepoPullRequestsToGitea: mockMirrorGitRepoPullRequestsToGitea,
|
||||
mirrorGitRepoLabelsToGitea: mockMirrorGitRepoLabelsToGitea,
|
||||
mirrorGitRepoMilestonesToGitea: mockMirrorGitRepoMilestonesToGitea,
|
||||
}
|
||||
);
|
||||
|
||||
expect(result).toEqual({ success: true });
|
||||
|
||||
const mirrorSyncCalls = mockHttpPost.mock.calls.filter((call) =>
|
||||
String(call[0]).includes("/mirror-sync")
|
||||
);
|
||||
expect(mirrorSyncCalls).toHaveLength(1);
|
||||
expect(String(mirrorSyncCalls[0][0])).toContain("/api/v1/repos/starred/test-repo/mirror-sync");
|
||||
expect(String(mirrorSyncCalls[0][0])).not.toContain("/api/v1/repos/ceph/test-repo/mirror-sync");
|
||||
});
|
||||
|
||||
test("blocks sync when pre-sync snapshot fails and blocking is enabled", async () => {
|
||||
mockShouldCreatePreSyncBackup = true;
|
||||
mockShouldBlockSyncOnBackupFailure = true;
|
||||
|
||||
@@ -52,6 +52,41 @@ interface GiteaRepoInfo {
|
||||
private: boolean;
|
||||
}
|
||||
|
||||
interface SyncTargetCandidate {
|
||||
owner: string;
|
||||
repoName: string;
|
||||
}
|
||||
|
||||
function parseMirroredLocation(location?: string | null): SyncTargetCandidate | null {
|
||||
if (!location) return null;
|
||||
|
||||
const trimmed = location.trim();
|
||||
if (!trimmed) return null;
|
||||
|
||||
const slashIndex = trimmed.indexOf("/");
|
||||
if (slashIndex <= 0 || slashIndex === trimmed.length - 1) return null;
|
||||
|
||||
const owner = trimmed.slice(0, slashIndex).trim();
|
||||
const repoName = trimmed.slice(slashIndex + 1).trim();
|
||||
if (!owner || !repoName) return null;
|
||||
|
||||
return { owner, repoName };
|
||||
}
|
||||
|
||||
function dedupeSyncTargets(targets: SyncTargetCandidate[]): SyncTargetCandidate[] {
|
||||
const seen = new Set<string>();
|
||||
const deduped: SyncTargetCandidate[] = [];
|
||||
|
||||
for (const target of targets) {
|
||||
const key = `${target.owner}/${target.repoName}`.toLowerCase();
|
||||
if (seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
deduped.push(target);
|
||||
}
|
||||
|
||||
return deduped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a repository exists in Gitea and return its details
|
||||
*/
|
||||
@@ -285,19 +320,78 @@ export async function syncGiteaRepoEnhanced({
|
||||
})
|
||||
.where(eq(repositories.id, repository.id!));
|
||||
|
||||
// Get the expected owner
|
||||
// Resolve sync target in a backward-compatible order:
|
||||
// 1) recorded mirroredLocation (actual historical mirror location)
|
||||
// 2) owner derived from current strategy/config
|
||||
const dependencies = deps ?? (await import("./gitea"));
|
||||
const repoOwner = await dependencies.getGiteaRepoOwnerAsync({ config, repository });
|
||||
const expectedOwner = await dependencies.getGiteaRepoOwnerAsync({ config, repository });
|
||||
const recordedTarget = parseMirroredLocation(repository.mirroredLocation);
|
||||
const candidateTargets = dedupeSyncTargets([
|
||||
...(recordedTarget ? [recordedTarget] : []),
|
||||
{ owner: expectedOwner, repoName: repository.name },
|
||||
]);
|
||||
|
||||
// Check if repo exists and get its info
|
||||
const repoInfo = await getGiteaRepoInfo({
|
||||
config,
|
||||
owner: repoOwner,
|
||||
repoName: repository.name,
|
||||
});
|
||||
let repoOwner = expectedOwner;
|
||||
let repoName = repository.name;
|
||||
let repoInfo: GiteaRepoInfo | null = null;
|
||||
let firstNonMirrorTarget: SyncTargetCandidate | null = null;
|
||||
|
||||
for (const target of candidateTargets) {
|
||||
const candidateInfo = await getGiteaRepoInfo({
|
||||
config,
|
||||
owner: target.owner,
|
||||
repoName: target.repoName,
|
||||
});
|
||||
|
||||
if (!candidateInfo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!candidateInfo.mirror) {
|
||||
if (!firstNonMirrorTarget) {
|
||||
firstNonMirrorTarget = target;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
repoOwner = target.owner;
|
||||
repoName = target.repoName;
|
||||
repoInfo = candidateInfo;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!repoInfo) {
|
||||
throw new Error(`Repository ${repository.name} not found in Gitea at ${repoOwner}/${repository.name}`);
|
||||
if (firstNonMirrorTarget) {
|
||||
console.warn(
|
||||
`[Sync] Repository ${repository.name} exists at ${firstNonMirrorTarget.owner}/${firstNonMirrorTarget.repoName} but is not configured as a mirror`
|
||||
);
|
||||
|
||||
await db
|
||||
.update(repositories)
|
||||
.set({
|
||||
status: repoStatusEnum.parse("failed"),
|
||||
updatedAt: new Date(),
|
||||
errorMessage: "Repository exists in Gitea but is not configured as a mirror. Manual intervention required.",
|
||||
})
|
||||
.where(eq(repositories.id, repository.id!));
|
||||
|
||||
await createMirrorJob({
|
||||
userId: config.userId,
|
||||
repositoryId: repository.id,
|
||||
repositoryName: repository.name,
|
||||
message: `Cannot sync ${repository.name}: Not a mirror repository`,
|
||||
details: `Repository ${repository.name} exists in Gitea but is not configured as a mirror. You may need to delete and recreate it as a mirror, or manually configure it as a mirror in Gitea.`,
|
||||
status: "failed",
|
||||
});
|
||||
|
||||
throw new Error(`Repository ${repository.name} is not a mirror. Cannot sync.`);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Repository ${repository.name} not found in Gitea. Tried locations: ${candidateTargets
|
||||
.map((t) => `${t.owner}/${t.repoName}`)
|
||||
.join(", ")}`
|
||||
);
|
||||
}
|
||||
|
||||
// Check if it's a mirror repository
|
||||
@@ -342,7 +436,7 @@ export async function syncGiteaRepoEnhanced({
|
||||
giteaUrl: config.giteaConfig.url,
|
||||
giteaToken: decryptedConfig.giteaConfig.token,
|
||||
giteaOwner: repoOwner,
|
||||
giteaRepo: repository.name,
|
||||
giteaRepo: repoName,
|
||||
octokit: fpOctokit,
|
||||
githubOwner: repository.owner,
|
||||
githubRepo: repository.name,
|
||||
@@ -407,13 +501,13 @@ export async function syncGiteaRepoEnhanced({
|
||||
if (shouldBackupForStrategy(backupStrategy, forcePushDetected)) {
|
||||
const cloneUrl =
|
||||
repoInfo.clone_url ||
|
||||
`${config.giteaConfig.url.replace(/\/$/, "")}/${repoOwner}/${repository.name}.git`;
|
||||
`${config.giteaConfig.url.replace(/\/$/, "")}/${repoOwner}/${repoName}.git`;
|
||||
|
||||
try {
|
||||
const backupResult = await createPreSyncBundleBackup({
|
||||
config,
|
||||
owner: repoOwner,
|
||||
repoName: repository.name,
|
||||
repoName,
|
||||
cloneUrl,
|
||||
force: true, // Strategy already decided to backup; skip legacy gate
|
||||
});
|
||||
@@ -464,22 +558,22 @@ export async function syncGiteaRepoEnhanced({
|
||||
// Update mirror interval if needed
|
||||
if (config.giteaConfig?.mirrorInterval) {
|
||||
try {
|
||||
console.log(`[Sync] Updating mirror interval for ${repository.name} to ${config.giteaConfig.mirrorInterval}`);
|
||||
const updateUrl = `${config.giteaConfig.url}/api/v1/repos/${repoOwner}/${repository.name}`;
|
||||
console.log(`[Sync] Updating mirror interval for ${repoOwner}/${repoName} to ${config.giteaConfig.mirrorInterval}`);
|
||||
const updateUrl = `${config.giteaConfig.url}/api/v1/repos/${repoOwner}/${repoName}`;
|
||||
await httpPatch(updateUrl, {
|
||||
mirror_interval: config.giteaConfig.mirrorInterval,
|
||||
}, {
|
||||
Authorization: `token ${decryptedConfig.giteaConfig.token}`,
|
||||
});
|
||||
console.log(`[Sync] Successfully updated mirror interval for ${repository.name}`);
|
||||
console.log(`[Sync] Successfully updated mirror interval for ${repoOwner}/${repoName}`);
|
||||
} catch (updateError) {
|
||||
console.warn(`[Sync] Failed to update mirror interval for ${repository.name}:`, updateError);
|
||||
console.warn(`[Sync] Failed to update mirror interval for ${repoOwner}/${repoName}:`, updateError);
|
||||
// Continue with sync even if interval update fails
|
||||
}
|
||||
}
|
||||
|
||||
// Perform the sync
|
||||
const apiUrl = `${config.giteaConfig.url}/api/v1/repos/${repoOwner}/${repository.name}/mirror-sync`;
|
||||
const apiUrl = `${config.giteaConfig.url}/api/v1/repos/${repoOwner}/${repoName}/mirror-sync`;
|
||||
|
||||
try {
|
||||
const response = await httpPost(apiUrl, undefined, {
|
||||
@@ -536,7 +630,7 @@ export async function syncGiteaRepoEnhanced({
|
||||
octokit,
|
||||
repository,
|
||||
giteaOwner: repoOwner,
|
||||
giteaRepoName: repository.name,
|
||||
giteaRepoName: repoName,
|
||||
});
|
||||
metadataState.components.releases = true;
|
||||
metadataUpdated = true;
|
||||
@@ -568,7 +662,7 @@ export async function syncGiteaRepoEnhanced({
|
||||
octokit,
|
||||
repository,
|
||||
giteaOwner: repoOwner,
|
||||
giteaRepoName: repository.name,
|
||||
giteaRepoName: repoName,
|
||||
});
|
||||
metadataState.components.issues = true;
|
||||
metadataState.components.labels = true;
|
||||
@@ -601,7 +695,7 @@ export async function syncGiteaRepoEnhanced({
|
||||
octokit,
|
||||
repository,
|
||||
giteaOwner: repoOwner,
|
||||
giteaRepoName: repository.name,
|
||||
giteaRepoName: repoName,
|
||||
});
|
||||
metadataState.components.pullRequests = true;
|
||||
metadataUpdated = true;
|
||||
@@ -631,7 +725,7 @@ export async function syncGiteaRepoEnhanced({
|
||||
octokit,
|
||||
repository,
|
||||
giteaOwner: repoOwner,
|
||||
giteaRepoName: repository.name,
|
||||
giteaRepoName: repoName,
|
||||
});
|
||||
metadataState.components.labels = true;
|
||||
metadataUpdated = true;
|
||||
@@ -670,7 +764,7 @@ export async function syncGiteaRepoEnhanced({
|
||||
octokit,
|
||||
repository,
|
||||
giteaOwner: repoOwner,
|
||||
giteaRepoName: repository.name,
|
||||
giteaRepoName: repoName,
|
||||
});
|
||||
metadataState.components.milestones = true;
|
||||
metadataUpdated = true;
|
||||
@@ -708,7 +802,7 @@ export async function syncGiteaRepoEnhanced({
|
||||
updatedAt: new Date(),
|
||||
lastMirrored: new Date(),
|
||||
errorMessage: null,
|
||||
mirroredLocation: `${repoOwner}/${repository.name}`,
|
||||
mirroredLocation: `${repoOwner}/${repoName}`,
|
||||
metadata: metadataUpdated
|
||||
? serializeRepositoryMetadataState(metadataState)
|
||||
: repository.metadata ?? null,
|
||||
@@ -720,7 +814,7 @@ export async function syncGiteaRepoEnhanced({
|
||||
repositoryId: repository.id,
|
||||
repositoryName: repository.name,
|
||||
message: `Sync requested for repository: ${repository.name}`,
|
||||
details: `Mirror sync was requested for ${repository.name}.`,
|
||||
details: `Mirror sync was requested for ${repoOwner}/${repoName}.`,
|
||||
status: "synced",
|
||||
});
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import { db, organizations, repositories } from "./db";
|
||||
import { eq, and, ne } from "drizzle-orm";
|
||||
import { decryptConfigTokens } from "./utils/config-encryption";
|
||||
import { formatDateShort } from "./utils";
|
||||
import { buildGithubSourceAuthPayload } from "./utils/mirror-source-auth";
|
||||
import {
|
||||
parseRepositoryMetadataState,
|
||||
serializeRepositoryMetadataState,
|
||||
@@ -816,14 +817,22 @@ export const mirrorGithubRepoToGitea = async ({
|
||||
|
||||
// Add authentication for private repositories
|
||||
if (repository.isPrivate) {
|
||||
if (!config.githubConfig.token) {
|
||||
throw new Error(
|
||||
"GitHub token is required to mirror private repositories."
|
||||
);
|
||||
}
|
||||
// Use separate auth fields (required for Forgejo 12+ compatibility)
|
||||
migratePayload.auth_username = "oauth2"; // GitHub tokens work with any username
|
||||
migratePayload.auth_token = decryptedConfig.githubConfig.token;
|
||||
const githubOwner =
|
||||
(
|
||||
config.githubConfig as typeof config.githubConfig & {
|
||||
owner?: string;
|
||||
}
|
||||
).owner || "";
|
||||
|
||||
Object.assign(
|
||||
migratePayload,
|
||||
buildGithubSourceAuthPayload({
|
||||
token: decryptedConfig.githubConfig.token,
|
||||
githubOwner,
|
||||
githubUsername: config.githubConfig.username,
|
||||
repositoryOwner: repository.owner,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Track whether the Gitea migrate call succeeded so the catch block
|
||||
@@ -1496,14 +1505,22 @@ export async function mirrorGitHubRepoToGiteaOrg({
|
||||
|
||||
// Add authentication for private repositories
|
||||
if (repository.isPrivate) {
|
||||
if (!config.githubConfig?.token) {
|
||||
throw new Error(
|
||||
"GitHub token is required to mirror private repositories."
|
||||
);
|
||||
}
|
||||
// Use separate auth fields (required for Forgejo 12+ compatibility)
|
||||
migratePayload.auth_username = "oauth2"; // GitHub tokens work with any username
|
||||
migratePayload.auth_token = decryptedConfig.githubConfig.token;
|
||||
const githubOwner =
|
||||
(
|
||||
config.githubConfig as typeof config.githubConfig & {
|
||||
owner?: string;
|
||||
}
|
||||
)?.owner || "";
|
||||
|
||||
Object.assign(
|
||||
migratePayload,
|
||||
buildGithubSourceAuthPayload({
|
||||
token: decryptedConfig.githubConfig?.token,
|
||||
githubOwner,
|
||||
githubUsername: config.githubConfig?.username,
|
||||
repositoryOwner: repository.owner,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
let migrateSucceeded = false;
|
||||
|
||||
63
src/lib/utils/mirror-source-auth.test.ts
Normal file
63
src/lib/utils/mirror-source-auth.test.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { buildGithubSourceAuthPayload } from "./mirror-source-auth";
|
||||
|
||||
describe("buildGithubSourceAuthPayload", () => {
|
||||
test("uses configured owner when available", () => {
|
||||
const auth = buildGithubSourceAuthPayload({
|
||||
token: "ghp_test_token",
|
||||
githubOwner: "ConfiguredOwner",
|
||||
githubUsername: "fallback-user",
|
||||
repositoryOwner: "repo-owner",
|
||||
});
|
||||
|
||||
expect(auth).toEqual({
|
||||
auth_username: "ConfiguredOwner",
|
||||
auth_password: "ghp_test_token",
|
||||
auth_token: "ghp_test_token",
|
||||
});
|
||||
});
|
||||
|
||||
test("falls back to configured username then repository owner", () => {
|
||||
const authFromUsername = buildGithubSourceAuthPayload({
|
||||
token: "token1",
|
||||
githubUsername: "configured-user",
|
||||
repositoryOwner: "repo-owner",
|
||||
});
|
||||
|
||||
expect(authFromUsername.auth_username).toBe("configured-user");
|
||||
|
||||
const authFromRepoOwner = buildGithubSourceAuthPayload({
|
||||
token: "token2",
|
||||
repositoryOwner: "repo-owner",
|
||||
});
|
||||
|
||||
expect(authFromRepoOwner.auth_username).toBe("repo-owner");
|
||||
});
|
||||
|
||||
test("uses x-access-token as last-resort username", () => {
|
||||
const auth = buildGithubSourceAuthPayload({
|
||||
token: "ghp_test_token",
|
||||
});
|
||||
|
||||
expect(auth.auth_username).toBe("x-access-token");
|
||||
});
|
||||
|
||||
test("trims token whitespace", () => {
|
||||
const auth = buildGithubSourceAuthPayload({
|
||||
token: " ghp_trimmed ",
|
||||
githubUsername: "user",
|
||||
});
|
||||
|
||||
expect(auth.auth_password).toBe("ghp_trimmed");
|
||||
expect(auth.auth_token).toBe("ghp_trimmed");
|
||||
});
|
||||
|
||||
test("throws when token is missing", () => {
|
||||
expect(() =>
|
||||
buildGithubSourceAuthPayload({
|
||||
token: " ",
|
||||
githubUsername: "user",
|
||||
})
|
||||
).toThrow("GitHub token is required to mirror private repositories.");
|
||||
});
|
||||
});
|
||||
46
src/lib/utils/mirror-source-auth.ts
Normal file
46
src/lib/utils/mirror-source-auth.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
interface BuildGithubSourceAuthPayloadParams {
|
||||
token?: string | null;
|
||||
githubOwner?: string | null;
|
||||
githubUsername?: string | null;
|
||||
repositoryOwner?: string | null;
|
||||
}
|
||||
|
||||
export interface GithubSourceAuthPayload {
|
||||
auth_username: string;
|
||||
auth_password: string;
|
||||
auth_token: string;
|
||||
}
|
||||
|
||||
const DEFAULT_GITHUB_AUTH_USERNAME = "x-access-token";
|
||||
|
||||
function normalize(value?: string | null): string {
|
||||
return typeof value === "string" ? value.trim() : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Build source credentials for private GitHub repository mirroring.
|
||||
* GitHub expects username + token-as-password over HTTPS (not the GitLab-style "oauth2" username).
|
||||
*/
|
||||
export function buildGithubSourceAuthPayload({
|
||||
token,
|
||||
githubOwner,
|
||||
githubUsername,
|
||||
repositoryOwner,
|
||||
}: BuildGithubSourceAuthPayloadParams): GithubSourceAuthPayload {
|
||||
const normalizedToken = normalize(token);
|
||||
if (!normalizedToken) {
|
||||
throw new Error("GitHub token is required to mirror private repositories.");
|
||||
}
|
||||
|
||||
const authUsername =
|
||||
normalize(githubOwner) ||
|
||||
normalize(githubUsername) ||
|
||||
normalize(repositoryOwner) ||
|
||||
DEFAULT_GITHUB_AUTH_USERNAME;
|
||||
|
||||
return {
|
||||
auth_username: authUsername,
|
||||
auth_password: normalizedToken,
|
||||
auth_token: normalizedToken,
|
||||
};
|
||||
}
|
||||
@@ -13,7 +13,6 @@ try {
|
||||
activityData = jobs.flatMap((job: any) => {
|
||||
// Check if log exists before parsing
|
||||
if (!job.log) {
|
||||
console.warn(`Job ${job.id} has no log data`);
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ try {
|
||||
activityData = jobs.flatMap((job: any) => {
|
||||
// Check if log exists before parsing
|
||||
if (!job.log) {
|
||||
console.warn(`Job ${job.id} has no log data`);
|
||||
return [];
|
||||
}
|
||||
try {
|
||||
@@ -68,4 +67,4 @@ try {
|
||||
<body>
|
||||
<App page='dashboard' client:load />
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
42
www/pnpm-lock.yaml
generated
42
www/pnpm-lock.yaml
generated
@@ -1655,12 +1655,12 @@ packages:
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
picomatch@2.3.1:
|
||||
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
||||
picomatch@2.3.2:
|
||||
resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==}
|
||||
engines: {node: '>=8.6'}
|
||||
|
||||
picomatch@4.0.3:
|
||||
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
||||
picomatch@4.0.4:
|
||||
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
postcss@8.5.6:
|
||||
@@ -1801,8 +1801,8 @@ packages:
|
||||
sisteransi@1.0.5:
|
||||
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
|
||||
|
||||
smol-toml@1.6.0:
|
||||
resolution: {integrity: sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==}
|
||||
smol-toml@1.6.1:
|
||||
resolution: {integrity: sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
source-map-js@1.2.1:
|
||||
@@ -2091,7 +2091,7 @@ snapshots:
|
||||
|
||||
'@astrojs/internal-helpers@0.8.0':
|
||||
dependencies:
|
||||
picomatch: 4.0.3
|
||||
picomatch: 4.0.4
|
||||
|
||||
'@astrojs/markdown-remark@7.0.0':
|
||||
dependencies:
|
||||
@@ -2109,7 +2109,7 @@ snapshots:
|
||||
remark-rehype: 11.1.2
|
||||
remark-smartypants: 3.0.2
|
||||
shiki: 4.0.2
|
||||
smol-toml: 1.6.0
|
||||
smol-toml: 1.6.1
|
||||
unified: 11.0.5
|
||||
unist-util-remove-position: 5.0.0
|
||||
unist-util-visit: 5.1.0
|
||||
@@ -2553,7 +2553,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.8
|
||||
estree-walker: 2.0.2
|
||||
picomatch: 4.0.3
|
||||
picomatch: 4.0.4
|
||||
optionalDependencies:
|
||||
rollup: 4.59.0
|
||||
|
||||
@@ -2844,7 +2844,7 @@ snapshots:
|
||||
anymatch@3.1.3:
|
||||
dependencies:
|
||||
normalize-path: 3.0.0
|
||||
picomatch: 2.3.1
|
||||
picomatch: 2.3.2
|
||||
|
||||
argparse@2.0.1: {}
|
||||
|
||||
@@ -2891,11 +2891,11 @@ snapshots:
|
||||
p-queue: 9.1.0
|
||||
package-manager-detector: 1.6.0
|
||||
piccolore: 0.1.3
|
||||
picomatch: 4.0.3
|
||||
picomatch: 4.0.4
|
||||
rehype: 13.0.2
|
||||
semver: 7.7.4
|
||||
shiki: 4.0.2
|
||||
smol-toml: 1.6.0
|
||||
smol-toml: 1.6.1
|
||||
svgo: 4.0.1
|
||||
tinyclip: 0.1.12
|
||||
tinyexec: 1.0.2
|
||||
@@ -3181,9 +3181,9 @@ snapshots:
|
||||
|
||||
extend@3.0.2: {}
|
||||
|
||||
fdir@6.5.0(picomatch@4.0.3):
|
||||
fdir@6.5.0(picomatch@4.0.4):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.3
|
||||
picomatch: 4.0.4
|
||||
|
||||
flattie@1.1.1: {}
|
||||
|
||||
@@ -3987,9 +3987,9 @@ snapshots:
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
picomatch@2.3.1: {}
|
||||
picomatch@2.3.2: {}
|
||||
|
||||
picomatch@4.0.3: {}
|
||||
picomatch@4.0.4: {}
|
||||
|
||||
postcss@8.5.6:
|
||||
dependencies:
|
||||
@@ -4247,7 +4247,7 @@ snapshots:
|
||||
|
||||
sisteransi@1.0.5: {}
|
||||
|
||||
smol-toml@1.6.0: {}
|
||||
smol-toml@1.6.1: {}
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
@@ -4294,8 +4294,8 @@ snapshots:
|
||||
|
||||
tinyglobby@0.2.15:
|
||||
dependencies:
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
picomatch: 4.0.3
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
picomatch: 4.0.4
|
||||
|
||||
trim-lines@3.0.1: {}
|
||||
|
||||
@@ -4419,8 +4419,8 @@ snapshots:
|
||||
vite@7.3.1(@types/node@24.7.1)(jiti@2.6.1)(lightningcss@1.31.1):
|
||||
dependencies:
|
||||
esbuild: 0.27.3
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
picomatch: 4.0.3
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
picomatch: 4.0.4
|
||||
postcss: 8.5.6
|
||||
rollup: 4.59.0
|
||||
tinyglobby: 0.2.15
|
||||
|
||||
Reference in New Issue
Block a user