mirror of
https://github.com/mastodon/mastodon.git
synced 2025-05-12 04:31:11 +00:00
86 lines
2.3 KiB
TypeScript
86 lines
2.3 KiB
TypeScript
import { createHash } from 'node:crypto';
|
|
import { promises as fs } from 'node:fs';
|
|
import { resolve } from 'node:path';
|
|
|
|
import type { Plugin, Manifest } from 'vite';
|
|
|
|
export type Algorithm = 'sha256' | 'sha384' | 'sha512';
|
|
|
|
export interface Options {
|
|
/**
|
|
* Which hashing algorithms to use when calculate the integrity hash for each
|
|
* asset in the manifest.
|
|
* @default ['sha384']
|
|
*/
|
|
algorithms?: Algorithm[];
|
|
}
|
|
|
|
declare module 'vite' {
|
|
interface ManifestChunk {
|
|
integrity: string;
|
|
assetIntegrity: Record<string, string>;
|
|
}
|
|
}
|
|
|
|
export function manifestSRI(options: Options = {}): Plugin {
|
|
const { algorithms = ['sha384'] } = options;
|
|
|
|
return {
|
|
name: 'vite-plugin-manifest-sri',
|
|
apply: 'build',
|
|
enforce: 'post',
|
|
async writeBundle({ dir }) {
|
|
await augmentManifest('manifest.json', algorithms, dir ?? '');
|
|
},
|
|
};
|
|
}
|
|
|
|
async function augmentManifest(
|
|
manifestPath: string,
|
|
algorithms: string[],
|
|
outDir: string,
|
|
) {
|
|
const resolveInOutDir = (path: string) => resolve(outDir, path);
|
|
manifestPath = resolveInOutDir(manifestPath);
|
|
|
|
const manifest: Manifest | undefined = await fs
|
|
.readFile(manifestPath, 'utf-8')
|
|
.then((file) => JSON.parse(file) as Manifest);
|
|
|
|
if (!manifest) {
|
|
throw new Error(`Manifest file not found at ${manifestPath}`);
|
|
}
|
|
await Promise.all(
|
|
Object.values(manifest).map(async (chunk) => {
|
|
chunk.integrity = integrityForAsset(
|
|
await fs.readFile(resolveInOutDir(chunk.file)),
|
|
algorithms,
|
|
);
|
|
if (!chunk.assets && !chunk.css) {
|
|
return;
|
|
}
|
|
chunk.assetIntegrity = {};
|
|
await Promise.all(
|
|
(chunk.assets ?? []).concat(chunk.css ?? []).map(async (asset) => {
|
|
chunk.assetIntegrity[asset] = integrityForAsset(
|
|
await fs.readFile(resolveInOutDir(asset)),
|
|
algorithms,
|
|
);
|
|
}),
|
|
);
|
|
}),
|
|
);
|
|
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
}
|
|
|
|
function integrityForAsset(source: Buffer, algorithms: string[]) {
|
|
return algorithms
|
|
.map((algorithm) => calculateIntegrityHash(source, algorithm))
|
|
.join(' ');
|
|
}
|
|
|
|
export function calculateIntegrityHash(source: Buffer, algorithm: string) {
|
|
const hash = createHash(algorithm).update(source).digest().toString('base64');
|
|
return `${algorithm.toLowerCase()}-${hash}`;
|
|
}
|