- all authorization rules (owner/creator, per-channel read/write grants, who may remove whom, delete rules, structure management) live in ChannelPolicy; a test fails if they are reimplemented elsewhere - ChannelInfo carries what the caller may do (can_*), member and invitation lists say who can be removed; clients display it and never derive it - the host no longer stores or calls the file service: it only signs short-lived tickets from space permissions (create/put/get/delete/domain), and keeps an opaque fileDomain reference on the channel - a whole file is registered in one request (last frame header + per-frame length and digest) instead of one request per 1 MiB frame; the signed manifest still has to match every digest - drop file quota/threshold, receipts, reconcile and delete-queue code from the host - contract generator emits constants for enums, error codes, token roles, permissions and share states
47 lines
3.0 KiB
JavaScript
47 lines
3.0 KiB
JavaScript
import { readFileSync } from 'node:fs';
|
|
import { createHash } from 'node:crypto';
|
|
|
|
const read = (path) => readFileSync(new URL(`../${path}`, import.meta.url), 'utf8');
|
|
const semver = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
const contract = JSON.parse(read('contracts/api-contract.json'));
|
|
const fail = (message) => {
|
|
console.error(`API contract check failed: ${message}`);
|
|
process.exit(1);
|
|
};
|
|
|
|
const dtoSources = ['server/src/WebDrop.Api/Json/ApiJson.cs', 'server/src/WebDrop.Core/Channels/Dtos.cs', 'server/src/WebDrop.Core/Channels/Settings.cs', 'server/src/WebDrop.Core/Common/Enums.cs', 'server/src/WebDrop.Core/Spaces/Workspace.cs', 'server/src/WebDrop.Core/Accounts/AccountProfile.cs', 'server/src/WebDrop.Api/Passkeys/PasskeyDtos.cs', 'server/src/WebDrop.Core/Spaces/WorkspaceCrypto.cs', 'server/src/WebDrop.Api/Endpoints/Endpoints.WorkspaceCrypto.cs'];
|
|
const dtoDigest = createHash('sha256').update(dtoSources.map(path => `${path}\n${read(path).replace(/\r\n/g, '\n')}\n`).join('')).digest('hex');
|
|
const generatedDigest = /backend-contract-sha256: ([a-f0-9]{64})/.exec(read('web/src/api/generated.ts'))?.[1];
|
|
if (generatedDigest !== dtoDigest) fail('Backend DTOs changed. Regenerate with dotnet run --project tools/ContractGenerator -- .');
|
|
if (JSON.parse(read('contracts/api.schemas.json'))['x-backend-contract-sha256'] !== dtoDigest) fail('Generated API schemas are stale. Run ContractGenerator.');
|
|
|
|
if (typeof contract.version !== 'string' || !semver.test(contract.version)) {
|
|
fail('contracts/api-contract.json must contain a SemVer version.');
|
|
}
|
|
|
|
const apiMajor = Number(contract.version.split('.')[0]);
|
|
const openapi = read('docs/openapi.yaml');
|
|
const openapiVersion = /^info:\s*\r?\n(?:.*\r?\n)*?\s+version:\s*([^\s#]+)/m.exec(openapi)?.[1];
|
|
if (openapiVersion !== contract.version) fail(`OpenAPI version ${openapiVersion ?? '(missing)'} differs from ${contract.version}.`);
|
|
|
|
const backend = read('server/src/WebDrop.Api/Hosting/ApiContract.cs');
|
|
const backendVersion = /const string Version = "([^"]+)"/.exec(backend)?.[1];
|
|
if (backendVersion !== contract.version) fail(`backend version ${backendVersion ?? '(missing)'} differs from ${contract.version}.`);
|
|
|
|
const frontend = read('web/src/api/contract.ts');
|
|
const supportedMajor = Number(/SUPPORTED_API_CONTRACT_MAJOR\s*=\s*(\d+)/.exec(frontend)?.[1]);
|
|
if (supportedMajor !== apiMajor) {
|
|
fail(`backend contract is ${contract.version}; frontend still supports API major ${supportedMajor || '(missing)'}. Update web/src/api/contract.ts.`);
|
|
}
|
|
|
|
const tag = process.argv[2];
|
|
if (tag !== undefined) {
|
|
if (!tag.startsWith('v')) fail(`release tag ${tag} must start with v.`);
|
|
const releaseVersion = tag.slice(1);
|
|
if (!semver.test(releaseVersion) || releaseVersion.includes('+')) {
|
|
fail(`release tag ${tag} must be a SemVer version usable as an OCI image tag (expected vMAJOR.MINOR.PATCH[-prerelease]).`);
|
|
}
|
|
}
|
|
|
|
console.log(`API contract ${contract.version}: backend, OpenAPI, and frontend major ${supportedMajor} agree.`);
|