chore(plan): add targeted hosted schema command
This commit is contained in:
parent
48458f495f
commit
a6785f74ee
6 changed files with 92 additions and 28 deletions
|
|
@ -108,9 +108,11 @@ in `projects.plan_data`, separate from `cards.start_date` (which also controls
|
|||
commit linking). Client plan writes verify the signed-in account and project
|
||||
ownership before updating the project through the admin connection.
|
||||
|
||||
After deploying the new code, run the schema command above once to add the
|
||||
optional `projects.plan_data` JSON field. Existing projects start with an empty
|
||||
draft; no existing card dates are overwritten.
|
||||
Before using the new planner on the hosted instance, load `.env.local` as shown
|
||||
above and run `npm run setup-plan-schema`. This targeted command only adds the
|
||||
optional `projects.plan_data` JSON field; it does not run the full setup script
|
||||
or rewrite existing records. Existing projects start with an empty draft, and
|
||||
no existing card dates are overwritten.
|
||||
|
||||
## Specs and plans
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
"test:all": "vitest run",
|
||||
"seed": "tsx scripts/seed-demo.ts",
|
||||
"setup-remote": "tsx scripts/setup-remote.ts",
|
||||
"setup-plan-schema": "tsx scripts/apply-plan-schema.ts",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
|||
23
scripts/apply-plan-schema.ts
Normal file
23
scripts/apply-plan-schema.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/** Apply only the Plan field to the shared PocketBase instance. */
|
||||
import PocketBase from "pocketbase";
|
||||
|
||||
import { ensureProjectPlanDataField } from "./plan-schema";
|
||||
|
||||
async function main() {
|
||||
const url = process.env.NEXT_PUBLIC_PB_URL;
|
||||
const email = process.env.PB_SUPERUSER_EMAIL;
|
||||
const password = process.env.PB_SUPERUSER_PASSWORD;
|
||||
if (!url || !email || !password) {
|
||||
throw new Error("NEXT_PUBLIC_PB_URL and PocketBase superuser credentials are required");
|
||||
}
|
||||
const pb = new PocketBase(url);
|
||||
pb.autoCancellation(false);
|
||||
await pb.collection("_superusers").authWithPassword(email, password);
|
||||
const added = await ensureProjectPlanDataField(pb);
|
||||
console.log(added ? "Added projects.plan_data" : "projects.plan_data already exists");
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
24
scripts/plan-schema.ts
Normal file
24
scripts/plan-schema.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import type PocketBase from "pocketbase";
|
||||
|
||||
/** Add the optional JSON field used by the collaborative project planner. */
|
||||
export async function ensureProjectPlanDataField(pb: PocketBase): Promise<boolean> {
|
||||
const projects = await pb.collections.getOne("projects");
|
||||
if (projects.fields.some((field: { name: string }) => field.name === "plan_data")) {
|
||||
return false;
|
||||
}
|
||||
await pb.collections.update(projects.id, {
|
||||
fields: [
|
||||
...(projects.fields as unknown[]),
|
||||
{
|
||||
name: "plan_data",
|
||||
type: "json",
|
||||
required: false,
|
||||
maxSize: 400_000,
|
||||
presentable: false,
|
||||
hidden: false,
|
||||
system: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
|
||||
import PocketBase from "pocketbase";
|
||||
import { ensureProjectPlanDataField } from "./plan-schema";
|
||||
|
||||
const PB_URL =
|
||||
process.env.NEXT_PUBLIC_PB_URL ??
|
||||
|
|
@ -182,30 +183,6 @@ async function ensureProjectIngestFields(pb: PocketBase): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
/** One project JSON record keeps plan submission and approval together. */
|
||||
async function ensureProjectPlanDataField(pb: PocketBase): Promise<void> {
|
||||
const projects = await pb.collections.getOne("projects");
|
||||
if (projects.fields.some((field: { name: string }) => field.name === "plan_data")) {
|
||||
console.log(" = projects.plan_data already present");
|
||||
return;
|
||||
}
|
||||
await pb.collections.update(projects.id, {
|
||||
fields: [
|
||||
...(projects.fields as unknown[]),
|
||||
{
|
||||
name: "plan_data",
|
||||
type: "json",
|
||||
required: false,
|
||||
maxSize: 400_000,
|
||||
presentable: false,
|
||||
hidden: false,
|
||||
system: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
console.log(" + projects field: plan_data");
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the `summary`, `summary_version`, `summary_attempts` fields to commits.
|
||||
*
|
||||
|
|
@ -1578,7 +1555,9 @@ async function main(): Promise<void> {
|
|||
// Add the project/prototype kind field for Phase 6.
|
||||
await ensureProjectKindField(pb);
|
||||
await ensureProjectBackgroundMdField(pb);
|
||||
await ensureProjectPlanDataField(pb);
|
||||
console.log((await ensureProjectPlanDataField(pb))
|
||||
? " + projects field: plan_data"
|
||||
: " = projects.plan_data already present");
|
||||
|
||||
// Add the per-commit summary fields used by Phase 3b's AI one-liner job.
|
||||
await ensureCommitSummaryFields(pb);
|
||||
|
|
|
|||
35
src/lib/planning/schema.test.ts
Normal file
35
src/lib/planning/schema.test.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { ensureProjectPlanDataField } from "../../../scripts/plan-schema";
|
||||
|
||||
describe("targeted plan schema update", () => {
|
||||
it("adds only the optional project plan field", async () => {
|
||||
const update = vi.fn(async () => ({}));
|
||||
const pb = {
|
||||
collections: {
|
||||
getOne: vi.fn(async () => ({ id: "projects-id", fields: [{ name: "name", type: "text" }] })),
|
||||
update,
|
||||
},
|
||||
};
|
||||
expect(await ensureProjectPlanDataField(pb as never)).toBe(true);
|
||||
expect(update).toHaveBeenCalledOnce();
|
||||
expect(update).toHaveBeenCalledWith("projects-id", {
|
||||
fields: [
|
||||
{ name: "name", type: "text" },
|
||||
expect.objectContaining({ name: "plan_data", type: "json", required: false }),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("does nothing when the field already exists", async () => {
|
||||
const update = vi.fn();
|
||||
const pb = {
|
||||
collections: {
|
||||
getOne: vi.fn(async () => ({ id: "projects-id", fields: [{ name: "plan_data" }] })),
|
||||
update,
|
||||
},
|
||||
};
|
||||
expect(await ensureProjectPlanDataField(pb as never)).toBe(false);
|
||||
expect(update).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue