ezi 2.0.0 → 2.0.1
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 +14 -6
- package/dist/index.js +146 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,16 +36,24 @@ npm publish # package `ezi`, bin `ezi` -> dist/index.js
|
|
|
36
36
|
Users then run:
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
|
-
npx -y ezi .
|
|
39
|
+
npx -y ezi@latest . # first run downloads ~30-60MB binary to cache, opens editor
|
|
40
40
|
ezi . # after npm i -g ezi
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
Version resolution (no republish needed for editor releases):
|
|
44
|
+
|
|
45
|
+
1. `EZI_VERSION=0.1.2` pins that release, no network.
|
|
46
|
+
2. Unset (or `EZI_VERSION=latest`, just `npx ezi`) checks
|
|
47
|
+
`api.github.com/repos/<owner>/<repo>/releases/latest` on every run.
|
|
48
|
+
Same tag reuses the cached binary (no download); new tag downloads once
|
|
49
|
+
to `<cache>/vX.Y.Z/`. Offline / API failure reuses the last-seen tag,
|
|
50
|
+
else the compiled-in default near `app/Cargo.toml`.
|
|
51
|
+
|
|
52
|
+
Only republish `ezi` for launcher bugfixes. Skip the lookup with
|
|
53
|
+
`EZI_NO_UPDATE_CHECK=1`. Optional `EZI_GITHUB_TOKEN` raises API rate limits.
|
|
46
54
|
|
|
47
55
|
## Cache
|
|
48
56
|
|
|
49
|
-
- Linux/macOS: `~/.cache/ezi/v<version>/` (or `$XDG_CACHE_HOME`)
|
|
57
|
+
- Linux/macOS: `~/.cache/ezi/v<version>/` (or `$XDG_CACHE_HOME`) + `latest.json` tag cache
|
|
50
58
|
- Windows: `%LOCALAPPDATA%/ezi/cache/v<version>/`
|
|
51
|
-
- Override: `EZI_CACHE`, `EZI_VERSION`, `EZI_REPO`
|
|
59
|
+
- Override: `EZI_CACHE`, `EZI_VERSION`, `EZI_REPO`, `EZI_NO_UPDATE_CHECK=1`
|
package/dist/index.js
CHANGED
|
@@ -9,9 +9,11 @@
|
|
|
9
9
|
* npx ezi --version launcher + editor version
|
|
10
10
|
*
|
|
11
11
|
* Env overrides:
|
|
12
|
-
* EZI_VERSION
|
|
12
|
+
* EZI_VERSION pinned version without `v`, `latest`, or unset (default: auto-detect)
|
|
13
13
|
* EZI_REPO `owner/repo` (default: olovalabs/ezicode)
|
|
14
14
|
* EZI_CACHE cache dir override
|
|
15
|
+
* EZI_NO_UPDATE_CHECK set to 1 to skip GitHub lookup, use fallback immediately
|
|
16
|
+
* EZI_GITHUB_TOKEN optional token to raise api.github.com rate limits
|
|
15
17
|
*
|
|
16
18
|
* Design: zero npm dependencies (node builtins only) so the published
|
|
17
19
|
* package stays ~5KB. Platform binaries come from GitHub Releases:
|
|
@@ -58,10 +60,9 @@ const https = __importStar(require("node:https"));
|
|
|
58
60
|
const os = __importStar(require("node:os"));
|
|
59
61
|
const path = __importStar(require("node:path"));
|
|
60
62
|
const node_child_process_1 = require("node:child_process");
|
|
61
|
-
const LAUNCHER_VERSION = "2.0.
|
|
62
|
-
const
|
|
63
|
+
const LAUNCHER_VERSION = "2.0.1"; // npm package version
|
|
64
|
+
const FALLBACK_EDITOR_VERSION = "0.1.3"; // used when offline / API fails; keep near app/Cargo.toml
|
|
63
65
|
const REPO = process.env.EZI_REPO || "olovalabs/ezicode";
|
|
64
|
-
const TAG = `v${process.env.EZI_VERSION || EDITOR_VERSION}`;
|
|
65
66
|
function resolveTarget() {
|
|
66
67
|
const platform = process.platform;
|
|
67
68
|
const arch = process.arch;
|
|
@@ -81,15 +82,141 @@ function resolveTarget() {
|
|
|
81
82
|
`Prebuilt binaries exist for linux-x64, darwin-arm64, win32-x64 only. ` +
|
|
82
83
|
`Build from source: https://github.com/${REPO}`);
|
|
83
84
|
}
|
|
84
|
-
function
|
|
85
|
+
function baseCacheDir() {
|
|
85
86
|
if (process.env.EZI_CACHE)
|
|
86
87
|
return process.env.EZI_CACHE;
|
|
87
88
|
if (process.platform === "win32") {
|
|
88
89
|
const base = process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local");
|
|
89
|
-
return path.join(base, "ezi", "cache"
|
|
90
|
+
return path.join(base, "ezi", "cache");
|
|
90
91
|
}
|
|
91
92
|
const xdg = process.env.XDG_CACHE_HOME || path.join(os.homedir(), ".cache");
|
|
92
|
-
return path.join(xdg, "ezi"
|
|
93
|
+
return path.join(xdg, "ezi");
|
|
94
|
+
}
|
|
95
|
+
function cacheDirFor(tag) {
|
|
96
|
+
return path.join(baseCacheDir(), tag);
|
|
97
|
+
}
|
|
98
|
+
function latestCacheFile() {
|
|
99
|
+
return path.join(baseCacheDir(), "latest.json");
|
|
100
|
+
}
|
|
101
|
+
function normalizeTag(raw) {
|
|
102
|
+
const t = raw.trim();
|
|
103
|
+
return t.startsWith("v") ? t : `v${t}`;
|
|
104
|
+
}
|
|
105
|
+
function fetchLatestTag(verbose) {
|
|
106
|
+
return new Promise((resolve) => {
|
|
107
|
+
const headers = {
|
|
108
|
+
"User-Agent": "ezi-launcher",
|
|
109
|
+
Accept: "application/vnd.github+json",
|
|
110
|
+
};
|
|
111
|
+
if (process.env.EZI_GITHUB_TOKEN) {
|
|
112
|
+
headers.Authorization = `Bearer ${process.env.EZI_GITHUB_TOKEN}`;
|
|
113
|
+
}
|
|
114
|
+
const req = https.get(`https://api.github.com/repos/${REPO}/releases/latest`, { headers, timeout: 5000 }, (res) => {
|
|
115
|
+
const status = res.statusCode || 0;
|
|
116
|
+
if (status !== 200) {
|
|
117
|
+
if (verbose)
|
|
118
|
+
console.log(`ezi: release lookup failed (http ${status}), using fallback`);
|
|
119
|
+
res.resume();
|
|
120
|
+
resolve(null);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
let body = "";
|
|
124
|
+
res.setEncoding("utf8");
|
|
125
|
+
res.on("data", (chunk) => {
|
|
126
|
+
body += chunk;
|
|
127
|
+
// Guard against unexpectedly large payloads.
|
|
128
|
+
if (body.length > 256 * 1024) {
|
|
129
|
+
res.destroy();
|
|
130
|
+
resolve(null);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
res.on("end", () => {
|
|
134
|
+
try {
|
|
135
|
+
const json = JSON.parse(body);
|
|
136
|
+
if (typeof json.tag_name === "string" && json.tag_name.trim()) {
|
|
137
|
+
resolve(normalizeTag(json.tag_name));
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
resolve(null);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
resolve(null);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
req.on("timeout", () => {
|
|
149
|
+
if (verbose)
|
|
150
|
+
console.log("ezi: release lookup timed out, using fallback");
|
|
151
|
+
req.destroy();
|
|
152
|
+
resolve(null);
|
|
153
|
+
});
|
|
154
|
+
req.on("error", (err) => {
|
|
155
|
+
if (verbose)
|
|
156
|
+
console.log(`ezi: release lookup error (${err.message}), using fallback`);
|
|
157
|
+
resolve(null);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
function readLastSeenTag(verbose) {
|
|
162
|
+
try {
|
|
163
|
+
const raw = fs.readFileSync(latestCacheFile(), "utf8");
|
|
164
|
+
const cached = JSON.parse(raw);
|
|
165
|
+
if (typeof cached.tag !== "string" || !cached.tag)
|
|
166
|
+
return null;
|
|
167
|
+
if (verbose)
|
|
168
|
+
console.log(`ezi: last seen ${cached.tag}`);
|
|
169
|
+
return cached.tag;
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function writeCachedTag(tag) {
|
|
176
|
+
try {
|
|
177
|
+
fs.mkdirSync(baseCacheDir(), { recursive: true });
|
|
178
|
+
fs.writeFileSync(latestCacheFile(), JSON.stringify({ tag, checkedAt: Date.now() }), "utf8");
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
// Cache is best-effort; a read-only homedir must not break launches.
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Resolve which editor release to run:
|
|
186
|
+
* EZI_VERSION=<x.y.z|vX.Y.Z> -> that pin, no network.
|
|
187
|
+
* EZI_VERSION=latest (or unset) -> check GitHub every run; same tag reuses
|
|
188
|
+
* cached binary (no download), new tag downloads once, offline reuses last seen.
|
|
189
|
+
* EZI_NO_UPDATE_CHECK=1 -> skip network, use last seen or fallback.
|
|
190
|
+
*/
|
|
191
|
+
async function resolveTag(verbose) {
|
|
192
|
+
const pinned = (process.env.EZI_VERSION || "").trim();
|
|
193
|
+
if (pinned && pinned.toLowerCase() !== "latest") {
|
|
194
|
+
return normalizeTag(pinned);
|
|
195
|
+
}
|
|
196
|
+
if (process.env.EZI_NO_UPDATE_CHECK === "1") {
|
|
197
|
+
const last = readLastSeenTag(verbose);
|
|
198
|
+
if (last)
|
|
199
|
+
return last;
|
|
200
|
+
if (verbose)
|
|
201
|
+
console.log("ezi: update check disabled, using fallback");
|
|
202
|
+
return `v${FALLBACK_EDITOR_VERSION}`;
|
|
203
|
+
}
|
|
204
|
+
// Always check: cheap API call (~200ms). Same version -> cached binary, no download.
|
|
205
|
+
const latest = await fetchLatestTag(verbose);
|
|
206
|
+
if (latest) {
|
|
207
|
+
const prev = readLastSeenTag(false);
|
|
208
|
+
if (prev !== latest && verbose)
|
|
209
|
+
console.log(`ezi: new release ${latest} (was ${prev || "none"})`);
|
|
210
|
+
else if (verbose)
|
|
211
|
+
console.log(`ezi: latest is ${latest}, reusing cache`);
|
|
212
|
+
writeCachedTag(latest);
|
|
213
|
+
return latest;
|
|
214
|
+
}
|
|
215
|
+
// Offline/API failure: reuse last seen tag so repeat launches keep working.
|
|
216
|
+
const stale = readLastSeenTag(verbose);
|
|
217
|
+
if (stale)
|
|
218
|
+
return stale;
|
|
219
|
+
return `v${FALLBACK_EDITOR_VERSION}`;
|
|
93
220
|
}
|
|
94
221
|
function download(url, dest, redirects = 5) {
|
|
95
222
|
return new Promise((resolve, reject) => {
|
|
@@ -132,15 +259,15 @@ function extractTargz(archive, destDir) {
|
|
|
132
259
|
});
|
|
133
260
|
});
|
|
134
261
|
}
|
|
135
|
-
async function ensureBinary(verbose) {
|
|
262
|
+
async function ensureBinary(tag, verbose) {
|
|
136
263
|
const target = resolveTarget();
|
|
137
|
-
const dir =
|
|
264
|
+
const dir = cacheDirFor(tag);
|
|
138
265
|
fs.mkdirSync(dir, { recursive: true });
|
|
139
266
|
if (target.kind === "exe") {
|
|
140
267
|
const bin = path.join(dir, "ezicode.exe");
|
|
141
268
|
if (fs.existsSync(bin))
|
|
142
269
|
return bin;
|
|
143
|
-
const url = `https://github.com/${REPO}/releases/download/${
|
|
270
|
+
const url = `https://github.com/${REPO}/releases/download/${tag}/${target.asset}`;
|
|
144
271
|
const tmp = bin + ".download";
|
|
145
272
|
if (verbose)
|
|
146
273
|
console.log(`ezi: downloading ${url}`);
|
|
@@ -153,7 +280,7 @@ async function ensureBinary(verbose) {
|
|
|
153
280
|
fs.chmodSync(bin, 0o755);
|
|
154
281
|
return bin;
|
|
155
282
|
}
|
|
156
|
-
const url = `https://github.com/${REPO}/releases/download/${
|
|
283
|
+
const url = `https://github.com/${REPO}/releases/download/${tag}/${target.asset}`;
|
|
157
284
|
const archive = path.join(dir, target.asset);
|
|
158
285
|
if (!fs.existsSync(archive)) {
|
|
159
286
|
if (verbose)
|
|
@@ -168,7 +295,7 @@ async function ensureBinary(verbose) {
|
|
|
168
295
|
fs.chmodSync(bin, 0o755);
|
|
169
296
|
return bin;
|
|
170
297
|
}
|
|
171
|
-
function printHelp() {
|
|
298
|
+
function printHelp(fallbackTag) {
|
|
172
299
|
console.log(`ezi ${LAUNCHER_VERSION} — launcher for ezicode (https://github.com/${REPO})
|
|
173
300
|
|
|
174
301
|
Usage:
|
|
@@ -179,9 +306,10 @@ Usage:
|
|
|
179
306
|
ezi --clean remove cached binaries
|
|
180
307
|
|
|
181
308
|
Env:
|
|
182
|
-
EZI_VERSION release
|
|
309
|
+
EZI_VERSION pin release (e.g. 0.1.3), "latest", or unset for auto-detect (default ${fallbackTag})
|
|
183
310
|
EZI_REPO owner/repo (default ${REPO})
|
|
184
311
|
EZI_CACHE cache dir override
|
|
312
|
+
EZI_NO_UPDATE_CHECK=1 skip GitHub lookup, use cache/fallback
|
|
185
313
|
`);
|
|
186
314
|
}
|
|
187
315
|
async function main() {
|
|
@@ -189,19 +317,20 @@ async function main() {
|
|
|
189
317
|
const verbose = args.includes("--verbose") || !!process.env.EZI_VERBOSE;
|
|
190
318
|
const passthrough = args.filter((a) => a !== "--verbose");
|
|
191
319
|
if (passthrough.includes("--help") || passthrough.includes("-h")) {
|
|
192
|
-
printHelp();
|
|
320
|
+
printHelp(`v${FALLBACK_EDITOR_VERSION}`);
|
|
193
321
|
return;
|
|
194
322
|
}
|
|
323
|
+
const tag = await resolveTag(verbose);
|
|
195
324
|
if (passthrough.includes("--version") || passthrough.includes("-V")) {
|
|
196
|
-
console.log(`ezi ${LAUNCHER_VERSION} (editor ${
|
|
325
|
+
console.log(`ezi ${LAUNCHER_VERSION} (editor ${tag} @ ${REPO})`);
|
|
197
326
|
return;
|
|
198
327
|
}
|
|
199
328
|
if (passthrough.includes("--clean")) {
|
|
200
|
-
fs.rmSync(
|
|
329
|
+
fs.rmSync(baseCacheDir(), { recursive: true, force: true });
|
|
201
330
|
console.log("ezi: cache cleared");
|
|
202
331
|
return;
|
|
203
332
|
}
|
|
204
|
-
const bin = await ensureBinary(verbose);
|
|
333
|
+
const bin = await ensureBinary(tag, verbose);
|
|
205
334
|
if (passthrough.includes("--where")) {
|
|
206
335
|
console.log(bin);
|
|
207
336
|
return;
|