Compare commits

...
Author SHA1 Message Date
whyour ad29b7aa73 build: mark develop platform bundles as 2.24.0-dev.0 2026-10-25 19:26:23 +08:00
9 changed files with 78 additions and 31 deletions
@@ -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;
}
@@ -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 = {
+11
View File
@@ -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 下归档时发生异常。
升级前请备份完整持久化数据,并在目标设备验证脚本、依赖和通知。正式发布说明将在候选验收后整理。
+1 -1
View File
@@ -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",
+5 -2
View File
@@ -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(
+22 -4
View File
@@ -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');
+17 -4
View File
@@ -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,
);
});
+8 -4
View File
@@ -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(
+8 -14
View File
@@ -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. 升级前备份完整数据,并在目标设备验证脚本、依赖和通知