diff --git a/cli/src/internal/maintenance/panelUpgrade.cjs b/cli/src/internal/maintenance/panelUpgrade.cjs index 3b119093..68ab3aae 100644 --- a/cli/src/internal/maintenance/panelUpgrade.cjs +++ b/cli/src/internal/maintenance/panelUpgrade.cjs @@ -61,7 +61,9 @@ async function withPanelLifecycle(root, tmp, env, operation, signal) { async function targetVersion(source) { const text = await fs.readFile(path.join(source, 'version.yaml'), 'utf8'); - const version = text.match(/^\uFEFF?version:\s*["']?(\d+\.\d+\.\d+)/m)?.[1]; + const version = text.match( + /^\uFEFF?version:\s*["']?(\d+\.\d+\.\d+(?:-[\w.-]+)?(?:\+[\w.-]+)?)/m, + )?.[1]; if (!version) throw new Error('Invalid update version'); return version; } diff --git a/cli/src/internal/maintenance/upgradePrestart.cjs b/cli/src/internal/maintenance/upgradePrestart.cjs index e73f5880..a312805f 100644 --- a/cli/src/internal/maintenance/upgradePrestart.cjs +++ b/cli/src/internal/maintenance/upgradePrestart.cjs @@ -92,7 +92,9 @@ async function prestartBackend( .digest('hex'); const version = ( await fs.readFile(path.join(selected.source, 'version.yaml'), 'utf8') - ).match(/^\uFEFF?version:\s*["']?(\d+\.\d+\.\d+)/m)?.[1]; + ).match( + /^\uFEFF?version:\s*["']?(\d+\.\d+\.\d+(?:-[\w.-]+)?(?:\+[\w.-]+)?)/m, + )?.[1]; // An allowlist prevents NODE_OPTIONS, startup hooks, real data paths and // package-manager settings from escaping into the disposable backend. const env = { diff --git a/docs/releases/2.24.0-dev.0.md b/docs/releases/2.24.0-dev.0.md new file mode 100644 index 00000000..475b65e9 --- /dev/null +++ b/docs/releases/2.24.0-dev.0.md @@ -0,0 +1,11 @@ +# 青龙 2.24.0-dev.0 开发候选 + +这是 `develop` 渠道的开发构建,上一正式版本为 2.23.0。此版本尚未正式发布。 + +## 本轮变化 + +- Alpine 与 Debian 镜像统一使用 Node.js 24;SQLite 依赖升级至 1.1.4。 +- Alpine 镜像保留 arm/v6 和 arm/v7,Debian 镜像暂不提供 arm/v7。 +- ARM64 升级包改用原生 runner 构建,避免在 x64 QEMU 下归档时发生异常。 + +升级前请备份完整持久化数据,并在目标设备验证脚本、依赖和通知。正式发布说明将在候选验收后整理。 diff --git a/package.json b/package.json index 39ad141d..53b5a2d0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@whyour/qinglong", "packageManager": "pnpm@8.3.1", - "version": "2.23.0", + "version": "2.24.0-dev.0", "description": "Timed task management platform supporting Python3, JavaScript, Shell, Typescript", "repository": { "type": "git", diff --git a/scripts/publish-upgrade-bundles.cjs b/scripts/publish-upgrade-bundles.cjs index 28341124..866d206b 100644 --- a/scripts/publish-upgrade-bundles.cjs +++ b/scripts/publish-upgrade-bundles.cjs @@ -113,7 +113,7 @@ function publishBundles({ } }; const notes = - `Verified platform bundles for v${version}, source ${commit}, build ${buildId}. Existing Node runtime is retained.\n\n` + + `${development ? 'Development' : 'Verified'} platform bundles for v${version}, source ${commit}, build ${buildId}. Existing Node runtime is retained.\n\n` + Object.entries(index.packages) .map(([target, entry]) => `- [${target}](${entry.url})`) .join('\n') + @@ -147,7 +147,10 @@ function publishBundles({ console.log('Newer source exists; leaving version and channel unchanged'); return false; } - ensureRelease(tag, `Platform upgrade v${version} (${buildId})`); + ensureRelease( + tag, + `${development ? 'Development platform upgrade' : 'Platform upgrade'} v${version} (${buildId})`, + ); const assets = platforms .flatMap((target) => { const archive = path.join( diff --git a/test/back/panel-upgrade.test.cjs b/test/back/panel-upgrade.test.cjs index 2d283466..3fe0dae1 100644 --- a/test/back/panel-upgrade.test.cjs +++ b/test/back/panel-upgrade.test.cjs @@ -19,7 +19,7 @@ const { sourceCommit, } = require('../helpers/upgrade-fixture.cjs'); -async function fixture(t) { +async function fixture(t, targetVersion = '2.24.0') { const base = await fs.mkdtemp(path.join(os.tmpdir(), 'ql-panel-job-')); const root = path.join(base, 'panel'), tmp = path.join(root, '.tmp'); @@ -30,7 +30,10 @@ async function fixture(t) { await fs.writeFile(name, contents); }; for (const dir of [root, source]) { - await write(path.join(dir, 'package.json'), '{"version":"2.24.0"}'); + await write( + path.join(dir, 'package.json'), + JSON.stringify({ version: targetVersion }), + ); await write(path.join(dir, 'pnpm-lock.yaml'), 'lock'); await write(path.join(dir, 'sample/config.sample.sh'), '# config'); } @@ -39,7 +42,7 @@ async function fixture(t) { 'installed dependency', ); await write(path.join(root, 'version.yaml'), 'version: 2.23.0'); - await write(path.join(source, 'version.yaml'), 'version: 2.24.0'); + await write(path.join(source, 'version.yaml'), `version: ${targetVersion}`); await write(path.join(staticRoot, 'build/app.js'), 'new backend'); writeManifest(source, staticRoot); for (const repo of ['qinglong', 'qinglong-static']) @@ -81,7 +84,7 @@ fs.writeFileSync(args[args.indexOf('--output') + 1], buffer); ); await fs.chmod(path.join(base, 'bin/curl'), 0o700); const runtime = { - version: '2.24.0', + version: targetVersion, sourceCommit, instanceId: 'after-restart', parentPid: 100002, @@ -186,6 +189,21 @@ test('detached download survives its initiating request and is idempotent; apply ); }); +test('prerelease version is retained through download and restart verification', async (t) => { + const f = await fixture(t, '2.24.0-dev.0'); + await startJob(f, 'download'); + const ready = await finished(f.tmp); + assert.equal(ready.status, 'ready', ready.log); + assert.equal(ready.version, '2.24.0-dev.0'); + await startJob(f, 'apply'); + const complete = await finished(f.tmp); + assert.equal(complete.status, 'completed', complete.log); + assert.equal( + await fs.readFile(path.join(f.root, 'version.yaml'), 'utf8'), + 'version: 2.24.0-dev.0', + ); +}); + test('invalid staged payload fails asynchronously before stopping the panel', async (t) => { const f = await fixture(t); await startJob(f, 'download'); diff --git a/test/back/publish-upgrade-bundles.test.cjs b/test/back/publish-upgrade-bundles.test.cjs index 62ee74ec..1bad0613 100644 --- a/test/back/publish-upgrade-bundles.test.cjs +++ b/test/back/publish-upgrade-bundles.test.cjs @@ -337,20 +337,23 @@ test('existing master clients can migrate from indexes without version/build met assert.equal(f.index().buildId, '100-2'); }); -test('automatic development publication shares musl/glibc packages without changing version or latest production assets', (t) => { +test('automatic development publication keeps its prerelease version separate from production assets', (t) => { const f = fixture(t); f.publish(); const production = f.index(); - f.write(f.state.developHead, 'development'); + const developmentVersion = '2.24.0-dev.0'; + f.write(f.state.developHead, 'development', developmentVersion); assert.equal( f.publish({ branch: 'develop', commit: f.state.developHead, + version: developmentVersion, }), true, ); const development = f.index('upgrade-develop'); assert.equal(development.channel, 'develop'); + assert.equal(development.version, developmentVersion); assert.equal(development.sourceCommit, f.state.developHead); assert.equal(Object.keys(development.packages).length, 11); assert.ok(development.packages['linux-x64-musl']); @@ -363,12 +366,22 @@ test('automatic development publication shares musl/glibc packages without chang ...Object.values(development.packages), development.installer, ]) - assert.ok(entry.url.includes('/upgrade-build-develop-v2.24.0-100-1/')); + assert.ok(entry.url.includes('/upgrade-build-develop-v2.24.0-dev.0-100-1/')); + assert.ok( + f.state.calls.some( + (args) => + args[0] === 'release' && + args[1] === 'create' && + args[2] === 'upgrade-build-develop-v2.24.0-dev.0-100-1' && + args[args.indexOf('--title') + 1] === + 'Development platform upgrade v2.24.0-dev.0 (100-1)', + ), + ); assert.deepEqual(f.index(), production); assert.deepEqual(f.index('upgrade-v2.24.0'), production); assert.equal(f.state.releases.get('upgrade-build-v2.24.0-100-1').size, 24); assert.equal( - f.state.releases.get('upgrade-build-develop-v2.24.0-100-1').size, + f.state.releases.get('upgrade-build-develop-v2.24.0-dev.0-100-1').size, 24, ); }); diff --git a/test/back/upgrade-prestart.test.cjs b/test/back/upgrade-prestart.test.cjs index a6a009f7..853e98d5 100644 --- a/test/back/upgrade-prestart.test.cjs +++ b/test/back/upgrade-prestart.test.cjs @@ -10,8 +10,12 @@ const { repairDependencies, } = require('../../cli/src/internal/maintenance/upgradePrestart.cjs'); -for (const exitCode of [0, 1]) - test(`prestart exit ${exitCode} reaps children retaining output pipes before removing its workspace`, async (t) => { +for (const [version, exitCode] of [ + ['2.23.0', 0], + ['2.23.0', 1], + ['2.24.0-dev.0', 0], +]) + test(`prestart ${version} exit ${exitCode} reaps children retaining output pipes before removing its workspace`, async (t) => { const root = await fs.mkdtemp(path.join(os.tmpdir(), 'ql-probe-exit-')); t.after(() => fs.rm(root, { recursive: true, force: true })); const source = path.join(root, 'source'), @@ -19,7 +23,7 @@ for (const exitCode of [0, 1]) survivor = path.join(root, 'survivor'); await fs.mkdir(path.join(source, 'node_modules'), { recursive: true }); await fs.mkdir(path.join(statics, 'build/bootstrap'), { recursive: true }); - await fs.writeFile(path.join(source, 'version.yaml'), 'version: 2.23.0'); + await fs.writeFile(path.join(source, 'version.yaml'), `version: ${version}`); await fs.writeFile(path.join(statics, 'build/app.js'), 'backend'); await fs.writeFile( path.join(statics, 'build-info.json'), @@ -36,7 +40,7 @@ for (const exitCode of [0, 1]) )}],{stdio:'inherit'}); process.send({type:'upgrade-probe',ok:${ exitCode === 0 - },message:'simulated prestart failure',build:{sourceCommit:'a'.repeat(40),version:'2.23.0',backendSha256:crypto.createHash('sha256').update('backend').digest('hex')}},()=>process.exit(${exitCode})); + },message:'simulated prestart failure',build:{sourceCommit:'a'.repeat(40),version:${JSON.stringify(version)},backendSha256:crypto.createHash('sha256').update('backend').digest('hex')}},()=>process.exit(${exitCode})); `, ); const operation = prestartBackend( diff --git a/version.yaml b/version.yaml index 3f69801a..dfd071a7 100644 --- a/version.yaml +++ b/version.yaml @@ -1,15 +1,9 @@ -version: 2.23.0 -changeLogLink: https://github.com/whyour/qinglong/blob/master/docs/releases/2.23.0.md -publishTime: "2026-10-11 12:27:02" +version: 2.24.0-dev.0 +changeLogLink: https://github.com/whyour/qinglong/blob/develop/docs/releases/2.24.0-dev.0.md +publishTime: "2026-10-25 19:22:54" changeLog: | - 1. 新增脚本历史版本查看、对比与恢复,支持 CLI 和 OpenAPI;恢复前检查当前内容哈希,避免覆盖并发修改 - 2. 普通任务与订阅调度迁移到 node-cron,保留旧规则兼容及日期/星期 OR 语义,补充宏和夏令时说明 - 3. 修复已失效排队任务仍被执行的问题,完善任务进程超时、停止和 Shell 环境传递 - 4. 修复系统令牌主动续期与认证缓存并发更新;Shell API 遇到 401 时刷新令牌并重试一次 - 5. 降低任务启动开销,JS/Python 通知依赖改为首次调用时加载 - 6. 加固环境变量、订阅参数、文件访问和 SSH 配置输入,改善资源冲突与 CLI 错误反馈 - 7. 修复页面升级、ql update 与静态资源版本同步,完善离线备份和升级产物校验 - 8. 修复 Debian 依赖安装、通知与调度回归,补齐前端中英文翻译 - 9. 最小修补 HTTP 压缩、上传、gRPC、代理 IP、WebSocket 和 JSON 查询相关依赖漏洞 - 10. 远程 CLI 更新至 0.1.2,新增脚本历史命令,最低 Node.js 版本仍为 22.12 - 11. 本次为 Python 3.10 兼容镜像的最后一个版本,发布后停止维护;升级前备份完整数据并验证脚本、依赖和通知 + 1. 开发候选 2.24.0-dev.0,尚未正式发布;以 2.23.0 为上一正式版本 + 2. Alpine 与 Debian 镜像统一使用 Node.js 24,升级 sqlite3 至 1.1.4 + 3. Alpine 保留 arm/v6 和 arm/v7;Debian 暂不提供 arm/v7 镜像 + 4. ARM64 升级包在原生 runner 构建,避免 x64 QEMU 下的归档异常 + 5. 升级前备份完整数据,并在目标设备验证脚本、依赖和通知