ezi 0.0.2 → 2.0.0
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 +51 -48
- package/dist/index.js +223 -0
- package/package.json +32 -6
- package/app.js +0 -1
package/README.md
CHANGED
|
@@ -1,48 +1,51 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
#
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
1
|
+
# ezi — `npx ezi` launcher for ezicode
|
|
2
|
+
|
|
3
|
+
Zero-dependency TypeScript CLI. It detects OS/arch, downloads the matching
|
|
4
|
+
native `ezicode` binary from GitHub Releases on first run, caches it, and
|
|
5
|
+
launches the editor detached. No `npm install` required by users.
|
|
6
|
+
|
|
7
|
+
## Mono-repo layout
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
ezicode/ # Rust workspace (cargo members: app)
|
|
11
|
+
├── app/ # native editor (ezicode binary)
|
|
12
|
+
├── ezi-cli/ # npm launcher (this folder, NOT a cargo member)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Cargo ignores `ezi-cli/` (`members = ["app"]` in root `Cargo.toml:2`), npm
|
|
16
|
+
ignores the Rust side. One repo, two package managers.
|
|
17
|
+
|
|
18
|
+
## Develop
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
cd ezi-cli
|
|
22
|
+
npm install
|
|
23
|
+
npm run build # tsc -> dist/index.js
|
|
24
|
+
node ./dist/index.js --help
|
|
25
|
+
EZI_VERBOSE=1 node ./dist/index.js . # downloads real binary, opens editor
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Publish
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
cd ezi-cli
|
|
32
|
+
npm run build
|
|
33
|
+
npm publish # package `ezi`, bin `ezi` -> dist/index.js
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Users then run:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx -y ezi . # first run downloads ~30-60MB binary to cache, opens editor
|
|
40
|
+
ezi . # after npm i -g ezi
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Keep `VERSION` in `src/index.ts`, `version` in `package.json`, and `version`
|
|
44
|
+
in `app/Cargo.toml` in sync — the launcher builds its download URL as
|
|
45
|
+
`.../releases/download/v<VERSION>/ezicode-<target>.*`.
|
|
46
|
+
|
|
47
|
+
## Cache
|
|
48
|
+
|
|
49
|
+
- Linux/macOS: `~/.cache/ezi/v<version>/` (or `$XDG_CACHE_HOME`)
|
|
50
|
+
- Windows: `%LOCALAPPDATA%/ezi/cache/v<version>/`
|
|
51
|
+
- Override: `EZI_CACHE`, `EZI_VERSION`, `EZI_REPO`
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* ezi — zero-install launcher for the ezicode native editor.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx ezi [file|dir] [args...] open editor (downloads binary on first run)
|
|
8
|
+
* npx ezi --help show help
|
|
9
|
+
* npx ezi --version launcher + editor version
|
|
10
|
+
*
|
|
11
|
+
* Env overrides:
|
|
12
|
+
* EZI_VERSION GitHub tag version without `v` (default: package version)
|
|
13
|
+
* EZI_REPO `owner/repo` (default: olovalabs/ezicode)
|
|
14
|
+
* EZI_CACHE cache dir override
|
|
15
|
+
*
|
|
16
|
+
* Design: zero npm dependencies (node builtins only) so the published
|
|
17
|
+
* package stays ~5KB. Platform binaries come from GitHub Releases:
|
|
18
|
+
* linux x64 -> ezicode-x86_64-unknown-linux-gnu.tar.gz
|
|
19
|
+
* mac arm64 -> ezicode-aarch64-apple-darwin.tar.gz
|
|
20
|
+
* win x64 -> ezicode-x86_64-pc-windows-msvc.exe (standalone, no unzip)
|
|
21
|
+
*/
|
|
22
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
23
|
+
if (k2 === undefined) k2 = k;
|
|
24
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
25
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
26
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
27
|
+
}
|
|
28
|
+
Object.defineProperty(o, k2, desc);
|
|
29
|
+
}) : (function(o, m, k, k2) {
|
|
30
|
+
if (k2 === undefined) k2 = k;
|
|
31
|
+
o[k2] = m[k];
|
|
32
|
+
}));
|
|
33
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
34
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
35
|
+
}) : function(o, v) {
|
|
36
|
+
o["default"] = v;
|
|
37
|
+
});
|
|
38
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
39
|
+
var ownKeys = function(o) {
|
|
40
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
41
|
+
var ar = [];
|
|
42
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
43
|
+
return ar;
|
|
44
|
+
};
|
|
45
|
+
return ownKeys(o);
|
|
46
|
+
};
|
|
47
|
+
return function (mod) {
|
|
48
|
+
if (mod && mod.__esModule) return mod;
|
|
49
|
+
var result = {};
|
|
50
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
51
|
+
__setModuleDefault(result, mod);
|
|
52
|
+
return result;
|
|
53
|
+
};
|
|
54
|
+
})();
|
|
55
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
56
|
+
const fs = __importStar(require("node:fs"));
|
|
57
|
+
const https = __importStar(require("node:https"));
|
|
58
|
+
const os = __importStar(require("node:os"));
|
|
59
|
+
const path = __importStar(require("node:path"));
|
|
60
|
+
const node_child_process_1 = require("node:child_process");
|
|
61
|
+
const LAUNCHER_VERSION = "2.0.0"; // npm package version
|
|
62
|
+
const EDITOR_VERSION = "0.1.2"; // keep in sync with app/Cargo.toml (default download)
|
|
63
|
+
const REPO = process.env.EZI_REPO || "olovalabs/ezicode";
|
|
64
|
+
const TAG = `v${process.env.EZI_VERSION || EDITOR_VERSION}`;
|
|
65
|
+
function resolveTarget() {
|
|
66
|
+
const platform = process.platform;
|
|
67
|
+
const arch = process.arch;
|
|
68
|
+
if (platform === "linux" && arch === "x64") {
|
|
69
|
+
const t = "x86_64-unknown-linux-gnu";
|
|
70
|
+
return { kind: "targz", asset: `ezicode-${t}.tar.gz`, binRel: `ezicode-${t}/ezicode` };
|
|
71
|
+
}
|
|
72
|
+
if (platform === "darwin" && arch === "arm64") {
|
|
73
|
+
const t = "aarch64-apple-darwin";
|
|
74
|
+
return { kind: "targz", asset: `ezicode-${t}.tar.gz`, binRel: `ezicode-${t}/ezicode` };
|
|
75
|
+
}
|
|
76
|
+
if (platform === "win32" && arch === "x64") {
|
|
77
|
+
// Standalone exe on the release — no zip extraction needed.
|
|
78
|
+
return { kind: "exe", asset: "ezicode-x86_64-pc-windows-msvc.exe" };
|
|
79
|
+
}
|
|
80
|
+
throw new Error(`ezi: unsupported platform ${platform}/${arch}. ` +
|
|
81
|
+
`Prebuilt binaries exist for linux-x64, darwin-arm64, win32-x64 only. ` +
|
|
82
|
+
`Build from source: https://github.com/${REPO}`);
|
|
83
|
+
}
|
|
84
|
+
function cacheDir() {
|
|
85
|
+
if (process.env.EZI_CACHE)
|
|
86
|
+
return process.env.EZI_CACHE;
|
|
87
|
+
if (process.platform === "win32") {
|
|
88
|
+
const base = process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local");
|
|
89
|
+
return path.join(base, "ezi", "cache", TAG);
|
|
90
|
+
}
|
|
91
|
+
const xdg = process.env.XDG_CACHE_HOME || path.join(os.homedir(), ".cache");
|
|
92
|
+
return path.join(xdg, "ezi", TAG);
|
|
93
|
+
}
|
|
94
|
+
function download(url, dest, redirects = 5) {
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
const req = https.get(url, { headers: { "User-Agent": "ezi-launcher" } }, (res) => {
|
|
97
|
+
const status = res.statusCode || 0;
|
|
98
|
+
if (status >= 300 && status < 400 && res.headers.location) {
|
|
99
|
+
if (redirects === 0) {
|
|
100
|
+
reject(new Error(`ezi: too many redirects downloading ${url}`));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
res.resume();
|
|
104
|
+
download(res.headers.location, dest, redirects - 1).then(resolve, reject);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (status !== 200) {
|
|
108
|
+
res.resume();
|
|
109
|
+
reject(new Error(`ezi: download failed (${status}) for ${url}`));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const out = fs.createWriteStream(dest);
|
|
113
|
+
res.pipe(out);
|
|
114
|
+
out.on("finish", () => out.close(() => resolve()));
|
|
115
|
+
out.on("error", (err) => {
|
|
116
|
+
fs.rmSync(dest, { force: true });
|
|
117
|
+
reject(err);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
req.on("error", reject);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
function extractTargz(archive, destDir) {
|
|
124
|
+
return new Promise((resolve, reject) => {
|
|
125
|
+
// System tar exists on linux + macOS runners and images. Zero-dep by design.
|
|
126
|
+
(0, node_child_process_1.execFile)("tar", ["-xzf", archive, "-C", destDir], (err, _stdout, stderr) => {
|
|
127
|
+
if (err) {
|
|
128
|
+
reject(new Error(`ezi: failed to extract ${archive}: ${stderr || err.message}`));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
resolve();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
async function ensureBinary(verbose) {
|
|
136
|
+
const target = resolveTarget();
|
|
137
|
+
const dir = cacheDir();
|
|
138
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
139
|
+
if (target.kind === "exe") {
|
|
140
|
+
const bin = path.join(dir, "ezicode.exe");
|
|
141
|
+
if (fs.existsSync(bin))
|
|
142
|
+
return bin;
|
|
143
|
+
const url = `https://github.com/${REPO}/releases/download/${TAG}/${target.asset}`;
|
|
144
|
+
const tmp = bin + ".download";
|
|
145
|
+
if (verbose)
|
|
146
|
+
console.log(`ezi: downloading ${url}`);
|
|
147
|
+
await download(url, tmp);
|
|
148
|
+
fs.renameSync(tmp, bin);
|
|
149
|
+
return bin;
|
|
150
|
+
}
|
|
151
|
+
const bin = path.join(dir, target.binRel);
|
|
152
|
+
if (fs.existsSync(bin)) {
|
|
153
|
+
fs.chmodSync(bin, 0o755);
|
|
154
|
+
return bin;
|
|
155
|
+
}
|
|
156
|
+
const url = `https://github.com/${REPO}/releases/download/${TAG}/${target.asset}`;
|
|
157
|
+
const archive = path.join(dir, target.asset);
|
|
158
|
+
if (!fs.existsSync(archive)) {
|
|
159
|
+
if (verbose)
|
|
160
|
+
console.log(`ezi: downloading ${url}`);
|
|
161
|
+
await download(url, archive);
|
|
162
|
+
}
|
|
163
|
+
if (verbose)
|
|
164
|
+
console.log(`ezi: extracting ${target.asset}`);
|
|
165
|
+
await extractTargz(archive, dir);
|
|
166
|
+
if (!fs.existsSync(bin))
|
|
167
|
+
throw new Error(`ezi: binary not found after extract: ${bin}`);
|
|
168
|
+
fs.chmodSync(bin, 0o755);
|
|
169
|
+
return bin;
|
|
170
|
+
}
|
|
171
|
+
function printHelp() {
|
|
172
|
+
console.log(`ezi ${LAUNCHER_VERSION} — launcher for ezicode (https://github.com/${REPO})
|
|
173
|
+
|
|
174
|
+
Usage:
|
|
175
|
+
ezi [file|dir] [editor-args...] open in ezicode (downloads binary first run)
|
|
176
|
+
ezi --help this help
|
|
177
|
+
ezi --version launcher version
|
|
178
|
+
ezi --where print cached binary path
|
|
179
|
+
ezi --clean remove cached binaries
|
|
180
|
+
|
|
181
|
+
Env:
|
|
182
|
+
EZI_VERSION release version (default ${EDITOR_VERSION})
|
|
183
|
+
EZI_REPO owner/repo (default ${REPO})
|
|
184
|
+
EZI_CACHE cache dir override
|
|
185
|
+
`);
|
|
186
|
+
}
|
|
187
|
+
async function main() {
|
|
188
|
+
const args = process.argv.slice(2);
|
|
189
|
+
const verbose = args.includes("--verbose") || !!process.env.EZI_VERBOSE;
|
|
190
|
+
const passthrough = args.filter((a) => a !== "--verbose");
|
|
191
|
+
if (passthrough.includes("--help") || passthrough.includes("-h")) {
|
|
192
|
+
printHelp();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (passthrough.includes("--version") || passthrough.includes("-V")) {
|
|
196
|
+
console.log(`ezi ${LAUNCHER_VERSION} (editor ${TAG} @ ${REPO})`);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (passthrough.includes("--clean")) {
|
|
200
|
+
fs.rmSync(cacheDir(), { recursive: true, force: true });
|
|
201
|
+
console.log("ezi: cache cleared");
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const bin = await ensureBinary(verbose);
|
|
205
|
+
if (passthrough.includes("--where")) {
|
|
206
|
+
console.log(bin);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
// Launch detached so `npx ezi` returns immediately while the GUI stays open.
|
|
210
|
+
// stdio ignored: the editor owns its own window, not the terminal.
|
|
211
|
+
const child = (0, node_child_process_1.spawn)(bin, passthrough, {
|
|
212
|
+
detached: true,
|
|
213
|
+
stdio: "ignore",
|
|
214
|
+
windowsHide: true,
|
|
215
|
+
});
|
|
216
|
+
child.unref();
|
|
217
|
+
if (verbose)
|
|
218
|
+
console.log(`ezi: launched ${bin}`);
|
|
219
|
+
}
|
|
220
|
+
main().catch((err) => {
|
|
221
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
222
|
+
process.exit(1);
|
|
223
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "ezi",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "ezi",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Launch ezicode editor with npx — zero-install native binary launcher",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/olovalabs/ezicode.git",
|
|
9
|
+
"directory": "ezi-cli"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/olovalabs/ezicode",
|
|
12
|
+
"keywords": ["ezicode", "ezi", "editor", "ide", "cli", "launcher"],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18"
|
|
15
|
+
},
|
|
16
|
+
"bin": {
|
|
17
|
+
"ezi": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"files": ["dist/"],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc",
|
|
22
|
+
"dev": "tsc --watch",
|
|
23
|
+
"start": "node ./dist/index.js",
|
|
24
|
+
"prepublishOnly": "npm run build",
|
|
25
|
+
"typecheck": "tsc --noEmit"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^22.7.0",
|
|
30
|
+
"typescript": "^5.6.0"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/app.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
console.log("Hello world");
|