import fs from 'node:fs' import path from 'node:path' import crypto from 'node:crypto' import { fileURLToPath } from 'node:url' import { inventory, hash } from './upload.mjs' const project = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..') const stage = process.argv[2] if (!stage || !path.isAbsolute(stage) || fs.existsSync(stage)) throw new Error('NEW_ABSOLUTE_STAGING_DIRECTORY_REQUIRED') const source = inventory(project) const unique = [...new Map(source.entries.map(entry => [entry.objectKey, entry])).values()] const shared = source.entries.find(entry => entry.sourcePath === 'assets/share/guixiang-story-share-preview-v1.jpg') if (!shared) throw new Error('FIRST_VERIFIED_SAMPLE_NOT_FOUND') const sampleUrl = 'https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/uploads/images/20260908/202609081718480ee488780.jpg' const runId = crypto.randomUUID() const counters = { image: 0, audio: 0 } fs.mkdirSync(stage, { recursive: false }) const entries = unique.map(entry => { const sample = entry.sha256 === shared.sha256 const stagedName = sample ? null : `tang-20260908-1714-${entry.kind}-${String(++counters[entry.kind]).padStart(3, '0')}${path.extname(entry.sourcePath)}` const stagedPath = stagedName ? path.join(stage, stagedName) : null if (stagedPath) { const original = path.join(source.sourceDirectory, entry.sourcePath) fs.copyFileSync(original, stagedPath, fs.constants.COPYFILE_EXCL) if (hash(fs.readFileSync(stagedPath)) !== entry.sha256) throw new Error('STAGING_BYTES_DIFFER') } return { ...entry, stagedName, stagedPath, observedUrl: sample ? sampleUrl : null } }) const result = { schemaVersion: 1, uploadRunId: runId, sourceManifestSha256: source.sourceManifestSha256, sourceDirectory: source.sourceDirectory, stagingDirectory: stage, createdAt: new Date().toISOString(), entries } const output = path.join(project, 'build/tang-detective-admin-upload-plan.json') fs.writeFileSync(output, JSON.stringify(result, null, 2) + '\n', { flag: 'wx' }) console.log(JSON.stringify({ uploadRunId: runId, entries: entries.length, staged: counters, stage, output, originalsChanged: false }, null, 2))