fix: address release v2 reviews

This commit is contained in:
ajnart committed 2026-08-14 02:18:46 +02:00
1 parent 4905ea6738
commit 32732680e5
55 files changed
+925 -294

No files matched your search

+15 -3
View File
@@ -155,10 +155,22 @@ export const createFeatureCheckPlan = (root: string, kind: string): FeatureCheck
executable: "node",
args: ["--experimental-strip-types", "scripts/check-feature-contracts.mts"],
},
{ label: "Focused fixture and unit tests", executable: "pnpm", args: ["exec", "vitest", "run", ...sortedTests] },
...(sortedTests.length > 0
? [
{
label: "Focused fixture and unit tests",
executable: "pnpm",
args: ["exec", "vitest", "run", ...sortedTests],
},
]
: []),
packageCommand("Affected package typechecks", sortedPackages),
{ label: "Affected file lint", executable: "pnpm", args: ["exec", "oxlint", ...lintFiles] },
{ label: "Affected file formatting", executable: "pnpm", args: ["exec", "oxfmt", "--check", ...sortedFiles] },
...(lintFiles.length > 0
? [{ label: "Affected file lint", executable: "pnpm", args: ["exec", "oxlint", ...lintFiles] }]
: []),
...(sortedFiles.length > 0
? [{ label: "Affected file formatting", executable: "pnpm", args: ["exec", "oxfmt", "--check", ...sortedFiles] }]
: []),
];
return {
+3 -1
View File
@@ -74,10 +74,12 @@ const compareKeys = (
});
};
const ignoredDirectories = new Set([".git", ".turbo", ".next", "node_modules", "dist", "build", "coverage"]);
const walkFiles = (directory: string): string[] => {
if (!existsSync(directory)) return [];
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
if (entry.isDirectory() && [".git", ".turbo", "node_modules"].includes(entry.name)) return [];
if (entry.isDirectory() && ignoredDirectories.has(entry.name)) return [];
const path = join(directory, entry.name);
return entry.isDirectory() ? walkFiles(path) : [path];
});
@@ -3,7 +3,7 @@ import { test } from "node:test";
import { resolve } from "node:path";
import { checkFeatureContracts } from "./contracts.mts";
import { formatFeatureChanges, planFeatureGeneration } from "./generate-feature.mts";
import { formatFeatureChanges, getIntegrationGeneratorChoices, planFeatureGeneration } from "./generate-feature.mts";
// oxlint-disable-next-line import/no-unassigned-import -- Registers shared contract tests in this suite.
import "./check-feature.contract.mts";
@@ -13,6 +13,55 @@ test("the checked-in feature contracts are consistent", () => {
assert.deepEqual(checkFeatureContracts(root), []);
});
test("reads generator choices from the canonical integration definitions", () => {
const choices = getIntegrationGeneratorChoices(root);
assert.ok(choices.categories.includes("miscellaneous"));
assert.ok(choices.secretKinds.includes("apiKey"));
});
test("adds translations to empty JSON registries without a leading comma", async () => {
const fs = await import("node:fs");
const os = await import("node:os");
const path = await import("node:path");
const temporaryRoot = fs.mkdtempSync(path.join(os.tmpdir(), "homarr-feature-generator-"));
try {
for (const sourcePath of [
"packages/definitions/src/integration.ts",
"packages/definitions/src/widget.ts",
"packages/definitions/src/widget-integration-map.ts",
"packages/definitions/src/docs/widget-doc-slugs.ts",
"packages/widgets/src/manifest.ts",
"packages/widgets/src/catalog.ts",
"packages/widgets/src/index.tsx",
]) {
const target = path.join(temporaryRoot, sourcePath);
fs.mkdirSync(path.dirname(target), { recursive: true });
fs.copyFileSync(path.join(root, sourcePath), target);
}
const translationPath = path.join(temporaryRoot, "packages/translation/src/lang/en.json");
fs.mkdirSync(path.dirname(translationPath), { recursive: true });
fs.writeFileSync(translationPath, '{\n "integration": {},\n "widget": {}\n}\n');
const changes = planFeatureGeneration(temporaryRoot, {
widget: {
kind: "exampleEmptyRegistry",
name: "Example empty registry",
slug: "example-empty-registry",
description: "Verifies empty translation registry insertion.",
icon: "IconBox",
supportedIntegrations: [],
},
});
const translation = changes.find((change) => change.path === "packages/translation/src/lang/en.json");
assert.ok(translation);
assert.doesNotThrow(() => JSON.parse(translation.content));
} finally {
fs.rmSync(temporaryRoot, { recursive: true, force: true });
}
});
test("paired generation plans every formatter-clean contract without writing", async () => {
const description =
"A deliberately long feature description that verifies generated source stays formatter-clean when contributor input exceeds the configured print width.";
+17 -12
View File
@@ -91,7 +91,20 @@ const addJsonProperty = (source: string, property: "integration" | "widget", nam
.join("\n");
const beforeClose = source.slice(0, end);
const trimmed = beforeClose.trimEnd();
return `${trimmed},\n ${JSON.stringify(name)}: ${serialized}\n ${source.slice(end)}`;
const separator = body.trim().length === 0 ? "" : ",";
return `${trimmed}${separator}\n ${JSON.stringify(name)}: ${serialized}\n ${source.slice(end)}`;
};
export const getIntegrationGeneratorChoices = (root: string) => {
const source = read(root, "packages/definitions/src/integration.ts");
const categoryRange = containerRange(source, "const integrationCategories", "[");
const secretRange = containerRange(source, "const integrationSecretKindObject", "{");
return {
categories: [...source.slice(categoryRange.start, categoryRange.end).matchAll(/"([^"]+)"/g)].map(
(match) => match[1],
),
secretKinds: [...source.slice(secretRange.start, secretRange.end).matchAll(/^ (\w+):/gm)].map((match) => match[1]),
};
};
const addChange = (
@@ -123,19 +136,11 @@ const validate = (request: FeatureRequest) => {
const planIntegration = (root: string, feature: IntegrationFeature, changes: PlannedChange[]) => {
const definitionSource = read(root, "packages/definitions/src/integration.ts");
const categoryRange = containerRange(definitionSource, "const integrationCategories", "[");
const categories = new Set(
[...definitionSource.slice(categoryRange.start, categoryRange.end).matchAll(/"([^"]+)"/g)].map((match) => match[1]),
);
if (!categories.has(feature.category)) {
const choices = getIntegrationGeneratorChoices(root);
if (!choices.categories.includes(feature.category)) {
throw new Error(`Generator stopped: ${feature.category} is not a current integration category.`);
}
const secretObject = definitionSource.slice(
definitionSource.indexOf("const integrationSecretKindObject"),
definitionSource.indexOf("satisfies Record<string", definitionSource.indexOf("const integrationSecretKindObject")),
);
const validSecrets = new Set([...secretObject.matchAll(/^ (\w+):/gm)].map((match) => match[1]));
const unknownSecrets = feature.secretKinds.filter((kind) => !validSecrets.has(kind));
const unknownSecrets = feature.secretKinds.filter((kind) => !choices.secretKinds.includes(kind));
if (unknownSecrets.length > 0) {
throw new Error(`Generator stopped: unknown credential kind(s): ${unknownSecrets.join(", ")}.`);
}