coda-backend 0.0.0-stage → 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.
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env node
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.
6
+ import { spawn, spawnSync } from 'node:child_process';
7
+ import { existsSync, mkdtempSync, readFileSync, realpathSync, rmSync } from 'node:fs';
8
+ import { homedir, tmpdir } from 'node:os';
9
+ import { dirname, join } from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+
12
+ const script = fileURLToPath(import.meta.url);
13
+ const readJSON = file => JSON.parse(readFileSync(file, 'utf8'));
14
+ const log = message => console.log(`coda-backend: ${message}`);
15
+ function run(file, args) {
16
+ return new Promise((done, reject) => {
17
+ const child = spawn(file, args, { stdio: 'inherit', detached: true });
18
+ child.once('error', reject);
19
+ child.once('close', code => code === 0 ? done() : reject(new Error(`${file} 失败(${code})`)));
20
+ });
21
+ }
22
+
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);
27
+ }
28
+
29
+ export async function runBackend(root, { signals = process, report = log } = {}) {
30
+ let child, stopping = false, deadline;
31
+ const stop = () => {
32
+ stopping = true; child?.kill('SIGTERM');
33
+ deadline ??= setTimeout(() => child?.kill('SIGKILL'), 40_000);
34
+ };
35
+ signals.on('SIGTERM', stop); signals.on('SIGINT', stop);
36
+ try {
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
+ }
50
+ } finally {
51
+ clearTimeout(deadline);
52
+ signals.removeListener('SIGTERM', stop); signals.removeListener('SIGINT', stop);
53
+ }
54
+ }
55
+
56
+ async function main() {
57
+ const directory = dirname(dirname(script)), payload = join(directory, 'payload');
58
+ const version = readFileSync(join(payload, 'version'), 'utf8').trim(), root = join(homedir(), '.local/share/coda');
59
+ const args = process.argv.slice(2), command = args[0] ?? 'run';
60
+ if (command === '--version' || command === 'version') { console.log(version); return; }
61
+ if (!['run', 'install'].includes(command) || args.length > 1) {
62
+ console.log('coda-backend run:在前台运行 Coda 后端,交给本机的进程管理器常驻;在 Coda 客户端里更新。');
63
+ process.exitCode = ['help', '--help'].includes(command) ? 0 : 2; return;
64
+ }
65
+ if (Number(process.versions.node.split('.')[0]) < 24) throw new Error(`需要 Node.js 24 或更新,现在是 ${process.version}`);
66
+ if (spawnSync('git', ['--version'], { stdio: 'ignore' }).status !== 0) throw new Error('需要 Git,请先安装');
67
+ const pkg = readJSON(join(directory, 'package.json'));
68
+ const configFile = join(root, 'installation.json'), existing = existsSync(configFile) ? readJSON(configFile) : null;
69
+ if (existing && (existing.channel ?? 'stable') !== pkg.coda.channel) throw new Error('此后端属于另一个更新通道');
70
+ const installed = existsSync(join(root, 'backend-version')) ? readFileSync(join(root, 'backend-version'), 'utf8').trim() : null;
71
+ // A client may have updated it past this package since; it is never taken back.
72
+ if (newer(version, installed)) {
73
+ log(`安装后端 ${version} 到 ${root}`);
74
+ const stage = mkdtempSync(join(tmpdir(), 'coda-backend-'));
75
+ try {
76
+ await run('tar', ['-xzf', join(payload, 'backend.tar.gz'), '-C', stage]);
77
+ const install = [join(stage, 'scripts/backend-service.mjs'), 'install', '--no-start', '--foreground', '--source', join(stage, 'backend'),
78
+ '--package-version', version, '--channel', pkg.coda.channel];
79
+ const parent = join(payload, `supervisor-${process.platform}-${process.arch}.tar.gz`);
80
+ if (existsSync(parent)) {
81
+ await run('tar', ['-xzf', parent, '-C', stage]);
82
+ install.push('--supervisor', join(stage, 'coda-supervisor'));
83
+ }
84
+ await run(process.execPath, install);
85
+ } finally { rmSync(stage, { recursive: true, force: true }); }
86
+ }
87
+ if (command === 'install') return;
88
+ process.exitCode = await runBackend(root);
89
+ }
90
+
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,24 @@
1
1
  {
2
2
  "name": "coda-backend",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.250-preview",
4
+ "description": "Coda 后端,由本机的进程管理器常驻运行",
5
+ "type": "module",
6
+ "bin": {
7
+ "coda-backend": "bin/coda-backend.mjs"
8
+ },
9
+ "engines": {
10
+ "node": ">=24"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/coda-console/coda.git"
15
+ },
16
+ "coda": {
17
+ "channel": "preview"
18
+ },
19
+ "os": [
20
+ "linux",
21
+ "darwin"
22
+ ],
23
+ "license": "UNLICENSED"
24
+ }
@@ -0,0 +1,4 @@
1
+ [diffend] Oversized file quarantined before diffing.
2
+ name: package/payload/backend.tar.gz
3
+ size: 34674760 bytes
4
+ sha256: c13ef36ea3ec36f8c58e2ff88d5a02d651153a662be08f4dc08bf2e6157c7d29
@@ -0,0 +1 @@
1
+ 20261027090016-b448042e8485-b91d68b6c557
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.