chore(relink): CLI helper for off-server relink runs
This commit is contained in:
parent
7fc87042f6
commit
a1ef2ff55c
2 changed files with 88 additions and 6 deletions
82
scripts/relink.ts
Normal file
82
scripts/relink.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
/**
|
||||
* One-off relink helper.
|
||||
*
|
||||
* Re-runs the linking engine on a project without pulling in the server-only
|
||||
* modules — same pattern as scripts/backfill.ts. Run with:
|
||||
* set -a && source .env.local && set +a
|
||||
* npx tsx scripts/relink.ts <projectId>
|
||||
*/
|
||||
|
||||
import PocketBase from "pocketbase";
|
||||
|
||||
import { resolveCommitCard, type LinkCandidate } from "../src/lib/linking/resolve";
|
||||
|
||||
const PB_URL = process.env.NEXT_PUBLIC_PB_URL!;
|
||||
|
||||
async function main() {
|
||||
const projectId = process.argv[2];
|
||||
if (!projectId) throw new Error("Usage: tsx scripts/relink.ts <projectId>");
|
||||
|
||||
const pb = new PocketBase(PB_URL);
|
||||
pb.autoCancellation(false);
|
||||
await pb.collection("_superusers").authWithPassword(
|
||||
process.env.PB_SUPERUSER_EMAIL!,
|
||||
process.env.PB_SUPERUSER_PASSWORD!
|
||||
);
|
||||
|
||||
const cards = await pb.collection("cards").getFullList({
|
||||
filter: `project = "${projectId}"`,
|
||||
fields: "id,number,scope_rule,start_date,created,archived",
|
||||
});
|
||||
const candidates: LinkCandidate[] = cards.map((card) => ({
|
||||
id: card.id,
|
||||
number: card.number,
|
||||
scopeRule: card.scope_rule ?? "",
|
||||
startDate: card.start_date ?? "",
|
||||
created: card.created,
|
||||
archived: Boolean(card.archived),
|
||||
}));
|
||||
|
||||
const commits = await pb.collection("commits").getFullList({
|
||||
filter: `project = "${projectId}"`,
|
||||
fields: "id,message,scope,authored_at,is_merge,card,link_source",
|
||||
});
|
||||
|
||||
let linked = 0;
|
||||
let unlinked = 0;
|
||||
let preserved = 0;
|
||||
|
||||
for (const commit of commits) {
|
||||
if (commit.link_source === "manual") {
|
||||
preserved++;
|
||||
continue;
|
||||
}
|
||||
const resolution = resolveCommitCard(
|
||||
{
|
||||
message: commit.message ?? "",
|
||||
scope: commit.scope ?? "",
|
||||
authoredAt: commit.authored_at,
|
||||
isMerge: Boolean(commit.is_merge),
|
||||
},
|
||||
candidates
|
||||
);
|
||||
const nextCard = resolution.cardId ?? "";
|
||||
const nextSource = resolution.source;
|
||||
if (commit.card === nextCard && commit.link_source === nextSource) continue;
|
||||
await pb.collection("commits").update(commit.id, {
|
||||
card: nextCard,
|
||||
link_source: nextSource,
|
||||
});
|
||||
if (nextCard) linked++;
|
||||
else unlinked++;
|
||||
}
|
||||
|
||||
console.log(
|
||||
JSON.stringify({ examined: commits.length - preserved, linked, unlinked, preserved })
|
||||
);
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -4,10 +4,6 @@ import path from "node:path";
|
|||
export default defineConfig({
|
||||
resolve: {
|
||||
alias: { "@": path.resolve(__dirname, "./src") },
|
||||
// Used by every project — including the unit project, where pure modules
|
||||
// must stay free of `import "server-only"`. The integration project adds
|
||||
// `react-server` so its test files can import server-only modules.
|
||||
conditions: ["node", "import"],
|
||||
},
|
||||
test: {
|
||||
projects: [
|
||||
|
|
@ -22,7 +18,11 @@ export default defineConfig({
|
|||
{
|
||||
resolve: {
|
||||
alias: { "@": path.resolve(__dirname, "./src") },
|
||||
conditions: ["react-server", "node", "import"],
|
||||
// server-only throws on import unless this condition is set. The unit
|
||||
// project deliberately does NOT set it: pure modules must stay free
|
||||
// of server imports, and a failure there is the signal that one crept
|
||||
// in.
|
||||
conditions: ["react-server", "node", "import", "default"],
|
||||
},
|
||||
test: {
|
||||
name: "integration",
|
||||
|
|
@ -30,7 +30,7 @@ export default defineConfig({
|
|||
include: ["tests/integration/**/*.test.ts"],
|
||||
hookTimeout: 30_000,
|
||||
testTimeout: 30_000,
|
||||
fileParallelism: false,
|
||||
poolOptions: { forks: { singleFork: true } },
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
|||
Loading…
Reference in a new issue