coda-backend 0.2.246 → 0.2.250-preview
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/coda-backend.mjs
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// npm owns the package; the standard installer owns the backend. This foreground launcher
|
|
3
|
-
//
|
|
2
|
+
// npm owns the package; the standard installer owns the backend. This foreground launcher installs the package's backend
|
|
3
|
+
// when none is installed or the package's is newer, and keeps it running for the machine's process manager. Updates come
|
|
4
|
+
// from Coda's clients (docs/deployment.md「用 npm 自己部署」): the parent process switches releases in place, or the
|
|
5
|
+
// installer stops the backend and this starts the release it recorded.
|
|
4
6
|
import { spawn, spawnSync } from 'node:child_process';
|
|
5
7
|
import { existsSync, mkdtempSync, readFileSync, realpathSync, rmSync } from 'node:fs';
|
|
6
8
|
import { homedir, tmpdir } from 'node:os';
|
|
7
|
-
import { dirname, join
|
|
9
|
+
import { dirname, join } from 'node:path';
|
|
8
10
|
import { fileURLToPath } from 'node:url';
|
|
9
11
|
|
|
10
12
|
const script = fileURLToPath(import.meta.url);
|
|
@@ -18,60 +20,36 @@ function run(file, args) {
|
|
|
18
20
|
});
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
headers: { 'User-Agent': 'coda-backend', Accept: 'application/vnd.github+json' },
|
|
26
|
-
});
|
|
27
|
-
if (!response.ok) throw new Error(`检查更新失败:HTTP ${response.status}`);
|
|
28
|
-
const release = await response.json();
|
|
29
|
-
const offered = (channel === 'preview' ? /^v(\d+\.\d+\.\d+)-preview$/ : /^v(\d+\.\d+\.\d+)$/).exec(release.tag_name)?.[1];
|
|
30
|
-
if (release.draft || release.prerelease || !offered) throw new Error('发布不属于本后端的更新通道');
|
|
31
|
-
const current = pkg.version.replace(/-preview$/, '').split('.').map(Number);
|
|
32
|
-
if (!(offered.split('.').map((part, i) => Number(part) - current[i]).find(value => value !== 0) > 0)) return false;
|
|
33
|
-
const url = `https://github.com/${repository}/releases/download/${release.tag_name}/coda-backend.tgz`;
|
|
34
|
-
if (!release.assets?.some(asset => asset.name === 'coda-backend.tgz' && asset.browser_download_url === url))
|
|
35
|
-
throw new Error('发布没有 npm 安装包');
|
|
36
|
-
// Keep the same global prefix even if the process manager's npm config has changed.
|
|
37
|
-
const prefix = resolve(directory, '../../..');
|
|
38
|
-
if (join(prefix, 'lib/node_modules/coda-backend') !== directory) throw new Error('自动更新需要通过 npm install -g 安装');
|
|
39
|
-
log(`更新到 ${release.tag_name}`);
|
|
40
|
-
await execute('npm', ['install', '--global', '--prefix', prefix, '--no-audit', '--no-fund', url]);
|
|
41
|
-
signal?.throwIfAborted();
|
|
42
|
-
// Prepare dependencies while the current backend still serves. Let the installer finish its own cleanup on stop.
|
|
43
|
-
await execute(process.execPath, [join(directory, 'bin/coda-backend.mjs'), 'install']);
|
|
44
|
-
return true;
|
|
23
|
+
// A backend version starts with its build's UTC commit time (docs/deployment.md「客户端更新后更新后端」); one without is older.
|
|
24
|
+
export function newer(offered, installed) {
|
|
25
|
+
const time = version => /^(\d{14})-/.exec(version ?? '')?.[1] ?? '';
|
|
26
|
+
return !installed || time(offered) > time(installed);
|
|
45
27
|
}
|
|
46
28
|
|
|
47
|
-
export async function runBackend(root, {
|
|
48
|
-
|
|
49
|
-
const config = readJSON(join(root, 'installation.json'));
|
|
50
|
-
const child = spawn(config.supervisor ?? config.node, config.supervisor ? ['--root', root]
|
|
51
|
-
: [join(root, 'backend-service.mjs'), 'run', '--root', root], { stdio: 'inherit', detached: true });
|
|
52
|
-
const controller = new AbortController();
|
|
53
|
-
let timer, deadline, restart = false;
|
|
29
|
+
export async function runBackend(root, { signals = process, report = log } = {}) {
|
|
30
|
+
let child, stopping = false, deadline;
|
|
54
31
|
const stop = () => {
|
|
55
|
-
|
|
56
|
-
deadline ??= setTimeout(() => child
|
|
57
|
-
};
|
|
58
|
-
const shutdown = () => { restart = false; stop(); };
|
|
59
|
-
const poll = async () => {
|
|
60
|
-
try {
|
|
61
|
-
if (await check({ signal: controller.signal }) && !controller.signal.aborted) { restart = true; stop(); }
|
|
62
|
-
} catch (error) { if (!controller.signal.aborted) report(`自动更新未完成:${error.message};下次继续检查`); }
|
|
63
|
-
if (!controller.signal.aborted) timer = setTimeout(poll, interval);
|
|
32
|
+
stopping = true; child?.kill('SIGTERM');
|
|
33
|
+
deadline ??= setTimeout(() => child?.kill('SIGKILL'), 40_000);
|
|
64
34
|
};
|
|
65
|
-
signals.on('SIGTERM',
|
|
66
|
-
if (autoUpdate) timer = setTimeout(poll, firstCheck);
|
|
35
|
+
signals.on('SIGTERM', stop); signals.on('SIGINT', stop);
|
|
67
36
|
try {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
37
|
+
for (;;) {
|
|
38
|
+
const started = readJSON(join(root, 'installation.json'));
|
|
39
|
+
child = spawn(started.supervisor ?? started.node, started.supervisor ? ['--root', root]
|
|
40
|
+
: [join(root, 'backend-service.mjs'), 'run', '--root', root], { stdio: 'inherit', detached: true });
|
|
41
|
+
const code = await new Promise((done, reject) => {
|
|
42
|
+
child.once('error', reject); child.once('close', code => done(code ?? 1));
|
|
43
|
+
});
|
|
44
|
+
if (stopping) return code;
|
|
45
|
+
// The installer stopped it for the release it installed; anything else ends here, for the process manager.
|
|
46
|
+
const now = readJSON(join(root, 'installation.json'));
|
|
47
|
+
if (now.backend === started.backend && now.supervisor === started.supervisor) return code;
|
|
48
|
+
report('后端已更新,启动新版本');
|
|
49
|
+
}
|
|
72
50
|
} finally {
|
|
73
|
-
|
|
74
|
-
signals.removeListener('SIGTERM',
|
|
51
|
+
clearTimeout(deadline);
|
|
52
|
+
signals.removeListener('SIGTERM', stop); signals.removeListener('SIGINT', stop);
|
|
75
53
|
}
|
|
76
54
|
}
|
|
77
55
|
|
|
@@ -80,23 +58,23 @@ async function main() {
|
|
|
80
58
|
const version = readFileSync(join(payload, 'version'), 'utf8').trim(), root = join(homedir(), '.local/share/coda');
|
|
81
59
|
const args = process.argv.slice(2), command = args[0] ?? 'run';
|
|
82
60
|
if (command === '--version' || command === 'version') { console.log(version); return; }
|
|
83
|
-
if (!['run', 'install'].includes(command) || args.
|
|
84
|
-
console.log('coda-backend run
|
|
61
|
+
if (!['run', 'install'].includes(command) || args.length > 1) {
|
|
62
|
+
console.log('coda-backend run:在前台运行 Coda 后端,交给本机的进程管理器常驻;在 Coda 客户端里更新。');
|
|
85
63
|
process.exitCode = ['help', '--help'].includes(command) ? 0 : 2; return;
|
|
86
64
|
}
|
|
87
65
|
if (Number(process.versions.node.split('.')[0]) < 24) throw new Error(`需要 Node.js 24 或更新,现在是 ${process.version}`);
|
|
88
66
|
if (spawnSync('git', ['--version'], { stdio: 'ignore' }).status !== 0) throw new Error('需要 Git,请先安装');
|
|
89
67
|
const pkg = readJSON(join(directory, 'package.json'));
|
|
90
68
|
const configFile = join(root, 'installation.json'), existing = existsSync(configFile) ? readJSON(configFile) : null;
|
|
91
|
-
if (existing?.manager === 'coda-app') throw new Error('此后端由 Coda App 管理,请从该 App 更新');
|
|
92
69
|
if (existing && (existing.channel ?? 'stable') !== pkg.coda.channel) throw new Error('此后端属于另一个更新通道');
|
|
93
70
|
const installed = existsSync(join(root, 'backend-version')) ? readFileSync(join(root, 'backend-version'), 'utf8').trim() : null;
|
|
94
|
-
|
|
71
|
+
// A client may have updated it past this package since; it is never taken back.
|
|
72
|
+
if (newer(version, installed)) {
|
|
95
73
|
log(`安装后端 ${version} 到 ${root}`);
|
|
96
74
|
const stage = mkdtempSync(join(tmpdir(), 'coda-backend-'));
|
|
97
75
|
try {
|
|
98
76
|
await run('tar', ['-xzf', join(payload, 'backend.tar.gz'), '-C', stage]);
|
|
99
|
-
const install = [join(stage, 'scripts/backend-service.mjs'), 'install', '--no-start', '--source', join(stage, 'backend'),
|
|
77
|
+
const install = [join(stage, 'scripts/backend-service.mjs'), 'install', '--no-start', '--foreground', '--source', join(stage, 'backend'),
|
|
100
78
|
'--package-version', version, '--channel', pkg.coda.channel];
|
|
101
79
|
const parent = join(payload, `supervisor-${process.platform}-${process.arch}.tar.gz`);
|
|
102
80
|
if (existsSync(parent)) {
|
|
@@ -107,12 +85,7 @@ async function main() {
|
|
|
107
85
|
} finally { rmSync(stage, { recursive: true, force: true }); }
|
|
108
86
|
}
|
|
109
87
|
if (command === 'install') return;
|
|
110
|
-
|
|
111
|
-
autoUpdate: !args.includes('--no-auto-update') && process.env.CODA_NPM_AUTO_UPDATE !== '0',
|
|
112
|
-
check: options => checkForUpdate(directory, pkg, options),
|
|
113
|
-
});
|
|
114
|
-
if (result.restart) process.execve(process.execPath, [process.execPath, script, ...args], process.env);
|
|
115
|
-
process.exitCode = result.code;
|
|
88
|
+
process.exitCode = await runBackend(root);
|
|
116
89
|
}
|
|
117
90
|
|
|
118
91
|
if (process.argv[1] && realpathSync(process.argv[1]) === script) main().catch(error => { console.error(`coda-backend: ${error.message}`); process.exitCode = 1; });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coda-backend",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.250-preview",
|
|
4
4
|
"description": "Coda 后端,由本机的进程管理器常驻运行",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,9 +9,12 @@
|
|
|
9
9
|
"engines": {
|
|
10
10
|
"node": ">=24"
|
|
11
11
|
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/coda-console/coda.git"
|
|
15
|
+
},
|
|
12
16
|
"coda": {
|
|
13
|
-
"
|
|
14
|
-
"channel": "stable"
|
|
17
|
+
"channel": "preview"
|
|
15
18
|
},
|
|
16
19
|
"os": [
|
|
17
20
|
"linux",
|
package/payload/backend.tar.gz
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/payload/version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
20261027090016-b448042e8485-b91d68b6c557
|