d18ada4ee7
Docker Publish / docker (web, apps/web/Dockerfile, web) (push) Failing after 0s
Docker Publish / docker (api, apps/api/Dockerfile, api) (push) Failing after 1s
Publish Extension / detect-version (push) Has been skipped
Publish Extension / submit (push) Has been cancelled
233 lines
6.9 KiB
JavaScript
233 lines
6.9 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import fs from 'node:fs'
|
|
import path from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
|
const desktopDir = path.join(scriptDir, '..')
|
|
const packageJsonPath = path.join(desktopDir, 'package.json')
|
|
const changelogPath = path.join(desktopDir, 'changelogs', 'CHANGELOG.md')
|
|
const releaseMetadataPath = path.join(desktopDir, 'release-metadata.json')
|
|
const shanghaiDateFormatter = new Intl.DateTimeFormat('en-US', {
|
|
timeZone: 'Asia/Shanghai',
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit'
|
|
})
|
|
|
|
const command = process.argv[2]
|
|
const args = process.argv.slice(3)
|
|
|
|
const readJson = (filePath) => JSON.parse(fs.readFileSync(filePath, 'utf8'))
|
|
|
|
const writeJson = (filePath, data) => {
|
|
fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`)
|
|
}
|
|
|
|
const setOutput = (name, value) => {
|
|
const outputPath = process.env.GITHUB_OUTPUT
|
|
if (!outputPath) {
|
|
return
|
|
}
|
|
|
|
fs.appendFileSync(outputPath, `${name}=${value}\n`)
|
|
}
|
|
|
|
const getArgValue = (name) => {
|
|
const index = args.indexOf(name)
|
|
if (index === -1) {
|
|
return undefined
|
|
}
|
|
|
|
return args[index + 1]
|
|
}
|
|
|
|
const hasFlag = (name) => args.includes(name)
|
|
|
|
const formatShanghaiDate = (date = new Date()) => {
|
|
const parts = Object.fromEntries(
|
|
shanghaiDateFormatter.formatToParts(date).map((part) => [part.type, part.value])
|
|
)
|
|
|
|
return `${parts.year}-${parts.month}-${parts.day}`
|
|
}
|
|
|
|
const normalizeVersion = (version) => version.replace(/^v/i, '').trim()
|
|
|
|
const getNextPatchVersion = (currentVersion) => {
|
|
const match = currentVersion.trim().match(/^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/)
|
|
|
|
if (!match) {
|
|
throw new Error(`Unsupported app version format: ${currentVersion}`)
|
|
}
|
|
|
|
const [, major, minor, patch] = match
|
|
return `${major}.${minor}.${Number.parseInt(patch, 10) + 1}`
|
|
}
|
|
|
|
const getAuthHeaders = () => {
|
|
const token =
|
|
process.env.YTDLP_RELEASE_TOKEN ??
|
|
process.env.GITHUB_TOKEN ??
|
|
process.env.GH_TOKEN ??
|
|
process.env.ACCESS_TOKEN
|
|
|
|
if (!token) {
|
|
return {
|
|
Accept: 'application/vnd.github+json',
|
|
'User-Agent': 'VidBee ytdlp auto release'
|
|
}
|
|
}
|
|
|
|
return {
|
|
Accept: 'application/vnd.github+json',
|
|
Authorization: `Bearer ${token}`,
|
|
'User-Agent': 'VidBee ytdlp auto release'
|
|
}
|
|
}
|
|
|
|
const fetchLatestYtDlpVersion = async (overrideVersion) => {
|
|
if (overrideVersion) {
|
|
return normalizeVersion(overrideVersion)
|
|
}
|
|
|
|
const response = await fetch('https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest', {
|
|
headers: getAuthHeaders()
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error(
|
|
`Failed to fetch yt-dlp latest release: ${response.status} ${response.statusText}`
|
|
)
|
|
}
|
|
|
|
const data = await response.json()
|
|
const tagName = typeof data.tag_name === 'string' ? data.tag_name : ''
|
|
|
|
if (!tagName) {
|
|
throw new Error('yt-dlp latest release response did not include tag_name')
|
|
}
|
|
|
|
return normalizeVersion(tagName)
|
|
}
|
|
|
|
const prependChangelogEntry = ({
|
|
changelogContent,
|
|
releaseVersion,
|
|
releaseDate,
|
|
previousYtDlpVersion,
|
|
latestYtDlpVersion
|
|
}) => {
|
|
const versionSectionIndex = changelogContent.indexOf('\n## [')
|
|
|
|
if (versionSectionIndex === -1) {
|
|
throw new Error('Unable to find the first version heading in CHANGELOG.md')
|
|
}
|
|
|
|
const changelogEntry = [
|
|
`## [v${releaseVersion}](https://github.com/nexmoe/VidBee/releases/tag/v${releaseVersion}) - ${releaseDate}`,
|
|
'### Requirement Updates',
|
|
`- Updated the bundled yt-dlp runtime from v${previousYtDlpVersion} to v${latestYtDlpVersion} so site compatibility stays current.`,
|
|
''
|
|
].join('\n')
|
|
|
|
return `${changelogContent.slice(0, versionSectionIndex + 1)}${changelogEntry}${changelogContent.slice(versionSectionIndex + 1)}`
|
|
}
|
|
|
|
const readReleaseState = () => {
|
|
const packageJson = readJson(packageJsonPath)
|
|
const releaseMetadata = readJson(releaseMetadataPath)
|
|
|
|
return {
|
|
packageJson,
|
|
currentAppVersion: packageJson.version,
|
|
currentYtDlpVersion: normalizeVersion(releaseMetadata.ytDlpVersion ?? '')
|
|
}
|
|
}
|
|
|
|
const runCheck = async () => {
|
|
const latestYtDlpVersion = await fetchLatestYtDlpVersion(getArgValue('--latest-version'))
|
|
const { currentYtDlpVersion } = readReleaseState()
|
|
const updateAvailable = latestYtDlpVersion !== currentYtDlpVersion
|
|
|
|
console.log(`Tracked yt-dlp version: ${currentYtDlpVersion || 'unknown'}`)
|
|
console.log(`Latest yt-dlp version: ${latestYtDlpVersion}`)
|
|
console.log(updateAvailable ? 'yt-dlp update detected.' : 'yt-dlp is already up to date.')
|
|
|
|
setOutput('current_ytdlp_version', currentYtDlpVersion)
|
|
setOutput('latest_ytdlp_version', latestYtDlpVersion)
|
|
setOutput('update_available', String(updateAvailable))
|
|
}
|
|
|
|
const runPrepare = async () => {
|
|
const latestYtDlpVersion = await fetchLatestYtDlpVersion(getArgValue('--latest-version'))
|
|
const releaseDate = getArgValue('--release-date') ?? formatShanghaiDate()
|
|
const dryRun = hasFlag('--dry-run')
|
|
const { packageJson, currentAppVersion, currentYtDlpVersion } = readReleaseState()
|
|
|
|
if (latestYtDlpVersion === currentYtDlpVersion) {
|
|
console.log('yt-dlp is already up to date. No release changes prepared.')
|
|
setOutput('update_available', 'false')
|
|
setOutput('current_ytdlp_version', currentYtDlpVersion)
|
|
setOutput('latest_ytdlp_version', latestYtDlpVersion)
|
|
setOutput('release_version', currentAppVersion)
|
|
return
|
|
}
|
|
|
|
const releaseVersion = getNextPatchVersion(currentAppVersion)
|
|
const updatedPackageJson = { ...packageJson, version: releaseVersion }
|
|
const currentChangelog = fs.readFileSync(changelogPath, 'utf8')
|
|
const updatedChangelog = prependChangelogEntry({
|
|
changelogContent: currentChangelog,
|
|
releaseVersion,
|
|
releaseDate,
|
|
previousYtDlpVersion: currentYtDlpVersion,
|
|
latestYtDlpVersion
|
|
})
|
|
const updatedReleaseMetadata = {
|
|
ytDlpVersion: latestYtDlpVersion
|
|
}
|
|
|
|
if (!dryRun) {
|
|
writeJson(packageJsonPath, updatedPackageJson)
|
|
writeJson(releaseMetadataPath, updatedReleaseMetadata)
|
|
fs.writeFileSync(changelogPath, updatedChangelog)
|
|
}
|
|
|
|
console.log(`Prepared patch release ${currentAppVersion} -> ${releaseVersion}`)
|
|
console.log(`Bundled yt-dlp ${currentYtDlpVersion} -> ${latestYtDlpVersion}`)
|
|
if (dryRun) {
|
|
console.log('Dry run enabled. No files were written.')
|
|
}
|
|
|
|
setOutput('update_available', 'true')
|
|
setOutput('current_ytdlp_version', currentYtDlpVersion)
|
|
setOutput('latest_ytdlp_version', latestYtDlpVersion)
|
|
setOutput('release_date', releaseDate)
|
|
setOutput('release_version', releaseVersion)
|
|
}
|
|
|
|
const main = async () => {
|
|
if (command === 'check') {
|
|
await runCheck()
|
|
return
|
|
}
|
|
|
|
if (command === 'prepare') {
|
|
await runPrepare()
|
|
return
|
|
}
|
|
|
|
console.error(
|
|
'Usage: node scripts/ytdlp-auto-release.mjs <check|prepare> [--latest-version <version>] [--release-date <YYYY-MM-DD>] [--dry-run]'
|
|
)
|
|
process.exit(1)
|
|
}
|
|
|
|
main().catch((error) => {
|
|
const message = error instanceof Error ? error.message : String(error)
|
|
console.error(message)
|
|
process.exit(1)
|
|
})
|