mirror of
https://github.com/awalsh128/cache-apt-pkgs-action.git
synced 2026-07-06 17:14:36 +00:00
268 lines
6.3 KiB
JavaScript
268 lines
6.3 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import {
|
|
ROOT_DIR,
|
|
fail,
|
|
logError,
|
|
logInfo,
|
|
logSuccess,
|
|
runCaptureOutput,
|
|
REPO_SLUG,
|
|
} from "./lib.mjs";
|
|
|
|
import packageJson from "../package.json" with { type: "json" };
|
|
import vscodeSettingsJson from "../.vscode/settings.json" with { type: "json" };
|
|
import tsconfigJson from "../tsconfig.json" with { type: "json" };
|
|
import typedocJson from "../typedoc.json" with { type: "json" };
|
|
|
|
function tokenizePath(pathExpression) {
|
|
const tokens = [];
|
|
let index = 0;
|
|
|
|
while (index < pathExpression.length) {
|
|
const ch = pathExpression[index];
|
|
|
|
if (ch === ".") {
|
|
index += 1;
|
|
continue;
|
|
}
|
|
|
|
if (ch === "[") {
|
|
const endIndex = pathExpression.indexOf("]", index);
|
|
if (endIndex < 0) {
|
|
throw new Error(`Invalid path expression '${pathExpression}'`);
|
|
}
|
|
|
|
const indexText = pathExpression.slice(index + 1, endIndex).trim();
|
|
if (!/^\d+$/.test(indexText)) {
|
|
throw new Error(`Invalid array index in path '${pathExpression}'`);
|
|
}
|
|
|
|
tokens.push(Number(indexText));
|
|
index = endIndex + 1;
|
|
continue;
|
|
}
|
|
|
|
let endIndex = index;
|
|
while (
|
|
endIndex < pathExpression.length &&
|
|
pathExpression[endIndex] !== "." &&
|
|
pathExpression[endIndex] !== "["
|
|
) {
|
|
endIndex += 1;
|
|
}
|
|
|
|
tokens.push(pathExpression.slice(index, endIndex));
|
|
index = endIndex;
|
|
}
|
|
|
|
return tokens;
|
|
}
|
|
|
|
function getByPath(root, pathExpression) {
|
|
const tokens = tokenizePath(pathExpression);
|
|
let cursor = root;
|
|
|
|
for (const token of tokens) {
|
|
if (typeof token === "number") {
|
|
if (!Array.isArray(cursor) || token < 0 || token >= cursor.length) {
|
|
return { found: false, value: undefined };
|
|
}
|
|
cursor = cursor[token];
|
|
continue;
|
|
}
|
|
|
|
if (cursor === null || typeof cursor !== "object") {
|
|
return { found: false, value: undefined };
|
|
}
|
|
|
|
if (!Object.prototype.hasOwnProperty.call(cursor, token)) {
|
|
return { found: false, value: undefined };
|
|
}
|
|
|
|
cursor = cursor[token];
|
|
}
|
|
|
|
return { found: true, value: cursor };
|
|
}
|
|
|
|
function createErrorCollector(fileLabel) {
|
|
const errors = [];
|
|
return {
|
|
errors,
|
|
add(message) {
|
|
errors.push(`${fileLabel}: ${message}`);
|
|
},
|
|
};
|
|
}
|
|
|
|
function valueType(value) {
|
|
if (Array.isArray(value)) {
|
|
return "array";
|
|
}
|
|
|
|
if (value === null) {
|
|
return "null";
|
|
}
|
|
|
|
return typeof value;
|
|
}
|
|
|
|
function expectFieldType(doc, fieldPath, expectedType, addError) {
|
|
const result = getByPath(doc, fieldPath);
|
|
if (!result.found) {
|
|
addError(`missing required field '${fieldPath}'`);
|
|
return;
|
|
}
|
|
|
|
const actualType = valueType(result.value);
|
|
if (actualType !== expectedType) {
|
|
addError(
|
|
`field '${fieldPath}' type '${actualType}' does not match expected '${expectedType}'`,
|
|
);
|
|
}
|
|
}
|
|
|
|
function expectFieldValue(
|
|
doc,
|
|
fieldPath,
|
|
expectedValue,
|
|
addError,
|
|
errorMessage,
|
|
) {
|
|
const result = getByPath(doc, fieldPath);
|
|
if (!result.found) {
|
|
addError(`missing required field '${fieldPath}'`);
|
|
return;
|
|
}
|
|
|
|
if (result.value !== expectedValue) {
|
|
addError(
|
|
`field '${fieldPath}' value '${result.value}' does not match expected '${expectedValue}'${errorMessage ? `: ${errorMessage}` : ""}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
function validatePackageJson(json) {
|
|
const { errors, add } = createErrorCollector("package.json");
|
|
logInfo("Validating package.json...");
|
|
|
|
expectFieldValue(json, "name", "cache-apt-pkgs-action", add);
|
|
expectFieldValue(
|
|
json,
|
|
"version",
|
|
"0.0.0-semantically-released",
|
|
add,
|
|
"version is updated automatically by semantic-release and should not be changed manually",
|
|
);
|
|
expectFieldValue(json, "license", "Apache-2.0", add);
|
|
expectFieldValue(json, "repository.type", "git", add);
|
|
expectFieldValue(
|
|
json,
|
|
"repository.url",
|
|
`https://github.com/${REPO_SLUG}.git`,
|
|
add,
|
|
);
|
|
|
|
return errors;
|
|
}
|
|
|
|
function validateTsconfigJson(json) {
|
|
logInfo("Validating tsconfig.json...");
|
|
const { errors, add } = createErrorCollector("tsconfig.json");
|
|
|
|
expectFieldType(json, "extends", "string", add);
|
|
expectFieldType(json, "compilerOptions", "object", add);
|
|
|
|
return errors;
|
|
}
|
|
|
|
function validateTypedocJson(json) {
|
|
logInfo("Validating typedoc.json...");
|
|
const { errors, add } = createErrorCollector("typedoc.json");
|
|
|
|
expectFieldType(json, "entryPoints", "array", add);
|
|
expectFieldType(json, "out", "string", add);
|
|
|
|
return errors;
|
|
}
|
|
|
|
function validateVscodeSettingsJson(json) {
|
|
logInfo("Validating .vscode/settings.json...");
|
|
const { errors, add } = createErrorCollector(".vscode/settings.json");
|
|
|
|
expectFieldType(json, "chat.tools.terminal.autoApprove", "object", add);
|
|
expectFieldValue(json, "chat.useAgentsMdFile", true, add);
|
|
|
|
return errors;
|
|
}
|
|
|
|
function runGitNameOnly(args, cwd) {
|
|
const output = runCaptureOutput("git", args, {
|
|
cwd,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
if (!output) {
|
|
return [];
|
|
}
|
|
|
|
return output
|
|
.split(/\r?\n/)
|
|
.map((line) => line.trim())
|
|
.filter((line) => line.length > 0);
|
|
}
|
|
|
|
function getProhibitedPathsChanges() {
|
|
const prohibitedPaths = ["docs/api"];
|
|
const staged = runGitNameOnly(
|
|
["diff", "--name-only", "--cached", "--", ...prohibitedPaths],
|
|
ROOT_DIR,
|
|
);
|
|
const unstaged = runGitNameOnly(
|
|
["diff", "--name-only", "--", ...prohibitedPaths],
|
|
ROOT_DIR,
|
|
);
|
|
const untracked = runGitNameOnly(
|
|
["ls-files", "--others", "--exclude-standard", "--", ...prohibitedPaths],
|
|
ROOT_DIR,
|
|
);
|
|
|
|
return [...new Set([...staged, ...unstaged, ...untracked])].sort(
|
|
(left, right) => left.localeCompare(right),
|
|
);
|
|
}
|
|
|
|
function main() {
|
|
const errors = [
|
|
...validatePackageJson(packageJson),
|
|
...validateTsconfigJson(tsconfigJson),
|
|
...validateTypedocJson(typedocJson),
|
|
...validateVscodeSettingsJson(vscodeSettingsJson),
|
|
];
|
|
|
|
const changedPaths = getProhibitedPathsChanges();
|
|
if (changedPaths.length > 0) {
|
|
errors.push(
|
|
`git-change-check: detected changes in prohibited paths (docs/api): ${changedPaths.join(", ")}`,
|
|
);
|
|
}
|
|
|
|
if (errors.length > 0) {
|
|
logError(`Found ${errors.length} validation issue(s):`);
|
|
for (const errorText of errors) {
|
|
logError(`- ${errorText}`);
|
|
}
|
|
fail("Validation failed.");
|
|
}
|
|
|
|
logSuccess("Validation passed.");
|
|
}
|
|
|
|
try {
|
|
main();
|
|
} catch (error) {
|
|
fail(error instanceof Error ? error.message : String(error));
|
|
}
|