mirror of
https://github.com/immich-app/static-pages.git
synced 2026-09-30 13:23:05 +08:00
105 lines
3.9 KiB
TypeScript
105 lines
3.9 KiB
TypeScript
import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
import { join, parse } from 'node:path';
|
|
|
|
// no extension means kysely ignores this
|
|
export const ORDER_FILENAME = 'ORDER';
|
|
|
|
const isEqual = (a: string[], b: string[]) => a.length === b.length && a.every((item, i) => b[i] === item);
|
|
|
|
const findDuplicates = (names: string[]): string[] => [
|
|
...new Set(names.filter((name, index) => names.indexOf(name) !== index)),
|
|
];
|
|
|
|
export const computeOrder = (fileNames: string[]): string[] =>
|
|
fileNames
|
|
.filter((name) => name !== ORDER_FILENAME)
|
|
.map((name) => parse(name).name)
|
|
.toSorted();
|
|
|
|
export const parseOrder = (content: string): string[] =>
|
|
content
|
|
.split('\n')
|
|
.map((line) => line.trim())
|
|
.filter((line) => line.length > 0);
|
|
|
|
const findPrefixViolation = (base: string[], current: string[]): string | undefined => {
|
|
if (base.length > current.length) {
|
|
return `${ORDER_FILENAME} has fewer entries (${current.length}) than the baseline (${base.length}); migrations cannot be removed`;
|
|
}
|
|
|
|
const index = base.findIndex((name, i) => current[i] !== name);
|
|
return index === -1
|
|
? undefined
|
|
: `${ORDER_FILENAME} is not append-only: expected "${base[index]}" at position ${index + 1}, found "${current[index]}". New migrations must sort after all existing migrations`;
|
|
};
|
|
|
|
export type VerifyOrderInput = { actual: string[]; expected: string[]; appendOnlyFrom?: string[] };
|
|
|
|
export const verifyOrderContent = ({ actual, expected, appendOnlyFrom }: VerifyOrderInput): string[] => {
|
|
const expectedSet = new Set(expected);
|
|
const actualSet = new Set(actual);
|
|
|
|
const errors = [
|
|
...findDuplicates(expected).map((name) => `Duplicate migration name "${name}"`),
|
|
...findDuplicates(actual).map((name) => `Duplicate ${ORDER_FILENAME} entry "${name}"`),
|
|
...[...expectedSet.difference(actualSet)].map((name) => `Migration "${name}" is missing from ${ORDER_FILENAME}`),
|
|
...[...actualSet.difference(expectedSet)].map(
|
|
(name) => `"${name}" is listed in ${ORDER_FILENAME} but does not exist`,
|
|
),
|
|
];
|
|
|
|
if (errors.length === 0 && !isEqual(actual, expected)) {
|
|
errors.push(`${ORDER_FILENAME} entries are out of order (expected sorted migration names)`);
|
|
}
|
|
|
|
if (errors.length === 0 && appendOnlyFrom) {
|
|
const violation = findPrefixViolation(appendOnlyFrom, actual);
|
|
if (violation) {
|
|
errors.push(violation);
|
|
}
|
|
}
|
|
|
|
return errors;
|
|
};
|
|
|
|
export const listMigrationNames = (folder: string): string[] =>
|
|
computeOrder(
|
|
readdirSync(folder, { withFileTypes: true })
|
|
.filter((entry) => entry.isFile())
|
|
.map((entry) => entry.name),
|
|
);
|
|
|
|
export const readOrder = (folder: string): string[] | undefined => {
|
|
const path = join(folder, ORDER_FILENAME);
|
|
return existsSync(path) ? parseOrder(readFileSync(path, 'utf8')) : undefined;
|
|
};
|
|
|
|
export const writeOrder = (folder: string, names: string[]): void => {
|
|
writeFileSync(join(folder, ORDER_FILENAME), names.map((name) => `${name}\n`).join(''));
|
|
};
|
|
|
|
export const syncOrder = (folder: string): { previous?: string[]; next: string[]; changed: boolean } => {
|
|
const previous = readOrder(folder);
|
|
const next = listMigrationNames(folder);
|
|
const changed = previous === undefined || !isEqual(previous, next);
|
|
if (changed) {
|
|
writeOrder(folder, next);
|
|
}
|
|
|
|
return { previous, next, changed };
|
|
};
|
|
|
|
export const maybeSyncOrder = (folder: string): boolean =>
|
|
existsSync(join(folder, ORDER_FILENAME)) && syncOrder(folder).changed;
|
|
|
|
export type VerifyOrderOptions = { appendOnlyFrom?: string[] };
|
|
|
|
export const verifyOrder = (folder: string, { appendOnlyFrom }: VerifyOrderOptions = {}): string[] => {
|
|
const actual = readOrder(folder);
|
|
if (actual === undefined) {
|
|
return [`Missing ${ORDER_FILENAME} file in ${folder} (run \`sql-tools migrations sync-order\` to create it)`];
|
|
}
|
|
|
|
return verifyOrderContent({ actual, expected: listMigrationNames(folder), appendOnlyFrom });
|
|
};
|