← Заметки

Контроли качества Astro: почему успешный PR не развёртывает сайт

Успешная сборка PR подтверждает прохождение настроенных проверок. На этом сайте она не публикует кандидата: производственное развёртывание дополнительно требует ref main, разрешённого события и успешной зависимой сборки.

Прежняя настройка Jekyll использовала proof-html и bundler-audit. Текущий процесс Astro использует site-checks.yml для проверки и site-quality-gates.yml для Pages. Проверки Ruby относятся к прежним инструментам; они не проверяют npm-зависимости сборки этого сайта.

Разделяйте проверку и развёртывание

Проверка кандидата, условия main и события, развёртывание проверенного артефакта

Workflow сайта передаёт ${{ github.sha }} общему проверяющему workflow. Повторно используемый workflow извлекает этот входной ref, содержит задачу сборки и права contents только для чтения, оставляя опцию Pages-артефакта false, если вызывающий её не включает. Документация GitHub по workflow_call описывает интерфейс.

Настроенные проверки охватывают разные ошибки:

Проверка Что подтверждает
Тесты Python и Node Проверенные контракты сбора, языков, публикации и рендеринга
Извлечённые примеры статей Выбранные случаи выполнения на записанных инструментах
npm run check Правила контента и проверки типов/схемы Astro
npm run build Статический результат, внутренние ссылки, изображения, якоря и охват миграции

Проверка внутренних ссылок не подтверждает доступность внешних URL. Успешный пример не доказывает всё производственное поведение; корректно оформленная статья всё ещё может содержать необоснованное утверждение. Действующий workflow также не выполняет общий аудит уязвимостей npm.

Воспроизведите выбранные контракты развёртывания

С Node 24 выполните npm ci в checkout этого репозитория. Сохраните полный файл как deploy-contract.mjs в корне и запустите:

node deploy-contract.mjs

Скрипт проверяет реальные значения YAML, затем моделирует фиксированное условие развёртывания для 24 сочетаний входов. Шесть намеренно изменённых объектов workflow должны провалить контракт. Скрипт не имеет операций deploy или сети.

deploy-contract.mjs

import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import YAML from 'yaml';

const read = file => YAML.parse(readFileSync(`.github/workflows/${file}`, 'utf8'));
const site = read('site-quality-gates.yml');
const checks = read('site-checks.yml');
const condition = "github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')";
function verify(site, checks) {
    assert.equal(site.jobs.deploy.if, condition);
    assert.equal(site.jobs.deploy.needs, 'build');
    assert.equal(site.jobs.build.with.ref, '${{ github.sha }}');
    assert.equal(site.jobs.build.with.pages_artifact, '${{ ' + condition + ' }}');
    assert.deepEqual(Object.keys(checks.jobs), ['build']);
    assert.deepEqual(checks.permissions, { contents: 'read' });
    assert.equal(checks.on.workflow_call.inputs.pages_artifact.default, false);
    assert.equal(checks.jobs.build.steps[0].with.ref, '${{ inputs.ref }}');
    assert.ok(site.jobs.deploy.steps.some(step => step.uses === 'actions/deploy-pages@v4'));
}
verify(site, checks);

// A model of this fixed condition and successful dependency, not the Actions engine.
function allowed(ref, event, build) {
    return build === 'success' && ref === 'refs/heads/main' &&
        (event === 'push' || event === 'workflow_dispatch');
}
const expected = new Set([
    'refs/heads/main|push|success',
    'refs/heads/main|workflow_dispatch|success',
]);
let cases = 0;
for (const ref of ['refs/heads/main', 'refs/heads/develop', 'refs/heads/content/example', 'refs/pull/42/merge']) {
    for (const event of ['push', 'workflow_dispatch', 'pull_request']) {
        for (const build of ['success', 'failure']) {
            assert.equal(allowed(ref, event, build), expected.has(`${ref}|${event}|${build}`));
            cases++;
        }
    }
}

const mutations = [
    (site, checks) => { site.jobs.deploy.if = "github.event_name == 'pull_request'"; },
    (site, checks) => { site.jobs.deploy.needs = []; },
    (site, checks) => { site.jobs.build.with.ref = 'main'; },
    (site, checks) => { checks.permissions.pages = 'write'; },
    (site, checks) => { checks.on.workflow_call.inputs.pages_artifact.default = true; },
    (site, checks) => { checks.jobs.build.steps[0].with.ref = 'main'; },
];
for (const mutate of mutations) {
    const badSite = structuredClone(site), badChecks = structuredClone(checks);
    mutate(badSite, badChecks);
    assert.throws(() => verify(badSite, badChecks), assert.AssertionError);
}
console.log(`${cases} policy-model combinations and ${mutations.length} rejected workflow mutations passed.`);

Проверенный вывод:

24 policy-model combinations and 6 rejected workflow mutations passed.

Модель разрешает только успешные сборки на main с push или workflow_dispatch. Остальные сочетания отклоняются, включая ручной запуск feature-ветки. Запуск pull_request обычно использует ref слияния PR, поэтому его успех не проходит условие сайта. GitHub описывает условия задач и требования успешных зависимостей.

Это модель выбранного ref/события с успешной зависимой сборкой, не интерпретатор выражений Actions. Она проверяет успех и ошибку, не имитируя отмену, пропущенных предков, планирование runner, авторизацию токена, происхождение артефактов или Pages deploy. Утверждения YAML заставляют изменённую политику падать для явной проверки, а не незаметно сравнивать старую модель с новой конфигурацией.

Подтвердите опубликованный результат

После одобрения и слияния кандидата проверьте коммит запуска main, результат сборки и задачу deploy. Успешный PR с deploy: skipped ожидаем; одна успешная сборка не доказывает публикацию. Задача Pages использует проверенный артефакт по схеме развёртывания Astro Pages.

Откройте публичную статью и языковые ссылки, проверьте canonical URL, даты источников и sitemap. IndexNow запускается после развёртывания с допускающими ошибку шагами, поэтому может падать при успехе Pages. Принятие уведомления и запись sitemap также не доказывают индексацию или показы в поиске.

Статья о кандидатах объясняет предшествующий Draft PR review. Разделяйте технические проверки и решение публиковать; ни одно не заменяет проверки утверждений статьи.

Обсуждение

Посмотреть на GitHub ↗