Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
65 lines
2.1 KiB
TypeScript
65 lines
2.1 KiB
TypeScript
// Migration numbers are chosen by hand, so two concurrent PRs can pick the same
|
|
// one. The filenames differ, so git merges both cleanly and `main` ends up with
|
|
// a duplicate. This throws on duplicate or non-contiguous numbers; CI runs it
|
|
// against the post-merge tree, catching the collision before it lands.
|
|
|
|
// Numbers duplicated before this check existed. Don't add to this list.
|
|
export const GRANDFATHERED_DUPLICATE_NUMBERS: ReadonlySet<number> = new Set([27])
|
|
|
|
function pad(n: number) {
|
|
return String(n).padStart(3, '0')
|
|
}
|
|
|
|
export function validateMigrationFilenames(
|
|
filenames: string[],
|
|
allowedDuplicates: ReadonlySet<number> = GRANDFATHERED_DUPLICATE_NUMBERS
|
|
) {
|
|
const filesByNumber = new Map<number, string[]>()
|
|
for (const filename of filenames) {
|
|
const match = filename.match(/^(\d{3})_.+\.sql$/)
|
|
if (!match) {
|
|
throw new Error(
|
|
`Migration filename "${filename}" must match "NNN_description.sql" (a three-digit number, for example "032_add_thing.sql").`
|
|
)
|
|
}
|
|
const number = Number(match[1])
|
|
const existing = filesByNumber.get(number)
|
|
if (existing) {
|
|
existing.push(filename)
|
|
} else {
|
|
filesByNumber.set(number, [filename])
|
|
}
|
|
}
|
|
|
|
if (filesByNumber.size === 0) {
|
|
throw new Error('No migrations found.')
|
|
}
|
|
|
|
const numbers = [...filesByNumber.keys()].sort((a, b) => a - b)
|
|
|
|
// Numbers must be unique.
|
|
for (const number of numbers) {
|
|
const files = filesByNumber.get(number)!
|
|
if (files.length > 1 && !allowedDuplicates.has(number)) {
|
|
throw new Error(
|
|
`Duplicate migration number ${pad(number)}: ${[...files].sort().join(', ')}. ` +
|
|
`Each migration must use a unique number — bump one of them to the next free number.`
|
|
)
|
|
}
|
|
}
|
|
|
|
// Numbers must be contiguous starting at 000.
|
|
if (numbers[0] !== 0) {
|
|
throw new Error(`Migrations must start at 000, but the lowest number is ${pad(numbers[0])}.`)
|
|
}
|
|
for (let i = 1; i < numbers.length; i++) {
|
|
const prev = numbers[i - 1]
|
|
const curr = numbers[i]
|
|
if (curr !== prev + 1) {
|
|
throw new Error(
|
|
`Migration numbers must be contiguous: missing ${pad(prev + 1)} (jumps from ${pad(prev)} to ${pad(curr)}).`
|
|
)
|
|
}
|
|
}
|
|
}
|