@hsusulist/luc 0.2.5 → 0.3.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 +34 -14
- package/bin/luc.js +295 -5
- package/install.ps1 +222 -9
- package/package.json +4 -5
- package/scripts/build-linux.js +34 -0
- package/scripts/postinstall.js +8 -2
- package/scripts/postpack.js +14 -0
- package/scripts/prepack.js +27 -0
- package/src/lcode-linux/lcode.c +582 -0
- package/src/luc.h +303 -0
- package/src/luc_core.c +5527 -0
- package/src/luc_libs.c +5760 -0
- package/src/luc_nums.c +1747 -0
- package/src/luc_trans.c +1735 -0
package/README.md
CHANGED
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
# @hsusulist/luc
|
|
2
2
|
|
|
3
|
-
Install and launch the [LUC](https://github.com/hsusulist/luc) language on Windows.
|
|
3
|
+
Install and launch the [LUC](https://github.com/hsusulist/luc) language on Windows or Linux.
|
|
4
4
|
|
|
5
5
|
```powershell
|
|
6
6
|
npm install -g @hsusulist/luc
|
|
7
7
|
luc --version
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
On Linux, the package compiles the runtime during installation. Install a C
|
|
11
|
+
compiler such as GCC or Clang first; set `CC` if your compiler is not named
|
|
12
|
+
`cc`. Run `luc install` to rebuild the native runtime.
|
|
13
|
+
|
|
14
|
+
On Windows, the package installs the runtime into
|
|
15
|
+
`%LOCALAPPDATA%\Programs\LUC` (no PATH changes, no admin rights). If the
|
|
16
|
+
automatic install is skipped, run:
|
|
12
17
|
|
|
13
18
|
```powershell
|
|
14
|
-
luc
|
|
19
|
+
luc install
|
|
15
20
|
```
|
|
16
21
|
|
|
17
|
-
`luc
|
|
18
|
-
|
|
22
|
+
`luc install` opens the full-screen terminal installer. When LUC is already
|
|
23
|
+
installed, it offers component management and repair; installs made with the
|
|
24
|
+
Windows installer also include an uninstall option. Unattended installs:
|
|
19
25
|
|
|
20
26
|
```powershell
|
|
21
|
-
luc
|
|
22
|
-
luc
|
|
27
|
+
luc install -y --no-path
|
|
28
|
+
luc install -c window,ai -d "D:\Tools\LUC" -y
|
|
23
29
|
```
|
|
24
30
|
|
|
25
31
|
Available components: `window`, `ai`, `discord`, `lcode`, `vscode`, `source`.
|
|
@@ -27,10 +33,24 @@ LCode is optional and is not selected by default. Selecting it installs
|
|
|
27
33
|
`lcode.exe` alongside the interpreter. To install only the editor without the
|
|
28
34
|
LUC command, use `npm install -g @hsusulist/lcode`.
|
|
29
35
|
|
|
30
|
-
|
|
31
|
-
GitHub release and verified with SHA-256 and Ed25519 (minisign
|
|
32
|
-
is installed.
|
|
36
|
+
On Windows, the npm package is a small launcher; the runtime is downloaded from
|
|
37
|
+
the signed GitHub release and verified with SHA-256 and Ed25519 (minisign
|
|
38
|
+
format) before it is installed. On Linux, the package builds from its bundled
|
|
39
|
+
LUC and LCode C sources.
|
|
40
|
+
|
|
41
|
+
The editor-only package uses the separate, signed `lcode-portable.zip` release
|
|
42
|
+
asset on Windows. On Linux it compiles the included native terminal editor.
|
|
43
|
+
|
|
44
|
+
## Registry
|
|
45
|
+
|
|
46
|
+
```powershell
|
|
47
|
+
luc login
|
|
48
|
+
luc whoami
|
|
49
|
+
luc install -p @username/packagename
|
|
50
|
+
luc publish -p
|
|
51
|
+
```
|
|
33
52
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
53
|
+
`luc publish -p` uploads the `.luic` file and optional `README.md` from the
|
|
54
|
+
current folder, then opens a browser to enter the package name and summary.
|
|
55
|
+
Login state is scoped to the current terminal session. Set `LUC_REGISTRY_URL`
|
|
56
|
+
to use a different registry (defaults to `http://localhost:3000`).
|
package/bin/luc.js
CHANGED
|
@@ -5,6 +5,11 @@ const fs = require('fs');
|
|
|
5
5
|
const os = require('os');
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const { spawnSync } = require('child_process');
|
|
8
|
+
const http = require('http');
|
|
9
|
+
const https = require('https');
|
|
10
|
+
const { promisify } = require('util');
|
|
11
|
+
|
|
12
|
+
const args = process.argv.slice(2);
|
|
8
13
|
|
|
9
14
|
function stateBinary() {
|
|
10
15
|
const local = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
|
|
@@ -94,19 +99,304 @@ function mapInstallerArgs(args) {
|
|
|
94
99
|
return args.map((arg) => (Object.prototype.hasOwnProperty.call(map, arg) ? map[arg] : arg));
|
|
95
100
|
}
|
|
96
101
|
|
|
102
|
+
const sleep = promisify(setTimeout);
|
|
103
|
+
const registry = (process.env.LUC_REGISTRY_URL || 'http://localhost:3000').replace(/\/+$/, '');
|
|
104
|
+
|
|
105
|
+
function request(url, options = {}) {
|
|
106
|
+
return new Promise((resolve, reject) => {
|
|
107
|
+
const target = new URL(url);
|
|
108
|
+
const client = target.protocol === 'https:' ? https : http;
|
|
109
|
+
const req = client.request(target, {
|
|
110
|
+
method: options.method || 'GET',
|
|
111
|
+
headers: options.headers || {}
|
|
112
|
+
}, (res) => {
|
|
113
|
+
const chunks = [];
|
|
114
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
115
|
+
res.on('end', () => resolve({
|
|
116
|
+
status: res.statusCode || 0,
|
|
117
|
+
headers: res.headers,
|
|
118
|
+
body: Buffer.concat(chunks)
|
|
119
|
+
}));
|
|
120
|
+
});
|
|
121
|
+
req.on('error', reject);
|
|
122
|
+
if (options.body) req.write(options.body);
|
|
123
|
+
req.end();
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function requestJson(pathname, options = {}) {
|
|
128
|
+
const headers = Object.assign({}, options.headers || {});
|
|
129
|
+
let body = options.body;
|
|
130
|
+
if (body && !Buffer.isBuffer(body) && typeof body !== 'string') {
|
|
131
|
+
body = JSON.stringify(body);
|
|
132
|
+
headers['Content-Type'] = 'application/json';
|
|
133
|
+
}
|
|
134
|
+
const response = await request(registry + pathname, { method: options.method, headers, body });
|
|
135
|
+
let payload;
|
|
136
|
+
try {
|
|
137
|
+
payload = JSON.parse(response.body.toString('utf8'));
|
|
138
|
+
} catch (_) {
|
|
139
|
+
payload = { error: response.body.toString('utf8') || 'Unexpected server response.' };
|
|
140
|
+
}
|
|
141
|
+
if (response.status < 200 || response.status >= 300) {
|
|
142
|
+
throw new Error(payload.error || 'Request failed with status ' + response.status + '.');
|
|
143
|
+
}
|
|
144
|
+
return payload;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function authPath() {
|
|
148
|
+
const base = process.env.LOCALAPPDATA || process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
|
|
149
|
+
const terminal = process.env.LUC_TERMINAL_SESSION || String(process.ppid);
|
|
150
|
+
const key = require('crypto').createHash('sha256').update(terminal).digest('hex').slice(0, 24);
|
|
151
|
+
const dir = path.join(base, 'LUC', 'auth');
|
|
152
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
153
|
+
return path.join(dir, key + '.json');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function readAuth() {
|
|
157
|
+
try {
|
|
158
|
+
return JSON.parse(fs.readFileSync(authPath(), 'utf8'));
|
|
159
|
+
} catch (_) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function saveAuth(auth) {
|
|
165
|
+
fs.writeFileSync(authPath(), JSON.stringify(auth), { mode: 0o600 });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function clearAuth() {
|
|
169
|
+
try { fs.unlinkSync(authPath()); } catch (error) {
|
|
170
|
+
if (error.code !== 'ENOENT') throw error;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function openBrowser(url) {
|
|
175
|
+
let command;
|
|
176
|
+
let args;
|
|
177
|
+
if (process.platform === 'win32') {
|
|
178
|
+
command = 'rundll32.exe';
|
|
179
|
+
args = ['url.dll,FileProtocolHandler', url];
|
|
180
|
+
} else if (process.platform === 'darwin') {
|
|
181
|
+
command = 'open';
|
|
182
|
+
args = [url];
|
|
183
|
+
} else {
|
|
184
|
+
command = 'xdg-open';
|
|
185
|
+
args = [url];
|
|
186
|
+
}
|
|
187
|
+
const child = spawnSync(command, args, { stdio: 'ignore' });
|
|
188
|
+
if (child.error || child.status !== 0) {
|
|
189
|
+
console.error('luc: could not open a browser. Open this URL manually:\n' + url);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function login() {
|
|
194
|
+
const started = await requestJson('/api/auth/cli/start', { method: 'POST', body: {} });
|
|
195
|
+
console.log('Waiting for LUC browser sign-in…');
|
|
196
|
+
openBrowser(started.loginUrl);
|
|
197
|
+
const deadline = Date.now() + 10 * 60 * 1000;
|
|
198
|
+
while (Date.now() < deadline) {
|
|
199
|
+
await sleep(1500);
|
|
200
|
+
const response = await request(registry + '/api/auth/cli/poll', {
|
|
201
|
+
method: 'POST',
|
|
202
|
+
headers: { 'Content-Type': 'application/json' },
|
|
203
|
+
body: JSON.stringify({ code: started.code })
|
|
204
|
+
});
|
|
205
|
+
if (response.status === 202) continue;
|
|
206
|
+
let data;
|
|
207
|
+
try { data = JSON.parse(response.body.toString('utf8')); } catch (_) { data = {}; }
|
|
208
|
+
if (response.status < 200 || response.status >= 300) throw new Error(data.error || 'Sign-in failed.');
|
|
209
|
+
saveAuth({ token: data.token, username: data.user.username });
|
|
210
|
+
console.log('Logged in as @' + data.user.username + ' for this terminal.');
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
throw new Error('Sign-in timed out. Run luc login again.');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function whoami() {
|
|
217
|
+
const auth = readAuth();
|
|
218
|
+
if (!auth || !auth.token) throw new Error('Not logged in. Run "luc login" in this terminal.');
|
|
219
|
+
try {
|
|
220
|
+
const data = await requestJson('/api/auth/me', { headers: { Authorization: 'Bearer ' + auth.token } });
|
|
221
|
+
console.log('@' + data.user.username);
|
|
222
|
+
} catch (error) {
|
|
223
|
+
clearAuth();
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function installPackage(identifier) {
|
|
229
|
+
const match = identifier.match(/^@([a-z0-9_-]{3,32})\/([a-z][a-z0-9-]{1,38}[a-z0-9])$/i);
|
|
230
|
+
if (!match) throw new Error('Use a package name like @username/packagename.');
|
|
231
|
+
const username = match[1].toLowerCase();
|
|
232
|
+
const name = match[2].toLowerCase();
|
|
233
|
+
const data = await requestJson('/api/packages?q=' + encodeURIComponent(name) + '&sort=downloads');
|
|
234
|
+
const pkg = (data.packages || []).find((item) =>
|
|
235
|
+
item.name.toLowerCase() === name && String(item.author.handle || '').toLowerCase() === username
|
|
236
|
+
);
|
|
237
|
+
if (!pkg) throw new Error('No package named ' + identifier + ' was found.');
|
|
238
|
+
const response = await request(registry + '/api/packages/' + encodeURIComponent(pkg.slug) + '/file');
|
|
239
|
+
if (response.status < 200 || response.status >= 300) {
|
|
240
|
+
let message = response.body.toString('utf8');
|
|
241
|
+
try { message = JSON.parse(message).error || message; } catch (_) {}
|
|
242
|
+
if (pkg.official && ['libs', 'net', 'json'].includes(pkg.name)) {
|
|
243
|
+
await requestJson('/api/packages/' + encodeURIComponent(pkg.slug) + '/install', { method: 'POST', body: {} });
|
|
244
|
+
console.log(pkg.name + ' is already included with the LUC runtime.');
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (pkg.official && ['window', 'ai', 'discord'].includes(pkg.name)) {
|
|
248
|
+
const binary = process.platform === 'linux'
|
|
249
|
+
? path.join(__dirname, '..', 'native', 'luc')
|
|
250
|
+
: findBinary();
|
|
251
|
+
if (!binary || !fs.existsSync(binary)) throw new Error('The LUC runtime is missing. Run "luc install" first.');
|
|
252
|
+
const result = spawnSync(binary, ['install', pkg.name], { stdio: 'inherit' });
|
|
253
|
+
if (result.error || result.status !== 0) throw new Error(result.error ? result.error.message : 'LUC could not install ' + pkg.name + '.');
|
|
254
|
+
await requestJson('/api/packages/' + encodeURIComponent(pkg.slug) + '/install', { method: 'POST', body: {} });
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
throw new Error(message || 'The package has no downloadable .luic file.');
|
|
258
|
+
}
|
|
259
|
+
const destination = path.resolve(process.cwd(), pkg.name + '.luic');
|
|
260
|
+
if (fs.existsSync(destination)) throw new Error(pkg.name + '.luic already exists in this folder.');
|
|
261
|
+
fs.writeFileSync(destination, response.body, { flag: 'wx' });
|
|
262
|
+
await requestJson('/api/packages/' + encodeURIComponent(pkg.slug) + '/install', { method: 'POST', body: {} });
|
|
263
|
+
console.log('Installed ' + identifier + ' to ' + destination);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function publishPackage() {
|
|
267
|
+
const auth = readAuth();
|
|
268
|
+
if (!auth || !auth.token) throw new Error('Not logged in. Run "luc login" in this terminal first.');
|
|
269
|
+
const files = fs.readdirSync(process.cwd()).filter((name) => name.toLowerCase().endsWith('.luic'));
|
|
270
|
+
if (files.length === 0) throw new Error('No .luic file found in the current folder.');
|
|
271
|
+
if (files.length > 1) throw new Error('Found multiple .luic files. Keep only the package you want to publish in this folder.');
|
|
272
|
+
const filename = files[0];
|
|
273
|
+
const bytes = fs.readFileSync(path.join(process.cwd(), filename));
|
|
274
|
+
const upload = await requestJson('/api/auth/cli/uploads', {
|
|
275
|
+
method: 'POST',
|
|
276
|
+
headers: {
|
|
277
|
+
Authorization: 'Bearer ' + auth.token,
|
|
278
|
+
'Content-Type': 'application/octet-stream',
|
|
279
|
+
'X-File-Name': filename
|
|
280
|
+
},
|
|
281
|
+
body: bytes
|
|
282
|
+
});
|
|
283
|
+
const readmePath = ['README.md', 'readme.md', 'README'].map((name) => path.join(process.cwd(), name)).find(fs.existsSync);
|
|
284
|
+
const readme = readmePath ? fs.readFileSync(readmePath, 'utf8') : '';
|
|
285
|
+
if (readme.length > 60000) throw new Error('README.md must be 60,000 characters or fewer.');
|
|
286
|
+
await requestJson('/api/auth/cli/uploads/' + encodeURIComponent(upload.id) + '/readme', {
|
|
287
|
+
method: 'POST',
|
|
288
|
+
headers: { Authorization: 'Bearer ' + auth.token },
|
|
289
|
+
body: { readme }
|
|
290
|
+
});
|
|
291
|
+
const publishUrl = registry + '/#/publish?upload=' + encodeURIComponent(upload.id);
|
|
292
|
+
console.log('Package file and README attached. Finish the package name and description in your browser.');
|
|
293
|
+
openBrowser(publishUrl);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async function handleSiteCommand() {
|
|
297
|
+
const [command, ...rest] = args;
|
|
298
|
+
if (command === 'login') {
|
|
299
|
+
if (rest.length) throw new Error('Usage: luc login');
|
|
300
|
+
return login();
|
|
301
|
+
}
|
|
302
|
+
if (command === 'whoami') {
|
|
303
|
+
if (rest.length) throw new Error('Usage: luc whoami');
|
|
304
|
+
return whoami();
|
|
305
|
+
}
|
|
306
|
+
if (command === 'logout') {
|
|
307
|
+
if (rest.length) throw new Error('Usage: luc logout');
|
|
308
|
+
const auth = readAuth();
|
|
309
|
+
if (auth && auth.token) {
|
|
310
|
+
try {
|
|
311
|
+
await requestJson('/api/auth/logout', {
|
|
312
|
+
method: 'POST',
|
|
313
|
+
headers: { Authorization: 'Bearer ' + auth.token },
|
|
314
|
+
body: {}
|
|
315
|
+
});
|
|
316
|
+
} catch (_) {
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
clearAuth();
|
|
320
|
+
console.log('Logged out of this terminal.');
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
if (command === 'publish') {
|
|
324
|
+
if (rest.length !== 1 || rest[0] !== '-p') throw new Error('Usage: luc publish -p');
|
|
325
|
+
return publishPackage();
|
|
326
|
+
}
|
|
327
|
+
if (command === 'install' && rest[0] === '-p') {
|
|
328
|
+
if (rest.length !== 2 || !rest[1]) throw new Error('Usage: luc install -p @username/packagename');
|
|
329
|
+
return installPackage(rest[1]);
|
|
330
|
+
}
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async function runSiteCommand() {
|
|
335
|
+
try {
|
|
336
|
+
const handled = await handleSiteCommand();
|
|
337
|
+
if (handled !== null) return true;
|
|
338
|
+
return false;
|
|
339
|
+
} catch (error) {
|
|
340
|
+
console.error('luc: ' + (error.message || error));
|
|
341
|
+
process.exitCode = 1;
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (args[0] === 'login' || args[0] === 'whoami' || args[0] === 'logout' || args[0] === 'publish') {
|
|
347
|
+
runSiteCommand().then((handled) => { if (!handled) process.exitCode = 1; });
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (args[0] === 'install' && args[1] === '-p') {
|
|
352
|
+
runSiteCommand().then((handled) => { if (!handled) process.exitCode = 1; });
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (args[0] === 'install' && (args.length === 1 || args[1].startsWith('-'))) {
|
|
357
|
+
if (process.platform === 'linux') {
|
|
358
|
+
try {
|
|
359
|
+
const packageDir = path.join(__dirname, '..');
|
|
360
|
+
const { buildLinux } = require('../scripts/build-linux');
|
|
361
|
+
buildLinux(packageDir, 'luc');
|
|
362
|
+
} catch (error) {
|
|
363
|
+
console.error('luc: ' + error.message);
|
|
364
|
+
process.exitCode = 1;
|
|
365
|
+
}
|
|
366
|
+
} else if (process.platform === 'win32') {
|
|
367
|
+
process.exitCode = runInstaller(mapInstallerArgs(args.slice(1)));
|
|
368
|
+
} else {
|
|
369
|
+
console.error('luc: this package supports Windows and Linux only.');
|
|
370
|
+
process.exitCode = 1;
|
|
371
|
+
}
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (process.platform === 'linux') {
|
|
376
|
+
const packageDir = path.join(__dirname, '..');
|
|
377
|
+
const binary = path.join(packageDir, 'native', 'luc');
|
|
378
|
+
if (!fs.existsSync(binary)) {
|
|
379
|
+
console.error('luc: Linux runtime is missing; run "luc install" to build it.');
|
|
380
|
+
process.exit(1);
|
|
381
|
+
}
|
|
382
|
+
const result = spawnSync(binary, args, { stdio: 'inherit' });
|
|
383
|
+
if (result.error) {
|
|
384
|
+
console.error('luc: ' + result.error.message);
|
|
385
|
+
process.exit(1);
|
|
386
|
+
}
|
|
387
|
+
process.exit(result.status === null ? 1 : result.status);
|
|
388
|
+
}
|
|
389
|
+
|
|
97
390
|
if (process.platform !== 'win32') {
|
|
98
|
-
console.error('luc: this package
|
|
391
|
+
console.error('luc: this package supports Windows and Linux only.');
|
|
99
392
|
process.exit(1);
|
|
100
393
|
}
|
|
101
394
|
|
|
102
|
-
const args = process.argv.slice(2);
|
|
103
395
|
let binary = findBinary();
|
|
104
396
|
|
|
105
397
|
if (!binary) {
|
|
106
|
-
const
|
|
107
|
-
const status = runInstaller(isInstall ? mapInstallerArgs(args.slice(1)) : []);
|
|
398
|
+
const status = runInstaller([]);
|
|
108
399
|
if (status !== 0) process.exit(status);
|
|
109
|
-
if (isInstall) process.exit(0);
|
|
110
400
|
binary = findBinary();
|
|
111
401
|
if (!binary) {
|
|
112
402
|
console.error('luc: installation finished but luc.exe was not found.');
|