@hsusulist/luc 0.3.0 → 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 +21 -6
- package/bin/luc.js +292 -23
- package/install.ps1 +222 -9
- package/package.json +1 -1
- package/scripts/postinstall.js +2 -2
- package/scripts/prepack.js +1 -1
- package/src/lcode-linux/lcode.c +294 -2
- package/src/luc_core.c +5527 -5407
- package/src/luc_libs.c +5760 -5262
- package/src/luc_nums.c +1747 -0
- package/src/luc_trans.c +10 -19
package/README.md
CHANGED
|
@@ -9,22 +9,23 @@ luc --version
|
|
|
9
9
|
|
|
10
10
|
On Linux, the package compiles the runtime during installation. Install a C
|
|
11
11
|
compiler such as GCC or Clang first; set `CC` if your compiler is not named
|
|
12
|
-
`cc`. `luc
|
|
12
|
+
`cc`. Run `luc install` to rebuild the native runtime.
|
|
13
13
|
|
|
14
14
|
On Windows, the package installs the runtime into
|
|
15
15
|
`%LOCALAPPDATA%\Programs\LUC` (no PATH changes, no admin rights). If the
|
|
16
16
|
automatic install is skipped, run:
|
|
17
17
|
|
|
18
18
|
```powershell
|
|
19
|
-
luc
|
|
19
|
+
luc install
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
`luc
|
|
23
|
-
|
|
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:
|
|
24
25
|
|
|
25
26
|
```powershell
|
|
26
|
-
luc
|
|
27
|
-
luc
|
|
27
|
+
luc install -y --no-path
|
|
28
|
+
luc install -c window,ai -d "D:\Tools\LUC" -y
|
|
28
29
|
```
|
|
29
30
|
|
|
30
31
|
Available components: `window`, `ai`, `discord`, `lcode`, `vscode`, `source`.
|
|
@@ -39,3 +40,17 @@ LUC and LCode C sources.
|
|
|
39
40
|
|
|
40
41
|
The editor-only package uses the separate, signed `lcode-portable.zip` release
|
|
41
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
|
+
```
|
|
52
|
+
|
|
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,29 +5,12 @@ 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');
|
|
8
11
|
|
|
9
12
|
const args = process.argv.slice(2);
|
|
10
13
|
|
|
11
|
-
if (process.platform === 'linux') {
|
|
12
|
-
const packageDir = path.join(__dirname, '..');
|
|
13
|
-
const { buildLinux } = require('../scripts/build-linux');
|
|
14
|
-
if (args[0] === '--install') {
|
|
15
|
-
buildLinux(packageDir, 'luc');
|
|
16
|
-
process.exit(0);
|
|
17
|
-
}
|
|
18
|
-
const binary = path.join(packageDir, 'native', 'luc');
|
|
19
|
-
if (!fs.existsSync(binary)) {
|
|
20
|
-
console.error('luc: Linux runtime is missing; run "luc --install" to build it.');
|
|
21
|
-
process.exit(1);
|
|
22
|
-
}
|
|
23
|
-
const result = spawnSync(binary, args, { stdio: 'inherit' });
|
|
24
|
-
if (result.error) {
|
|
25
|
-
console.error('luc: ' + result.error.message);
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
process.exit(result.status === null ? 1 : result.status);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
14
|
function stateBinary() {
|
|
32
15
|
const local = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
|
|
33
16
|
try {
|
|
@@ -116,6 +99,294 @@ function mapInstallerArgs(args) {
|
|
|
116
99
|
return args.map((arg) => (Object.prototype.hasOwnProperty.call(map, arg) ? map[arg] : arg));
|
|
117
100
|
}
|
|
118
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
|
+
|
|
119
390
|
if (process.platform !== 'win32') {
|
|
120
391
|
console.error('luc: this package supports Windows and Linux only.');
|
|
121
392
|
process.exit(1);
|
|
@@ -124,10 +395,8 @@ if (process.platform !== 'win32') {
|
|
|
124
395
|
let binary = findBinary();
|
|
125
396
|
|
|
126
397
|
if (!binary) {
|
|
127
|
-
const
|
|
128
|
-
const status = runInstaller(isInstall ? mapInstallerArgs(args.slice(1)) : []);
|
|
398
|
+
const status = runInstaller([]);
|
|
129
399
|
if (status !== 0) process.exit(status);
|
|
130
|
-
if (isInstall) process.exit(0);
|
|
131
400
|
binary = findBinary();
|
|
132
401
|
if (!binary) {
|
|
133
402
|
console.error('luc: installation finished but luc.exe was not found.');
|