cache-apt-pkgs-action/scripts/ops/check_configs.mts
Andrew Walsh 9792dbe32f
style: set Prettier print width to 110 and reformat (#246)
* style: set Prettier print width to 110 and reformat

Without a repo config, the Prettier CLI and Trunk used the default
width of 80, while editors with a user printWidth reflowed files on
save, producing formatting-only diffs. Pin the width in .prettierrc.json
and reformat source, tests, scripts and JS/TS config files to match.

Files that already failed Prettier at width 80 for unrelated reasons
(tsconfig*.json, .trunk/*, V2_FAQ.MD) are left untouched.

No code changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: cover cache restore and save failure paths

Codecov flagged lines in #246 as uncovered patch lines; they were
untested before the reformat touched them. Add behavior tests for:

- Cache: cache miss, missing or empty manifests, unreadable cache dir,
  metapackage-only restore, install scripts running after their archive
  with dpkg actions, existing archives, unavailable cache service, and
  save failures.
- ManifestEntry.fromJSON round trip.
- buildFileList dropping missing files, updateAptLists skipping update
  for populated lists and searching only to max depth, and fromInput
  ignoring a version without a package name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 11:45:29 -08:00

226 lines
5.9 KiB
TypeScript
Executable file

#!/usr/bin/env -S node --experimental-strip-types
import { fail, logInfo, logSuccess, logError, readJsonFile, ROOT_DIR, REPO_SLUG } from "../devopslib.mts";
type ErrorCollector = {
errors: string[];
add: (message: string) => void;
};
type JsonRecord = Record<string, unknown>;
const packageJson = readJsonFile(`${ROOT_DIR}/package.json`) as JsonRecord;
const vscodeSettingsJson = readJsonFile(`${ROOT_DIR}/.vscode/settings.json`) as JsonRecord;
const tsconfigJson = readJsonFile(`${ROOT_DIR}/tsconfig.json`) as JsonRecord;
const typedocJson = readJsonFile(`${ROOT_DIR}/typedoc.json`) as JsonRecord;
function tokenizePath(pathExpression: string): Array<string | number> {
const tokens: Array<string | number> = [];
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: unknown, pathExpression: string): { found: boolean; value: unknown } {
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 as Record<string, unknown>)[token];
}
return { found: true, value: cursor };
}
function createErrorCollector(fileLabel: string): ErrorCollector {
const errors: string[] = [];
return {
errors,
add(message: string) {
errors.push(`${fileLabel}: ${message}`);
},
};
}
function valueType(value: unknown): string {
if (Array.isArray(value)) {
return "array";
}
if (value === null) {
return "null";
}
return typeof value;
}
function expectFieldType(
doc: unknown,
fieldPath: string,
expectedType: string,
addError: (message: string) => void,
) {
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: unknown,
fieldPath: string,
expectedValue: unknown,
addError: (message: string) => void,
errorMessage?: string,
) {
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: JsonRecord): string[] {
const { errors, add } = createErrorCollector("package.json");
logInfo("Validating package.json...");
expectFieldValue(json, "name", "ts-apt", add);
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: JsonRecord): string[] {
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: JsonRecord): string[] {
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: JsonRecord): string[] {
logInfo("Validating .vscode/settings.json...");
const { errors, add } = createErrorCollector(".vscode/settings.json");
const autoApprove = json["chat.tools.terminal.autoApprove"];
if (
autoApprove === undefined ||
autoApprove === null ||
typeof autoApprove !== "object" ||
Array.isArray(autoApprove)
) {
add("missing required field 'chat.tools.terminal.autoApprove'");
}
const useAgentsMdFile = json["chat.useAgentsMdFile"];
if (useAgentsMdFile !== true) {
add("missing required field 'chat.useAgentsMdFile'");
}
return errors;
}
function main(): void {
const errors = [
...validatePackageJson(packageJson),
...validateTsconfigJson(tsconfigJson),
...validateTypedocJson(typedocJson),
...validateVscodeSettingsJson(vscodeSettingsJson),
];
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));
}