mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 01:29:56 +00:00
fix(lint): handle omitted schemas in sparse checkouts (#162553)
* fix(lint): preserve sparse checkout and metadata commands * fix(tooling): prepare sparse schemas in both lint launchers
This commit is contained in:
parent
b9c7034d1b
commit
64ce172a96
6 changed files with 83 additions and 27 deletions
|
|
@ -53,10 +53,13 @@ them into each workspace package. On supported macOS volumes, this also lets pnp
|
|||
reuse whole-package APFS clones instead of importing every file separately.
|
||||
|
||||
Installation also prepares Kysely declarations from the SQL schemas in `src/state`.
|
||||
The generated files live in ignored `.artifacts/kysely/`; build, typecheck, and
|
||||
test entrypoints refresh them automatically, including after an install with
|
||||
The generated files live in ignored `.artifacts/kysely/`; build, typecheck,
|
||||
type-aware lint, and test entrypoints refresh them automatically, including after an install with
|
||||
`--ignore-scripts`. Unchanged inputs reuse the declarations without rewriting
|
||||
them. `pnpm db:kysely:check` checks schema projection and generator contracts.
|
||||
Sparse core, script, and root-test lint prepare only the available schemas and
|
||||
retire declarations for omitted schemas. Normal source generation, verification,
|
||||
and extension-boundary builds still require complete schemas.
|
||||
|
||||
Give each source checkout its own physical dependency installation. Tooling does
|
||||
not automatically link a missing `node_modules` to another checkout. Existing
|
||||
|
|
|
|||
|
|
@ -112,24 +112,42 @@ async function generateKyselyTypes(schemaSource: string): Promise<string> {
|
|||
}
|
||||
}
|
||||
|
||||
export async function ensureKyselyTypes(cwd = REPO_ROOT, verify = false): Promise<void> {
|
||||
// Packaged installs and dependency-only Docker stages have no source schemas.
|
||||
if (
|
||||
!verify &&
|
||||
!SCHEMAS.some((name) => fs.existsSync(path.join(cwd, "src/state", `${name}-schema.sql`)))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
export async function ensureKyselyTypes(
|
||||
cwd = REPO_ROOT,
|
||||
verify = false,
|
||||
options: { allowPartialCheckout?: boolean } = {},
|
||||
): Promise<void> {
|
||||
const outputDir = path.join(cwd, ".artifacts/kysely");
|
||||
const stampFile = path.join(outputDir, "inputs.sha256");
|
||||
const schemas = SCHEMAS.map((name) => ({
|
||||
source: fs.readFileSync(path.join(cwd, "src/state", `${name}-schema.sql`), "utf8"),
|
||||
const inputs = SCHEMAS.map((name) => ({
|
||||
name,
|
||||
input: path.join(cwd, "src/state", `${name}-schema.sql`),
|
||||
output: path.join(outputDir, `${name}-db.generated.ts`),
|
||||
}));
|
||||
const available = inputs.filter((schema) => fs.existsSync(schema.input));
|
||||
const partial = !verify && options.allowPartialCheckout === true;
|
||||
if (partial) {
|
||||
// Sparse lint must not consume a previous projection whose source is now omitted.
|
||||
for (const schema of inputs.filter((input) => !available.includes(input))) {
|
||||
fs.rmSync(schema.output, { force: true });
|
||||
}
|
||||
}
|
||||
// Packaged installs and dependency-only Docker stages have no source schemas.
|
||||
if (!verify && available.length === 0) {
|
||||
if (partial) {
|
||||
fs.rmSync(stampFile, { force: true });
|
||||
}
|
||||
return;
|
||||
}
|
||||
const schemas = (partial ? available : inputs).map((schema) => ({
|
||||
name: schema.name,
|
||||
source: fs.readFileSync(schema.input, "utf8"),
|
||||
output: schema.output,
|
||||
}));
|
||||
const fingerprint = () => {
|
||||
const hash = createHash("sha256").update(fs.readFileSync(fileURLToPath(import.meta.url)));
|
||||
for (const schema of schemas) {
|
||||
hash.update(schema.source).update("\0");
|
||||
hash.update(schema.name).update("\0").update(schema.source).update("\0");
|
||||
hash.update(fs.existsSync(schema.output) ? fs.readFileSync(schema.output) : "missing");
|
||||
}
|
||||
return hash.digest("hex");
|
||||
|
|
|
|||
|
|
@ -401,7 +401,7 @@ export async function main(
|
|||
shouldPrepareOxlintArtifacts([...shard.args, ...shardArgs.oxlintArgs]),
|
||||
)
|
||||
) {
|
||||
await ensureKyselyTypes(process.cwd());
|
||||
await ensureKyselyTypes(process.cwd(), false, { allowPartialCheckout: true });
|
||||
}
|
||||
if (needsArtifacts) {
|
||||
const code = await runManagedCommand({
|
||||
|
|
|
|||
|
|
@ -669,7 +669,7 @@ export async function runOxlint(
|
|||
const run = async (ownedDirectory?: string) => {
|
||||
if (shouldPrepareOxlintArtifacts(argv) && env.OPENCLAW_OXLINT_SKIP_PREPARE !== "1") {
|
||||
// Source-backed core lint skips plugin declarations, not generated schema types.
|
||||
await ensureKyselyTypes(process.cwd());
|
||||
await ensureKyselyTypes(process.cwd(), false, { allowPartialCheckout: true });
|
||||
}
|
||||
if (needsArtifactPreparation) {
|
||||
// Declaration compilation owns its Go policy; lint limits belong to the oxlint child.
|
||||
|
|
|
|||
|
|
@ -104,6 +104,30 @@ describe("Kysely declarations", () => {
|
|||
expect(missing.stderr).toContain("direct-run.mjs");
|
||||
});
|
||||
|
||||
it("retires omitted sparse projections and still requires complete generation", async () => {
|
||||
const { root, schemas, output } = createSchemaFixture();
|
||||
const agentOutput = path.join(root, ".artifacts/kysely/openclaw-agent-db.generated.ts");
|
||||
await ensureKyselyTypes(root);
|
||||
fs.unlinkSync(schemas[1]!);
|
||||
await ensureKyselyTypes(root, false, { allowPartialCheckout: true });
|
||||
expect(fs.readFileSync(output, "utf8")).toContain("export interface Records");
|
||||
expect(fs.existsSync(agentOutput)).toBe(false);
|
||||
await expect(ensureKyselyTypes(root)).rejects.toThrow("openclaw-agent-schema.sql");
|
||||
await expect(ensureKyselyTypes(root, true, { allowPartialCheckout: true })).rejects.toThrow(
|
||||
"openclaw-agent-schema.sql",
|
||||
);
|
||||
fs.writeFileSync(schemas[1]!, `${schema}\nALTER TABLE records ADD COLUMN restored TEXT;`);
|
||||
await ensureKyselyTypes(root);
|
||||
expect(fs.readFileSync(agentOutput, "utf8")).toContain(" restored: string | null;");
|
||||
for (const source of schemas) {
|
||||
fs.unlinkSync(source);
|
||||
}
|
||||
await ensureKyselyTypes(root, false, { allowPartialCheckout: true });
|
||||
expect(fs.existsSync(output)).toBe(false);
|
||||
expect(fs.existsSync(agentOutput)).toBe(false);
|
||||
expect(fs.existsSync(path.join(root, ".artifacts/kysely/inputs.sha256"))).toBe(false);
|
||||
});
|
||||
|
||||
it("skips source-less installs but rejects an incomplete schema checkout", async () => {
|
||||
const root = tempDirs.make("kysely-source-less-");
|
||||
fs.mkdirSync(path.join(root, "src/state"), { recursive: true });
|
||||
|
|
|
|||
|
|
@ -99,18 +99,29 @@ const striped = [
|
|||
|
||||
describe("typed lint Kysely prerequisites", () => {
|
||||
it.each([
|
||||
{ name: "direct", args: direct },
|
||||
{ name: "striped", args: striped },
|
||||
])("prepares cold declarations before $name core lint without plugin artifacts", ({ args }) => {
|
||||
const fixture = createLintFixture();
|
||||
expect(fs.existsSync(fixture.output)).toBe(false);
|
||||
const result = fixture.run(args);
|
||||
expect(result.status, result.stdout + result.stderr).toBe(0);
|
||||
expect(fs.readFileSync(fixture.output, "utf8")).toContain("title: string;");
|
||||
expect(fs.existsSync(path.join(fixture.root, ".artifacts/extension-package-boundary"))).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
{ name: "direct", args: direct, sparse: false },
|
||||
{ name: "striped", args: striped, sparse: false },
|
||||
{ name: "sparse direct", args: direct, sparse: true },
|
||||
{ name: "sparse striped", args: striped, sparse: true },
|
||||
])(
|
||||
"prepares cold declarations before $name core lint without plugin artifacts",
|
||||
({ args, sparse }) => {
|
||||
const fixture = createLintFixture();
|
||||
const agentProjection = ".artifacts/kysely/openclaw-agent-db.generated.ts";
|
||||
if (sparse) {
|
||||
fs.unlinkSync(path.join(fixture.root, "src/state/openclaw-agent-schema.sql"));
|
||||
fixture.write(agentProjection, "export interface Stale {}\n");
|
||||
}
|
||||
expect(fs.existsSync(fixture.output)).toBe(false);
|
||||
const result = fixture.run(args);
|
||||
expect(result.status, result.stdout + result.stderr).toBe(0);
|
||||
expect(fs.readFileSync(fixture.output, "utf8")).toContain("title: string;");
|
||||
expect(fs.existsSync(path.join(fixture.root, agentProjection))).toBe(!sparse);
|
||||
expect(fs.existsSync(path.join(fixture.root, ".artifacts/extension-package-boundary"))).toBe(
|
||||
false,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
{ name: "direct", args: direct },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue