@getopencrawl/cli 0.4.12 → 0.4.14
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 +12 -7
- package/bin/opencrawl.cjs +51 -44
- package/package.json +7 -7
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
|
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.14",
|
|
4
4
|
"description": "OpenCrawl CLI: sign in now, query the index as features become available",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
],
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|
|
16
|
-
"url": "https://github.com/
|
|
16
|
+
"url": "https://github.com/getopencrawl/opencrawl-core.git",
|
|
17
17
|
"directory": "cli"
|
|
18
18
|
},
|
|
19
19
|
"engines": {
|
|
@@ -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.14",
|
|
28
|
+
"@getopencrawl/cli-darwin-x64": "0.4.14",
|
|
29
|
+
"@getopencrawl/cli-linux-arm64": "0.4.14",
|
|
30
|
+
"@getopencrawl/cli-linux-x64": "0.4.14",
|
|
31
|
+
"@getopencrawl/cli-win32-x64": "0.4.14"
|
|
32
32
|
}
|
|
33
33
|
}
|