@getopencrawl/cli 0.4.11 → 0.4.13
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/README.md +23 -7
- package/bin/opencrawl.cjs +51 -44
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -55,18 +55,23 @@ launcher version; there are no install scripts.
|
|
|
55
55
|
Before ordinary commands, the launcher checks for updates at most once per day.
|
|
56
56
|
It awaits the update in the invoking process; hooks never check for updates.
|
|
57
57
|
It checks the npm `next` release channel, installs into a private user-owned cache with
|
|
58
|
-
npm integrity verification and install scripts disabled, then atomically
|
|
59
|
-
the complete release.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
npm integrity verification and install scripts disabled, then atomically publishes
|
|
59
|
+
the complete release. Each invocation selects the highest complete cached version
|
|
60
|
+
newer than the npm-installed copy. Concurrent installers keep separate temporary
|
|
61
|
+
stages and reuse a complete same-version winner; a delayed older update cannot
|
|
62
|
+
lower the selected version. No sudo or global-directory writes are needed.
|
|
63
|
+
Offline/update failures preserve the working copy and retry after 15 minutes.
|
|
64
|
+
An unused installation updates when it is next invoked, not on a timer.
|
|
63
65
|
|
|
64
66
|
`OPENCRAWL_NO_UPDATE=1` disables automatic updates for reproducible agent runs.
|
|
65
67
|
`opencrawl update [--json]` explicitly updates from that same channel. Cached releases live under
|
|
66
68
|
`$XDG_CACHE_HOME/opencrawl` (default `~/.cache/opencrawl`); previous releases remain
|
|
67
69
|
available for recovery. Install a newer npm version directly to override an older
|
|
68
|
-
cached version.
|
|
69
|
-
|
|
70
|
+
cached version. `current.json` remains a compatibility hint for older installed
|
|
71
|
+
launchers; removing it does not disable cached releases in current launchers.
|
|
72
|
+
To return to the npm-installed version, stop running CLI processes, set the
|
|
73
|
+
opt-out, and move the cache's `releases` directory outside the cache. Keep it for
|
|
74
|
+
recovery. Standalone binary installation/self-update is not offered in
|
|
70
75
|
this release; distribute the npm command.
|
|
71
76
|
|
|
72
77
|
## Development and release
|
|
@@ -128,6 +133,17 @@ parses permitted transcripts, spools immutable batches and uploads; other hooks
|
|
|
128
133
|
exit after saving their notices. On Unix the shell and Node dispatcher replace
|
|
129
134
|
themselves with the native CLI using exec, keeping the harness-owned PID. Capture
|
|
130
135
|
never detaches a child, starts a service or launches a replacement worker.
|
|
136
|
+
Each uploader has a 90-second deadline, including during outages or lock waits,
|
|
137
|
+
and checks once per second that its original parent is still alive. Parent loss
|
|
138
|
+
also ends the uploader. Unacknowledged batches stay queued for the next hook;
|
|
139
|
+
explicit foreground flush/backfill is not subject to this background deadline.
|
|
140
|
+
Uploader/session ownership uses SQLite transaction locks built into the CLI runtime;
|
|
141
|
+
no database service is installed. The OS releases ownership on process death.
|
|
142
|
+
Numeric PID files remain for diagnostics and conservative compatibility: a live
|
|
143
|
+
or uncertain legacy owner is not evicted, including a potentially reused PID.
|
|
144
|
+
Full mutual exclusion with older clients that ignore the SQLite lock is not promised.
|
|
145
|
+
If the harness itself dies, the host must reap its adopted children, as for any
|
|
146
|
+
process; capture does not install an init service to replace that host duty.
|
|
131
147
|
Foreground flush/backfill waits for upload ownership and confirmed receipts.
|
|
132
148
|
|
|
133
149
|
No systemd, launchd, cron, or installed daemon is required. Reinstall removes
|
package/bin/opencrawl.cjs
CHANGED
|
@@ -46,14 +46,28 @@ function binary(root) {
|
|
|
46
46
|
if (!fs.existsSync(file)) throw new Error('The OpenCrawl platform binary is missing. Reinstall @getopencrawl/cli.');
|
|
47
47
|
return file;
|
|
48
48
|
}
|
|
49
|
+
function releaseRoot(version) {
|
|
50
|
+
return path.join(cacheRoot(), 'releases', version, 'node_modules', '@getopencrawl', 'cli');
|
|
51
|
+
}
|
|
52
|
+
function validRelease(root, version) {
|
|
53
|
+
try {
|
|
54
|
+
const pkg = read(path.join(root, 'package.json'));
|
|
55
|
+
return pkg?.name === PACKAGE && pkg.version === version &&
|
|
56
|
+
fs.statSync(path.join(root, 'bin/opencrawl.cjs')).isFile() && fs.statSync(binary(root)).isFile();
|
|
57
|
+
} catch { return false; }
|
|
58
|
+
}
|
|
49
59
|
function activeRoot(root = ROOT) {
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
const installed = read(path.join(root, 'package.json'))?.version;
|
|
61
|
+
if (!stable(installed)) return root;
|
|
62
|
+
let versions;
|
|
63
|
+
try { versions = fs.readdirSync(path.join(cacheRoot(), 'releases')).filter(stable).sort((a, b) => compare(b, a)); }
|
|
64
|
+
catch { return root; }
|
|
65
|
+
// Complete release directories are immutable publications. The mutable legacy
|
|
66
|
+
// pointer can be stale or overwritten by an older updater; it grants no priority.
|
|
67
|
+
for (const version of versions) {
|
|
68
|
+
if (compare(version, installed) <= 0) break;
|
|
69
|
+
const cached = releaseRoot(version);
|
|
70
|
+
if (validRelease(cached, version)) return cached;
|
|
57
71
|
}
|
|
58
72
|
return root;
|
|
59
73
|
}
|
|
@@ -66,49 +80,37 @@ function npmCli(nodeExecutable = process.execPath) {
|
|
|
66
80
|
}
|
|
67
81
|
throw new Error('npm is required for updates. Install Node.js with npm and retry.');
|
|
68
82
|
}
|
|
69
|
-
function acquireUpdateLock() {
|
|
70
|
-
const lock = path.join(cacheRoot(), 'update.lock');
|
|
71
|
-
fs.mkdirSync(cacheRoot(), {recursive: true, mode: 0o700});
|
|
72
|
-
try { const fd = fs.openSync(lock, 'wx', 0o600); fs.writeFileSync(fd, String(process.pid)); fs.closeSync(fd); }
|
|
73
|
-
catch (error) {
|
|
74
|
-
if (error.code !== 'EEXIST') throw error;
|
|
75
|
-
const pid = Number(fs.readFileSync(lock, 'utf8'));
|
|
76
|
-
if (pid > 0) {
|
|
77
|
-
try { process.kill(pid, 0); return null; }
|
|
78
|
-
catch (check) { if (check.code !== 'ESRCH') return null; }
|
|
79
|
-
} else if (Date.now() - fs.statSync(lock).mtimeMs < 5 * 60_000) return null;
|
|
80
|
-
try { fs.unlinkSync(lock); } catch {}
|
|
81
|
-
return acquireUpdateLock();
|
|
82
|
-
}
|
|
83
|
-
return () => fs.unlinkSync(lock);
|
|
84
|
-
}
|
|
85
83
|
async function update({force = false, root = ROOT, fetcher = fetch, install = installRelease} = {}) {
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
84
|
+
const stateFile = path.join(cacheRoot(), 'update.json');
|
|
85
|
+
const state = read(stateFile);
|
|
86
|
+
const current = read(path.join(activeRoot(root), 'package.json')).version;
|
|
87
|
+
if (!force && state?.checkedAt > Date.now() - DAY) return {updated: false, version: current};
|
|
88
|
+
const response = await fetcher(`${REGISTRY}/@getopencrawl%2fcli/${CHANNEL}`, {redirect: 'error', signal: AbortSignal.timeout(10_000)});
|
|
89
|
+
if (!response.ok) throw new Error('The npm registry could not check for updates.');
|
|
90
|
+
const latest = await response.json();
|
|
91
|
+
if (latest.name !== PACKAGE || !stable(latest.version)) throw new Error('The registry returned invalid release metadata.');
|
|
92
|
+
if (compare(latest.version, current) <= 0) { write(stateFile, {checkedAt: Date.now()}); return {updated: false, version: current}; }
|
|
93
|
+
await install(latest.version);
|
|
94
|
+
if (!validRelease(releaseRoot(latest.version), latest.version)) throw new Error('The installed update is incomplete.');
|
|
95
|
+
const selected = read(path.join(activeRoot(root), 'package.json')).version;
|
|
96
|
+
// Keep old npm-installed bootstraps able to delegate to the cached launcher.
|
|
97
|
+
// New launchers select the maximum complete release, even if this hint races.
|
|
98
|
+
write(path.join(cacheRoot(), 'current.json'), {version: selected});
|
|
99
|
+
write(stateFile, {checkedAt: Date.now()});
|
|
100
|
+
return {updated: compare(selected, current) > 0, version: selected};
|
|
103
101
|
}
|
|
104
102
|
async function installRelease(version, {registry = REGISTRY} = {}) {
|
|
103
|
+
if (!stable(version)) throw new Error('Invalid release version.');
|
|
105
104
|
const origin = new URL(registry);
|
|
106
105
|
if (registry !== REGISTRY && !(origin.protocol === 'http:' && ['127.0.0.1', 'localhost'].includes(origin.hostname))) throw new Error('Invalid release registry.');
|
|
107
106
|
const releases = path.join(cacheRoot(), 'releases');
|
|
108
107
|
fs.mkdirSync(releases, {recursive: true, mode: 0o700});
|
|
109
108
|
const destination = path.join(releases, version);
|
|
110
109
|
// Reuse a fully staged version if pointer publication was interrupted.
|
|
111
|
-
if (fs.existsSync(destination)) {
|
|
110
|
+
if (fs.existsSync(destination)) {
|
|
111
|
+
if (!validRelease(releaseRoot(version), version)) throw new Error('The cached update is incomplete.');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
112
114
|
const stage = fs.mkdtempSync(path.join(releases, '.stage-'));
|
|
113
115
|
try {
|
|
114
116
|
fs.writeFileSync(path.join(stage, 'package.json'), '{"private":true}\n');
|
|
@@ -119,8 +121,13 @@ async function installRelease(version, {registry = REGISTRY} = {}) {
|
|
|
119
121
|
const installed = path.join(stage, 'node_modules/@getopencrawl/cli');
|
|
120
122
|
const pkg = read(path.join(installed, 'package.json'));
|
|
121
123
|
if (pkg?.name !== PACKAGE || pkg.version !== version) throw new Error('The installed release does not match the requested version.');
|
|
122
|
-
|
|
123
|
-
fs.renameSync(stage, destination);
|
|
124
|
+
if (!validRelease(installed, version)) throw new Error('The installed update is incomplete.');
|
|
125
|
+
try { fs.renameSync(stage, destination); }
|
|
126
|
+
catch (error) {
|
|
127
|
+
// Another installer may have published this exact immutable release while
|
|
128
|
+
// npm was running. Validate its complete result and discard only our stage.
|
|
129
|
+
if (!validRelease(releaseRoot(version), version)) throw error;
|
|
130
|
+
}
|
|
124
131
|
} finally { fs.rmSync(stage, {recursive: true, force: true}); }
|
|
125
132
|
}
|
|
126
133
|
async function automaticUpdate() {
|
|
@@ -145,7 +152,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
145
152
|
catch {} // The native parser produces the public, versioned usage error.
|
|
146
153
|
if (parsed?.positionals.length === 1 && parsed.positionals[0] === 'update' && !parsed.values.help && !parsed.values.version) {
|
|
147
154
|
const data = await update({force: true});
|
|
148
|
-
process.stdout.write(args.includes('--json') ? JSON.stringify({schemaVersion: 1, data}) + '\n' : data.
|
|
155
|
+
process.stdout.write(args.includes('--json') ? JSON.stringify({schemaVersion: 1, data}) + '\n' : data.updated ? `Installed opencrawl ${data.version}. Your next invocation will use it.\n` : `opencrawl ${data.version} is current.\n`);
|
|
149
156
|
return 0;
|
|
150
157
|
}
|
|
151
158
|
if (!(args[0] === 'collect' && ['hook','worker'].includes(args[1]))) await automaticUpdate();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getopencrawl/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.13",
|
|
4
4
|
"description": "OpenCrawl CLI: sign in now, query the index as features become available",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -24,10 +24,10 @@
|
|
|
24
24
|
"registry": "https://registry.npmjs.org"
|
|
25
25
|
},
|
|
26
26
|
"optionalDependencies": {
|
|
27
|
-
"@getopencrawl/cli-darwin-arm64": "0.4.
|
|
28
|
-
"@getopencrawl/cli-darwin-x64": "0.4.
|
|
29
|
-
"@getopencrawl/cli-linux-arm64": "0.4.
|
|
30
|
-
"@getopencrawl/cli-linux-x64": "0.4.
|
|
31
|
-
"@getopencrawl/cli-win32-x64": "0.4.
|
|
27
|
+
"@getopencrawl/cli-darwin-arm64": "0.4.13",
|
|
28
|
+
"@getopencrawl/cli-darwin-x64": "0.4.13",
|
|
29
|
+
"@getopencrawl/cli-linux-arm64": "0.4.13",
|
|
30
|
+
"@getopencrawl/cli-linux-x64": "0.4.13",
|
|
31
|
+
"@getopencrawl/cli-win32-x64": "0.4.13"
|
|
32
32
|
}
|
|
33
33
|
}
|