Compare commits

...

7 Commits

Author SHA1 Message Date
Arunavo Ray
c87513b648 chore: bump version to 3.14.2 2026-03-27 13:55:56 +05:30
ARUNAVO RAY
4f3cbc866e fix private github mirror auth (#255) 2026-03-27 13:49:36 +05:30
ARUNAVO RAY
60548f2062 fix sync target resolution for mirrored repos (#249) 2026-03-27 12:33:59 +05:30
dependabot[bot]
74dab43e89 build(deps): bump picomatch (#251)
Bumps the npm_and_yarn group with 1 update in the /www directory: [picomatch](https://github.com/micromatch/picomatch).


Updates `picomatch` from 2.3.1 to 2.3.2
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/picomatch/compare/2.3.1...2.3.2)

---
updated-dependencies:
- dependency-name: picomatch
  dependency-version: 2.3.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-27 09:39:17 +05:30
dependabot[bot]
01a8025140 build(deps): bump smol-toml (#250)
Bumps the npm_and_yarn group with 1 update in the /www directory: [smol-toml](https://github.com/squirrelchat/smol-toml).


Updates `smol-toml` from 1.6.0 to 1.6.1
- [Release notes](https://github.com/squirrelchat/smol-toml/releases)
- [Commits](https://github.com/squirrelchat/smol-toml/compare/v1.6.0...v1.6.1)

---
updated-dependencies:
- dependency-name: smol-toml
  dependency-version: 1.6.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-26 22:35:57 +05:30
Arunavo Ray
8346748f5a fix: move --accept-flake-config before -- in bun2nix step
The flag was being passed to bun2nix instead of nix, causing
"unexpected argument" error.
2026-03-24 08:22:04 +05:30
Arunavo Ray
38002019ea fix: regenerate bun.nix in CI to prevent stale dependency errors
The Nix build has been failing since v3.9.6 because bun.nix fell out
of sync with bun.lock. During the sandboxed build bun install cannot
fetch missing packages, causing ConnectionRefused errors.

- Add bun2nix regeneration step before nix build in CI
- Trigger workflow on bun.lock and package.json changes
- Update flake.nix version from 3.9.6 to 3.14.1
2026-03-24 08:20:26 +05:30
11 changed files with 348 additions and 66 deletions

View File

@@ -9,6 +9,8 @@ on:
- 'flake.nix' - 'flake.nix'
- 'flake.lock' - 'flake.lock'
- 'bun.nix' - 'bun.nix'
- 'bun.lock'
- 'package.json'
- '.github/workflows/nix-build.yml' - '.github/workflows/nix-build.yml'
pull_request: pull_request:
branches: [main] branches: [main]
@@ -16,6 +18,8 @@ on:
- 'flake.nix' - 'flake.nix'
- 'flake.lock' - 'flake.lock'
- 'bun.nix' - 'bun.nix'
- 'bun.lock'
- 'package.json'
- '.github/workflows/nix-build.yml' - '.github/workflows/nix-build.yml'
permissions: permissions:
@@ -39,6 +43,9 @@ jobs:
- name: Setup Nix Cache - name: Setup Nix Cache
uses: DeterminateSystems/magic-nix-cache-action@main 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 - name: Check flake
run: nix flake check --accept-flake-config run: nix flake check --accept-flake-config

View File

@@ -31,7 +31,7 @@
# Build the application # Build the application
gitea-mirror = pkgs.stdenv.mkDerivation { gitea-mirror = pkgs.stdenv.mkDerivation {
pname = "gitea-mirror"; pname = "gitea-mirror";
version = "3.9.6"; version = "3.14.1";
src = ./.; src = ./.;

View File

@@ -1,7 +1,7 @@
{ {
"name": "gitea-mirror", "name": "gitea-mirror",
"type": "module", "type": "module",
"version": "3.14.1", "version": "3.14.2",
"engines": { "engines": {
"bun": ">=1.2.9" "bun": ">=1.2.9"
}, },

View File

@@ -555,6 +555,63 @@ describe("Enhanced Gitea Operations", () => {
expect(releaseCall.octokit).toBeDefined(); 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 () => { test("blocks sync when pre-sync snapshot fails and blocking is enabled", async () => {
mockShouldCreatePreSyncBackup = true; mockShouldCreatePreSyncBackup = true;
mockShouldBlockSyncOnBackupFailure = true; mockShouldBlockSyncOnBackupFailure = true;

View File

@@ -52,6 +52,41 @@ interface GiteaRepoInfo {
private: boolean; 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 * 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!)); .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 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 let repoOwner = expectedOwner;
const repoInfo = await getGiteaRepoInfo({ let repoName = repository.name;
config, let repoInfo: GiteaRepoInfo | null = null;
owner: repoOwner, let firstNonMirrorTarget: SyncTargetCandidate | null = null;
repoName: repository.name,
}); 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) { 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 // Check if it's a mirror repository
@@ -342,7 +436,7 @@ export async function syncGiteaRepoEnhanced({
giteaUrl: config.giteaConfig.url, giteaUrl: config.giteaConfig.url,
giteaToken: decryptedConfig.giteaConfig.token, giteaToken: decryptedConfig.giteaConfig.token,
giteaOwner: repoOwner, giteaOwner: repoOwner,
giteaRepo: repository.name, giteaRepo: repoName,
octokit: fpOctokit, octokit: fpOctokit,
githubOwner: repository.owner, githubOwner: repository.owner,
githubRepo: repository.name, githubRepo: repository.name,
@@ -407,13 +501,13 @@ export async function syncGiteaRepoEnhanced({
if (shouldBackupForStrategy(backupStrategy, forcePushDetected)) { if (shouldBackupForStrategy(backupStrategy, forcePushDetected)) {
const cloneUrl = const cloneUrl =
repoInfo.clone_url || repoInfo.clone_url ||
`${config.giteaConfig.url.replace(/\/$/, "")}/${repoOwner}/${repository.name}.git`; `${config.giteaConfig.url.replace(/\/$/, "")}/${repoOwner}/${repoName}.git`;
try { try {
const backupResult = await createPreSyncBundleBackup({ const backupResult = await createPreSyncBundleBackup({
config, config,
owner: repoOwner, owner: repoOwner,
repoName: repository.name, repoName,
cloneUrl, cloneUrl,
force: true, // Strategy already decided to backup; skip legacy gate force: true, // Strategy already decided to backup; skip legacy gate
}); });
@@ -464,22 +558,22 @@ export async function syncGiteaRepoEnhanced({
// Update mirror interval if needed // Update mirror interval if needed
if (config.giteaConfig?.mirrorInterval) { if (config.giteaConfig?.mirrorInterval) {
try { try {
console.log(`[Sync] Updating mirror interval for ${repository.name} to ${config.giteaConfig.mirrorInterval}`); console.log(`[Sync] Updating mirror interval for ${repoOwner}/${repoName} to ${config.giteaConfig.mirrorInterval}`);
const updateUrl = `${config.giteaConfig.url}/api/v1/repos/${repoOwner}/${repository.name}`; const updateUrl = `${config.giteaConfig.url}/api/v1/repos/${repoOwner}/${repoName}`;
await httpPatch(updateUrl, { await httpPatch(updateUrl, {
mirror_interval: config.giteaConfig.mirrorInterval, mirror_interval: config.giteaConfig.mirrorInterval,
}, { }, {
Authorization: `token ${decryptedConfig.giteaConfig.token}`, 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) { } 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 // Continue with sync even if interval update fails
} }
} }
// Perform the sync // 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 { try {
const response = await httpPost(apiUrl, undefined, { const response = await httpPost(apiUrl, undefined, {
@@ -536,7 +630,7 @@ export async function syncGiteaRepoEnhanced({
octokit, octokit,
repository, repository,
giteaOwner: repoOwner, giteaOwner: repoOwner,
giteaRepoName: repository.name, giteaRepoName: repoName,
}); });
metadataState.components.releases = true; metadataState.components.releases = true;
metadataUpdated = true; metadataUpdated = true;
@@ -568,7 +662,7 @@ export async function syncGiteaRepoEnhanced({
octokit, octokit,
repository, repository,
giteaOwner: repoOwner, giteaOwner: repoOwner,
giteaRepoName: repository.name, giteaRepoName: repoName,
}); });
metadataState.components.issues = true; metadataState.components.issues = true;
metadataState.components.labels = true; metadataState.components.labels = true;
@@ -601,7 +695,7 @@ export async function syncGiteaRepoEnhanced({
octokit, octokit,
repository, repository,
giteaOwner: repoOwner, giteaOwner: repoOwner,
giteaRepoName: repository.name, giteaRepoName: repoName,
}); });
metadataState.components.pullRequests = true; metadataState.components.pullRequests = true;
metadataUpdated = true; metadataUpdated = true;
@@ -631,7 +725,7 @@ export async function syncGiteaRepoEnhanced({
octokit, octokit,
repository, repository,
giteaOwner: repoOwner, giteaOwner: repoOwner,
giteaRepoName: repository.name, giteaRepoName: repoName,
}); });
metadataState.components.labels = true; metadataState.components.labels = true;
metadataUpdated = true; metadataUpdated = true;
@@ -670,7 +764,7 @@ export async function syncGiteaRepoEnhanced({
octokit, octokit,
repository, repository,
giteaOwner: repoOwner, giteaOwner: repoOwner,
giteaRepoName: repository.name, giteaRepoName: repoName,
}); });
metadataState.components.milestones = true; metadataState.components.milestones = true;
metadataUpdated = true; metadataUpdated = true;
@@ -708,7 +802,7 @@ export async function syncGiteaRepoEnhanced({
updatedAt: new Date(), updatedAt: new Date(),
lastMirrored: new Date(), lastMirrored: new Date(),
errorMessage: null, errorMessage: null,
mirroredLocation: `${repoOwner}/${repository.name}`, mirroredLocation: `${repoOwner}/${repoName}`,
metadata: metadataUpdated metadata: metadataUpdated
? serializeRepositoryMetadataState(metadataState) ? serializeRepositoryMetadataState(metadataState)
: repository.metadata ?? null, : repository.metadata ?? null,
@@ -720,7 +814,7 @@ export async function syncGiteaRepoEnhanced({
repositoryId: repository.id, repositoryId: repository.id,
repositoryName: repository.name, repositoryName: repository.name,
message: `Sync requested for repository: ${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", status: "synced",
}); });

View File

@@ -13,6 +13,7 @@ import { db, organizations, repositories } from "./db";
import { eq, and, ne } from "drizzle-orm"; import { eq, and, ne } from "drizzle-orm";
import { decryptConfigTokens } from "./utils/config-encryption"; import { decryptConfigTokens } from "./utils/config-encryption";
import { formatDateShort } from "./utils"; import { formatDateShort } from "./utils";
import { buildGithubSourceAuthPayload } from "./utils/mirror-source-auth";
import { import {
parseRepositoryMetadataState, parseRepositoryMetadataState,
serializeRepositoryMetadataState, serializeRepositoryMetadataState,
@@ -816,14 +817,22 @@ export const mirrorGithubRepoToGitea = async ({
// Add authentication for private repositories // Add authentication for private repositories
if (repository.isPrivate) { if (repository.isPrivate) {
if (!config.githubConfig.token) { const githubOwner =
throw new Error( (
"GitHub token is required to mirror private repositories." config.githubConfig as typeof config.githubConfig & {
); owner?: string;
} }
// Use separate auth fields (required for Forgejo 12+ compatibility) ).owner || "";
migratePayload.auth_username = "oauth2"; // GitHub tokens work with any username
migratePayload.auth_token = decryptedConfig.githubConfig.token; 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 // Track whether the Gitea migrate call succeeded so the catch block
@@ -1496,14 +1505,22 @@ export async function mirrorGitHubRepoToGiteaOrg({
// Add authentication for private repositories // Add authentication for private repositories
if (repository.isPrivate) { if (repository.isPrivate) {
if (!config.githubConfig?.token) { const githubOwner =
throw new Error( (
"GitHub token is required to mirror private repositories." config.githubConfig as typeof config.githubConfig & {
); owner?: string;
} }
// Use separate auth fields (required for Forgejo 12+ compatibility) )?.owner || "";
migratePayload.auth_username = "oauth2"; // GitHub tokens work with any username
migratePayload.auth_token = decryptedConfig.githubConfig.token; Object.assign(
migratePayload,
buildGithubSourceAuthPayload({
token: decryptedConfig.githubConfig?.token,
githubOwner,
githubUsername: config.githubConfig?.username,
repositoryOwner: repository.owner,
})
);
} }
let migrateSucceeded = false; let migrateSucceeded = false;

View 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.");
});
});

View 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,
};
}

View File

@@ -13,7 +13,6 @@ try {
activityData = jobs.flatMap((job: any) => { activityData = jobs.flatMap((job: any) => {
// Check if log exists before parsing // Check if log exists before parsing
if (!job.log) { if (!job.log) {
console.warn(`Job ${job.id} has no log data`);
return []; return [];
} }

View File

@@ -28,7 +28,6 @@ try {
activityData = jobs.flatMap((job: any) => { activityData = jobs.flatMap((job: any) => {
// Check if log exists before parsing // Check if log exists before parsing
if (!job.log) { if (!job.log) {
console.warn(`Job ${job.id} has no log data`);
return []; return [];
} }
try { try {

42
www/pnpm-lock.yaml generated
View File

@@ -1655,12 +1655,12 @@ packages:
picocolors@1.1.1: picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
picomatch@2.3.1: picomatch@2.3.2:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==}
engines: {node: '>=8.6'} engines: {node: '>=8.6'}
picomatch@4.0.3: picomatch@4.0.4:
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
engines: {node: '>=12'} engines: {node: '>=12'}
postcss@8.5.6: postcss@8.5.6:
@@ -1801,8 +1801,8 @@ packages:
sisteransi@1.0.5: sisteransi@1.0.5:
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
smol-toml@1.6.0: smol-toml@1.6.1:
resolution: {integrity: sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==} resolution: {integrity: sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==}
engines: {node: '>= 18'} engines: {node: '>= 18'}
source-map-js@1.2.1: source-map-js@1.2.1:
@@ -2091,7 +2091,7 @@ snapshots:
'@astrojs/internal-helpers@0.8.0': '@astrojs/internal-helpers@0.8.0':
dependencies: dependencies:
picomatch: 4.0.3 picomatch: 4.0.4
'@astrojs/markdown-remark@7.0.0': '@astrojs/markdown-remark@7.0.0':
dependencies: dependencies:
@@ -2109,7 +2109,7 @@ snapshots:
remark-rehype: 11.1.2 remark-rehype: 11.1.2
remark-smartypants: 3.0.2 remark-smartypants: 3.0.2
shiki: 4.0.2 shiki: 4.0.2
smol-toml: 1.6.0 smol-toml: 1.6.1
unified: 11.0.5 unified: 11.0.5
unist-util-remove-position: 5.0.0 unist-util-remove-position: 5.0.0
unist-util-visit: 5.1.0 unist-util-visit: 5.1.0
@@ -2553,7 +2553,7 @@ snapshots:
dependencies: dependencies:
'@types/estree': 1.0.8 '@types/estree': 1.0.8
estree-walker: 2.0.2 estree-walker: 2.0.2
picomatch: 4.0.3 picomatch: 4.0.4
optionalDependencies: optionalDependencies:
rollup: 4.59.0 rollup: 4.59.0
@@ -2844,7 +2844,7 @@ snapshots:
anymatch@3.1.3: anymatch@3.1.3:
dependencies: dependencies:
normalize-path: 3.0.0 normalize-path: 3.0.0
picomatch: 2.3.1 picomatch: 2.3.2
argparse@2.0.1: {} argparse@2.0.1: {}
@@ -2891,11 +2891,11 @@ snapshots:
p-queue: 9.1.0 p-queue: 9.1.0
package-manager-detector: 1.6.0 package-manager-detector: 1.6.0
piccolore: 0.1.3 piccolore: 0.1.3
picomatch: 4.0.3 picomatch: 4.0.4
rehype: 13.0.2 rehype: 13.0.2
semver: 7.7.4 semver: 7.7.4
shiki: 4.0.2 shiki: 4.0.2
smol-toml: 1.6.0 smol-toml: 1.6.1
svgo: 4.0.1 svgo: 4.0.1
tinyclip: 0.1.12 tinyclip: 0.1.12
tinyexec: 1.0.2 tinyexec: 1.0.2
@@ -3181,9 +3181,9 @@ snapshots:
extend@3.0.2: {} extend@3.0.2: {}
fdir@6.5.0(picomatch@4.0.3): fdir@6.5.0(picomatch@4.0.4):
optionalDependencies: optionalDependencies:
picomatch: 4.0.3 picomatch: 4.0.4
flattie@1.1.1: {} flattie@1.1.1: {}
@@ -3987,9 +3987,9 @@ snapshots:
picocolors@1.1.1: {} picocolors@1.1.1: {}
picomatch@2.3.1: {} picomatch@2.3.2: {}
picomatch@4.0.3: {} picomatch@4.0.4: {}
postcss@8.5.6: postcss@8.5.6:
dependencies: dependencies:
@@ -4247,7 +4247,7 @@ snapshots:
sisteransi@1.0.5: {} sisteransi@1.0.5: {}
smol-toml@1.6.0: {} smol-toml@1.6.1: {}
source-map-js@1.2.1: {} source-map-js@1.2.1: {}
@@ -4294,8 +4294,8 @@ snapshots:
tinyglobby@0.2.15: tinyglobby@0.2.15:
dependencies: dependencies:
fdir: 6.5.0(picomatch@4.0.3) fdir: 6.5.0(picomatch@4.0.4)
picomatch: 4.0.3 picomatch: 4.0.4
trim-lines@3.0.1: {} 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): vite@7.3.1(@types/node@24.7.1)(jiti@2.6.1)(lightningcss@1.31.1):
dependencies: dependencies:
esbuild: 0.27.3 esbuild: 0.27.3
fdir: 6.5.0(picomatch@4.0.3) fdir: 6.5.0(picomatch@4.0.4)
picomatch: 4.0.3 picomatch: 4.0.4
postcss: 8.5.6 postcss: 8.5.6
rollup: 4.59.0 rollup: 4.59.0
tinyglobby: 0.2.15 tinyglobby: 0.2.15