149 lines
4.6 KiB
JavaScript
149 lines
4.6 KiB
JavaScript
import { execFileSync } from 'node:child_process';
|
|
import { createHash } from 'node:crypto';
|
|
import {
|
|
cpSync,
|
|
existsSync,
|
|
mkdirSync,
|
|
readFileSync,
|
|
readdirSync,
|
|
rmSync,
|
|
statSync,
|
|
writeFileSync,
|
|
} from 'node:fs';
|
|
import { dirname, isAbsolute, join, parse, relative, resolve, sep } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const DEFAULT_OUTPUT = '.kt-workspace/napcat-desktop-cn-build';
|
|
const DEFAULT_UPSTREAM_BASE = '5c18a62530d87dbadf53d267002894faa6ca7e90';
|
|
|
|
function readArg(name, fallback = '') {
|
|
const index = process.argv.indexOf(`--${name}`);
|
|
return index >= 0 && process.argv[index + 1] ? process.argv[index + 1] : fallback;
|
|
}
|
|
|
|
function sha256File(filePath) {
|
|
return createHash('sha256').update(readFileSync(filePath)).digest('hex');
|
|
}
|
|
|
|
function listDirectory(directory) {
|
|
return readdirSync(directory).sort();
|
|
}
|
|
|
|
function sha256Directory(directory) {
|
|
const hash = createHash('sha256');
|
|
const stack = [directory];
|
|
const files = [];
|
|
while (stack.length > 0) {
|
|
const current = stack.pop();
|
|
for (const entry of listDirectory(current)) {
|
|
const absolute = join(current, entry);
|
|
if (statSync(absolute).isDirectory()) {
|
|
stack.push(absolute);
|
|
} else {
|
|
files.push(absolute);
|
|
}
|
|
}
|
|
}
|
|
files.sort();
|
|
for (const file of files) {
|
|
hash.update(relative(directory, file).split(sep).join('/'));
|
|
hash.update('\0');
|
|
hash.update(readFileSync(file));
|
|
hash.update('\0');
|
|
}
|
|
return hash.digest('hex');
|
|
}
|
|
|
|
function gitCommit(repoRoot) {
|
|
return execFileSync('git', ['-C', repoRoot, 'rev-parse', 'HEAD'], {
|
|
encoding: 'utf8',
|
|
}).trim();
|
|
}
|
|
|
|
function copyIntoContext(source, target) {
|
|
cpSync(source, target, { recursive: true });
|
|
}
|
|
|
|
function isInsideDirectory(parent, candidate) {
|
|
const relativePath = relative(parent, candidate);
|
|
return Boolean(relativePath) && !relativePath.startsWith('..') && !isAbsolute(relativePath);
|
|
}
|
|
|
|
function assertSafeOutputRoot(outputRootToCheck, apiRoot, napcatRootToCheck) {
|
|
const workspaceRoot = resolve(apiRoot, '.kt-workspace');
|
|
const forbiddenRoots = new Set([
|
|
parse(outputRootToCheck).root,
|
|
resolve(apiRoot),
|
|
resolve(apiRoot, '..', '..'),
|
|
workspaceRoot,
|
|
resolve(napcatRootToCheck),
|
|
dirname(resolve(napcatRootToCheck)),
|
|
]);
|
|
|
|
if (forbiddenRoots.has(resolve(outputRootToCheck))) {
|
|
throw new Error(`Refusing to delete unsafe output root: ${outputRootToCheck}`);
|
|
}
|
|
if (!isInsideDirectory(workspaceRoot, outputRootToCheck)) {
|
|
throw new Error(`Output root must stay inside ${workspaceRoot}`);
|
|
}
|
|
}
|
|
|
|
const apiRoot = resolve(fileURLToPath(new URL('..', import.meta.url)));
|
|
const napcatRootArg = readArg('napcat-root');
|
|
const napcatRoot = resolve(napcatRootArg);
|
|
const outputRoot = resolve(apiRoot, readArg('out', DEFAULT_OUTPUT));
|
|
const upstreamBaseCommit = readArg('upstream-base-commit', DEFAULT_UPSTREAM_BASE);
|
|
const upstreamReleaseTag = readArg('upstream-release-tag', 'unknown');
|
|
const upstreamReleaseCommit = readArg('upstream-release-commit', upstreamBaseCommit);
|
|
const napcatBaseImageDigest = readArg('napcat-base-image-digest', '');
|
|
const jenkinsBuildUrl = readArg('jenkins-build-url', '');
|
|
const shellDist = resolve(napcatRoot, 'packages/napcat-shell/dist');
|
|
const napcatMjs = resolve(shellDist, 'napcat.mjs');
|
|
|
|
if (!napcatRootArg || !existsSync(napcatRoot)) {
|
|
throw new Error('--napcat-root must point to the NapCatQQ fork repository');
|
|
}
|
|
if (!existsSync(napcatMjs)) {
|
|
throw new Error(`NapCat shell build output is missing: ${napcatMjs}`);
|
|
}
|
|
assertSafeOutputRoot(outputRoot, apiRoot, napcatRoot);
|
|
|
|
rmSync(outputRoot, { force: true, recursive: true });
|
|
mkdirSync(resolve(outputRoot, 'ci/napcat-desktop-cn'), { recursive: true });
|
|
copyIntoContext(
|
|
resolve(apiRoot, 'ci/napcat-desktop-cn/Dockerfile'),
|
|
resolve(outputRoot, 'ci/napcat-desktop-cn/Dockerfile'),
|
|
);
|
|
copyIntoContext(
|
|
resolve(apiRoot, 'ci/napcat-desktop-cn/verify.sh'),
|
|
resolve(outputRoot, 'ci/napcat-desktop-cn/verify.sh'),
|
|
);
|
|
copyIntoContext(
|
|
resolve(apiRoot, 'ci/napcat-desktop-cn/entrypoint-device-profile.patch.sh'),
|
|
resolve(outputRoot, 'ci/napcat-desktop-cn/entrypoint-device-profile.patch.sh'),
|
|
);
|
|
copyIntoContext(shellDist, resolve(outputRoot, 'NapCat.Shell'));
|
|
|
|
const marker = {
|
|
builtAt: new Date().toISOString(),
|
|
distSha256: sha256Directory(shellDist),
|
|
forkCommit: gitCommit(napcatRoot),
|
|
jenkinsBuildUrl,
|
|
napcatBaseImageDigest,
|
|
napcatMjsSha256: sha256File(napcatMjs),
|
|
upstreamBaseCommit,
|
|
upstreamReleaseCommit,
|
|
upstreamReleaseTag,
|
|
};
|
|
|
|
writeFileSync(
|
|
resolve(outputRoot, 'ci/napcat-desktop-cn/fork-artifact.json'),
|
|
`${JSON.stringify(marker, null, 2)}\n`,
|
|
'utf8',
|
|
);
|
|
|
|
process.stdout.write(`${JSON.stringify({
|
|
marker,
|
|
outputRoot,
|
|
}, null, 2)}\n`);
|