primo-cli 0.1.12 → 0.1.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/dist/utils/binary.js +55 -12
- package/package.json +1 -1
package/dist/utils/binary.js
CHANGED
|
@@ -9,7 +9,7 @@ import ora from 'ora';
|
|
|
9
9
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
10
|
const PRIMO_HOME = path.join(os.homedir(), '.primo');
|
|
11
11
|
const BIN_DIR = path.join(PRIMO_HOME, 'bin');
|
|
12
|
-
const VERSION = '3.2.
|
|
12
|
+
const VERSION = '3.2.2'; // matches primo releases
|
|
13
13
|
// Path to locally built binary (for development)
|
|
14
14
|
// The binary is at primo/primo (inside the primo repo directory)
|
|
15
15
|
const LOCAL_BINARY = path.resolve(__dirname, '..', '..', '..', 'primo', 'primo');
|
|
@@ -50,6 +50,16 @@ function get_download_url(platform) {
|
|
|
50
50
|
const filename = `primo_${platform.os}_${platform.arch}${platform.ext}`;
|
|
51
51
|
return `${base}/v${VERSION}/${filename}`;
|
|
52
52
|
}
|
|
53
|
+
// Extract a bare semver (e.g. "3.2.1") from a binary's --version output.
|
|
54
|
+
// The server prints a build banner first, then "<name> version vX.Y.Z", so we
|
|
55
|
+
// scan for the semver rather than trusting the whole string. Returns null when
|
|
56
|
+
// no semver is present (e.g. a "dev" build), which callers treat as a mismatch.
|
|
57
|
+
function parse_semver(output) {
|
|
58
|
+
if (!output)
|
|
59
|
+
return null;
|
|
60
|
+
const match = output.match(/\bv?(\d+\.\d+\.\d+)\b/);
|
|
61
|
+
return match ? match[1] : null;
|
|
62
|
+
}
|
|
53
63
|
export async function get_binary_path() {
|
|
54
64
|
// Explicit override wins — layout-independent, the way to point at a
|
|
55
65
|
// local server build regardless of where this CLI lives on disk.
|
|
@@ -95,29 +105,50 @@ export async function ensure_binary() {
|
|
|
95
105
|
if (process.env.PRIMO_BINARY) {
|
|
96
106
|
return await get_binary_path();
|
|
97
107
|
}
|
|
108
|
+
// A sibling dev build (running from source next to a `primo` checkout) is
|
|
109
|
+
// developer-chosen — never replace it with a download, even if its version
|
|
110
|
+
// differs from the pinned release.
|
|
111
|
+
try {
|
|
112
|
+
await fs.access(LOCAL_BINARY, fs.constants.X_OK);
|
|
113
|
+
return LOCAL_BINARY;
|
|
114
|
+
}
|
|
115
|
+
catch { }
|
|
116
|
+
// A managed binary already on disk is reused only when it matches the pinned
|
|
117
|
+
// version. A stale binary (older release, or a pre-rename "palacms" build
|
|
118
|
+
// reporting a different version) is re-downloaded so fixes actually reach
|
|
119
|
+
// users who already have a binary installed.
|
|
120
|
+
let updating_from = null;
|
|
98
121
|
if (await is_binary_installed()) {
|
|
99
|
-
|
|
122
|
+
if (await is_binary_current()) {
|
|
123
|
+
return await get_binary_path();
|
|
124
|
+
}
|
|
125
|
+
updating_from = await get_binary_version();
|
|
100
126
|
}
|
|
101
127
|
// Need to download - get the target path
|
|
102
128
|
const platform = get_platform();
|
|
103
129
|
const binary_path = path.join(BIN_DIR, `primo${platform.ext}`);
|
|
104
|
-
const spinner = ora(
|
|
130
|
+
const spinner = ora(updating_from
|
|
131
|
+
? `Updating primo ${updating_from} → ${VERSION}...`
|
|
132
|
+
: 'Setting up Primo...').start();
|
|
105
133
|
try {
|
|
106
134
|
// Create directories
|
|
107
135
|
await fs.mkdir(BIN_DIR, { recursive: true });
|
|
108
136
|
const url = get_download_url(platform);
|
|
109
137
|
spinner.text = `Downloading primo for ${platform.os}/${platform.arch}...`;
|
|
110
|
-
// Download binary
|
|
138
|
+
// Download binary. Stream to a temp path and rename into place so an
|
|
139
|
+
// interrupted download can't leave a half-written binary that later
|
|
140
|
+
// looks "installed". rename() is atomic within the same directory.
|
|
111
141
|
const response = await fetch(url);
|
|
112
142
|
if (!response.ok) {
|
|
113
143
|
throw new Error(`Download failed: ${response.status} ${response.statusText}`);
|
|
114
144
|
}
|
|
115
|
-
|
|
116
|
-
const file_stream = createWriteStream(
|
|
145
|
+
const tmp_path = `${binary_path}.download`;
|
|
146
|
+
const file_stream = createWriteStream(tmp_path);
|
|
117
147
|
await pipeline(response.body, file_stream);
|
|
118
|
-
// Make executable
|
|
119
|
-
await fs.chmod(
|
|
120
|
-
|
|
148
|
+
// Make executable, then atomically replace any existing binary.
|
|
149
|
+
await fs.chmod(tmp_path, 0o755);
|
|
150
|
+
await fs.rename(tmp_path, binary_path);
|
|
151
|
+
spinner.succeed(updating_from ? `Primo updated to ${VERSION}` : 'Primo setup complete');
|
|
121
152
|
return binary_path;
|
|
122
153
|
}
|
|
123
154
|
catch (error) {
|
|
@@ -132,14 +163,26 @@ export async function ensure_binary() {
|
|
|
132
163
|
throw error;
|
|
133
164
|
}
|
|
134
165
|
}
|
|
166
|
+
// Return the installed binary's semver (e.g. "3.2.1"), or null if it can't be
|
|
167
|
+
// determined (missing binary, dev build, or unparseable output).
|
|
135
168
|
export async function get_binary_version() {
|
|
136
169
|
try {
|
|
137
170
|
const binary_path = await get_binary_path();
|
|
138
|
-
const {
|
|
139
|
-
|
|
140
|
-
|
|
171
|
+
const { execFileSync } = await import('child_process');
|
|
172
|
+
// execFile (not execSync) avoids shell quoting issues with the path, and
|
|
173
|
+
// the timeout prevents a wedged binary from hanging CLI startup.
|
|
174
|
+
const output = execFileSync(binary_path, ['--version'], { encoding: 'utf-8', timeout: 5000 });
|
|
175
|
+
return parse_semver(output);
|
|
141
176
|
}
|
|
142
177
|
catch {
|
|
143
178
|
return null;
|
|
144
179
|
}
|
|
145
180
|
}
|
|
181
|
+
// Whether the installed binary matches the version this CLI pins. A dev/unknown
|
|
182
|
+
// version (null) counts as not-current so we refresh to a known-good release.
|
|
183
|
+
// Skipped when PRIMO_BINARY or a sibling dev build is in use — those are
|
|
184
|
+
// developer-chosen and must not be clobbered by a download.
|
|
185
|
+
async function is_binary_current() {
|
|
186
|
+
const installed = await get_binary_version();
|
|
187
|
+
return installed === VERSION;
|
|
188
|
+
}
|