mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-25 05:16:19 -07:00
108 lines
3.8 KiB
TypeScript
108 lines
3.8 KiB
TypeScript
import { spawnSync } from 'node:child_process';
|
|
import { writeFileSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { versionOutputPath } from './docs-versioning';
|
|
|
|
// Frozen `/v/<version>/` doc builds live in an R2 bucket and are served by the Pages
|
|
// Function in `docs-site/functions/v/[[path]].ts`, so they never count toward the Pages
|
|
// deployment. Transfers go through the AWS CLI's S3 API (preinstalled on GitHub runners).
|
|
|
|
// Written last on upload; an archive without it is treated as missing and rebuilt.
|
|
export const ARCHIVE_MARKER = '_archive.json';
|
|
|
|
export type DocsArchiveStore = {
|
|
has(version: string): boolean;
|
|
upload(version: string, dir: string, builtFrom: string): void;
|
|
};
|
|
|
|
export type DocsArchiveStoreEnv = {
|
|
accountId?: string;
|
|
accessKeyId?: string;
|
|
secretAccessKey?: string;
|
|
bucket?: string;
|
|
};
|
|
|
|
export function archiveStoreEnvFromProcess(env: NodeJS.ProcessEnv): DocsArchiveStoreEnv {
|
|
return {
|
|
accountId: env.CLOUDFLARE_ACCOUNT_ID,
|
|
accessKeyId: env.DOCS_ARCHIVE_R2_ACCESS_KEY_ID,
|
|
secretAccessKey: env.DOCS_ARCHIVE_R2_SECRET_ACCESS_KEY,
|
|
bucket: env.DOCS_ARCHIVE_R2_BUCKET,
|
|
};
|
|
}
|
|
|
|
export function archiveKeyPrefix(version: string): string {
|
|
return `${versionOutputPath(version)}/`;
|
|
}
|
|
|
|
// Returns null when credentials are absent so local builds can skip archive sync.
|
|
export function createArchiveStore(config: DocsArchiveStoreEnv): DocsArchiveStore | null {
|
|
const { accountId, accessKeyId, secretAccessKey, bucket } = config;
|
|
if (!accountId || !accessKeyId || !secretAccessKey || !bucket) {
|
|
return null;
|
|
}
|
|
|
|
const endpoint = `https://${accountId}.r2.cloudflarestorage.com`;
|
|
const env: NodeJS.ProcessEnv = {
|
|
...process.env,
|
|
AWS_ACCESS_KEY_ID: accessKeyId,
|
|
AWS_SECRET_ACCESS_KEY: secretAccessKey,
|
|
AWS_DEFAULT_REGION: 'auto',
|
|
// AWS CLI >= 2.23 sends CRC checksums by default, which R2 does not fully support.
|
|
AWS_REQUEST_CHECKSUM_CALCULATION: 'when_required',
|
|
AWS_RESPONSE_CHECKSUM_VALIDATION: 'when_required',
|
|
};
|
|
|
|
function aws(args: string[]) {
|
|
return spawnSync('aws', [...args, '--endpoint-url', endpoint], {
|
|
env,
|
|
encoding: 'utf8',
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
});
|
|
}
|
|
|
|
return {
|
|
has(version) {
|
|
const result = aws([
|
|
's3api',
|
|
'head-object',
|
|
'--bucket',
|
|
bucket,
|
|
'--key',
|
|
`${archiveKeyPrefix(version)}${ARCHIVE_MARKER}`,
|
|
]);
|
|
if (result.error) throw result.error;
|
|
if (result.status === 0) return true;
|
|
if (/\b404\b|Not Found/i.test(result.stderr)) return false;
|
|
throw new Error(`Unable to check docs archive ${version}: ${result.stderr.trim()}`);
|
|
},
|
|
|
|
upload(version, dir, builtFrom) {
|
|
const target = `s3://${bucket}/${archiveKeyPrefix(version)}`;
|
|
// Unmark first so a rebuild that fails partway is retried by the next deploy
|
|
// instead of being skipped as complete. Deleting a missing key succeeds.
|
|
const unmark = aws(['s3', 'rm', `${target}${ARCHIVE_MARKER}`, '--only-show-errors']);
|
|
if (unmark.error) throw unmark.error;
|
|
if (unmark.status !== 0) {
|
|
throw new Error(`Unable to unmark docs archive ${version}: ${unmark.stderr.trim()}`);
|
|
}
|
|
|
|
const sync = aws(['s3', 'sync', dir, target, '--only-show-errors']);
|
|
if (sync.error) throw sync.error;
|
|
if (sync.status !== 0) {
|
|
throw new Error(`Unable to upload docs archive ${version}: ${sync.stderr.trim()}`);
|
|
}
|
|
|
|
const markerPath = join(dir, ARCHIVE_MARKER);
|
|
writeFileSync(
|
|
markerPath,
|
|
`${JSON.stringify({ version, builtFrom, builtAt: new Date().toISOString() }, null, 2)}\n`,
|
|
);
|
|
const marker = aws(['s3', 'cp', markerPath, `${target}${ARCHIVE_MARKER}`]);
|
|
if (marker.status !== 0) {
|
|
throw new Error(`Unable to mark docs archive ${version}: ${marker.stderr.trim()}`);
|
|
}
|
|
},
|
|
};
|
|
}
|