@jsorm/cli 0.1.0 → 0.1.3
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/index.mjs +225 -206
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -1,28 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { chmodSync, createReadStream, createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
3
|
+
import { chmodSync, createReadStream, createWriteStream, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, statSync } from "node:fs";
|
|
5
4
|
import { dirname, join } from "node:path";
|
|
6
5
|
import { homedir } from "node:os";
|
|
7
6
|
import { fileURLToPath } from "node:url";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const ENGINES_DIR = join(homedir(), ".jsorm", "engines");
|
|
13
|
-
const DOWNLOAD_BASE = "https://download.jsorm.dev";
|
|
14
|
-
const STABLE_MANIFEST_URL = `${DOWNLOAD_BASE}/engine/stable/manifest`;
|
|
15
|
-
const STABLE_DOWNLOAD_URL = `${DOWNLOAD_BASE}/engine/download`;
|
|
16
|
-
const EMBEDDED_PUBLIC_KEY_B64 = "pFJ+WOzwsMPbZyvF/bMPB0UNCvuosqFN7Hvjd/qmP78=";
|
|
17
|
-
function getSpkiPublicKey() {
|
|
18
|
-
const rawKey = Buffer.from(EMBEDDED_PUBLIC_KEY_B64, "base64");
|
|
19
|
-
const spkiDer = Buffer.concat([Buffer.from("302a300506032b6570032100", "hex"), rawKey]);
|
|
20
|
-
return createPublicKey({
|
|
21
|
-
key: spkiDer,
|
|
22
|
-
format: "der",
|
|
23
|
-
type: "spki"
|
|
24
|
-
});
|
|
25
|
-
}
|
|
7
|
+
import { createHash, createPublicKey, verify } from "node:crypto";
|
|
8
|
+
import { Readable } from "node:stream";
|
|
9
|
+
import { pipeline } from "node:stream/promises";
|
|
10
|
+
//#region src/platform.ts
|
|
26
11
|
function detectPlatform() {
|
|
27
12
|
switch (process.platform) {
|
|
28
13
|
case "linux": return "linux";
|
|
@@ -38,6 +23,10 @@ function detectArch() {
|
|
|
38
23
|
default: throw new Error(`Unsupported architecture: ${process.arch}`);
|
|
39
24
|
}
|
|
40
25
|
}
|
|
26
|
+
function getEngineBinaryName() {
|
|
27
|
+
const platform = detectPlatform();
|
|
28
|
+
return `jsorm-${platform}-${detectArch()}${platform === "windows" ? ".exe" : ""}`;
|
|
29
|
+
}
|
|
41
30
|
function detectPackageManager() {
|
|
42
31
|
let dir = process.cwd();
|
|
43
32
|
for (let i = 0; i < 10; i++) {
|
|
@@ -95,37 +84,78 @@ function detectFramework() {
|
|
|
95
84
|
}
|
|
96
85
|
return "none";
|
|
97
86
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/manifest.ts
|
|
89
|
+
const EMBEDDED_PUBLIC_KEY_B64 = "6AUQMvF7jBV+S8yv9K8hnnoYzIznQw4nf5x5LAuEsfM=";
|
|
90
|
+
function getSpkiPublicKey(rawKeyB64 = EMBEDDED_PUBLIC_KEY_B64) {
|
|
91
|
+
const rawKey = Buffer.from(rawKeyB64, "base64");
|
|
92
|
+
const spkiDer = Buffer.concat([Buffer.from("302a300506032b6570032100", "hex"), rawKey]);
|
|
93
|
+
return createPublicKey({
|
|
94
|
+
key: spkiDer,
|
|
95
|
+
format: "der",
|
|
96
|
+
type: "spki"
|
|
97
|
+
});
|
|
108
98
|
}
|
|
109
|
-
function
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
99
|
+
function invalid(reason) {
|
|
100
|
+
return {
|
|
101
|
+
status: "invalid",
|
|
102
|
+
reason
|
|
103
|
+
};
|
|
113
104
|
}
|
|
114
|
-
|
|
105
|
+
/**
|
|
106
|
+
* Verifies the envelope `{ success, signature, signed_payload }` and returns
|
|
107
|
+
* the manifest decoded from `signed_payload`, the exact bytes that were signed.
|
|
108
|
+
* The unsigned `envelope.manifest` copy is never trusted.
|
|
109
|
+
*/
|
|
110
|
+
function parseSignedEnvelope(envelope, publicKey) {
|
|
111
|
+
if (!envelope || typeof envelope !== "object") return invalid("Invalid manifest response");
|
|
112
|
+
const { success, signature, signed_payload } = envelope;
|
|
113
|
+
if (success !== true) return invalid("Invalid manifest response");
|
|
114
|
+
if (typeof signature !== "string" || typeof signed_payload !== "string") return invalid("Manifest signature or signed payload missing. Refusing to continue without signature verification.");
|
|
115
|
+
const sigBytes = Buffer.from(signature, "base64");
|
|
116
|
+
if (sigBytes.length !== 64) return invalid(`Invalid signature length: ${sigBytes.length} (expected 64)`);
|
|
117
|
+
const signedBytes = Buffer.from(signed_payload, "base64");
|
|
118
|
+
let valid = false;
|
|
119
|
+
try {
|
|
120
|
+
valid = verify(null, signedBytes, publicKey, sigBytes);
|
|
121
|
+
} catch {}
|
|
122
|
+
if (!valid) return invalid("Manifest signature verification failed. The manifest may have been tampered with.");
|
|
123
|
+
let manifest;
|
|
115
124
|
try {
|
|
116
|
-
|
|
117
|
-
return verify(null, data, publicKey, signature);
|
|
125
|
+
manifest = JSON.parse(signedBytes.toString("utf-8"));
|
|
118
126
|
} catch {
|
|
119
|
-
return
|
|
127
|
+
return invalid("Signed manifest is not valid JSON");
|
|
120
128
|
}
|
|
129
|
+
if (typeof manifest?.version !== "string" || !manifest.targets || typeof manifest.targets !== "object") return invalid("Invalid manifest format");
|
|
130
|
+
return {
|
|
131
|
+
status: "ok",
|
|
132
|
+
manifest
|
|
133
|
+
};
|
|
121
134
|
}
|
|
122
|
-
function
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
135
|
+
async function fetchManifest(url, publicKey = getSpkiPublicKey()) {
|
|
136
|
+
let res;
|
|
137
|
+
try {
|
|
138
|
+
res = await fetch(url, { signal: AbortSignal.timeout(1e4) });
|
|
139
|
+
} catch (err) {
|
|
140
|
+
return {
|
|
141
|
+
status: "unreachable",
|
|
142
|
+
reason: `Manifest fetch error: ${err instanceof Error ? err.message : err}`
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
if (!res.ok) return {
|
|
146
|
+
status: "unreachable",
|
|
147
|
+
reason: `Manifest fetch failed: ${res.status} ${res.statusText}`
|
|
148
|
+
};
|
|
149
|
+
let envelope;
|
|
150
|
+
try {
|
|
151
|
+
envelope = await res.json();
|
|
152
|
+
} catch {
|
|
153
|
+
return invalid("Invalid manifest response");
|
|
126
154
|
}
|
|
127
|
-
return
|
|
155
|
+
return parseSignedEnvelope(envelope, publicKey);
|
|
128
156
|
}
|
|
157
|
+
//#endregion
|
|
158
|
+
//#region src/install.ts
|
|
129
159
|
function compareVersions(a, b) {
|
|
130
160
|
const pa = a.split(".").map(Number);
|
|
131
161
|
const pb = b.split(".").map(Number);
|
|
@@ -137,148 +167,162 @@ function compareVersions(a, b) {
|
|
|
137
167
|
}
|
|
138
168
|
return 0;
|
|
139
169
|
}
|
|
140
|
-
function
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (existsSync(binPath)) return dir.slice(1);
|
|
149
|
-
}
|
|
170
|
+
function getVersionDir(enginesDir, version) {
|
|
171
|
+
return join(enginesDir, `v${version}`);
|
|
172
|
+
}
|
|
173
|
+
/** Newest installed version that holds `binaryName`, e.g. "0.1.3". */
|
|
174
|
+
function detectInstalledVersion(enginesDir, binaryName) {
|
|
175
|
+
if (!existsSync(enginesDir)) return null;
|
|
176
|
+
const dirs = readdirSync(enginesDir).filter((d) => d.startsWith("v")).sort((a, b) => compareVersions(b.slice(1), a.slice(1)));
|
|
177
|
+
for (const dir of dirs) if (existsSync(join(enginesDir, dir, binaryName))) return dir.slice(1);
|
|
150
178
|
return null;
|
|
151
179
|
}
|
|
152
|
-
function
|
|
153
|
-
|
|
180
|
+
async function sha256File(filePath) {
|
|
181
|
+
const hash = createHash("sha256");
|
|
182
|
+
await pipeline(createReadStream(filePath), hash);
|
|
183
|
+
return hash.digest("hex");
|
|
184
|
+
}
|
|
185
|
+
async function downloadToFile(url, destPath) {
|
|
186
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(6e4) });
|
|
187
|
+
if (!res.ok || !res.body) throw new Error(`Download failed: ${res.status} ${res.statusText}`);
|
|
188
|
+
await pipeline(Readable.fromWeb(res.body), createWriteStream(destPath));
|
|
154
189
|
}
|
|
155
|
-
|
|
190
|
+
function extractTarGz(tarPath, destDir) {
|
|
156
191
|
try {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
const envelope = await manifestRes.json();
|
|
164
|
-
if (!envelope.success || !envelope.manifest) {
|
|
165
|
-
console.log(` ✖ Invalid manifest response`);
|
|
166
|
-
return null;
|
|
167
|
-
}
|
|
168
|
-
const manifest = envelope.manifest;
|
|
169
|
-
if (!manifest.targets || typeof manifest.targets !== "object") {
|
|
170
|
-
console.log(` ✖ Invalid manifest format`);
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
|
-
if (envelope.signature && envelope.signed_payload) {
|
|
174
|
-
const signedBytes = Buffer.from(envelope.signed_payload, "base64");
|
|
175
|
-
const sigBytes = Buffer.from(envelope.signature, "base64");
|
|
176
|
-
console.log(` Verifying manifest signature...`);
|
|
177
|
-
if (!verifyManifest(signedBytes, sigBytes)) {
|
|
178
|
-
console.error(` ✖ Manifest signature verification failed.`);
|
|
179
|
-
console.error(` The manifest may have been tampered with.`);
|
|
180
|
-
process.exit(1);
|
|
181
|
-
}
|
|
182
|
-
console.log(` ✓ Manifest signature verified`);
|
|
183
|
-
} else {
|
|
184
|
-
console.error(` ✖ Manifest signature or signed payload missing.`);
|
|
185
|
-
console.error(` Refusing to continue without signature verification.`);
|
|
186
|
-
return null;
|
|
187
|
-
}
|
|
188
|
-
console.log(` ✓ Manifest v${manifest.version} (${Object.keys(manifest.targets).length} targets)`);
|
|
189
|
-
return {
|
|
190
|
-
manifest,
|
|
191
|
-
stableVersion: manifest.version
|
|
192
|
-
};
|
|
192
|
+
execFileSync("tar", [
|
|
193
|
+
"-xzf",
|
|
194
|
+
tarPath,
|
|
195
|
+
"-C",
|
|
196
|
+
destDir
|
|
197
|
+
], { stdio: "pipe" });
|
|
193
198
|
} catch (err) {
|
|
194
|
-
|
|
195
|
-
|
|
199
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
200
|
+
throw new Error(`tar extraction failed: ${msg}`);
|
|
196
201
|
}
|
|
197
202
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const tempDir = join(ENGINES_DIR, ".tmp");
|
|
209
|
-
mkdirSync(tempDir, { recursive: true });
|
|
210
|
-
const tarPath = join(tempDir, `${binaryName}.tar.gz`);
|
|
203
|
+
/**
|
|
204
|
+
* Downloads, verifies and extracts the engine inside a private temp directory,
|
|
205
|
+
* and only then moves the binary into `<enginesDir>/v<version>/<binaryName>`.
|
|
206
|
+
* A failed or interrupted install never leaves a binary at the final path.
|
|
207
|
+
* Returns the installed binary path.
|
|
208
|
+
*/
|
|
209
|
+
async function installEngine(options) {
|
|
210
|
+
const { url, enginesDir, version, binaryName, expectedSha256, log = () => {} } = options;
|
|
211
|
+
mkdirSync(enginesDir, { recursive: true });
|
|
212
|
+
const tempDir = mkdtempSync(join(enginesDir, ".tmp-"));
|
|
211
213
|
try {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
const fileStream = createWriteStream(tarPath);
|
|
219
|
-
if (res.body) {
|
|
220
|
-
const reader = res.body.getReader();
|
|
221
|
-
while (true) {
|
|
222
|
-
const { done, value } = await reader.read();
|
|
223
|
-
if (done) break;
|
|
224
|
-
fileStream.write(value);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
fileStream.end();
|
|
228
|
-
await new Promise((resolve, reject) => {
|
|
229
|
-
fileStream.on("finish", resolve);
|
|
230
|
-
fileStream.on("error", reject);
|
|
231
|
-
});
|
|
232
|
-
const tarSize = statSync(tarPath).size;
|
|
233
|
-
console.log(` ✓ Downloaded ${(tarSize / 1024).toFixed(1)} KB`);
|
|
234
|
-
console.log(` Verifying checksum...`);
|
|
214
|
+
const tarPath = join(tempDir, "engine.tar.gz");
|
|
215
|
+
log(` Downloading from ${url}...`);
|
|
216
|
+
await downloadToFile(url, tarPath);
|
|
217
|
+
log(` ✓ Downloaded ${(statSync(tarPath).size / 1024).toFixed(1)} KB`);
|
|
218
|
+
log(` Verifying checksum...`);
|
|
235
219
|
const hash = await sha256File(tarPath);
|
|
236
|
-
if (hash !== expectedSha256) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
await extractTarGz(tarPath, destDir);
|
|
247
|
-
const extractedPath = join(destDir, "jsorm");
|
|
220
|
+
if (hash !== expectedSha256) throw new Error(`Checksum mismatch!\n Expected: ${expectedSha256}\n Got: ${hash}`);
|
|
221
|
+
log(` ✓ Checksum verified`);
|
|
222
|
+
log(` Extracting...`);
|
|
223
|
+
const extractDir = join(tempDir, "out");
|
|
224
|
+
mkdirSync(extractDir);
|
|
225
|
+
extractTarGz(tarPath, extractDir);
|
|
226
|
+
const extractedPath = join(extractDir, "jsorm");
|
|
227
|
+
if (!existsSync(extractedPath)) throw new Error(`Archive does not contain the "jsorm" binary`);
|
|
228
|
+
if (process.platform !== "win32") chmodSync(extractedPath, 493);
|
|
229
|
+
const destDir = getVersionDir(enginesDir, version);
|
|
248
230
|
const destPath = join(destDir, binaryName);
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
return
|
|
253
|
-
} catch (err) {
|
|
254
|
-
console.log(` ✖ Extract error: ${err instanceof Error ? err.message : err}`);
|
|
255
|
-
return false;
|
|
231
|
+
mkdirSync(destDir, { recursive: true });
|
|
232
|
+
renameSync(extractedPath, destPath);
|
|
233
|
+
log(` ✓ Engine installed: ${destPath}`);
|
|
234
|
+
return destPath;
|
|
256
235
|
} finally {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
236
|
+
rmSync(tempDir, {
|
|
237
|
+
recursive: true,
|
|
238
|
+
force: true
|
|
239
|
+
});
|
|
260
240
|
}
|
|
261
241
|
}
|
|
262
|
-
|
|
263
|
-
|
|
242
|
+
//#endregion
|
|
243
|
+
//#region src/resolve.ts
|
|
244
|
+
/**
|
|
245
|
+
* Returns the path of an engine binary that is safe to run: the installed one
|
|
246
|
+
* when it is current, or a freshly installed stable release. When the server
|
|
247
|
+
* cannot be reached, an installed engine is used as is. Throws when no engine
|
|
248
|
+
* can be provided or when the server's answer cannot be trusted.
|
|
249
|
+
*/
|
|
250
|
+
async function resolveEngine(options) {
|
|
251
|
+
const { enginesDir, binaryName, manifestUrl, downloadUrl, publicKey = getSpkiPublicKey(), log = () => {} } = options;
|
|
252
|
+
const installedVer = detectInstalledVersion(enginesDir, binaryName);
|
|
253
|
+
const installedPath = installedVer ? join(getVersionDir(enginesDir, installedVer), binaryName) : null;
|
|
254
|
+
log(` Fetching manifest from ${manifestUrl}...`);
|
|
255
|
+
const result = await fetchManifest(manifestUrl, publicKey);
|
|
256
|
+
if (result.status === "unreachable") {
|
|
257
|
+
if (installedPath) {
|
|
258
|
+
log(` ⚠ ${result.reason}`);
|
|
259
|
+
log(` ⚠ Using installed engine v${installedVer} without checking for updates`);
|
|
260
|
+
return installedPath;
|
|
261
|
+
}
|
|
262
|
+
throw new Error(`${result.reason}\n ✖ Failed to fetch manifest.`);
|
|
263
|
+
}
|
|
264
|
+
if (result.status === "invalid") throw new Error(result.reason);
|
|
265
|
+
const { manifest } = result;
|
|
266
|
+
const stableVersion = manifest.version;
|
|
267
|
+
log(` ✓ Manifest signature verified`);
|
|
268
|
+
log(` ✓ Manifest v${stableVersion} (${Object.keys(manifest.targets).length} targets)`);
|
|
269
|
+
const target = manifest.targets[binaryName];
|
|
270
|
+
if (!target) throw new Error(`No binary available for ${binaryName}\n Available: ${Object.keys(manifest.targets).join(", ")}`);
|
|
271
|
+
if (installedPath && installedVer && compareVersions(stableVersion, installedVer) <= 0) {
|
|
272
|
+
log(` ✓ Engine v${installedVer} is up to date (stable: v${stableVersion})`);
|
|
273
|
+
return installedPath;
|
|
274
|
+
}
|
|
275
|
+
log(installedVer ? ` Update available: v${installedVer} → v${stableVersion}` : ` Engine not found. Downloading v${stableVersion}...`);
|
|
276
|
+
log(` Requesting download URL...`);
|
|
277
|
+
const dlRes = await fetch(`${downloadUrl}/${binaryName}`, { signal: AbortSignal.timeout(1e4) });
|
|
278
|
+
if (!dlRes.ok) throw new Error(`Download request failed: ${dlRes.status} ${dlRes.statusText}`);
|
|
279
|
+
const dl = await dlRes.json();
|
|
280
|
+
if (dl.version !== stableVersion) throw new Error(`Version mismatch: server returned v${dl.version}, manifest says v${stableVersion}`);
|
|
281
|
+
if (dl.sha256 !== target.sha256) throw new Error(`SHA-256 mismatch: server returned ${String(dl.sha256).slice(0, 16)}, manifest says ${target.sha256.slice(0, 16)}`);
|
|
282
|
+
return installEngine({
|
|
283
|
+
url: dl.url,
|
|
284
|
+
enginesDir,
|
|
285
|
+
version: stableVersion,
|
|
286
|
+
binaryName,
|
|
287
|
+
expectedSha256: target.sha256,
|
|
288
|
+
log
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
//#endregion
|
|
292
|
+
//#region src/index.ts
|
|
293
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
294
|
+
const __dirname = dirname(__filename);
|
|
295
|
+
const ENGINES_DIR = join(homedir(), ".jsorm", "engines");
|
|
296
|
+
const DOWNLOAD_BASE = "https://download.jsorm.dev";
|
|
297
|
+
const STABLE_MANIFEST_URL = `${DOWNLOAD_BASE}/engine/stable/manifest`;
|
|
298
|
+
const STABLE_DOWNLOAD_URL = `${DOWNLOAD_BASE}/engine/download`;
|
|
299
|
+
function readVersion() {
|
|
300
|
+
try {
|
|
301
|
+
return JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8")).version;
|
|
302
|
+
} catch {
|
|
303
|
+
return "unknown";
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function getLocalDevEnginePath() {
|
|
307
|
+
const debug = join(__dirname, "..", "..", "..", "engine", "target", "debug", "jsorm");
|
|
308
|
+
if (existsSync(debug)) return debug;
|
|
309
|
+
const release = join(__dirname, "..", "..", "..", "engine", "target", "release", "jsorm");
|
|
310
|
+
if (existsSync(release)) return release;
|
|
311
|
+
return null;
|
|
264
312
|
}
|
|
265
313
|
function executeEngine(enginePath, args) {
|
|
266
|
-
const projectDir = process.cwd();
|
|
267
|
-
const framework = detectFramework();
|
|
268
|
-
const pm = detectPackageManager();
|
|
269
|
-
const runtime = detectRuntime();
|
|
270
314
|
const engineArgs = [
|
|
271
315
|
...args,
|
|
272
316
|
"--framework",
|
|
273
|
-
|
|
317
|
+
detectFramework(),
|
|
274
318
|
"--pm",
|
|
275
|
-
|
|
319
|
+
detectPackageManager(),
|
|
276
320
|
"--runtime",
|
|
277
|
-
|
|
321
|
+
detectRuntime()
|
|
278
322
|
];
|
|
279
323
|
const child = spawn(enginePath, engineArgs, {
|
|
280
324
|
stdio: "inherit",
|
|
281
|
-
cwd:
|
|
325
|
+
cwd: process.cwd()
|
|
282
326
|
});
|
|
283
327
|
child.on("error", (err) => {
|
|
284
328
|
console.error(` ✖ Failed to start engine: ${err.message}`);
|
|
@@ -290,8 +334,8 @@ function executeEngine(enginePath, args) {
|
|
|
290
334
|
}
|
|
291
335
|
async function main() {
|
|
292
336
|
const args = process.argv.slice(2);
|
|
293
|
-
if (args.includes("--version") || args.includes("-v")) {
|
|
294
|
-
console.log(
|
|
337
|
+
if (args.includes("--version") || args.includes("-v") || args.includes("-V")) {
|
|
338
|
+
console.log(readVersion());
|
|
295
339
|
return;
|
|
296
340
|
}
|
|
297
341
|
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
@@ -300,7 +344,7 @@ async function main() {
|
|
|
300
344
|
executeEngine(localEnginePath, args);
|
|
301
345
|
return;
|
|
302
346
|
}
|
|
303
|
-
console.log(`jsorm ${
|
|
347
|
+
console.log(`jsorm ${readVersion()}`);
|
|
304
348
|
console.log("JSORM CLI — TypeScript ORM code generation");
|
|
305
349
|
console.log();
|
|
306
350
|
console.log("Commands:");
|
|
@@ -309,9 +353,9 @@ async function main() {
|
|
|
309
353
|
console.log(" watch Watch model files and regenerate on changes");
|
|
310
354
|
console.log();
|
|
311
355
|
console.log("Options:");
|
|
312
|
-
console.log(" --help, -h
|
|
313
|
-
console.log(" --version, -V
|
|
314
|
-
console.log(" --dry-run
|
|
356
|
+
console.log(" --help, -h Print help");
|
|
357
|
+
console.log(" --version, -v, -V Print version");
|
|
358
|
+
console.log(" --dry-run Show what would be generated without writing files");
|
|
315
359
|
return;
|
|
316
360
|
}
|
|
317
361
|
const localEnginePath = getLocalDevEnginePath();
|
|
@@ -319,45 +363,20 @@ async function main() {
|
|
|
319
363
|
executeEngine(localEnginePath, args);
|
|
320
364
|
return;
|
|
321
365
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
console.error(`
|
|
333
|
-
process.exit(1);
|
|
334
|
-
}
|
|
335
|
-
const installedPath = getInstalledEnginePath(binaryName);
|
|
336
|
-
const installedVer = installedPath ? detectInstalledVersion(binaryName) : null;
|
|
337
|
-
if (installedVer && compareVersions(stableVersion, installedVer) <= 0) {
|
|
338
|
-
console.log(` ✓ Engine v${installedVer} is up to date (stable: v${stableVersion})`);
|
|
339
|
-
executeEngine(installedPath, args);
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
if (installedVer) console.log(` Update available: v${installedVer} → v${stableVersion}`);
|
|
343
|
-
else console.log(` Engine not found. Downloading v${stableVersion}...`);
|
|
344
|
-
console.log(` Requesting download URL...`);
|
|
345
|
-
const dlRes = await fetch(`${STABLE_DOWNLOAD_URL}/${binaryName}`, { signal: AbortSignal.timeout(1e4) });
|
|
346
|
-
if (!dlRes.ok) {
|
|
347
|
-
console.error(` ✖ Download request failed: ${dlRes.status} ${dlRes.statusText}`);
|
|
348
|
-
process.exit(1);
|
|
349
|
-
}
|
|
350
|
-
const dl = await dlRes.json();
|
|
351
|
-
if (dl.version !== stableVersion) {
|
|
352
|
-
console.error(` ✖ Version mismatch: server returned v${dl.version}, manifest says v${stableVersion}`);
|
|
353
|
-
process.exit(1);
|
|
354
|
-
}
|
|
355
|
-
if (dl.sha256 !== target.sha256) {
|
|
356
|
-
console.error(` ✖ SHA-256 mismatch: server returned ${dl.sha256.slice(0, 16)}, manifest says ${target.sha256.slice(0, 16)}`);
|
|
366
|
+
let enginePath;
|
|
367
|
+
try {
|
|
368
|
+
enginePath = await resolveEngine({
|
|
369
|
+
enginesDir: ENGINES_DIR,
|
|
370
|
+
binaryName: getEngineBinaryName(),
|
|
371
|
+
manifestUrl: STABLE_MANIFEST_URL,
|
|
372
|
+
downloadUrl: STABLE_DOWNLOAD_URL,
|
|
373
|
+
log: console.log
|
|
374
|
+
});
|
|
375
|
+
} catch (err) {
|
|
376
|
+
console.error(` ✖ ${err instanceof Error ? err.message : err}`);
|
|
357
377
|
process.exit(1);
|
|
358
378
|
}
|
|
359
|
-
|
|
360
|
-
if (await downloadAndExtractEngine(dl.url, destDir, binaryName, target.sha256)) executeEngine(join(destDir, binaryName), args);
|
|
379
|
+
executeEngine(enginePath, args);
|
|
361
380
|
}
|
|
362
381
|
main();
|
|
363
382
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsorm/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.3",
|
|
5
5
|
"description": "CLI de JSORM",
|
|
6
6
|
"license": "MPL-2.0",
|
|
7
7
|
"repository": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"main": "./dist/index.mjs",
|
|
21
21
|
"types": "./dist/index.d.mts",
|
|
22
22
|
"bin": {
|
|
23
|
-
"
|
|
23
|
+
"jsorm": "./dist/index.mjs"
|
|
24
24
|
},
|
|
25
25
|
"exports": {
|
|
26
26
|
".": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"access": "public"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@jsorm/core": "1.0.
|
|
38
|
+
"@jsorm/core": "1.0.3"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/node": "^26.1.1",
|