MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

expand Node compatibility coverage across builtins and packages

- Add Object.getOwnPropertyDescriptors support
- Treat native C functions as objects for Object prototype operations.
- Preserve display names for registered builtin module namespaces and use node: aliases in missing named export errors.
- Attach module context metadata to cached builtin namespaces.
- Register fs constants as the constants module.
- Add fs chmod/chmodSync support with numeric and octal-string modes.
- Add fs readlink/readlinkSync support.
- Export fs constants with additional open and stat mode constants.
- Add fs.realpath.native callback compatibility.
- Add path/posix and path/win32 registered module variants.
- Add buffer constants, kMaxLength, kStringMaxLength, and INSPECT_MAX_BYTES exports.
- Brand AbortSignal objects and validate abort signal APIs by brand.
- Add process.emitWarning formatting and warning event payload support.
- Add stream default high water mark getters and setters.
- Add util.callbackify.
- Add util.aborted for AbortSignal promises.
- Add parser support for parenthesized comma expressions in arrow parsing.
- Allow class members named static when they are methods or fields.
- Add super spread-call support through a SUPER_APPLY opcode.
- Add package resolver support for npm: alias dependency specs.
- Include parent path in package traversal de-duplication keys.
- Reuse cached package entries with file counts during exec temp linking.
- Avoid skipping installed packages when cached file counts indicate incomplete installs.
- Infer package binary names from package.json for exec temp runs.

+1794 -249
+1 -1
examples/results.txt
··· 1143 1143 compat-table/es2017/Atomics.wait.js: OK 1144 1144 compat-table/es2017/Atomics.xor.js: OK 1145 1145 compat-table/es2017/Object.entries.js: OK 1146 - compat-table/es2017/Object.getOwnPropertyDescriptors.js: TypeError: undefined is not a function 1146 + compat-table/es2017/Object.getOwnPropertyDescriptors.js: OK 1147 1147 compat-table/es2017/Object.getOwnPropertyDescriptors.no-undefined.js: TypeError: undefined is not a function 1148 1148 compat-table/es2017/Object.values.js: OK 1149 1149 compat-table/es2017/SharedArrayBuffer.Symbol.species.js: failed
+9
examples/spec/arrow.js
··· 17 17 const implicit = (a, b) => a - b; 18 18 test('arrow implicit return', implicit(10, 3), 7); 19 19 20 + const trailingSingle = a => a + 1; 21 + test('arrow trailing comma single param', trailingSingle(4), 5); 22 + 23 + const trailingMulti = (a, b) => a * b; 24 + test('arrow trailing comma multi param', trailingMulti(4, 5), 20); 25 + 26 + const trailingDestructure = ({ value }, extra) => value + extra; 27 + test('arrow trailing comma destructuring param', trailingDestructure({ value: 7 }, 3), 10); 28 + 20 29 const block = (a, b) => { 21 30 const sum = a + b; 22 31 return sum * 2;
+35
examples/spec/atomics.js
··· 38 38 test('Atomics.compareExchange no match', Atomics.compareExchange(int32, 0, 99, 10), 5); 39 39 test('after no match', Atomics.load(int32, 0), 5); 40 40 41 + Atomics.store(int32, 0, 1); 42 + const asyncNotEqual = Atomics.waitAsync(int32, 0, 2); 43 + test('Atomics.waitAsync not-equal sync', asyncNotEqual.async, false); 44 + test('Atomics.waitAsync not-equal value', asyncNotEqual.value, 'not-equal'); 45 + 46 + const asyncTimedOutSync = Atomics.waitAsync(int32, 0, 1, 0); 47 + test('Atomics.waitAsync zero timeout sync', asyncTimedOutSync.async, false); 48 + test('Atomics.waitAsync zero timeout value', asyncTimedOutSync.value, 'timed-out'); 49 + 50 + const timedWait = Atomics.waitAsync(int32, 0, 1, 5); 51 + test('Atomics.waitAsync timeout is async', timedWait.async, true); 52 + test('Atomics.waitAsync timeout value', await timedWait.value, 'timed-out'); 53 + 54 + Atomics.store(int32, 0, 3); 55 + const notifiedWait = Atomics.waitAsync(int32, 0, 3, 1000); 56 + test('Atomics.waitAsync notify is async', notifiedWait.async, true); 57 + test('Atomics.notify resolves async waiter count', Atomics.notify(int32, 0, 1), 1); 58 + test('Atomics.waitAsync notify value', await notifiedWait.value, 'ok'); 59 + 60 + Atomics.store(int32, 0, 4); 61 + let keepChecking = true; 62 + let waitAsyncChecks = 0; 63 + function checkWithoutSpinning() { 64 + if (!keepChecking) return; 65 + waitAsyncChecks++; 66 + const result = Atomics.waitAsync(int32, 0, 4, 1000); 67 + if (result.async) result.value.then(checkWithoutSpinning); 68 + else setImmediate(checkWithoutSpinning); 69 + } 70 + checkWithoutSpinning(); 71 + await new Promise(resolve => setImmediate(resolve)); 72 + test('pending waitAsync yields to immediates', waitAsyncChecks, 1); 73 + keepChecking = false; 74 + Atomics.notify(int32, 0, 1); 75 + 41 76 summary();
+15 -10
examples/spec/buffer.js
··· 1 1 import { test, testThrows, summary } from './helpers.js'; 2 + import * as buffer from 'node:buffer'; 2 3 3 4 console.log('Buffer Tests\n'); 4 5 ··· 71 72 test('BigUint64Array length', bu64.length, 2); 72 73 test('BigUint64Array BYTES_PER_ELEMENT', bu64.BYTES_PER_ELEMENT, 8); 73 74 74 - const buffer = new ArrayBuffer(16); 75 - const view8 = new Uint8Array(buffer); 76 - const view16 = new Uint16Array(buffer); 77 - const view32 = new Uint32Array(buffer); 78 - const view16f = new Float16Array(buffer); 75 + const backingBuffer = new ArrayBuffer(16); 76 + const view8 = new Uint8Array(backingBuffer); 77 + const view16 = new Uint16Array(backingBuffer); 78 + const view32 = new Uint32Array(backingBuffer); 79 + const view16f = new Float16Array(backingBuffer); 79 80 80 81 test('Uint8Array view length', view8.length, 16); 81 82 test('Uint16Array view length', view16.length, 8); 82 83 test('Uint32Array view length', view32.length, 4); 83 84 test('Float16Array view length', view16f.length, 8); 84 85 85 - const viewWithOffset = new Uint8Array(buffer, 4); 86 + const viewWithOffset = new Uint8Array(backingBuffer, 4); 86 87 test('view with offset length', viewWithOffset.length, 12); 87 88 test('view with offset byteOffset', viewWithOffset.byteOffset, 4); 88 89 89 - const viewWithOffsetAndLength = new Uint8Array(buffer, 4, 8); 90 + const viewWithOffsetAndLength = new Uint8Array(backingBuffer, 4, 8); 90 91 test('view offset+length length', viewWithOffsetAndLength.length, 8); 91 92 test('view offset+length byteOffset', viewWithOffsetAndLength.byteOffset, 4); 92 93 93 - const dv = new DataView(buffer); 94 + const dv = new DataView(backingBuffer); 94 95 test('DataView byteLength', dv.byteLength, 16); 95 96 test('DataView byteOffset', dv.byteOffset, 0); 96 97 97 - const dv2 = new DataView(buffer, 4); 98 + const dv2 = new DataView(backingBuffer, 4); 98 99 test('DataView offset byteLength', dv2.byteLength, 12); 99 100 test('DataView offset byteOffset', dv2.byteOffset, 4); 100 101 101 - const dv3 = new DataView(buffer, 4, 8); 102 + const dv3 = new DataView(backingBuffer, 4, 8); 102 103 test('DataView offset+length byteLength', dv3.byteLength, 8); 103 104 test('DataView offset+length byteOffset', dv3.byteOffset, 4); 104 105 ··· 167 168 test('Buffer.byteLength string', Buffer.byteLength('Hello'), 5); 168 169 test('Buffer.byteLength buffer', Buffer.byteLength(Buffer.from('Hello')), 5); 169 170 test('Buffer.byteLength empty', Buffer.byteLength(''), 0); 171 + test('buffer.constants exists', typeof buffer.constants, 'object'); 172 + test('buffer.constants.MAX_STRING_LENGTH exists', typeof buffer.constants.MAX_STRING_LENGTH, 'number'); 173 + test('buffer kStringMaxLength mirrors constants', buffer.kStringMaxLength, buffer.constants.MAX_STRING_LENGTH); 174 + test('buffer kMaxLength mirrors constants', buffer.kMaxLength, buffer.constants.MAX_LENGTH); 170 175 171 176 const concatBuf1 = Buffer.from('Hello'); 172 177 const concatBuf2 = Buffer.from(' ');
+36 -18
examples/spec/child_process.js
··· 1 1 import { test, testThrows, summary } from './helpers.js'; 2 2 import { spawn, exec, execSync, spawnSync } from 'child_process'; 3 + import { ChildProcess as NodeChildProcess } from 'node:child_process'; 3 4 4 5 console.log('Child Process Tests\n'); 5 6 ··· 45 46 46 47 const child = spawn('sh', ['-c', 'echo line1; echo line2']); 47 48 49 + test('node:child_process exports ChildProcess', typeof NodeChildProcess, 'function'); 48 50 test('spawn returns object', typeof child, 'object'); 51 + test('spawn returns ChildProcess instance', child instanceof NodeChildProcess, true); 49 52 test('spawn has pid', child.pid > 0, true); 50 53 test('spawn has on method', typeof child.on, 'function'); 51 54 test('spawn has once method', typeof child.once, 'function'); 52 55 test('spawn has kill method', typeof child.kill, 'function'); 53 56 57 + const constructedChild = new NodeChildProcess(); 58 + let constructedChildEvent = false; 59 + constructedChild.on('ping', () => { 60 + constructedChildEvent = true; 61 + }); 62 + constructedChild.emit('ping'); 63 + test('new ChildProcess has EventEmitter behavior', constructedChildEvent, true); 64 + 54 65 child.on('stdout', data => { 55 66 spawnStdout += data; 56 67 }); ··· 59 70 spawnExitCode = code; 60 71 }); 61 72 62 - const childClosedP = new Promise(resolve => child.on('close', () => { 63 - spawnClosed = true; 64 - test('spawn collects stdout', spawnStdout.trim(), 'line1\nline2'); 65 - test('spawn exit code', spawnExitCode, 0); 66 - test('spawn close event fired', spawnClosed, true); 67 - resolve(); 68 - })); 73 + const childClosedP = new Promise(resolve => 74 + child.on('close', () => { 75 + spawnClosed = true; 76 + test('spawn collects stdout', spawnStdout.trim(), 'line1\nline2'); 77 + test('spawn exit code', spawnExitCode, 0); 78 + test('spawn close event fired', spawnClosed, true); 79 + resolve(); 80 + }) 81 + ); 69 82 70 83 let stdinRoundTrip = ''; 71 84 const stdinChild = spawn('cat'); ··· 75 88 stdinChild.stdin.write('ping'); 76 89 stdinChild.stdin.end(); 77 90 78 - const stdinChildDoneP = new Promise(resolve => stdinChild.on('close', () => { 79 - test('spawn stdin round trip', stdinRoundTrip, 'ping'); 80 - resolve(); 81 - })); 91 + const stdinChildDoneP = new Promise(resolve => 92 + stdinChild.on('close', () => { 93 + test('spawn stdin round trip', stdinRoundTrip, 'ping'); 94 + resolve(); 95 + }) 96 + ); 82 97 83 - const shellChildDoneP = childClosedP.then(() => new Promise(resolve => { 84 - const shellChild = spawn('echo $HOME', [], { shell: true }); 85 - shellChild.on('close', () => { 86 - test('spawn with shell option', shellChild.stdout.length > 0, true); 87 - resolve(); 88 - }); 89 - })); 98 + const shellChildDoneP = childClosedP.then( 99 + () => 100 + new Promise(resolve => { 101 + const shellChild = spawn('echo $HOME', [], { shell: true }); 102 + shellChild.on('close', () => { 103 + test('spawn with shell option', shellChild.stdout.length > 0, true); 104 + resolve(); 105 + }); 106 + }) 107 + ); 90 108 91 109 const stderrChild = spawn('sh', ['-c', 'echo err >&2']); 92 110 let stderrData = '';
+31
examples/spec/classes.js
··· 160 160 let bird = new Bird('Tweety'); 161 161 test('super[method]()', bird.speak(), 'Tweety makes a sound - chirp!'); 162 162 163 + class SpreadBase { 164 + constructor(a, b) { 165 + this.sum = a + b; 166 + } 167 + } 168 + 169 + class SpreadDerived extends SpreadBase { 170 + constructor() { 171 + super(...arguments); 172 + } 173 + } 174 + 175 + let spreadDerived = new SpreadDerived(3, 4); 176 + test('super spread arguments', spreadDerived.sum, 7); 177 + 163 178 class Static { 164 179 static value = 42; 165 180 static method() { ··· 169 184 170 185 test('static property', Static.value, 42); 171 186 test('static method', Static.method(), 'static'); 187 + 188 + class StaticNamedElements { 189 + static(value) { 190 + return value + 1; 191 + } 192 + } 193 + 194 + let staticNamedElements = new StaticNamedElements(); 195 + test('instance method named static', staticNamedElements.static(4), 5); 196 + 197 + class StaticNamedField { 198 + static = 7; 199 + } 200 + 201 + let staticNamedField = new StaticNamedField(); 202 + test('instance field named static', staticNamedField.static, 7); 172 203 173 204 class BankAccount { 174 205 #balance = 0;
+78 -1
examples/spec/events.js
··· 1 - import { test, summary } from './helpers.js'; 1 + import { test, testDeep, summary } from './helpers.js'; 2 2 import { EventEmitter } from 'ant:events'; 3 + import { once as nodeOnce, addAbortListener, getMaxListeners, setMaxListeners } from 'node:events'; 3 4 4 5 console.log('EventEmitter Tests\n'); 5 6 ··· 206 207 207 208 const ce3 = new CustomEvent('tagged'); 208 209 test('CustomEvent Symbol.toStringTag', Object.prototype.toString.call(ce3), '[object CustomEvent]'); 210 + 211 + console.log('\nnode:events Helper Tests\n'); 212 + 213 + const eeNodeOnce = new EventEmitter(); 214 + const eeNodeOncePromise = nodeOnce(eeNodeOnce, 'ready'); 215 + eeNodeOnce.emit('ready', 'ok', 7); 216 + testDeep('events.once resolves emitted args', await eeNodeOncePromise, ['ok', 7]); 217 + 218 + const etNodeOnce = new EventTarget(); 219 + const etNodeOncePromise = nodeOnce(etNodeOnce, 'ping'); 220 + etNodeOnce.dispatchEvent(new Event('ping')); 221 + const etNodeOnceArgs = await etNodeOncePromise; 222 + test('events.once supports EventTarget', etNodeOnceArgs[0].type, 'ping'); 223 + 224 + const abortController = new AbortController(); 225 + const abortPromise = nodeOnce(abortController.signal, 'abort'); 226 + abortController.abort('stop'); 227 + const abortArgs = await abortPromise; 228 + test('events.once supports AbortSignal', abortArgs[0].type, 'abort'); 229 + 230 + let duckOnceRejected = false; 231 + try { 232 + await nodeOnce( 233 + { 234 + once(_name, listener) { 235 + listener('bad'); 236 + } 237 + }, 238 + 'ready' 239 + ); 240 + } catch { 241 + duckOnceRejected = true; 242 + } 243 + test('events.once rejects once-shaped plain objects', duckOnceRejected, true); 244 + 245 + let protoEmitterRejected = false; 246 + try { 247 + await nodeOnce(Object.create(EventEmitter.prototype), 'ready'); 248 + } catch { 249 + protoEmitterRejected = true; 250 + } 251 + test('events.once rejects EventEmitter prototype spoofing', protoEmitterRejected, true); 252 + 253 + let duckTargetRejected = false; 254 + try { 255 + await nodeOnce( 256 + { 257 + addEventListener(_name, listener) { 258 + listener(new Event('bad')); 259 + } 260 + }, 261 + 'ready' 262 + ); 263 + } catch { 264 + duckTargetRejected = true; 265 + } 266 + test('events.once rejects EventTarget-shaped plain objects', duckTargetRejected, true); 267 + 268 + let protoTargetRejected = false; 269 + try { 270 + await nodeOnce(Object.create(EventTarget.prototype), 'ready'); 271 + } catch { 272 + protoTargetRejected = true; 273 + } 274 + test('events.once rejects EventTarget prototype spoofing', protoTargetRejected, true); 275 + 276 + let addAbortValue = 0; 277 + const addAbortController = new AbortController(); 278 + const disposable = addAbortListener(addAbortController.signal, () => { 279 + addAbortValue++; 280 + }); 281 + addAbortController.abort(); 282 + test('events.addAbortListener fires on abort', addAbortValue, 1); 283 + test('events.addAbortListener returns disposable', typeof disposable.dispose, 'function'); 284 + test('events.getMaxListeners default', getMaxListeners(eeNodeOnce), 10); 285 + test('events.setMaxListeners no-op return', setMaxListeners(20, eeNodeOnce), undefined); 209 286 210 287 summary();
+15 -1
examples/spec/iterators.js
··· 16 16 test('string iterator third', strIter.next().value, 'c'); 17 17 test('string iterator done', strIter.next().done, true); 18 18 19 - const map = new Map([['a', 1], ['b', 2]]); 19 + const map = new Map([ 20 + ['a', 1], 21 + ['b', 2] 22 + ]); 20 23 const mapIter = map.entries(); 21 24 testDeep('map entries first', mapIter.next().value, ['a', 1]); 22 25 testDeep('map entries second', mapIter.next().value, ['b', 2]); ··· 58 61 customSum += n; 59 62 } 60 63 test('custom iterator', customSum, 60); 64 + 65 + let capturedNestedForOf = 0; 66 + const capturedFns = {}; 67 + for (const { values = [] } of [{ values: ['a', 'bb'] }]) { 68 + for (const value of values) { 69 + capturedFns[value] = () => value.length; 70 + capturedNestedForOf++; 71 + } 72 + } 73 + test('captured nested for-of count', capturedNestedForOf, 2); 74 + test('captured nested for-of closure', capturedFns.bb(), 2); 61 75 62 76 summary();
+43 -3
examples/spec/objects.js
··· 60 60 let { y = 10 } = {}; 61 61 test('destructuring default', y, 10); 62 62 63 - test('hasOwnProperty true', ({ a: 1 }).hasOwnProperty('a'), true); 64 - test('hasOwnProperty false', ({ a: 1 }).hasOwnProperty('b'), false); 63 + test('hasOwnProperty true', { a: 1 }.hasOwnProperty('a'), true); 64 + test('hasOwnProperty false', { a: 1 }.hasOwnProperty('b'), false); 65 65 66 66 let frozen = Object.freeze({ a: 1 }); 67 67 test('Object.isFrozen', Object.isFrozen(frozen), true); ··· 75 75 76 76 test('Object.getPrototypeOf', Object.getPrototypeOf([]) === Array.prototype, true); 77 77 78 - testDeep('Object.fromEntries', Object.fromEntries([['a', 1], ['b', 2]]), { a: 1, b: 2 }); 78 + testDeep( 79 + 'Object.fromEntries', 80 + Object.fromEntries([ 81 + ['a', 1], 82 + ['b', 2] 83 + ]), 84 + { a: 1, b: 2 } 85 + ); 86 + 87 + let descriptorSource = {}; 88 + const descriptorSymbol = Symbol('descriptor'); 89 + Object.defineProperty(descriptorSource, 'hidden', { 90 + value: 42, 91 + enumerable: false, 92 + writable: false, 93 + configurable: false 94 + }); 95 + Object.defineProperty(descriptorSource, 'computed', { 96 + get() { 97 + return 7; 98 + }, 99 + enumerable: true, 100 + configurable: true 101 + }); 102 + descriptorSource.visible = 'yes'; 103 + descriptorSource[descriptorSymbol] = 'symbol value'; 104 + 105 + const descriptors = Object.getOwnPropertyDescriptors(descriptorSource); 106 + test('Object.getOwnPropertyDescriptors exists', typeof Object.getOwnPropertyDescriptors, 'function'); 107 + test('descriptors data value', descriptors.visible.value, 'yes'); 108 + test('descriptors hidden value', descriptors.hidden.value, 42); 109 + test('descriptors hidden enumerable', descriptors.hidden.enumerable, false); 110 + test('descriptors hidden writable', descriptors.hidden.writable, false); 111 + test('descriptors hidden configurable', descriptors.hidden.configurable, false); 112 + test('descriptors accessor getter', typeof descriptors.computed.get, 'function'); 113 + test('descriptors accessor enumerable', descriptors.computed.enumerable, true); 114 + test('descriptors symbol value', descriptors[descriptorSymbol].value, 'symbol value'); 115 + 116 + const arrayDescriptors = Object.getOwnPropertyDescriptors(['item']); 117 + test('array descriptor index value', arrayDescriptors[0].value, 'item'); 118 + test('array descriptor length value', arrayDescriptors.length.value, 1); 79 119 80 120 summary();
+7
examples/spec/path.js
··· 1 1 import { test, summary } from './helpers.js'; 2 2 import path from 'ant:path'; 3 + import nodePathPosix from 'node:path/posix'; 4 + import antPathPosix from 'ant:path/posix'; 5 + import barePathPosix from 'path/posix'; 3 6 4 7 console.log('Path Tests\n'); 5 8 ··· 32 35 33 36 test('path.sep', path.sep, '/'); 34 37 test('path.delimiter', path.delimiter, ':'); 38 + 39 + test('node:path/posix join', nodePathPosix.join('a', 'b'), 'a/b'); 40 + test('ant:path/posix sep', antPathPosix.sep, '/'); 41 + test('path/posix dirname', barePathPosix.dirname('/a/b/c.txt'), '/a/b'); 35 42 36 43 summary();
+8
examples/spec/process.js
··· 1 1 import { test, summary } from './helpers.js'; 2 + import nodeProcess, { cwd as nodeCwd, execPath as nodeExecPath, hrtime as nodeHrtime, stdout as nodeStdout } from 'node:process'; 2 3 3 4 console.log('Process Tests\n'); 4 5 5 6 test('process exists', typeof process, 'object'); 7 + test('node:process default is global process', nodeProcess, process); 8 + test('node:process named cwd is function', typeof nodeCwd, 'function'); 9 + test('node:process named execPath is string', typeof nodeExecPath, 'string'); 10 + test('node:process named hrtime is function', typeof nodeHrtime, 'function'); 11 + test('node:process named hrtime.bigint is function', typeof nodeHrtime.bigint, 'function'); 12 + test('node:process named hrtime matches process.hrtime', nodeHrtime, process.hrtime); 13 + test('node:process named stdout is global stdout', nodeStdout, process.stdout); 6 14 test('process toStringTag', Object.prototype.toString.call(process), '[object process]'); 7 15 8 16 test('pid is number', typeof process.pid, 'number');
+4 -1
include/common.h
··· 114 114 BRAND_WASM_TAG, 115 115 BRAND_WASM_EXCEPTION, 116 116 BRAND_DATE, 117 - BRAND_MODULE_NAMESPACE 117 + BRAND_MODULE_NAMESPACE, 118 + BRAND_ABORT_SIGNAL, 119 + BRAND_EVENTEMITTER, 120 + BRAND_EVENTTARGET 118 121 } object_brand_id_t; 119 122 120 123 static inline void *mantissa_chk(void *p, const char *func) {
+1
include/gc/modules.h
··· 6 6 typedef void (*gc_mark_fn)(ant_t *js, ant_value_t v); 7 7 8 8 void gc_mark_timers(ant_t *js, gc_mark_fn mark); 9 + void gc_mark_atomics(ant_t *js, gc_mark_fn mark); 9 10 void gc_mark_ffi(ant_t *js, gc_mark_fn mark); 10 11 void gc_mark_fetch(ant_t *js, gc_mark_fn mark); 11 12 void gc_mark_fs(ant_t *js, gc_mark_fn mark);
+1
include/modules/fs.h
··· 5 5 6 6 ant_value_t fs_library(ant_t *js); 7 7 ant_value_t fs_promises_library(ant_t *js); 8 + ant_value_t fs_constants_library(ant_t *js); 8 9 9 10 int has_pending_fs_ops(void); 10 11 void init_fs_module(void);
+2
include/modules/path.h
··· 4 4 #include "types.h" 5 5 6 6 ant_value_t path_library(ant_t *js); 7 + ant_value_t path_posix_library(ant_t *js); 8 + ant_value_t path_win32_library(ant_t *js); 7 9 8 10 #endif
+1
include/silver/opcode.h
··· 162 162 OP_DEF( TAIL_CALL_METHOD, 3, 2, 0, npop) 163 163 OP_DEF( NEW, 3, 2, 1, npop) /* func new.target args -> obj */ 164 164 OP_DEF( APPLY, 3, 3, 1, u16) /* func this [args] -> result */ 165 + OP_DEF( SUPER_APPLY, 3, 3, 1, u16) /* super this [args] -> result */ 165 166 OP_DEF( NEW_APPLY, 3, 2, 1, u16) /* func new.target [args] -> obj */ 166 167 OP_DEF( EVAL, 5, 1, 1, npop) /* direct eval */ 167 168 OP_DEF( RETURN, 1, 1, 0, none)
+79 -3
src/ant.c
··· 5604 5604 uint8_t t = vtype(obj); 5605 5605 5606 5606 if (t == T_STR || t == T_NUM || t == T_BOOL || t == T_BIGINT) return get_prototype_for_type(js, t); 5607 + if (t == T_CFUNC) return get_prototype_for_type(js, t); 5607 5608 if (is_object_type(obj)) return get_proto(js, obj); 5608 5609 5609 5610 return js_mknull(); ··· 5616 5617 ant_value_t proto = args[1]; 5617 5618 5618 5619 uint8_t t = vtype(obj); 5620 + if (t == T_CFUNC) { 5621 + obj = js_cfunc_promote(js, obj); 5622 + t = T_FUNC; 5623 + } 5624 + 5619 5625 if (t != T_OBJ && t != T_ARR && t != T_FUNC) { 5620 5626 return js_mkerr(js, "Object.setPrototypeOf: first argument must be an object"); 5621 5627 } 5622 5628 5623 5629 uint8_t pt = vtype(proto); 5630 + if (pt == T_CFUNC) { 5631 + proto = js_cfunc_promote(js, proto); 5632 + pt = T_FUNC; 5633 + } 5634 + 5624 5635 if (pt != T_OBJ && pt != T_ARR && pt != T_FUNC && pt != T_NULL) { 5625 5636 return js_mkerr(js, "Object.setPrototypeOf: prototype must be an object or null"); 5626 5637 } ··· 6327 6338 return proxy_get_own_property_descriptor(js, as_obj, key); 6328 6339 } 6329 6340 6341 + bool is_arr_obj = array_obj_ptr(as_obj) != NULL; 6342 + bool is_arr_length = !is_sym && is_arr_obj && is_length_key(key_str, key_len); 6343 + bool has_arr_index = false; 6344 + ant_value_t arr_index_val = js_mkundef(); 6345 + 6346 + if (!is_sym && is_arr_obj && is_array_index(key_str, key_len)) { 6347 + unsigned long idx; 6348 + if ( 6349 + parse_array_index(key_str, key_len, get_array_length(js, as_obj), &idx) && 6350 + arr_has(js, as_obj, (ant_offset_t)idx) 6351 + ) { has_arr_index = true; arr_index_val = arr_get(js, as_obj, (ant_offset_t)idx); } 6352 + } 6353 + 6330 6354 ant_offset_t sym_off = is_sym ? (ant_offset_t)vdata(key) : 0; 6331 6355 prop_meta_t sym_meta; prop_meta_t str_meta; 6332 6356 ··· 6334 6358 bool has_str_meta = is_sym ? false : lookup_string_prop_meta(js, as_obj, key_str, (size_t)key_len, &str_meta); 6335 6359 6336 6360 ant_offset_t prop_off = is_sym ? lkp_sym(js, as_obj, sym_off) : lkp(js, as_obj, key_str, key_len); 6337 - if (prop_off == 0 && !(is_sym ? has_sym_meta : has_str_meta)) { 6361 + if (prop_off == 0 && !(is_sym ? has_sym_meta : has_str_meta) && !is_arr_length && !has_arr_index) { 6338 6362 return js_mkundef(); 6339 6363 } 6340 6364 ··· 6378 6402 if (prop_off != 0) { 6379 6403 prop_val = propref_load(js, prop_off); 6380 6404 has_value_out = true; 6381 - } else if (!is_sym && is_length_key(key_str, key_len) && array_obj_ptr(as_obj)) { 6405 + } else if (has_arr_index) { 6406 + prop_val = arr_index_val; 6407 + has_value_out = true; 6408 + } else if (is_arr_length) { 6382 6409 prop_val = tov((double)get_array_length(js, as_obj)); 6383 6410 has_value_out = true; 6384 6411 } ··· 6471 6498 } 6472 6499 6473 6500 return mkval(T_ARR, vdata(arr)); 6501 + } 6502 + 6503 + static ant_value_t object_add_descriptors_for_keys( 6504 + ant_t *js, 6505 + ant_value_t result, 6506 + ant_value_t obj, 6507 + ant_value_t keys 6508 + ) { 6509 + ant_offset_t len = get_array_length(js, keys); 6510 + 6511 + for (ant_offset_t i = 0; i < len; i++) { 6512 + ant_value_t key = arr_get(js, keys, i); 6513 + if (vtype(key) == T_UNDEF) continue; 6514 + 6515 + ant_value_t desc_args[2] = { obj, key }; 6516 + ant_value_t desc = builtin_object_getOwnPropertyDescriptor(js, desc_args, 2); 6517 + if (is_err(desc)) return desc; 6518 + if (vtype(desc) == T_UNDEF) continue; 6519 + 6520 + ant_value_t set_result = js_setprop(js, result, key, desc); 6521 + if (is_err(set_result)) return set_result; 6522 + } 6523 + 6524 + return js_mkundef(); 6525 + } 6526 + 6527 + static ant_value_t builtin_object_getOwnPropertyDescriptors(ant_t *js, ant_value_t *args, int nargs) { 6528 + ant_value_t result = js_mkobj(js); 6529 + if (nargs == 0) return result; 6530 + 6531 + ant_value_t obj = args[0]; 6532 + uint8_t t = vtype(obj); 6533 + if (t != T_OBJ && t != T_ARR && t != T_FUNC) return result; 6534 + 6535 + ant_value_t names = builtin_object_getOwnPropertyNames(js, &obj, 1); 6536 + if (is_err(names)) return names; 6537 + ant_value_t err = object_add_descriptors_for_keys(js, result, obj, names); 6538 + if (is_err(err)) return err; 6539 + 6540 + ant_value_t symbols = builtin_object_getOwnPropertySymbols(js, &obj, 1); 6541 + if (is_err(symbols)) return symbols; 6542 + err = object_add_descriptors_for_keys(js, result, obj, symbols); 6543 + if (is_err(err)) return err; 6544 + 6545 + return result; 6474 6546 } 6475 6547 6476 6548 static ant_value_t builtin_object_isExtensible(ant_t *js, ant_value_t *args, int nargs) { ··· 11819 11891 GC_ROOT_RESTORE(js, root_mark); 11820 11892 return filename_val; 11821 11893 } 11894 + 11822 11895 GC_ROOT_PIN(js, filename_val); 11823 11896 setprop_cstr(js, module_ctx, "filename", 8, filename_val); 11897 + setprop_cstr(js, module_ctx, "displayName", 11, filename_val); 11824 11898 11825 11899 ant_value_t import_meta = js_create_import_meta_for_context(js, module_ctx, filename, is_main); 11826 11900 if (is_err(import_meta)) { 11827 11901 GC_ROOT_RESTORE(js, root_mark); 11828 11902 return import_meta; 11829 11903 } 11904 + 11830 11905 GC_ROOT_PIN(js, import_meta); 11831 11906 setprop_cstr(js, module_ctx, "meta", 4, import_meta); 11832 - 11833 11907 GC_ROOT_RESTORE(js, root_mark); 11908 + 11834 11909 return module_ctx; 11835 11910 } 11836 11911 ··· 12688 12763 defmethod(js, obj_func_obj, "isSealed", 8, js_mkfun(builtin_object_isSealed)); 12689 12764 defmethod(js, obj_func_obj, "fromEntries", 11, js_mkfun(builtin_object_fromEntries)); 12690 12765 defmethod(js, obj_func_obj, "getOwnPropertyDescriptor", 24, js_mkfun(builtin_object_getOwnPropertyDescriptor)); 12766 + defmethod(js, obj_func_obj, "getOwnPropertyDescriptors", 25, js_mkfun(builtin_object_getOwnPropertyDescriptors)); 12691 12767 defmethod(js, obj_func_obj, "getOwnPropertyNames", 19, js_mkfun(builtin_object_getOwnPropertyNames)); 12692 12768 defmethod(js, obj_func_obj, "getOwnPropertySymbols", 21, js_mkfun(builtin_object_getOwnPropertySymbols)); 12693 12769 defmethod(js, obj_func_obj, "isExtensible", 12, js_mkfun(builtin_object_isExtensible));
+26 -3
src/esm/library.c
··· 1 1 #include "esm/library.h" 2 2 #include "gc/roots.h" 3 3 #include "ant.h" 4 + #include "internal.h" 4 5 5 6 #include <stdarg.h> 6 7 #include <string.h> ··· 8 9 9 10 typedef struct ant_library_entry { 10 11 char name[256]; 12 + char display_name[256]; 11 13 ant_library_init_fn init_fn; 12 14 ant_value_t cached_ns; 13 15 bool ns_initialized; ··· 17 19 18 20 static ant_library_entry_t *library_registry = NULL; 19 21 22 + static bool ant_library_display_name_is_better(const char *candidate, const char *current) { 23 + if (!candidate || !candidate[0]) return false; 24 + if (!current || !current[0]) return true; 25 + 26 + bool candidate_is_node = strncmp(candidate, "node:", 5) == 0; 27 + bool current_is_node = strncmp(current, "node:", 5) == 0; 28 + return candidate_is_node && !current_is_node; 29 + } 30 + 20 31 void ant_register_library(ant_library_init_fn init_fn, const char *name, ...) { 21 32 va_list args; 22 33 const char *alias = name; ··· 26 37 while (alias != NULL) { 27 38 ant_library_entry_t *lib = (ant_library_entry_t *)ant_calloc(sizeof(ant_library_entry_t)); 28 39 if (!lib) break; 29 - 40 + 30 41 strncpy(lib->name, alias, sizeof(lib->name) - 1); 31 42 lib->name[sizeof(lib->name) - 1] = '\0'; 32 43 lib->init_fn = init_fn; 33 44 lib->ns_initialized = false; 34 - 45 + 35 46 if (!canonical_entry) canonical_entry = lib; 36 47 lib->canonical = canonical_entry; 37 - 48 + 49 + if (ant_library_display_name_is_better(alias, canonical_entry->display_name)) { 50 + strncpy(canonical_entry->display_name, alias, sizeof(canonical_entry->display_name) - 1); 51 + canonical_entry->display_name[sizeof(canonical_entry->display_name) - 1] = '\0'; 52 + } 53 + 38 54 HASH_ADD_STR(library_registry, name, lib); 39 55 alias = va_arg(args, const char *); 40 56 } ··· 70 86 if (canon->ns_initialized) return canon->cached_ns; 71 87 72 88 canon->cached_ns = canon->init_fn(js); 89 + if (is_object_type(canon->cached_ns)) { 90 + const char *display_name = canon->display_name[0] ? canon->display_name : canon->name; 91 + ant_value_t module_ctx = js_create_module_context(js, display_name, false); 92 + if (is_err(module_ctx)) return module_ctx; 93 + if (is_object_type(module_ctx)) js_set_slot_wb(js, canon->cached_ns, SLOT_MODULE_CTX, module_ctx); 94 + } 95 + 73 96 canon->ns_initialized = true; 74 97 gc_register_root(&canon->cached_ns); 75 98
+1
src/gc/objects.c
··· 486 486 487 487 gc_visit_roots(js, gc_mark_value); 488 488 gc_mark_timers(js, gc_mark_value); 489 + gc_mark_atomics(js, gc_mark_value); 489 490 gc_mark_ffi(js, gc_mark_value); 490 491 gc_mark_fetch(js, gc_mark_value); 491 492 gc_mark_fs(js, gc_mark_value);
+4
src/main.c
··· 643 643 ant_standard_library("buffer", buffer_library); 644 644 ant_standard_library("path", path_library); 645 645 ant_standard_library("fs", fs_library); 646 + ant_standard_library("constants", fs_constants_library); 646 647 ant_standard_library("os", os_library); 647 648 ant_standard_library("url", url_library); 648 649 ant_standard_library("perf_hooks", perf_hooks_library); ··· 659 660 ant_standard_library("string_decoder", string_decoder_library); 660 661 ant_standard_library("stream", stream_library); 661 662 ant_standard_library("timers", timers_library); 663 + 664 + ant_register_library(path_posix_library, "path/posix", "ant:path/posix", "node:path/posix", NULL); 665 + ant_register_library(path_win32_library, "path/win32", "ant:path/win32", "node:path/win32", NULL); 662 666 663 667 ant_standard_library("fs/promises", fs_promises_library); 664 668 ant_standard_library("timers/promises", timers_promises_library);
+7 -6
src/modules/abort.c
··· 56 56 57 57 static abort_signal_data_t *get_signal_data_if_signal_object(ant_value_t obj) { 58 58 if (!g_initialized || !is_object_type(obj)) return NULL; 59 - if (js_get_slot(obj, SLOT_PROTO) != g_signal_proto) return NULL; 59 + if (!js_check_brand(obj, BRAND_ABORT_SIGNAL)) return NULL; 60 60 return get_signal_data(obj); 61 61 } 62 62 ··· 155 155 } 156 156 157 157 void abort_signal_remove_listener(ant_t *js, ant_value_t signal, ant_value_t callback) { 158 - abort_signal_data_t *data = get_signal_data(signal); 158 + abort_signal_data_t *data = get_signal_data_if_signal_object(signal); 159 159 if (!data) return; 160 160 161 161 unsigned int n = abort_array_len(data->listeners); ··· 187 187 188 188 ant_value_t obj = js_mkobj(js); 189 189 js_set_slot(obj, SLOT_DATA, ANT_PTR(data)); 190 + js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_ABORT_SIGNAL)); 190 191 if (g_initialized) js_set_slot_wb(js, obj, SLOT_PROTO, g_signal_proto); 191 192 192 193 js_set(js, obj, "aborted", js_false); ··· 472 473 } 473 474 474 475 bool abort_signal_is_aborted(ant_value_t signal) { 475 - abort_signal_data_t *data = get_signal_data(signal); 476 + abort_signal_data_t *data = get_signal_data_if_signal_object(signal); 476 477 return data && data->aborted; 477 478 } 478 479 479 480 bool abort_signal_is_signal(ant_value_t signal) { 480 - return get_signal_data(signal) != NULL; 481 + return get_signal_data_if_signal_object(signal) != NULL; 481 482 } 482 483 483 484 ant_value_t abort_signal_get_reason(ant_value_t signal) { 484 - abort_signal_data_t *data = get_signal_data(signal); 485 + abort_signal_data_t *data = get_signal_data_if_signal_object(signal); 485 486 return data ? data->reason : js_mkundef(); 486 487 } 487 488 488 489 void abort_signal_add_listener(ant_t *js, ant_value_t signal, ant_value_t callback) { 489 - abort_signal_data_t *data = get_signal_data(signal); 490 + abort_signal_data_t *data = get_signal_data_if_signal_object(signal); 490 491 if (!data) return; 491 492 492 493 if (data->aborted) {
+17
src/modules/buffer.c
··· 19 19 20 20 #define BUFFER_REGISTRY_INITIAL_CAP 64 21 21 22 + // Node compatibility exports only 23 + // Ant does not enforce these as allocation limits 24 + #define BUFFER_COMPAT_MAX_LENGTH 4294967296.0 25 + #define BUFFER_COMPAT_MAX_STRING_LENGTH 536870888.0 26 + #define BUFFER_COMPAT_INSPECT_MAX_BYTES 50.0 27 + 22 28 static size_t ta_metadata_bytes = 0; 23 29 static size_t buffer_registry_count = 0; 24 30 static size_t buffer_registry_cap = 0; ··· 2362 2368 return obj; 2363 2369 } 2364 2370 2371 + static ant_value_t buffer_make_constants(ant_t *js) { 2372 + ant_value_t constants = js_newobj(js); 2373 + js_set(js, constants, "MAX_LENGTH", js_mknum(BUFFER_COMPAT_MAX_LENGTH)); 2374 + js_set(js, constants, "MAX_STRING_LENGTH", js_mknum(BUFFER_COMPAT_MAX_STRING_LENGTH)); 2375 + return constants; 2376 + } 2377 + 2365 2378 ant_value_t buffer_library(ant_t *js) { 2366 2379 ant_value_t glob = js_glob(js); 2367 2380 ant_value_t lib = js_newobj(js); ··· 2371 2384 js_set(js, lib, "File", js_get(js, glob, "File")); 2372 2385 js_set(js, lib, "atob", js_get(js, glob, "atob")); 2373 2386 js_set(js, lib, "btoa", js_get(js, glob, "btoa")); 2387 + js_set(js, lib, "constants", buffer_make_constants(js)); 2388 + js_set(js, lib, "kMaxLength", js_mknum(BUFFER_COMPAT_MAX_LENGTH)); 2389 + js_set(js, lib, "kStringMaxLength", js_mknum(BUFFER_COMPAT_MAX_STRING_LENGTH)); 2390 + js_set(js, lib, "INSPECT_MAX_BYTES", js_mknum(BUFFER_COMPAT_INSPECT_MAX_BYTES)); 2374 2391 2375 2392 return lib; 2376 2393 }
+39 -1
src/modules/child_process.c
··· 31 31 #include "gc/modules.h" 32 32 #include "modules/child_process.h" 33 33 #include "modules/buffer.h" 34 + #include "modules/events.h" 34 35 #include "modules/symbol.h" 35 36 36 37 #define MAX_CHILD_LISTENERS 16 ··· 108 109 109 110 static child_process_t *pending_children_head = NULL; 110 111 static child_process_t *pending_children_tail = NULL; 112 + 113 + static ant_value_t g_child_process_proto = 0; 114 + static ant_value_t g_child_process_ctor = 0; 111 115 112 116 static void fprint_js_str_raw(FILE *out, ant_t *js, ant_value_t s) { 113 117 if (vtype(s) != T_STR) { ··· 501 505 502 506 ant_value_t cp_ptr = js_get_slot(this_obj, SLOT_DATA); 503 507 if (vtype(cp_ptr) == T_UNDEF) return js_mkerr(js, "Invalid child process object"); 504 - 505 508 child_process_t *cp = (child_process_t *)(uintptr_t)js_getnum(cp_ptr); 506 509 507 510 size_t name_len; ··· 679 682 return child_end_impl(cp); 680 683 } 681 684 685 + static ant_value_t child_process_ctor(ant_t *js, ant_value_t *args, int nargs) { 686 + ant_value_t obj = js_mkobj(js); 687 + if (is_object_type(g_child_process_proto)) js_set_proto_init(obj, g_child_process_proto); 688 + 689 + js_set(js, obj, "pid", js_mkundef()); 690 + js_set(js, obj, "exitCode", js_mknull()); 691 + js_set(js, obj, "signalCode", js_mknull()); 692 + js_set(js, obj, "killed", js_false); 693 + js_set(js, obj, "connected", js_false); 694 + js_set(js, obj, "stdin", js_mknull()); 695 + js_set(js, obj, "stdout", js_mknull()); 696 + js_set(js, obj, "stderr", js_mknull()); 697 + 698 + return obj; 699 + } 700 + 701 + static void child_process_init_constructor(ant_t *js) { 702 + if (g_child_process_ctor && g_child_process_proto) return; 703 + ant_value_t ee_proto = eventemitter_prototype(js); 704 + 705 + g_child_process_proto = js_mkobj(js); 706 + if (is_object_type(ee_proto)) js_set_proto_init(g_child_process_proto, ee_proto); 707 + js_set(js, g_child_process_proto, "kill", js_mkfun(child_kill)); 708 + js_set(js, g_child_process_proto, "ref", js_mkfun(child_ref)); 709 + js_set(js, g_child_process_proto, "unref", js_mkfun(child_unref)); 710 + 711 + g_child_process_ctor = js_make_ctor( 712 + js, child_process_ctor, 713 + g_child_process_proto, "ChildProcess", 12 714 + ); 715 + } 716 + 682 717 static uv_handle_t *child_stream_handle(child_process_t *cp, child_stream_kind_t kind) { 683 718 if (!cp) return NULL; 684 719 switch (kind) { ··· 863 898 864 899 static ant_value_t create_child_object(ant_t *js, child_process_t *cp) { 865 900 ant_value_t obj = js_mkobj(js); 901 + if (is_object_type(g_child_process_proto)) js_set_proto_init(obj, g_child_process_proto); 866 902 867 903 js_set_slot(obj, SLOT_DATA, ANT_PTR(cp)); 868 904 js_set(js, obj, "pid", js_mknum((double)cp->process.pid)); ··· 1703 1739 1704 1740 ant_value_t child_process_library(ant_t *js) { 1705 1741 ant_value_t lib = js_mkobj(js); 1742 + child_process_init_constructor(js); 1706 1743 1744 + js_set(js, lib, "ChildProcess", g_child_process_ctor); 1707 1745 js_set(js, lib, "spawn", js_mkfun(builtin_spawn)); 1708 1746 js_set(js, lib, "exec", js_mkfun(builtin_exec)); 1709 1747 js_set(js, lib, "execFile", js_mkfun(builtin_execFile));
+196 -3
src/modules/events.c
··· 28 28 static ant_value_t g_isTrusted_getter = 0; 29 29 static ant_value_t g_eventemitter_ctor = 0; 30 30 static ant_value_t g_eventemitter_proto = 0; 31 + static ant_value_t g_eventtarget_proto = 0; 31 32 static ant_value_t g_event_proto = 0; 32 33 static ant_value_t g_customevent_proto = 0; 33 34 static ant_value_t g_errorevent_proto = 0; ··· 206 207 return (t == T_STR || t == T_SYMBOL) ? arg : 0; 207 208 } 208 209 210 + static bool is_eventemitter_instance(ant_value_t target) { 211 + return js_check_brand(target, BRAND_EVENTEMITTER); 212 + } 213 + 214 + static bool is_eventtarget_instance(ant_value_t target) { 215 + return js_check_brand(target, BRAND_EVENTTARGET); 216 + } 217 + 209 218 static void js_init_event_obj(ant_t *js, ant_value_t obj, ant_value_t type_val, bool bubbles, bool cancelable) { 210 219 js_set(js, obj, "type", type_val); 211 220 js_set(js, obj, "target", js_mknull()); ··· 260 269 return js_false; 261 270 } 262 271 272 + static ant_value_t js_eventemitter_ctor(ant_t *js, ant_value_t *args, int nargs) { 273 + if (vtype(js->new_target) == T_UNDEF) 274 + return js_mkerr_typed(js, JS_ERR_TYPE, "EventEmitter constructor requires 'new'"); 275 + 276 + ant_value_t this_obj = js_getthis(js); 277 + if (is_object_type(this_obj)) js_set_slot(this_obj, SLOT_BRAND, js_mknum(BRAND_EVENTEMITTER)); 278 + return js_mkundef(); 279 + } 280 + 263 281 static ant_value_t js_event_preventDefault(ant_t *js, ant_value_t *args, int nargs) { 264 282 ant_value_t this_obj = js_getthis(js); 265 283 ant_value_t cancelable = js_get(js, this_obj, "cancelable"); ··· 437 455 return this_obj; 438 456 } 439 457 458 + static ant_value_t js_eventtarget_ctor(ant_t *js, ant_value_t *args, int nargs) { 459 + if (vtype(js->new_target) == T_UNDEF) 460 + return js_mkerr_typed(js, JS_ERR_TYPE, "EventTarget constructor requires 'new'"); 461 + 462 + ant_value_t this_obj = js_getthis(js); 463 + if (is_object_type(this_obj)) js_set_slot(this_obj, SLOT_BRAND, js_mknum(BRAND_EVENTTARGET)); 464 + return js_mkundef(); 465 + } 466 + 440 467 static bool parse_addEventListener_options(ant_t *js, ant_value_t *args, int nargs, bool *once, bool *capture, ant_value_t *signal) { 441 468 *once = false; *capture = false; *signal = js_mkundef(); 442 469 if (nargs < 3) return true; ··· 450 477 ant_value_t sig = js_get(js, opts, "signal"); 451 478 if (vtype(sig) == T_NULL) return false; 452 479 if (vtype(sig) != T_UNDEF) { 453 - if (!is_object_type(sig)) return false; 480 + if (!abort_signal_is_signal(sig)) return false; 454 481 *signal = sig; 455 482 } 456 483 ··· 871 898 return result; 872 899 } 873 900 901 + static ant_value_t js_events_once_listener(ant_t *js, ant_value_t *args, int nargs) { 902 + ant_value_t state = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 903 + if (!is_object_type(state)) return js_mkundef(); 904 + 905 + ant_value_t promise = js_get_slot(state, SLOT_DATA); 906 + if (vtype(promise) != T_PROMISE) return js_mkundef(); 907 + 908 + ant_value_t settled = js_get_slot(state, SLOT_SETTLED); 909 + if (vtype(settled) == T_BOOL && settled == js_true) return js_mkundef(); 910 + js_set_slot(state, SLOT_SETTLED, js_true); 911 + 912 + ant_value_t values = js_mkarr(js); 913 + for (int i = 0; i < nargs; i++) js_arr_push(js, values, args[i]); 914 + js_resolve_promise(js, promise, values); 915 + 916 + return js_mkundef(); 917 + } 918 + 919 + static ant_value_t js_events_once_abort_listener(ant_t *js, ant_value_t *args, int nargs) { 920 + ant_value_t state = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 921 + if (!is_object_type(state)) return js_mkundef(); 922 + 923 + ant_value_t promise = js_get_slot(state, SLOT_DATA); 924 + if (vtype(promise) != T_PROMISE) return js_mkundef(); 925 + 926 + ant_value_t settled = js_get_slot(state, SLOT_SETTLED); 927 + if (vtype(settled) == T_BOOL && settled == js_true) return js_mkundef(); 928 + js_set_slot(state, SLOT_SETTLED, js_true); 929 + 930 + ant_value_t signal = js_get(js, state, "signal"); 931 + ant_value_t reason = abort_signal_get_reason(signal); 932 + if (vtype(reason) == T_UNDEF) reason = js_mkerr(js, "The operation was aborted"); 933 + js_reject_promise(js, promise, reason); 934 + 935 + return js_mkundef(); 936 + } 937 + 938 + static bool js_events_once_is_abort_key(ant_t *js, ant_value_t key) { 939 + size_t key_len = 0; 940 + const char *key_str = vtype(key) == T_STR ? js_getstr(js, key, &key_len) : NULL; 941 + return key_str && key_len == 5 && memcmp(key_str, "abort", 5) == 0; 942 + } 943 + 944 + static void js_events_once_reject_aborted(ant_t *js, ant_value_t promise, ant_value_t signal) { 945 + ant_value_t reason = abort_signal_get_reason(signal); 946 + if (vtype(reason) == T_UNDEF) reason = js_mkerr(js, "The operation was aborted"); 947 + js_reject_promise(js, promise, reason); 948 + } 949 + 950 + static ant_value_t js_events_once_attach( 951 + ant_t *js, 952 + ant_value_t promise, 953 + ant_value_t target, 954 + ant_value_t key, 955 + ant_value_t listener, 956 + ant_value_t signal 957 + ) { 958 + if (abort_signal_is_signal(target)) { 959 + if (!js_events_once_is_abort_key(js, key)) { 960 + js_reject_promise(js, promise, js_mkerr_typed(js, JS_ERR_TYPE, "AbortSignal only supports the abort event")); 961 + return promise; 962 + } 963 + abort_signal_add_listener(js, target, listener); 964 + return promise; 965 + } 966 + 967 + if (is_eventemitter_instance(target)) { 968 + if (!eventemitter_add_listener_val(js, target, key, listener, true)) 969 + js_reject_promise(js, promise, js_mkerr(js, "listener must be a function")); 970 + return promise; 971 + } 972 + 973 + if (is_eventtarget_instance(target)) { 974 + ant_value_t listener_options = js_mkobj(js); 975 + js_set(js, listener_options, "once", js_true); 976 + if (abort_signal_is_signal(signal)) js_set(js, listener_options, "signal", signal); 977 + 978 + ant_value_t call_args[3] = { key, listener, listener_options }; 979 + ant_value_t result = add_listener_to(js, call_args, 3, find_or_create_emitter_event_type(js, target, key)); 980 + if (is_err(result)) js_reject_promise(js, promise, result); 981 + return promise; 982 + } 983 + 984 + js_reject_promise(js, promise, js_mkerr_typed(js, JS_ERR_TYPE, "target is not an EventEmitter or EventTarget")); 985 + return promise; 986 + } 987 + 988 + static ant_value_t js_events_once(ant_t *js, ant_value_t *args, int nargs) { 989 + if (nargs < 2 || !is_object_type(args[0])) 990 + return js_mkerr_typed(js, JS_ERR_TYPE, "events.once requires an emitter and event name"); 991 + 992 + ant_value_t key = evt_key_from_arg(args[1]); 993 + if (!key) return js_mkerr_typed(js, JS_ERR_TYPE, "event must be a string or Symbol"); 994 + 995 + ant_value_t promise = js_mkpromise(js); 996 + if (is_err(promise)) return promise; 997 + 998 + ant_value_t state = js_mkobj(js); 999 + js_set_slot(state, SLOT_DATA, promise); 1000 + js_set_slot(state, SLOT_SETTLED, js_false); 1001 + 1002 + ant_value_t listener = js_heavy_mkfun(js, js_events_once_listener, state); 1003 + ant_value_t target = args[0]; 1004 + ant_value_t options = nargs >= 3 ? args[2] : js_mkundef(); 1005 + ant_value_t signal = js_mkundef(); 1006 + 1007 + if (is_object_type(options)) signal = js_get(js, options, "signal"); 1008 + if (abort_signal_is_signal(signal)) { 1009 + if (abort_signal_is_aborted(signal)) { 1010 + js_events_once_reject_aborted(js, promise, signal); 1011 + return promise; 1012 + } 1013 + js_set(js, state, "signal", signal); 1014 + abort_signal_add_listener(js, signal, js_heavy_mkfun(js, js_events_once_abort_listener, state)); 1015 + } 1016 + 1017 + return js_events_once_attach(js, promise, target, key, listener, signal); 1018 + } 1019 + 1020 + // TODO: fix stub 1021 + static ant_value_t js_events_disposable_dispose(ant_t *js, ant_value_t *args, int nargs) { 1022 + return js_mkundef(); 1023 + } 1024 + 1025 + static ant_value_t js_events_add_abort_listener(ant_t *js, ant_value_t *args, int nargs) { 1026 + if (nargs < 2 || !abort_signal_is_signal(args[0]) || !is_callable(args[1])) 1027 + return js_mkerr_typed(js, JS_ERR_TYPE, "events.addAbortListener requires an AbortSignal and listener"); 1028 + 1029 + if (abort_signal_is_aborted(args[0])) { 1030 + ant_value_t event = js_mkobj(js); 1031 + js_set(js, event, "type", js_mkstr(js, "abort", 5)); 1032 + sv_vm_call(js->vm, js, args[1], args[0], &event, 1, NULL, false); 1033 + } else abort_signal_add_listener(js, args[0], args[1]); 1034 + 1035 + ant_value_t disposable = js_mkobj(js); 1036 + js_set(js, disposable, "dispose", js_mkfun(js_events_disposable_dispose)); 1037 + 1038 + return disposable; 1039 + } 1040 + 1041 + // TODO: fix stub 1042 + static ant_value_t js_events_set_max_listeners(ant_t *js, ant_value_t *args, int nargs) { 1043 + return js_mkundef(); 1044 + } 1045 + 1046 + // TODO: fix stub 1047 + static ant_value_t js_events_get_max_listeners(ant_t *js, ant_value_t *args, int nargs) { 1048 + return js_mknum(10); 1049 + } 1050 + 1051 + // TODO: fix stub 1052 + static ant_value_t js_events_on(ant_t *js, ant_value_t *args, int nargs) { 1053 + return js_mkerr_typed(js, JS_ERR_TYPE, "events.on async iterator is not implemented"); 1054 + } 1055 + 874 1056 ant_value_t events_library(ant_t *js) { 875 1057 ant_value_t lib = js_mkobj(js); 876 1058 877 1059 eventemitter_prototype(js); 878 1060 js_set_module_default(js, lib, g_eventemitter_ctor, "EventEmitter"); 1061 + js_set(js, lib, "once", js_mkfun(js_events_once)); 1062 + js_set(js, lib, "on", js_mkfun(js_events_on)); 1063 + js_set(js, lib, "addAbortListener", js_mkfun(js_events_add_abort_listener)); 1064 + js_set(js, lib, "setMaxListeners", js_mkfun(js_events_set_max_listeners)); 1065 + js_set(js, lib, "getMaxListeners", js_mkfun(js_events_get_max_listeners)); 879 1066 js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "events", 6)); 880 1067 881 1068 return lib; ··· 900 1087 js_set(js, eventemitter_proto, "eventNames", js_mkfun(js_eventemitter_eventNames)); 901 1088 js_set_sym(js, eventemitter_proto, get_toStringTag_sym(), js_mkstr(js, "EventEmitter", 12)); 902 1089 1090 + js_set_slot(eventemitter_ctor, SLOT_CFUNC, js_mkfun(js_eventemitter_ctor)); 903 1091 js_mkprop_fast(js, eventemitter_ctor, "prototype", 9, eventemitter_proto); 904 1092 js_mkprop_fast(js, eventemitter_ctor, "name", 4, ANT_STRING("EventEmitter")); 905 1093 js_set_descriptor(js, eventemitter_ctor, "name", 4, 0); 906 1094 907 1095 g_eventemitter_proto = eventemitter_proto; 908 - g_eventemitter_ctor = js_obj_to_func_ex(eventemitter_ctor, SV_CALL_IS_DEFAULT_CTOR); 1096 + g_eventemitter_ctor = js_obj_to_func(eventemitter_ctor); 1097 + js_set(js, eventemitter_proto, "constructor", g_eventemitter_ctor); 1098 + js_set_descriptor(js, eventemitter_proto, "constructor", 11, JS_DESC_W | JS_DESC_C); 909 1099 910 1100 return g_eventemitter_proto; 911 1101 } ··· 956 1146 js_set(js, global, "PromiseRejectionEvent", pre_fn); 957 1147 958 1148 ant_value_t eventtarget_proto = js_mkobj(js); 1149 + g_eventtarget_proto = eventtarget_proto; 959 1150 js_set(js, eventtarget_proto, "addEventListener", js_mkfun(js_add_event_listener_method)); 960 1151 js_set(js, eventtarget_proto, "removeEventListener", js_mkfun(js_remove_event_listener_method)); 961 1152 js_set(js, eventtarget_proto, "dispatchEvent", js_mkfun(js_dispatch_event_method)); 962 1153 js_set_sym(js, eventtarget_proto, get_toStringTag_sym(), js_mkstr(js, "EventTarget", 11)); 963 1154 964 1155 ant_value_t eventtarget_ctor = js_mkobj(js); 1156 + js_set_slot(eventtarget_ctor, SLOT_CFUNC, js_mkfun(js_eventtarget_ctor)); 965 1157 js_mkprop_fast(js, eventtarget_ctor, "prototype", 9, eventtarget_proto); 966 1158 js_mkprop_fast(js, eventtarget_ctor, "name", 4, ANT_STRING("EventTarget")); 967 1159 js_set_descriptor(js, eventtarget_ctor, "name", 4, 0); 968 - ant_value_t eventtarget_fn = js_obj_to_func_ex(eventtarget_ctor, SV_CALL_IS_DEFAULT_CTOR); 1160 + ant_value_t eventtarget_fn = js_obj_to_func(eventtarget_ctor); 969 1161 js_set(js, eventtarget_proto, "constructor", eventtarget_fn); 970 1162 js_set_descriptor(js, eventtarget_proto, "constructor", 11, JS_DESC_W | JS_DESC_C); 971 1163 ··· 994 1186 if (g_isTrusted_getter) mark(js, g_isTrusted_getter); 995 1187 if (g_eventemitter_ctor) mark(js, g_eventemitter_ctor); 996 1188 if (g_eventemitter_proto) mark(js, g_eventemitter_proto); 1189 + if (g_eventtarget_proto) mark(js, g_eventtarget_proto); 997 1190 if (g_event_proto) mark(js, g_event_proto); 998 1191 if (g_customevent_proto) mark(js, g_customevent_proto); 999 1192 if (g_errorevent_proto) mark(js, g_errorevent_proto);
+225 -3
src/modules/fs.c
··· 60 60 FS_OP_READ_FD, 61 61 FS_OP_OPEN, 62 62 FS_OP_CLOSE, 63 - FS_OP_MKDTEMP 63 + FS_OP_MKDTEMP, 64 + FS_OP_CHMOD 64 65 } fs_op_type_t; 65 66 66 67 typedef struct fs_request_s { ··· 199 200 ant_t *js, int err_num, 200 201 const char *syscall, const char *path, const char *dest 201 202 ); 203 + 204 + static bool fs_parse_mode(ant_t *js, ant_value_t arg, mode_t *out) { 205 + if (vtype(arg) == T_NUM) { 206 + double mode = js_getnum(arg); 207 + if (mode < 0) return false; 208 + *out = (mode_t)mode; 209 + return true; 210 + } 211 + 212 + if (vtype(arg) != T_STR) return false; 213 + 214 + size_t len = 0; 215 + const char *str = js_getstr(js, arg, &len); 216 + if (!str) return false; 217 + 218 + size_t start = 0; 219 + if (len > 2 && str[0] == '0' && (str[1] == 'o' || str[1] == 'O')) start = 2; 220 + if (start == len) return false; 221 + 222 + mode_t mode = 0; 223 + for (size_t i = start; i < len; i++) { 224 + if (str[i] < '0' || str[i] > '7') return false; 225 + mode = (mode_t)((mode << 3) + (str[i] - '0')); 226 + } 227 + 228 + *out = mode; 229 + return true; 230 + } 202 231 203 232 static ant_value_t fs_stream_error(ant_t *js, ant_value_t stream_obj, const char *op, int uv_code) { 204 233 ant_value_t props = js_mkobj(js); ··· 1644 1673 free_fs_request(req); 1645 1674 } 1646 1675 1676 + static void on_chmod_complete(uv_fs_t *uv_req) { 1677 + fs_request_t *req = (fs_request_t *)uv_req->data; 1678 + 1679 + if (uv_req->result < 0) { 1680 + fs_request_fail(req, (int)uv_req->result); 1681 + req->completed = 1; 1682 + complete_request(req); 1683 + return; 1684 + } 1685 + 1686 + req->completed = 1; 1687 + js_resolve_promise(req->js, req->promise, js_mkundef()); 1688 + remove_pending_request(req); 1689 + free_fs_request(req); 1690 + } 1691 + 1647 1692 static void on_readdir_complete(uv_fs_t *uv_req) { 1648 1693 fs_request_t *req = (fs_request_t *)uv_req->data; 1649 1694 ··· 2734 2779 return js_mkundef(); 2735 2780 } 2736 2781 2782 + static ant_value_t builtin_fs_chmodSync(ant_t *js, ant_value_t *args, int nargs) { 2783 + if (nargs < 1) return js_mkerr(js, "chmodSync() requires a path argument"); 2784 + if (nargs < 2) return js_mkerr(js, "chmodSync() requires a mode argument"); 2785 + 2786 + ant_value_t path_val = fs_coerce_path(js, args[0]); 2787 + if (vtype(path_val) != T_STR) return js_mkerr(js, "chmodSync() path must be a string or URL"); 2788 + 2789 + mode_t mode = 0; 2790 + if (!fs_parse_mode(js, args[1], &mode)) { 2791 + return js_mkerr(js, "chmodSync() mode must be a number or octal string"); 2792 + } 2793 + 2794 + size_t path_len = 0; 2795 + char *path = js_getstr(js, path_val, &path_len); 2796 + if (!path) return js_mkerr(js, "Failed to get path string"); 2797 + 2798 + char *path_cstr = strndup(path, path_len); 2799 + if (!path_cstr) return js_mkerr(js, "Out of memory"); 2800 + 2801 + uv_fs_t req; 2802 + int result = uv_fs_chmod(uv_default_loop(), &req, path_cstr, mode, NULL); 2803 + 2804 + if (result < 0) { 2805 + ant_value_t err = fs_mk_uv_error(js, result, "chmod", path_cstr, NULL); 2806 + uv_fs_req_cleanup(&req); 2807 + free(path_cstr); 2808 + return err; 2809 + } 2810 + 2811 + uv_fs_req_cleanup(&req); 2812 + free(path_cstr); 2813 + return js_mkundef(); 2814 + } 2815 + 2816 + static ant_value_t builtin_fs_chmod(ant_t *js, ant_value_t *args, int nargs) { 2817 + if (nargs < 1) return js_mkerr(js, "chmod() requires a path argument"); 2818 + if (nargs < 2) return js_mkerr(js, "chmod() requires a mode argument"); 2819 + 2820 + ant_value_t path_val = fs_coerce_path(js, args[0]); 2821 + if (vtype(path_val) != T_STR) return js_mkerr(js, "chmod() path must be a string or URL"); 2822 + 2823 + mode_t mode = 0; 2824 + if (!fs_parse_mode(js, args[1], &mode)) { 2825 + return js_mkerr(js, "chmod() mode must be a number or octal string"); 2826 + } 2827 + 2828 + size_t path_len = 0; 2829 + char *path = js_getstr(js, path_val, &path_len); 2830 + if (!path) return js_mkerr(js, "Failed to get path string"); 2831 + 2832 + fs_request_t *req = calloc(1, sizeof(fs_request_t)); 2833 + if (!req) return js_mkerr(js, "Out of memory"); 2834 + 2835 + req->js = js; 2836 + req->op_type = FS_OP_CHMOD; 2837 + req->promise = js_mkpromise(js); 2838 + req->path = strndup(path, path_len); 2839 + req->uv_req.data = req; 2840 + 2841 + if (!req->path) { 2842 + free_fs_request(req); 2843 + return js_mkerr(js, "Out of memory"); 2844 + } 2845 + 2846 + utarray_push_back(pending_requests, &req); 2847 + int result = uv_fs_chmod(uv_default_loop(), &req->uv_req, req->path, mode, on_chmod_complete); 2848 + 2849 + if (result < 0) { 2850 + fs_request_fail(req, result); 2851 + req->completed = 1; 2852 + complete_request(req); 2853 + } 2854 + 2855 + return req->promise; 2856 + } 2857 + 2737 2858 static ant_value_t builtin_fs_access(ant_t *js, ant_value_t *args, int nargs) { 2738 2859 if (nargs < 1) return js_mkerr(js, "access() requires a path argument"); 2739 2860 if (vtype(args[0]) != T_STR) return js_mkerr(js, "access() path must be a string"); ··· 2813 2934 2814 2935 utarray_push_back(pending_requests, &req); 2815 2936 int result = uv_fs_realpath(uv_default_loop(), &req->uv_req, req->path, on_realpath_complete); 2937 + 2938 + if (result < 0) { 2939 + fs_request_fail(req, result); 2940 + req->completed = 1; 2941 + complete_request(req); 2942 + } 2943 + 2944 + return req->promise; 2945 + } 2946 + 2947 + static ant_value_t builtin_fs_readlinkSync(ant_t *js, ant_value_t *args, int nargs) { 2948 + if (nargs < 1) return js_mkerr(js, "readlinkSync() requires a path argument"); 2949 + 2950 + ant_value_t path_val = fs_coerce_path(js, args[0]); 2951 + if (vtype(path_val) != T_STR) return js_mkerr(js, "readlinkSync() path must be a string"); 2952 + 2953 + size_t path_len = 0; 2954 + const char *path = js_getstr(js, path_val, &path_len); 2955 + if (!path) return js_mkerr(js, "Failed to get path string"); 2956 + 2957 + char *path_cstr = strndup(path, path_len); 2958 + if (!path_cstr) return js_mkerr(js, "Out of memory"); 2959 + 2960 + uv_fs_t req; 2961 + int result = uv_fs_readlink(NULL, &req, path_cstr, NULL); 2962 + 2963 + if (result < 0 || !req.ptr) { 2964 + ant_value_t err = fs_mk_uv_error(js, result, "readlink", path_cstr, NULL); 2965 + uv_fs_req_cleanup(&req); 2966 + free(path_cstr); 2967 + return err; 2968 + } 2969 + 2970 + ant_value_t link = js_mkstr(js, (const char *)req.ptr, strlen((const char *)req.ptr)); 2971 + uv_fs_req_cleanup(&req); 2972 + free(path_cstr); 2973 + return link; 2974 + } 2975 + 2976 + static ant_value_t builtin_fs_readlink(ant_t *js, ant_value_t *args, int nargs) { 2977 + if (nargs < 1) return js_mkerr(js, "readlink() requires a path argument"); 2978 + 2979 + ant_value_t path_val = fs_coerce_path(js, args[0]); 2980 + if (vtype(path_val) != T_STR) return js_mkerr(js, "readlink() path must be a string"); 2981 + 2982 + size_t path_len = 0; 2983 + const char *path = js_getstr(js, path_val, &path_len); 2984 + if (!path) return js_mkerr(js, "Failed to get path string"); 2985 + 2986 + fs_request_t *req = calloc(1, sizeof(fs_request_t)); 2987 + if (!req) return js_mkerr(js, "Out of memory"); 2988 + 2989 + req->js = js; 2990 + req->op_type = FS_OP_REALPATH; 2991 + req->promise = js_mkpromise(js); 2992 + req->path = strndup(path, path_len); 2993 + req->uv_req.data = req; 2994 + 2995 + if (!req->path) { 2996 + free_fs_request(req); 2997 + return js_mkerr(js, "Out of memory"); 2998 + } 2999 + 3000 + utarray_push_back(pending_requests, &req); 3001 + int result = uv_fs_readlink(uv_default_loop(), &req->uv_req, req->path, on_realpath_complete); 2816 3002 2817 3003 if (result < 0) { 2818 3004 fs_request_fail(req, result); ··· 3802 3988 js_set(js, lib, "futimes", js_mkfun(builtin_fs_futimes)); 3803 3989 js_set(js, lib, "exists", js_mkfun(builtin_fs_exists)); 3804 3990 js_set(js, lib, "access", js_mkfun(builtin_fs_access)); 3991 + js_set(js, lib, "chmod", js_mkfun(builtin_fs_chmod)); 3805 3992 js_set(js, lib, "readdir", js_mkfun(builtin_fs_readdir)); 3806 3993 js_set(js, lib, "realpath", js_mkfun(builtin_fs_realpath)); 3994 + js_set(js, lib, "readlink", js_mkfun(builtin_fs_readlink)); 3807 3995 } 3808 3996 3809 3997 static void fs_set_callback_compatible_methods(ant_t *js, ant_value_t lib) { 3998 + ant_value_t realpath = fs_make_callback_wrapper(js, js_mkfun(builtin_fs_realpath), false); 3999 + 3810 4000 js_set(js, lib, "readFile", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_readFile), false)); 3811 4001 js_set(js, lib, "open", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_open_fd), false)); 3812 4002 js_set(js, lib, "close", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_close_fd), false)); ··· 3824 4014 js_set(js, lib, "futimes", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_futimes), false)); 3825 4015 js_set(js, lib, "exists", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_exists), true)); 3826 4016 js_set(js, lib, "access", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_access), false)); 4017 + js_set(js, lib, "chmod", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_chmod), false)); 3827 4018 js_set(js, lib, "readdir", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_readdir), false)); 3828 - js_set(js, lib, "realpath", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_realpath), false)); 4019 + js_set(js, lib, "readlink", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_readlink), false)); 4020 + 4021 + js_set(js, realpath, "native", realpath); 4022 + js_set(js, lib, "realpath", realpath); 3829 4023 } 3830 4024 3831 4025 static ant_value_t fs_make_constants(ant_t *js) { 3832 - ant_value_t constants = js_mkobj(js); 4026 + ant_value_t constants = js_newobj(js); 3833 4027 js_set(js, constants, "F_OK", js_mknum(F_OK)); 3834 4028 js_set(js, constants, "R_OK", js_mknum(R_OK)); 3835 4029 js_set(js, constants, "W_OK", js_mknum(W_OK)); ··· 3841 4035 js_set(js, constants, "O_EXCL", js_mknum(O_EXCL)); 3842 4036 js_set(js, constants, "O_TRUNC", js_mknum(O_TRUNC)); 3843 4037 js_set(js, constants, "O_APPEND", js_mknum(O_APPEND)); 4038 + #ifdef O_SYMLINK 4039 + js_set(js, constants, "O_SYMLINK", js_mknum(O_SYMLINK)); 4040 + #endif 4041 + #ifdef O_NOFOLLOW 4042 + js_set(js, constants, "O_NOFOLLOW", js_mknum(O_NOFOLLOW)); 4043 + #endif 4044 + #ifdef S_IFMT 4045 + js_set(js, constants, "S_IFMT", js_mknum(S_IFMT)); 4046 + #endif 4047 + #ifdef S_IFREG 4048 + js_set(js, constants, "S_IFREG", js_mknum(S_IFREG)); 4049 + #endif 4050 + #ifdef S_IFDIR 4051 + js_set(js, constants, "S_IFDIR", js_mknum(S_IFDIR)); 4052 + #endif 4053 + #ifdef S_IFLNK 4054 + js_set(js, constants, "S_IFLNK", js_mknum(S_IFLNK)); 4055 + #endif 3844 4056 return constants; 3845 4057 } 3846 4058 ··· 3887 4099 js_set(js, lib, "futimesSync", js_mkfun(builtin_fs_futimesSync)); 3888 4100 js_set(js, lib, "existsSync", js_mkfun(builtin_fs_existsSync)); 3889 4101 js_set(js, lib, "accessSync", js_mkfun(builtin_fs_accessSync)); 4102 + js_set(js, lib, "chmodSync", js_mkfun(builtin_fs_chmodSync)); 3890 4103 js_set(js, lib, "readdirSync", js_mkfun(builtin_fs_readdirSync)); 3891 4104 js_set(js, lib, "realpathSync", realpath_sync); 4105 + js_set(js, lib, "readlinkSync", js_mkfun(builtin_fs_readlinkSync)); 3892 4106 js_set(js, lib, "watch", js_mkfun(builtin_fs_watch)); 3893 4107 js_set(js, lib, "watchFile", js_mkfun(builtin_fs_watchFile)); 3894 4108 js_set(js, lib, "unwatchFile", js_mkfun(builtin_fs_unwatchFile)); ··· 3918 4132 3919 4133 js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "fs/promises", 11)); 3920 4134 return lib; 4135 + } 4136 + 4137 + ant_value_t fs_constants_library(ant_t *js) { 4138 + ant_value_t constants = fs_make_constants(js); 4139 + js_set(js, constants, "default", constants); 4140 + js_set_slot_wb(js, constants, SLOT_DEFAULT, constants); 4141 + js_set_sym(js, constants, get_toStringTag_sym(), js_mkstr(js, "constants", 9)); 4142 + return constants; 3921 4143 } 3922 4144 3923 4145 int has_pending_fs_ops(void) {
+14
src/modules/path.c
··· 872 872 873 873 return lib; 874 874 } 875 + 876 + ant_value_t path_posix_library(ant_t *js) { 877 + path_api_t api = path_build_api_for_style(js, PATH_STYLE_POSIX); 878 + ant_value_t lib = path_make_variant(js, &api, PATH_STYLE_POSIX); 879 + js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "path", 4)); 880 + return lib; 881 + } 882 + 883 + ant_value_t path_win32_library(ant_t *js) { 884 + path_api_t api = path_build_api_for_style(js, PATH_STYLE_WIN32); 885 + ant_value_t lib = path_make_variant(js, &api, PATH_STYLE_WIN32); 886 + js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "path", 4)); 887 + return lib; 888 + }
+151 -51
src/modules/process.c
··· 1638 1638 return js_true; 1639 1639 } 1640 1640 1641 + static bool process_is_error_object(ant_value_t value) { 1642 + if (is_err(value)) return true; 1643 + return is_object_type(value) && js_get_slot(value, SLOT_ERROR_BRAND) == js_true; 1644 + } 1645 + 1646 + static void process_get_warning_options( 1647 + ant_t *js, ant_value_t options, 1648 + const char **type, const char **code, const char **detail, 1649 + ant_offset_t *detail_len 1650 + ) { 1651 + if (!is_object_type(options)) return; 1652 + 1653 + ant_value_t type_val = js_get(js, options, "type"); 1654 + ant_value_t code_val = js_get(js, options, "code"); 1655 + ant_value_t detail_val = js_get(js, options, "detail"); 1656 + 1657 + if (vtype(type_val) == T_STR) *type = js_getstr(js, type_val, NULL); 1658 + if (vtype(code_val) == T_STR) *code = js_getstr(js, code_val, NULL); 1659 + if (vtype(detail_val) == T_STR) *detail = (const char *)(uintptr_t)vstr(js, detail_val, detail_len); 1660 + } 1661 + 1662 + static ant_value_t process_make_warning_object( 1663 + ant_t *js, const char *type, js_cstr_t msg, const char *code, 1664 + const char *detail, ant_offset_t detail_len 1665 + ) { 1666 + ant_value_t warning_obj = js_mkobj(js); 1667 + js_set_proto_init(warning_obj, js_get_ctor_proto(js, "Error", 5)); 1668 + js_set_slot(warning_obj, SLOT_ERROR_BRAND, js_true); 1669 + js_set(js, warning_obj, "name", js_mkstr(js, type, strlen(type))); 1670 + js_set(js, warning_obj, "message", js_mkstr(js, msg.ptr, msg.len)); 1671 + 1672 + if (code) js_set(js, warning_obj, "code", js_mkstr(js, code, strlen(code))); 1673 + if (detail) js_set(js, warning_obj, "detail", js_mkstr(js, detail, detail_len)); 1674 + 1675 + js_capture_stack(js, warning_obj); 1676 + return warning_obj; 1677 + } 1678 + 1679 + static ant_value_t process_emit_warning(ant_t *js, ant_value_t *args, int nargs) { 1680 + if (nargs < 1) return js_mkundef(); 1681 + 1682 + ant_value_t warning = args[0]; 1683 + const char *type = "Warning"; 1684 + const char *code = NULL; 1685 + const char *detail = NULL; 1686 + ant_offset_t detail_len = 0; 1687 + 1688 + if (nargs >= 2) { 1689 + if (vtype(args[1]) == T_STR) type = js_getstr(js, args[1], NULL); 1690 + else process_get_warning_options(js, args[1], &type, &code, &detail, &detail_len); 1691 + } 1692 + 1693 + if (nargs >= 3 && vtype(args[2]) == T_STR) code = js_getstr(js, args[2], NULL); 1694 + 1695 + char msg_buf[512]; 1696 + js_cstr_t msg = {0}; 1697 + ant_value_t warning_event_arg = warning; 1698 + bool is_error = process_is_error_object(warning); 1699 + 1700 + if (is_error) { 1701 + ant_value_t warning_obj = js_as_obj(warning); 1702 + ant_offset_t prop_len = 0; 1703 + const char *name_prop = get_str_prop(js, warning_obj, "name", 4, &prop_len); 1704 + if (name_prop) type = name_prop; 1705 + 1706 + const char *message_prop = get_str_prop(js, warning_obj, "message", 7, &prop_len); 1707 + msg.ptr = message_prop ? message_prop : ""; 1708 + msg.len = message_prop ? prop_len : 0; 1709 + msg.needs_free = false; 1710 + 1711 + code = get_str_prop(js, warning_obj, "code", 4, NULL); 1712 + detail = get_str_prop(js, warning_obj, "detail", 6, &detail_len); 1713 + warning_event_arg = warning_obj; 1714 + } else { 1715 + msg = js_to_cstr(js, warning, msg_buf, sizeof(msg_buf)); 1716 + warning_event_arg = process_make_warning_object(js, type, msg, code, detail, detail_len); 1717 + } 1718 + 1719 + fprintf(stderr, "(%s:%d) ", "ant", (int)getpid()); 1720 + if (code) fprintf(stderr, "[%s] ", code); 1721 + 1722 + fprintf(stderr, "%s: %.*s\n", type ? type : "Warning", (int)msg.len, msg.ptr); 1723 + if (detail) fprintf(stderr, "%.*s\n", (int)detail_len, detail); 1724 + 1725 + emit_process_event("warning", &warning_event_arg, 1); 1726 + if (msg.needs_free) free((void *)msg.ptr); 1727 + 1728 + return js_mkundef(); 1729 + } 1730 + 1641 1731 static ant_value_t process_listener_count(ant_t *js, ant_value_t *args, int nargs) { 1642 1732 if (nargs < 1) return js_mknum(0); 1643 1733 ··· 1665 1755 return js_mknum(max_listeners); 1666 1756 } 1667 1757 1668 - ant_value_t process_library(ant_t *js) { 1669 - return js_get(js, js_glob(js), "process"); 1670 - } 1671 - 1672 1758 static ant_value_t process_next_tick(ant_t *js, ant_value_t *args, int nargs) { 1673 1759 if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "process.nextTick requires a callback"); 1674 1760 ··· 1682 1768 return js_mkundef(); 1683 1769 } 1684 1770 1685 - void init_process_module() { 1686 - ant_t *js = rt->js; 1687 - ant_value_t global = js_glob(js); 1688 - 1689 - process_start_time = uv_hrtime(); 1690 - ant_value_t process_proto = js_mkobj(js); 1691 - 1692 - js_set(js, process_proto, "exit", js_mkfun(process_exit)); 1693 - js_set(js, process_proto, "on", js_mkfun(process_on)); 1694 - js_set(js, process_proto, "addListener", js_mkfun(process_on)); 1695 - js_set(js, process_proto, "once", js_mkfun(process_once)); 1696 - js_set(js, process_proto, "off", js_mkfun(process_off)); 1697 - js_set(js, process_proto, "removeListener", js_mkfun(process_off)); 1698 - js_set(js, process_proto, "removeAllListeners", js_mkfun(process_remove_all_listeners)); 1699 - js_set(js, process_proto, "emit", js_mkfun(process_emit)); 1700 - js_set(js, process_proto, "listenerCount", js_mkfun(process_listener_count)); 1701 - js_set(js, process_proto, "setMaxListeners", js_mkfun(process_set_max_listeners)); 1702 - js_set(js, process_proto, "getMaxListeners", js_mkfun(process_get_max_listeners)); 1703 - js_set(js, process_proto, "cwd", js_mkfun(process_cwd)); 1704 - js_set(js, process_proto, "chdir", js_mkfun(process_chdir)); 1705 - js_set(js, process_proto, "uptime", js_mkfun(process_uptime)); 1706 - js_set(js, process_proto, "cpuUsage", js_mkfun(process_cpu_usage)); 1707 - js_set(js, process_proto, "kill", js_mkfun(process_kill)); 1708 - js_set(js, process_proto, "abort", js_mkfun(process_abort)); 1709 - js_set(js, process_proto, "umask", js_mkfun(process_umask)); 1710 - js_set(js, process_proto, "nextTick", js_mkfun(process_next_tick)); 1711 - 1771 + static void process_set_methods(ant_t *js, ant_value_t obj, bool include_event_methods) { 1772 + js_set(js, obj, "exit", js_mkfun(process_exit)); 1773 + js_set(js, obj, "cwd", js_mkfun(process_cwd)); 1774 + js_set(js, obj, "chdir", js_mkfun(process_chdir)); 1775 + js_set(js, obj, "uptime", js_mkfun(process_uptime)); 1776 + js_set(js, obj, "cpuUsage", js_mkfun(process_cpu_usage)); 1777 + js_set(js, obj, "kill", js_mkfun(process_kill)); 1778 + js_set(js, obj, "abort", js_mkfun(process_abort)); 1779 + js_set(js, obj, "umask", js_mkfun(process_umask)); 1780 + js_set(js, obj, "nextTick", js_mkfun(process_next_tick)); 1781 + js_set(js, obj, "emitWarning", js_mkfun(process_emit_warning)); 1782 + js_set(js, obj, "dlopen", js_mkfun(napi_process_dlopen_js)); 1783 + 1712 1784 ant_value_t mem_usage_fn = js_heavy_mkfun(js, process_memory_usage, js_mkundef()); 1713 1785 js_set(js, mem_usage_fn, "rss", js_mkfun(process_memory_usage_rss)); 1714 - js_set(js, process_proto, "memoryUsage", mem_usage_fn); 1715 - 1786 + js_set(js, obj, "memoryUsage", mem_usage_fn); 1787 + 1716 1788 ant_value_t hrtime_fn = js_heavy_mkfun(js, process_hrtime, js_mkundef()); 1717 1789 js_set(js, hrtime_fn, "bigint", js_mkfun(process_hrtime_bigint)); 1718 - js_set(js, process_proto, "hrtime", hrtime_fn); 1719 - js_set(js, process_proto, "dlopen", js_mkfun(napi_process_dlopen_js)); 1720 - 1790 + js_set(js, obj, "hrtime", hrtime_fn); 1791 + 1792 + if (include_event_methods) { 1793 + js_set(js, obj, "on", js_mkfun(process_on)); 1794 + js_set(js, obj, "addListener", js_mkfun(process_on)); 1795 + js_set(js, obj, "once", js_mkfun(process_once)); 1796 + js_set(js, obj, "off", js_mkfun(process_off)); 1797 + js_set(js, obj, "removeListener", js_mkfun(process_off)); 1798 + js_set(js, obj, "removeAllListeners", js_mkfun(process_remove_all_listeners)); 1799 + js_set(js, obj, "emit", js_mkfun(process_emit)); 1800 + js_set(js, obj, "listenerCount", js_mkfun(process_listener_count)); 1801 + js_set(js, obj, "setMaxListeners", js_mkfun(process_set_max_listeners)); 1802 + js_set(js, obj, "getMaxListeners", js_mkfun(process_get_max_listeners)); 1803 + } 1804 + 1721 1805 #ifndef _WIN32 1722 - js_set(js, process_proto, "getuid", js_mkfun(process_getuid)); 1723 - js_set(js, process_proto, "geteuid", js_mkfun(process_geteuid)); 1724 - js_set(js, process_proto, "getgid", js_mkfun(process_getgid)); 1725 - js_set(js, process_proto, "getegid", js_mkfun(process_getegid)); 1726 - js_set(js, process_proto, "getgroups", js_mkfun(process_getgroups)); 1727 - js_set(js, process_proto, "setuid", js_mkfun(process_setuid)); 1728 - js_set(js, process_proto, "setgid", js_mkfun(process_setgid)); 1729 - js_set(js, process_proto, "seteuid", js_mkfun(process_seteuid)); 1730 - js_set(js, process_proto, "setegid", js_mkfun(process_setegid)); 1731 - js_set(js, process_proto, "setgroups", js_mkfun(process_setgroups)); 1732 - js_set(js, process_proto, "initgroups", js_mkfun(process_initgroups)); 1806 + js_set(js, obj, "getuid", js_mkfun(process_getuid)); 1807 + js_set(js, obj, "geteuid", js_mkfun(process_geteuid)); 1808 + js_set(js, obj, "getgid", js_mkfun(process_getgid)); 1809 + js_set(js, obj, "getegid", js_mkfun(process_getegid)); 1810 + js_set(js, obj, "getgroups", js_mkfun(process_getgroups)); 1811 + js_set(js, obj, "setuid", js_mkfun(process_setuid)); 1812 + js_set(js, obj, "setgid", js_mkfun(process_setgid)); 1813 + js_set(js, obj, "seteuid", js_mkfun(process_seteuid)); 1814 + js_set(js, obj, "setegid", js_mkfun(process_setegid)); 1815 + js_set(js, obj, "setgroups", js_mkfun(process_setgroups)); 1816 + js_set(js, obj, "initgroups", js_mkfun(process_initgroups)); 1733 1817 #endif 1734 - 1818 + } 1819 + 1820 + ant_value_t process_library(ant_t *js) { 1821 + ant_value_t process_obj = js_get(js, js_glob(js), "process"); 1822 + js_set_slot_wb(js, process_obj, SLOT_DEFAULT, process_obj); 1823 + return process_obj; 1824 + } 1825 + 1826 + void init_process_module() { 1827 + ant_t *js = rt->js; 1828 + ant_value_t global = js_glob(js); 1829 + 1830 + process_start_time = uv_hrtime(); 1831 + ant_value_t process_proto = js_mkobj(js); 1832 + 1833 + process_set_methods(js, process_proto, true); 1735 1834 js_set_sym(js, process_proto, get_toStringTag_sym(), js_mkstr(js, "process", 7)); 1736 - 1835 + 1737 1836 ant_value_t process_obj = js_mkobj(js); 1738 1837 ant_value_t env_obj = js_mkobj(js); 1838 + 1739 1839 js_set_proto_init(process_obj, process_proto); 1840 + process_set_methods(js, process_obj, false); 1740 1841 1741 1842 load_dotenv_file(js, env_obj); 1742 1843 js_set_keys(env_obj, env_keys); ··· 1861 1962 ProcessEventType *tables[] = {process_events, stdin_events, stdout_events, stderr_events}; 1862 1963 for (int t = 0; t < 4; t++) { 1863 1964 ProcessEventType *evt, *tmp; 1864 - HASH_ITER(hh, tables[t], evt, tmp) { 1965 + HASH_ITER(hh, tables[t], evt, tmp) 1865 1966 for (int i = 0; i < evt->listener_count; i++) mark(js, evt->listeners[i].listener); 1866 - } 1867 1967 } 1868 1968 } 1869 1969
+38 -3
src/modules/stream.c
··· 34 34 static ant_value_t g_passthrough_proto = 0; 35 35 static ant_value_t g_passthrough_ctor = 0; 36 36 37 + static double g_default_high_water_mark = 16384.0; 38 + static double g_default_object_high_water_mark = 16.0; 39 + 37 40 static ant_value_t stream_readable_maybe_read(ant_t *js, ant_value_t stream_obj); 38 41 static ant_value_t stream_readable_flush(ant_t *js, ant_value_t stream_obj); 39 42 static ant_value_t stream_readable_push_value(ant_t *js, ant_value_t stream_obj, ant_value_t chunk, ant_value_t encoding); ··· 260 263 return js_get(js, options, name); 261 264 } 262 265 266 + static double stream_default_high_water_mark(bool object_mode) { 267 + return object_mode ? g_default_object_high_water_mark : g_default_high_water_mark; 268 + } 269 + 270 + static double stream_high_water_mark_from_options(ant_t *js, ant_value_t options, bool object_mode) { 271 + ant_value_t hwm = stream_get_option(js, options, "highWaterMark"); 272 + return (vtype(hwm) == T_NUM && js_getnum(hwm) > 0) 273 + ? js_getnum(hwm) 274 + : stream_default_high_water_mark(object_mode); 275 + } 276 + 263 277 static ant_value_t stream_make_base_object(ant_t *js, ant_value_t proto) { 264 278 ant_value_t obj = js_mkobj(js); 265 279 if (is_object_type(proto)) js_set_proto_init(obj, proto); ··· 281 295 ant_value_t options = is_object_type(raw_options) ? raw_options : js_mkobj(js); 282 296 ant_value_t state = js_mkobj(js); 283 297 ant_value_t read_fn = stream_get_option(js, options, "read"); 284 - ant_value_t hwm = stream_get_option(js, options, "highWaterMark"); 285 - double high_water_mark = (vtype(hwm) == T_NUM && js_getnum(hwm) > 0) ? js_getnum(hwm) : 16384.0; 298 + 299 + bool object_mode = js_truthy(js, stream_get_option(js, options, "objectMode")); 300 + double high_water_mark = stream_high_water_mark_from_options(js, options, object_mode); 286 301 287 302 stream_init_base(js, obj, raw_options); 288 303 js_set(js, obj, "readable", js_true); 289 304 js_set(js, obj, "writable", js_false); 290 305 js_set(js, obj, "readableEnded", js_false); 291 306 292 - js_set(js, state, "objectMode", js_bool(js_truthy(js, stream_get_option(js, options, "objectMode")))); 307 + js_set(js, state, "objectMode", js_bool(object_mode)); 293 308 js_set(js, state, "ended", js_false); 294 309 js_set(js, state, "endEmitted", js_false); 295 310 js_set(js, state, "flowing", js_false); ··· 1588 1603 return stream_readable_push_value(js, stream_obj, chunk, encoding); 1589 1604 } 1590 1605 1606 + static ant_value_t js_stream_get_default_high_water_mark(ant_t *js, ant_value_t *args, int nargs) { 1607 + bool object_mode = nargs > 0 && js_truthy(js, args[0]); 1608 + return js_mknum(stream_default_high_water_mark(object_mode)); 1609 + } 1610 + 1611 + static ant_value_t js_stream_set_default_high_water_mark(ant_t *js, ant_value_t *args, int nargs) { 1612 + if (nargs < 2 || vtype(args[1]) != T_NUM || js_getnum(args[1]) < 0) 1613 + return js_mkerr_typed(js, JS_ERR_RANGE, "setDefaultHighWaterMark requires a non-negative number"); 1614 + 1615 + bool object_mode = js_truthy(js, args[0]); 1616 + if (object_mode) g_default_object_high_water_mark = js_getnum(args[1]); 1617 + else g_default_high_water_mark = js_getnum(args[1]); 1618 + 1619 + return js_mkundef(); 1620 + } 1621 + 1591 1622 ant_value_t stream_library(ant_t *js) { 1592 1623 ant_value_t lib = js_mkobj(js); 1593 1624 ant_value_t promises = js_mkobj(js); ··· 1604 1635 js_set(js, lib, "PassThrough", g_passthrough_ctor); 1605 1636 js_set(js, lib, "pipeline", js_mkfun(js_stream_pipeline)); 1606 1637 js_set(js, lib, "finished", js_mkfun(js_stream_finished)); 1638 + js_set(js, lib, "getDefaultHighWaterMark", js_mkfun(js_stream_get_default_high_water_mark)); 1639 + js_set(js, lib, "setDefaultHighWaterMark", js_mkfun(js_stream_set_default_high_water_mark)); 1607 1640 js_set(js, lib, "promises", promises); 1608 1641 1609 1642 js_set(js, g_stream_ctor, "Readable", g_readable_ctor); ··· 1613 1646 js_set(js, g_stream_ctor, "PassThrough", g_passthrough_ctor); 1614 1647 js_set(js, g_stream_ctor, "pipeline", js_get(js, lib, "pipeline")); 1615 1648 js_set(js, g_stream_ctor, "finished", js_get(js, lib, "finished")); 1649 + js_set(js, g_stream_ctor, "getDefaultHighWaterMark", js_get(js, lib, "getDefaultHighWaterMark")); 1650 + js_set(js, g_stream_ctor, "setDefaultHighWaterMark", js_get(js, lib, "setDefaultHighWaterMark")); 1616 1651 js_set(js, g_stream_ctor, "promises", promises); 1617 1652 1618 1653 js_set(js, promises, "default", promises);
+104 -1
src/modules/util.c
··· 19 19 #include "modules/json.h" 20 20 #include "modules/symbol.h" 21 21 #include "modules/util.h" 22 + #include "modules/abort.h" 22 23 #include "modules/collections.h" 23 24 24 25 typedef struct { ··· 33 34 const char *close; 34 35 } util_style_entry_t; 35 36 36 - // migrate to crprintf 37 + // TODO: migrate to crprintf 37 38 static const util_style_entry_t util_styles[] = { 38 39 {"bold", "\x1b[1m", "\x1b[22m"}, 39 40 {"dim", "\x1b[2m", "\x1b[22m"}, ··· 865 866 return promise; 866 867 } 867 868 869 + static ant_value_t util_callbackify_success(ant_t *js, ant_value_t *args, int nargs) { 870 + ant_value_t state = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 871 + if (!is_object_type(state)) return js_mkundef(); 872 + 873 + ant_value_t callback = js_get_slot(state, SLOT_DATA); 874 + if (!is_callable(callback)) return js_mkundef(); 875 + 876 + ant_value_t cb_args[2] = { 877 + js_mknull(), 878 + nargs > 0 ? args[0] : js_mkundef() 879 + }; 880 + 881 + return sv_vm_call(js->vm, js, callback, js_mkundef(), cb_args, 2, NULL, false); 882 + } 883 + 884 + static ant_value_t util_callbackify_error(ant_t *js, ant_value_t *args, int nargs) { 885 + ant_value_t state = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 886 + if (!is_object_type(state)) return js_mkundef(); 887 + 888 + ant_value_t callback = js_get_slot(state, SLOT_DATA); 889 + if (!is_callable(callback)) return js_mkundef(); 890 + 891 + ant_value_t err = nargs > 0 ? args[0] : js_mkerr(js, "Promise was rejected"); 892 + ant_value_t cb_args[1] = { err }; 893 + return sv_vm_call(js->vm, js, callback, js_mkundef(), cb_args, 1, NULL, false); 894 + } 895 + 896 + static ant_value_t util_callbackified_call(ant_t *js, ant_value_t *args, int nargs) { 897 + ant_value_t fn = js_getcurrentfunc(js); 898 + ant_value_t original = js_get_slot(fn, SLOT_DATA); 899 + 900 + if (!is_callable(original)) return js_mkerr(js, "callbackified target is not callable"); 901 + if (nargs < 1 || !is_callable(args[nargs - 1])) 902 + return js_mkerr_typed(js, JS_ERR_TYPE, "callbackified function requires a callback"); 903 + 904 + ant_value_t callback = args[nargs - 1]; 905 + int call_nargs = nargs - 1; 906 + ant_value_t *call_args = NULL; 907 + 908 + if (call_nargs > 0) { 909 + call_args = (ant_value_t *)malloc((size_t)call_nargs * sizeof(ant_value_t)); 910 + if (!call_args) return js_mkerr(js, "Out of memory"); 911 + for (int i = 0; i < call_nargs; i++) call_args[i] = args[i]; 912 + } 913 + 914 + ant_value_t result = sv_vm_call(js->vm, js, original, js_getthis(js), call_args, call_nargs, NULL, false); 915 + free(call_args); 916 + 917 + if (is_err(result) || js->thrown_exists) { 918 + ant_value_t ex = js->thrown_exists ? js->thrown_value : result; 919 + js->thrown_exists = false; 920 + js->thrown_value = js_mkundef(); 921 + js->thrown_stack = js_mkundef(); 922 + ant_value_t cb_args[1] = { ex }; 923 + sv_vm_call(js->vm, js, callback, js_mkundef(), cb_args, 1, NULL, false); 924 + return js_mkundef(); 925 + } 926 + 927 + if (vtype(result) != T_PROMISE) { 928 + ant_value_t cb_args[2] = { js_mknull(), result }; 929 + sv_vm_call(js->vm, js, callback, js_mkundef(), cb_args, 2, NULL, false); 930 + return js_mkundef(); 931 + } 932 + 933 + ant_value_t state = js_mkobj(js); 934 + js_set_slot(state, SLOT_DATA, callback); 935 + ant_value_t success = js_heavy_mkfun(js, util_callbackify_success, state); 936 + ant_value_t error = js_heavy_mkfun(js, util_callbackify_error, state); 937 + js_promise_then(js, result, success, error); 938 + 939 + return js_mkundef(); 940 + } 941 + 868 942 static ant_value_t util_deprecated_call(ant_t *js, ant_value_t *args, int nargs) { 869 943 ant_value_t fn = js_getcurrentfunc(js); 870 944 ant_value_t ctx = js_get_slot(fn, SLOT_DATA); ··· 903 977 return js_heavy_mkfun(js, util_promisified_call, args[0]); 904 978 } 905 979 980 + static ant_value_t util_callbackify(ant_t *js, ant_value_t *args, int nargs) { 981 + if (nargs < 1 || !is_callable(args[0])) { 982 + return js_mkerr(js, "callbackify(fn) requires a function"); 983 + } 984 + return js_heavy_mkfun(js, util_callbackified_call, args[0]); 985 + } 986 + 987 + static ant_value_t util_aborted_listener(ant_t *js, ant_value_t *args, int nargs) { 988 + ant_value_t promise = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 989 + if (vtype(promise) == T_PROMISE) js_resolve_promise(js, promise, js_mkundef()); 990 + return js_mkundef(); 991 + } 992 + 993 + static ant_value_t util_aborted(ant_t *js, ant_value_t *args, int nargs) { 994 + if (nargs < 1 || !abort_signal_is_signal(args[0])) 995 + return js_mkerr_typed(js, JS_ERR_TYPE, "aborted(signal, resource) requires an AbortSignal"); 996 + 997 + ant_value_t promise = js_mkpromise(js); 998 + if (abort_signal_is_aborted(args[0])) { 999 + js_resolve_promise(js, promise, js_mkundef()); 1000 + return promise; 1001 + } 1002 + 1003 + abort_signal_add_listener(js, args[0], js_heavy_mkfun(js, util_aborted_listener, promise)); 1004 + return promise; 1005 + } 1006 + 906 1007 static ant_value_t util_inherits(ant_t *js, ant_value_t *args, int nargs) { 907 1008 if (nargs < 2 || !is_callable(args[0]) || !is_callable(args[1])) { 908 1009 return js_mkerr(js, "inherits(ctor, superCtor) requires constructor functions"); ··· 935 1036 js_set(js, lib, "inherits", js_mkfun(util_inherits)); 936 1037 js_set(js, lib, "parseEnv", js_mkfun(util_parse_env)); 937 1038 js_set(js, lib, "promisify", js_mkfun(util_promisify)); 1039 + js_set(js, lib, "callbackify", js_mkfun(util_callbackify)); 1040 + js_set(js, lib, "aborted", js_mkfun(util_aborted)); 938 1041 js_set(js, lib, "stripVTControlCharacters", js_mkfun(util_strip_vt_control_characters)); 939 1042 js_set(js, lib, "styleText", js_mkfun(util_style_text)); 940 1043 js_set(js, lib, "types", types);
+25 -2
src/pkg/linker.zig
··· 193 193 var has_existing_package = false; 194 194 195 195 { 196 - const existing = node_modules.openDir(install_path, .{}) catch null; 196 + const existing = node_modules.openDir(install_path, .{ .iterate = true }) catch null; 197 197 if (existing) |dir| { 198 198 has_existing_install = true; 199 199 var installed_dir = dir; ··· 202 202 const installed_version = readPackageVersion(self.allocator, installed_dir); 203 203 defer if (installed_version) |v| self.allocator.free(v); 204 204 has_existing_package = installed_version != null; 205 - should_skip = packageVersionsMatch(source_version, installed_version); 205 + should_skip = packageVersionsMatch(source_version, installed_version) and 206 + installedFileCountMatches(installed_dir, pkg.file_count); 206 207 } 207 208 } 208 209 ··· 281 282 const src = source_version orelse return false; 282 283 const dst = installed_version orelse return false; 283 284 return std.mem.eql(u8, src, dst); 285 + } 286 + 287 + fn installedFileCountMatches(dir: std.fs.Dir, expected_files: u32) bool { 288 + if (expected_files == 0) return true; 289 + const actual_files = countFilesRecursive(dir) catch return false; 290 + return actual_files >= expected_files; 291 + } 292 + 293 + fn countFilesRecursive(dir: std.fs.Dir) !u32 { 294 + var count: u32 = 0; 295 + var iter = dir.iterate(); 296 + while (try iter.next()) |entry| { 297 + switch (entry.kind) { 298 + .file => count += 1, 299 + .directory => { 300 + var child = dir.openDir(entry.name, .{ .iterate = true }) catch continue; 301 + defer child.close(); 302 + count += try countFilesRecursive(child); 303 + }, 304 + else => {}, 305 + }} 306 + return count; 284 307 } 285 308 286 309 fn createBinSymlink(self: *Linker, pkg_name: []const u8, cmd_name: []const u8, bin_path: []const u8, bin_dir: std.fs.Dir) !void {
+199 -114
src/pkg/resolver.zig
··· 251 251 } 252 252 }; 253 253 254 + const DependencySpec = struct { 255 + install_name: []const u8, 256 + package_name: []const u8, 257 + constraint: []const u8, 258 + }; 259 + 260 + const NpmAliasSpec = struct { 261 + package_name: []const u8, 262 + constraint: []const u8, 263 + }; 264 + 265 + fn parseNpmAliasSpec(spec: []const u8) ?NpmAliasSpec { 266 + if (!std.mem.startsWith(u8, spec, "npm:")) return null; 267 + 268 + const rest = spec["npm:".len..]; 269 + if (rest.len == 0) return null; 270 + 271 + const version_at: ?usize = if (rest[0] == '@') blk: { 272 + const slash = std.mem.indexOfScalar(u8, rest, '/') orelse return null; 273 + if (slash + 1 >= rest.len) return null; 274 + if (std.mem.indexOfScalar(u8, rest[slash + 1 ..], '@')) |rel_at| { 275 + break :blk slash + 1 + rel_at; 276 + } 277 + break :blk null; 278 + } else std.mem.indexOfScalar(u8, rest, '@'); 279 + 280 + const package_name = if (version_at) |at| rest[0..at] else rest; 281 + if (package_name.len == 0) return null; 282 + 283 + const constraint = if (version_at) |at| blk: { 284 + const alias_constraint = rest[at + 1 ..]; 285 + break :blk if (alias_constraint.len > 0) alias_constraint else "latest"; 286 + } else "latest"; 287 + 288 + return .{ .package_name = package_name, .constraint = constraint }; 289 + } 290 + 291 + fn dependencySpec(install_name: []const u8, constraint: []const u8) DependencySpec { 292 + if (parseNpmAliasSpec(constraint)) |alias| { 293 + return .{ 294 + .install_name = install_name, 295 + .package_name = alias.package_name, 296 + .constraint = alias.constraint, 297 + }; 298 + } 299 + return .{ 300 + .install_name = install_name, 301 + .package_name = install_name, 302 + .constraint = constraint, 303 + }; 304 + } 305 + 254 306 pub const VersionInfo = struct { 255 307 version: Version, 256 308 version_str: []const u8, ··· 260 312 optional_dependencies: std.StringHashMap([]const u8), 261 313 peer_dependencies: std.StringHashMap([]const u8), 262 314 peer_dependencies_meta: std.StringHashMap(bool), 263 - os: ?[]const u8, cpu: ?[]const u8, libc: ?[]const u8, 315 + os: ?[]const u8, 316 + cpu: ?[]const u8, 317 + libc: ?[]const u8, 264 318 bin: std.StringHashMap([]const u8), 265 319 allocator: std.mem.Allocator, 266 320 ··· 311 365 .freebsd => "freebsd", 312 366 else => "unknown", 313 367 }; 314 - 368 + 315 369 const current_cpu = comptime switch (builtin.cpu.arch) { 316 370 .aarch64 => "arm64", 317 371 .x86_64 => "x64", ··· 319 373 .arm => "arm", 320 374 else => "unknown", 321 375 }; 322 - 323 - const current_libc: ?[]const u8 = comptime if (builtin.os.tag != .linux) null 324 - else if (builtin.abi == .gnu or builtin.abi == .gnueabi or builtin.abi == .gnueabihf) "glibc" 325 - else if (builtin.abi == .musl or builtin.abi == .musleabi or builtin.abi == .musleabihf) "musl" 326 - else null; 376 + 377 + const current_libc: ?[]const u8 = comptime if (builtin.os.tag != .linux) null else if (builtin.abi == .gnu or builtin.abi == .gnueabi or builtin.abi == .gnueabihf) "glibc" else if (builtin.abi == .musl or builtin.abi == .musleabi or builtin.abi == .musleabihf) "musl" else null; 327 378 328 379 if (self.os) |os_filter| if (!matchesFilter(os_filter, current_os)) return false; 329 380 if (self.cpu) |cpu_filter| if (!matchesFilter(cpu_filter, current_cpu)) return false; ··· 356 407 } 357 408 }; 358 409 359 - fn parseDepsMap( 410 + fn parseDepsMap( 360 411 allocator: std.mem.Allocator, 361 412 maybe_obj: ?std.json.Value, 362 413 ) std.StringHashMap([]const u8) { ··· 367 418 368 419 for (deps_obj.object.keys(), deps_obj.object.values()) |dep_name, dep_ver| { 369 420 if (dep_ver != .string) continue; 370 - 421 + 371 422 const key = allocator.dupe(u8, dep_name) catch continue; 372 423 const val = allocator.dupe(u8, dep_ver.string) catch { 373 424 allocator.free(key); 374 425 continue; 375 426 }; 376 - 427 + 377 428 map.put(key, val) catch { 378 - allocator.free(key); 379 - allocator.free(val); 429 + allocator.free(key); 430 + allocator.free(val); 380 431 }; 381 432 } 382 - 433 + 383 434 return map; 384 435 } 385 436 ··· 388 439 maybe_obj: ?std.json.Value, 389 440 ) std.StringHashMap(bool) { 390 441 var map = std.StringHashMap(bool).init(allocator); 391 - 442 + 392 443 const meta_obj = maybe_obj orelse return map; 393 444 if (meta_obj != .object) return map; 394 - 445 + 395 446 for (meta_obj.object.keys(), meta_obj.object.values()) |dep_name, meta_val| { 396 447 if (meta_val != .object) continue; 397 - 398 - const is_optional = if (meta_val.object.get("optional")) |opt| (opt == .bool and opt.bool) 399 - else false; 400 - 448 + 449 + const is_optional = if (meta_val.object.get("optional")) |opt| (opt == .bool and opt.bool) else false; 450 + 401 451 if (is_optional) { 402 452 const key = allocator.dupe(u8, dep_name) catch continue; 403 453 map.put(key, true) catch allocator.free(key); ··· 436 486 pub fn parseFromJson(allocator: std.mem.Allocator, json_data: []const u8) !PackageMetadata { 437 487 const parsed = std.json.parseFromSlice(std.json.Value, allocator, json_data, .{}) catch { 438 488 return error.ParseError; 439 - }; defer parsed.deinit(); 489 + }; 490 + defer parsed.deinit(); 440 491 441 492 const root = parsed.value; 442 493 if (root != .object) return error.ParseError; ··· 557 608 if (version_data.object.get("bin")) |bin_val| { 558 609 if (bin_val == .object) { 559 610 for (bin_val.object.keys(), bin_val.object.values()) |key, val| { 560 - if (val == .string) bin.put(allocator.dupe(u8, key) 561 - catch continue, allocator.dupe(u8, val.string) catch continue) catch {}; 611 + if (val == .string) bin.put(allocator.dupe(u8, key) catch continue, allocator.dupe(u8, val.string) catch continue) catch {}; 562 612 } 563 613 } else if (bin_val == .string) { 564 614 const bin_name = allocator.dupe(u8, name) catch continue; 565 615 const bin_path = allocator.dupe(u8, bin_val.string) catch { 566 - allocator.free(bin_name); continue; 616 + allocator.free(bin_name); 617 + continue; 567 618 }; 568 619 bin.put(bin_name, bin_path) catch { 569 620 allocator.free(bin_name); ··· 581 632 .optional_dependencies = opt_deps, 582 633 .peer_dependencies = peer_deps, 583 634 .peer_dependencies_meta = peer_meta, 584 - .os = os_filter, .cpu = cpu_filter, .libc = libc_filter, 585 - .bin = bin, .allocator = allocator, 635 + .os = os_filter, 636 + .cpu = cpu_filter, 637 + .libc = libc_filter, 638 + .bin = bin, 639 + .allocator = allocator, 586 640 }); 587 641 } 588 642 ··· 626 680 pub fn installPath(self: *const ResolvedPackage, allocator: std.mem.Allocator) ![]const u8 { 627 681 if (self.parent_path) |parent| { 628 682 return std.fmt.allocPrint(allocator, "{s}/node_modules/{s}", .{ parent, self.name.slice() }); 629 - } return allocator.dupe(u8, self.name.slice()); 683 + } 684 + return allocator.dupe(u8, self.name.slice()); 630 685 } 631 686 }; 632 687 633 - 634 - pub const OnPackageResolvedFn = *const fn ( 635 - pkg: *const ResolvedPackage, 636 - user_data: ?*anyopaque 637 - ) void; 688 + pub const OnPackageResolvedFn = *const fn (pkg: *const ResolvedPackage, user_data: ?*anyopaque) void; 638 689 639 690 pub const Resolver = struct { 640 691 allocator: std.mem.Allocator, ··· 685 736 while (key_iter.next()) |key| { 686 737 self.allocator.free(key.*); 687 738 } 688 - 739 + 689 740 var iter = self.resolved.valueIterator(); 690 741 while (iter.next()) |pkg| { 691 742 pkg.*.deinit(); 692 743 self.allocator.destroy(pkg.*); 693 - } self.resolved.deinit(); 744 + } 745 + self.resolved.deinit(); 694 746 695 747 var cons_key_iter = self.constraints.keyIterator(); 696 748 while (cons_key_iter.next()) |key| { 697 749 self.allocator.free(key.*); 698 750 } 699 - 751 + 700 752 var cons_iter = self.constraints.valueIterator(); 701 753 while (cons_iter.next()) |list| { 702 754 list.deinit(self.allocator); ··· 733 785 } 734 786 entry.value_ptr.deinit(self.allocator); 735 787 self.allocator.free(entry.key_ptr.*); 736 - } all_constraints.deinit(); 788 + } 789 + all_constraints.deinit(); 737 790 } 738 791 739 792 const CollectItem = struct { ··· 762 815 .depth = 0, 763 816 }); 764 817 } 765 - 818 + 766 819 var dev_iter = pkg_json.dev_dependencies.iterator(); 767 820 while (dev_iter.next()) |entry| { 768 821 try collect_queue.append(self.allocator, .{ ··· 781 834 defer to_fetch.deinit(self.allocator); 782 835 783 836 for (collect_queue.items) |item| { 784 - if (!self.metadata_cache.contains(item.name)) { 837 + const spec = dependencySpec(item.name, item.constraint_str); 838 + if (!self.metadata_cache.contains(spec.package_name)) { 785 839 var loaded_from_disk = false; 786 840 if (self.cache_db) |db| { 787 - if (db.lookupMetadata(item.name, self.allocator)) |json_data| { 841 + if (db.lookupMetadata(spec.package_name, self.allocator)) |json_data| { 788 842 const metadata = PackageMetadata.parseFromJson(self.cache_allocator, json_data) catch { 789 843 self.allocator.free(json_data); 790 844 continue; 791 845 }; 792 846 self.allocator.free(json_data); 793 - const cache_key = self.cache_allocator.dupe(u8, item.name) catch continue; 847 + const cache_key = self.cache_allocator.dupe(u8, spec.package_name) catch continue; 794 848 self.metadata_cache.put(cache_key, metadata) catch { 795 849 self.cache_allocator.free(cache_key); 796 850 continue; ··· 801 855 if (!loaded_from_disk) { 802 856 var already_listed = false; 803 857 for (to_fetch.items) |f| { 804 - if (std.mem.eql(u8, f, item.name)) { 858 + if (std.mem.eql(u8, f, spec.package_name)) { 805 859 already_listed = true; 806 860 break; 807 861 } 808 862 } 809 - if (!already_listed) try to_fetch.append(self.allocator, item.name); 863 + if (!already_listed) try to_fetch.append(self.allocator, spec.package_name); 810 864 } 811 865 } 812 866 } ··· 833 887 }; 834 888 835 889 for (ctx.collect_queue_items) |item| { 836 - if (!std.mem.eql(u8, item.name, name)) continue; 890 + const spec = dependencySpec(item.name, item.constraint_str); 891 + if (!std.mem.eql(u8, spec.package_name, name)) continue; 837 892 838 - const constraint = Constraint.parse(item.constraint_str) catch continue; 893 + const constraint = Constraint.parse(spec.constraint) catch continue; 839 894 const best = ctx.resolver.selectBestVersion(&metadata, constraint) orelse continue; 840 895 if (!best.matchesPlatform()) continue; 841 896 842 897 var dep_it = best.dependencies.iterator(); 843 898 while (dep_it.next()) |entry| { 844 - const dep_name = entry.key_ptr.*; 845 - if (ctx.resolver.metadata_cache.contains(dep_name)) continue; 899 + const dep_spec = dependencySpec(entry.key_ptr.*, entry.value_ptr.*); 900 + if (ctx.resolver.metadata_cache.contains(dep_spec.package_name)) continue; 846 901 847 902 var already_queued = false; 848 903 for (ctx.prefetch_queue.items) |q| { 849 - if (std.mem.eql(u8, q, dep_name)) { 850 - already_queued = true; break; 904 + if (std.mem.eql(u8, q, dep_spec.package_name)) { 905 + already_queued = true; 906 + break; 851 907 } 852 908 } 853 - if (!already_queued) ctx.prefetch_queue.append(ctx.allocator, dep_name) catch {}; 854 - } break; 909 + if (!already_queued) ctx.prefetch_queue.append(ctx.allocator, dep_spec.package_name) catch {}; 910 + } 911 + break; 855 912 } 856 913 } 857 914 }; ··· 896 953 } 897 954 try seen_collect.put(seen_key, {}); 898 955 899 - const constraint = Constraint.parse(item.constraint_str) catch continue; 900 - const gop = try all_constraints.getOrPut(item.name); 956 + const spec = dependencySpec(item.name, item.constraint_str); 957 + const constraint = Constraint.parse(spec.constraint) catch continue; 958 + const gop = try all_constraints.getOrPut(spec.package_name); 901 959 if (!gop.found_existing) { 902 - gop.key_ptr.* = try self.allocator.dupe(u8, item.name); 960 + gop.key_ptr.* = try self.allocator.dupe(u8, spec.package_name); 903 961 gop.value_ptr.* = .{}; 904 962 } 905 963 try gop.value_ptr.append(self.allocator, .{ 906 964 .constraint = constraint, 907 - .constraint_str = try self.allocator.dupe(u8, item.constraint_str), 965 + .constraint_str = try self.allocator.dupe(u8, spec.constraint), 908 966 .requester = try self.allocator.dupe(u8, item.requester), 909 967 .depth = item.depth, 910 968 }); 911 969 912 - if (self.metadata_cache.get(item.name)) |metadata| { 970 + if (self.metadata_cache.get(spec.package_name)) |metadata| { 913 971 const best = self.selectBestVersion(&metadata, constraint) orelse continue; 914 972 if (!best.matchesPlatform()) continue; 915 973 ··· 925 983 926 984 var opt_it = best.optional_dependencies.iterator(); 927 985 while (opt_it.next()) |entry| { 928 - if (self.metadata_cache.get(entry.key_ptr.*)) |opt_meta| { 929 - const opt_con = Constraint.parse(entry.value_ptr.*) catch continue; 986 + const opt_spec = dependencySpec(entry.key_ptr.*, entry.value_ptr.*); 987 + if (self.metadata_cache.get(opt_spec.package_name)) |opt_meta| { 988 + const opt_con = Constraint.parse(opt_spec.constraint) catch continue; 930 989 const opt_best = self.selectBestVersion(&opt_meta, opt_con) orelse continue; 931 990 if (!opt_best.matchesPlatform()) continue; 932 991 } ··· 982 1041 if (best) |b| { 983 1042 if (b.version.prerelease) |pre| { 984 1043 debug.log(" {s}: optimal={d}.{d}.{d}-{s} (satisfies {d}/{d} constraints)", .{ 985 - pkg_name, b.version.major, b.version.minor, b.version.patch, pre, 1044 + pkg_name, b.version.major, b.version.minor, b.version.patch, pre, 986 1045 self.countSatisfied(&metadata, b, constraint_list), constraint_list.len, 987 1046 }); 988 1047 } else { 989 1048 debug.log(" {s}: optimal={d}.{d}.{d} (satisfies {d}/{d} constraints)", .{ 990 - pkg_name, b.version.major, b.version.minor, b.version.patch, 1049 + pkg_name, b.version.major, b.version.minor, b.version.patch, 991 1050 self.countSatisfied(&metadata, b, constraint_list), constraint_list.len, 992 1051 }); 993 1052 } ··· 1051 1110 errdefer next_queue.deinit(self.allocator); 1052 1111 1053 1112 for (queue.items) |item| { 1054 - const key = std.fmt.allocPrint(self.allocator, "{s}@{s}", .{ item.name, item.constraint }) catch continue; 1113 + const key = std.fmt.allocPrint(self.allocator, "{s}|{s}@{s}", .{ 1114 + item.parent_name orelse "", 1115 + item.name, 1116 + item.constraint, 1117 + }) catch continue; 1055 1118 if (processed.contains(key)) { 1056 1119 self.allocator.free(key); 1057 1120 continue; ··· 1067 1130 defer self.allocator.free(pkg_install_path); 1068 1131 1069 1132 for (pkg.dependencies.items) |dep| { 1070 - const dep_key = std.fmt.allocPrint(self.allocator, "{s}@{s}", .{ dep.name.slice(), dep.constraint }) catch continue; 1133 + const dep_key = std.fmt.allocPrint(self.allocator, "{s}|{s}@{s}", .{ 1134 + pkg_install_path, 1135 + dep.name.slice(), 1136 + dep.constraint, 1137 + }) catch continue; 1071 1138 defer self.allocator.free(dep_key); 1072 1139 if (!processed.contains(dep_key)) { 1073 1140 try next_queue.append(self.allocator, .{ ··· 1108 1175 1109 1176 var want_prerelease = false; 1110 1177 for (constraint_list) |info| { 1111 - if (info.constraint.version.prerelease != null) { want_prerelease = true; break; } 1178 + if (info.constraint.version.prerelease != null) { 1179 + want_prerelease = true; 1180 + break; 1181 + } 1112 1182 } 1113 1183 1114 1184 for (metadata.versions.items) |*v| { ··· 1141 1211 parent_name: ?[]const u8, 1142 1212 optimal_versions: *std.StringHashMap(*const VersionInfo), 1143 1213 ) !*ResolvedPackage { 1144 - const constraint = try Constraint.parse(constraint_str); 1214 + const dep_spec = dependencySpec(name, constraint_str); 1215 + const constraint = try Constraint.parse(dep_spec.constraint); 1145 1216 1146 - if (self.resolved.get(name)) |existing_pkg| { 1217 + if (self.resolved.get(dep_spec.install_name)) |existing_pkg| { 1147 1218 if (constraint.satisfies(existing_pkg.version)) { 1148 1219 if (direct) existing_pkg.direct = true; 1149 1220 if (depth < existing_pkg.depth) existing_pkg.depth = depth; ··· 1151 1222 } 1152 1223 1153 1224 if (parent_name) |parent| { 1154 - var metadata = try self.fetchMetadata(name); 1225 + var metadata = try self.fetchMetadata(dep_spec.package_name); 1155 1226 const nested_best = self.selectBestVersion(&metadata, constraint) orelse return existing_pkg; 1156 1227 if (!nested_best.matchesPlatform()) return existing_pkg; 1157 1228 1158 1229 debug.log(" nested: {s}@{d}.{d}.{d} under {s} (hoisted: {d}.{d}.{d})", .{ 1159 - name, 1230 + dep_spec.install_name, 1160 1231 nested_best.version.major, 1161 1232 nested_best.version.minor, 1162 1233 nested_best.version.patch, ··· 1166 1237 existing_pkg.version.patch, 1167 1238 }); 1168 1239 1169 - return try self.createNestedPackage(name, nested_best, depth, parent); 1240 + return try self.createNestedPackage(dep_spec.install_name, nested_best, depth, parent); 1170 1241 } 1171 1242 1172 1243 return existing_pkg; 1173 1244 } 1174 1245 1175 - var metadata = try self.fetchMetadata(name); 1246 + var metadata = try self.fetchMetadata(dep_spec.package_name); 1176 1247 const version_info = blk: { 1177 - if (optimal_versions.get(name)) |optimal| { 1248 + if (optimal_versions.get(dep_spec.package_name)) |optimal| { 1178 1249 if (constraint.satisfies(optimal.version) and optimal.matchesPlatform()) break :blk optimal; 1179 1250 } 1180 1251 break :blk self.selectBestVersion(&metadata, constraint) orelse return error.NoMatchingVersion; ··· 1182 1253 1183 1254 if (!version_info.matchesPlatform()) return error.PlatformMismatch; 1184 1255 1185 - const cons_gop = try self.constraints.getOrPut(name); 1256 + const cons_gop = try self.constraints.getOrPut(dep_spec.package_name); 1186 1257 if (!cons_gop.found_existing) { 1187 - cons_gop.key_ptr.* = try self.allocator.dupe(u8, name); 1258 + cons_gop.key_ptr.* = try self.allocator.dupe(u8, dep_spec.package_name); 1188 1259 cons_gop.value_ptr.* = .{}; 1189 - } try cons_gop.value_ptr.append(self.allocator, constraint); 1260 + } 1261 + try cons_gop.value_ptr.append(self.allocator, constraint); 1190 1262 1191 1263 const pkg = try self.allocator.create(ResolvedPackage); 1192 1264 errdefer self.allocator.destroy(pkg); 1193 1265 1194 1266 pkg.* = .{ 1195 - .name = try self.string_pool.intern(name), 1267 + .name = try self.string_pool.intern(dep_spec.install_name), 1196 1268 .version = version_info.version, 1197 1269 .integrity = version_info.integrity, 1198 1270 .tarball_url = try self.allocator.dupe(u8, version_info.tarball_url), ··· 1204 1276 .allocator = self.allocator, 1205 1277 }; 1206 1278 1207 - const name_key = try self.allocator.dupe(u8, name); 1279 + const name_key = try self.allocator.dupe(u8, dep_spec.install_name); 1208 1280 errdefer self.allocator.free(name_key); 1209 1281 try self.resolved.put(name_key, pkg); 1210 1282 ··· 1219 1291 1220 1292 var opt_it = version_info.optional_dependencies.iterator(); 1221 1293 while (opt_it.next()) |entry| { 1222 - if (self.metadata_cache.get(entry.key_ptr.*)) |opt_meta| { 1223 - const opt_con = Constraint.parse(entry.value_ptr.*) catch continue; 1294 + const opt_spec = dependencySpec(entry.key_ptr.*, entry.value_ptr.*); 1295 + if (self.metadata_cache.get(opt_spec.package_name)) |opt_meta| { 1296 + const opt_con = Constraint.parse(opt_spec.constraint) catch continue; 1224 1297 const opt_best = self.selectBestVersion(&opt_meta, opt_con) orelse continue; 1225 1298 if (!opt_best.matchesPlatform()) continue; 1226 1299 } ··· 1249 1322 } 1250 1323 1251 1324 fn resolveSingle(self: *Resolver, name: []const u8, constraint_str: []const u8, depth: u32, direct: bool, parent_name: ?[]const u8) !*ResolvedPackage { 1252 - const constraint = try Constraint.parse(constraint_str); 1325 + const dep_spec = dependencySpec(name, constraint_str); 1326 + const constraint = try Constraint.parse(dep_spec.constraint); 1253 1327 1254 - if (self.resolved.get(name)) |existing_pkg| { 1328 + if (self.resolved.get(dep_spec.install_name)) |existing_pkg| { 1255 1329 if (constraint.satisfies(existing_pkg.version)) { 1256 1330 if (direct) existing_pkg.direct = true; 1257 1331 if (depth < existing_pkg.depth) existing_pkg.depth = depth; 1258 1332 return existing_pkg; 1259 1333 } 1260 1334 1261 - const cons_gop = try self.constraints.getOrPut(name); 1335 + const cons_gop = try self.constraints.getOrPut(dep_spec.package_name); 1262 1336 if (!cons_gop.found_existing) { 1263 - cons_gop.key_ptr.* = try self.allocator.dupe(u8, name); 1337 + cons_gop.key_ptr.* = try self.allocator.dupe(u8, dep_spec.package_name); 1264 1338 cons_gop.value_ptr.* = .{}; 1265 1339 } 1266 1340 try cons_gop.value_ptr.append(self.allocator, constraint); 1267 1341 1268 - var metadata = try self.fetchMetadata(name); 1342 + var metadata = try self.fetchMetadata(dep_spec.package_name); 1269 1343 const all_constraints = cons_gop.value_ptr.items; 1270 1344 const best = self.selectBestVersionForConstraints(&metadata, all_constraints); 1271 1345 ··· 1276 1350 1277 1351 if (b.version.order(existing_pkg.version) != .eq) { 1278 1352 debug.log(" re-resolve {s}: {d}.{d}.{d} -> {d}.{d}.{d}", .{ 1279 - name, 1353 + dep_spec.install_name, 1280 1354 existing_pkg.version.major, 1281 1355 existing_pkg.version.minor, 1282 1356 existing_pkg.version.patch, ··· 1302 1376 } else { 1303 1377 if (parent_name) |parent| { 1304 1378 const nested_best = self.selectBestVersion(&metadata, constraint) orelse { 1305 - debug.log(" nested: no version for {s} {s}", .{ name, constraint_str }); 1379 + debug.log(" nested: no version for {s} {s}", .{ dep_spec.install_name, dep_spec.constraint }); 1306 1380 return existing_pkg; 1307 1381 }; 1308 1382 1309 1383 if (!nested_best.matchesPlatform()) return existing_pkg; 1310 1384 debug.log(" nested: {s}@{d}.{d}.{d} under {s} (hoisted: {d}.{d}.{d})", .{ 1311 - name, 1385 + dep_spec.install_name, 1312 1386 nested_best.version.major, 1313 1387 nested_best.version.minor, 1314 1388 nested_best.version.patch, ··· 1318 1392 existing_pkg.version.patch, 1319 1393 }); 1320 1394 1321 - return try self.createNestedPackage(name, nested_best, depth, parent); 1395 + return try self.createNestedPackage(dep_spec.install_name, nested_best, depth, parent); 1322 1396 } else { 1323 - debug.log(" version conflict for {s}: no version satisfies all constraints", .{name}); 1397 + debug.log(" version conflict for {s}: no version satisfies all constraints", .{dep_spec.install_name}); 1324 1398 return existing_pkg; 1325 1399 } 1326 1400 } 1327 1401 } 1328 1402 1329 - const cons_gop = try self.constraints.getOrPut(name); 1403 + const cons_gop = try self.constraints.getOrPut(dep_spec.package_name); 1330 1404 if (!cons_gop.found_existing) { 1331 - cons_gop.key_ptr.* = try self.allocator.dupe(u8, name); 1405 + cons_gop.key_ptr.* = try self.allocator.dupe(u8, dep_spec.package_name); 1332 1406 cons_gop.value_ptr.* = .{}; 1333 1407 } 1334 1408 try cons_gop.value_ptr.append(self.allocator, constraint); 1335 1409 1336 - var metadata = try self.fetchMetadata(name); 1410 + var metadata = try self.fetchMetadata(dep_spec.package_name); 1337 1411 const best = self.selectBestVersion(&metadata, constraint) orelse { 1338 1412 return error.NoMatchingVersion; 1339 1413 }; ··· 1346 1420 errdefer self.allocator.destroy(pkg); 1347 1421 1348 1422 pkg.* = .{ 1349 - .name = try self.string_pool.intern(name), 1423 + .name = try self.string_pool.intern(dep_spec.install_name), 1350 1424 .version = best.version, 1351 1425 .integrity = best.integrity, 1352 1426 .tarball_url = try self.allocator.dupe(u8, best.tarball_url), ··· 1358 1432 .allocator = self.allocator, 1359 1433 }; 1360 1434 1361 - const name_key = try self.allocator.dupe(u8, name); 1435 + const name_key = try self.allocator.dupe(u8, dep_spec.install_name); 1362 1436 errdefer self.allocator.free(name_key); 1363 1437 try self.resolved.put(name_key, pkg); 1364 1438 ··· 1378 1452 while (opt_iter.next()) |entry| { 1379 1453 const dep_name = entry.key_ptr.*; 1380 1454 const dep_constraint = entry.value_ptr.*; 1455 + const opt_spec = dependencySpec(dep_name, dep_constraint); 1381 1456 1382 - if (self.metadata_cache.get(dep_name)) |opt_metadata| { 1383 - const opt_constraint = Constraint.parse(dep_constraint) catch continue; 1457 + if (self.metadata_cache.get(opt_spec.package_name)) |opt_metadata| { 1458 + const opt_constraint = Constraint.parse(opt_spec.constraint) catch continue; 1384 1459 const opt_best = self.selectBestVersion(&opt_metadata, opt_constraint) orelse continue; 1385 1460 1386 1461 if (!opt_best.matchesPlatform()) continue; ··· 1453 1528 while (opt_iter.next()) |entry| { 1454 1529 const dep_name = entry.key_ptr.*; 1455 1530 const dep_constraint = entry.value_ptr.*; 1531 + const opt_spec = dependencySpec(dep_name, dep_constraint); 1456 1532 1457 - if (self.metadata_cache.get(dep_name)) |opt_metadata| { 1458 - const opt_constraint = Constraint.parse(dep_constraint) catch continue; 1533 + if (self.metadata_cache.get(opt_spec.package_name)) |opt_metadata| { 1534 + const opt_constraint = Constraint.parse(opt_spec.constraint) catch continue; 1459 1535 const opt_best = self.selectBestVersion(&opt_metadata, opt_constraint) orelse continue; 1460 1536 if (!opt_best.matchesPlatform()) continue; 1461 1537 } ··· 1485 1561 self.allocator.free(in_progress_key); 1486 1562 } 1487 1563 1488 - const constraint = try Constraint.parse(constraint_str); 1489 - const cons_gop = try self.constraints.getOrPut(name); 1490 - 1564 + const dep_spec = dependencySpec(name, constraint_str); 1565 + const constraint = try Constraint.parse(dep_spec.constraint); 1566 + const cons_gop = try self.constraints.getOrPut(dep_spec.package_name); 1567 + 1491 1568 if (!cons_gop.found_existing) { 1492 - cons_gop.key_ptr.* = try self.allocator.dupe(u8, name); 1569 + cons_gop.key_ptr.* = try self.allocator.dupe(u8, dep_spec.package_name); 1493 1570 cons_gop.value_ptr.* = .{}; 1494 1571 } 1495 1572 try cons_gop.value_ptr.append(self.allocator, constraint); 1496 1573 1497 - if (self.resolved.get(name)) |existing_pkg| { 1574 + if (self.resolved.get(dep_spec.install_name)) |existing_pkg| { 1498 1575 if (constraint.satisfies(existing_pkg.version)) { 1499 1576 if (depth == 0) existing_pkg.direct = true; 1500 1577 if (depth < existing_pkg.depth) existing_pkg.depth = depth; 1501 1578 return existing_pkg; 1502 1579 } 1503 1580 1504 - var metadata = try self.fetchMetadata(name); 1581 + var metadata = try self.fetchMetadata(dep_spec.package_name); 1505 1582 const all_constraints = cons_gop.value_ptr.items; 1506 1583 const best = self.selectBestVersionForConstraints(&metadata, all_constraints) orelse { 1507 - debug.log(" version conflict for {s}: no version satisfies all constraints", .{name}); 1584 + debug.log(" version conflict for {s}: no version satisfies all constraints", .{dep_spec.install_name}); 1508 1585 return existing_pkg; 1509 1586 }; 1510 1587 1511 1588 if (best.version.order(existing_pkg.version) != .eq) { 1512 1589 debug.log(" re-resolve {s}: {d}.{d}.{d} -> {d}.{d}.{d}", .{ 1513 - name, 1590 + dep_spec.install_name, 1514 1591 existing_pkg.version.major, 1515 1592 existing_pkg.version.minor, 1516 1593 existing_pkg.version.patch, ··· 1531 1608 return existing_pkg; 1532 1609 } 1533 1610 1534 - var metadata = try self.fetchMetadata(name); 1611 + var metadata = try self.fetchMetadata(dep_spec.package_name); 1535 1612 const all_constraints = cons_gop.value_ptr.items; 1536 1613 const best = self.selectBestVersionForConstraints(&metadata, all_constraints) orelse { 1537 1614 return error.NoMatchingVersion; ··· 1541 1618 errdefer self.allocator.destroy(pkg); 1542 1619 1543 1620 pkg.* = .{ 1544 - .name = try self.string_pool.intern(name), 1621 + .name = try self.string_pool.intern(dep_spec.install_name), 1545 1622 .version = best.version, 1546 1623 .integrity = best.integrity, 1547 1624 .tarball_url = try self.allocator.dupe(u8, best.tarball_url), ··· 1553 1630 .allocator = self.allocator, 1554 1631 }; 1555 1632 1556 - const name_key = try self.allocator.dupe(u8, name); 1633 + const name_key = try self.allocator.dupe(u8, dep_spec.install_name); 1557 1634 errdefer self.allocator.free(name_key); 1558 1635 try self.resolved.put(name_key, pkg); 1559 1636 ··· 1583 1660 continue; 1584 1661 }; 1585 1662 1586 - const opt_metadata = self.fetchMetadata(dep_name) catch continue; 1587 - const opt_constraint = Constraint.parse(dep_constraint) catch continue; 1663 + const opt_spec = dependencySpec(dep_name, dep_constraint); 1664 + const opt_metadata = self.fetchMetadata(opt_spec.package_name) catch continue; 1665 + const opt_constraint = Constraint.parse(opt_spec.constraint) catch continue; 1588 1666 const opt_best = self.selectBestVersion(&opt_metadata, opt_constraint) orelse continue; 1589 1667 1590 1668 if (!opt_best.matchesPlatform()) { 1591 - if (self.resolved.fetchRemove(dep_name)) |kv| { 1669 + if (self.resolved.fetchRemove(opt_spec.install_name)) |kv| { 1592 1670 self.allocator.free(kv.key); 1593 1671 kv.value.deinit(); 1594 1672 self.allocator.destroy(kv.value); ··· 1659 1737 1660 1738 const metadata = try PackageMetadata.parseFromJson(self.cache_allocator, json_data); 1661 1739 const cache_key = try self.cache_allocator.dupe(u8, name); 1662 - 1740 + 1663 1741 try self.metadata_cache.put(cache_key, metadata); 1664 1742 return self.metadata_cache.get(name).?; 1665 1743 } ··· 1711 1789 var satisfies_all = true; 1712 1790 for (all_constraints) |c| { 1713 1791 if (!c.satisfies(v.version)) { 1714 - satisfies_all = false; break; 1792 + satisfies_all = false; 1793 + break; 1715 1794 } 1716 1795 } 1717 1796 ··· 1725 1804 for (metadata.versions.items) |*v| { 1726 1805 var satisfies_all = true; 1727 1806 for (all_constraints) |c| { 1728 - if (!c.satisfies(v.version)) { satisfies_all = false; break; } 1807 + if (!c.satisfies(v.version)) { 1808 + satisfies_all = false; 1809 + break; 1810 + } 1729 1811 } 1730 1812 if (satisfies_all and (best == null or v.version.order(best.?.version) == .gt)) best = v; 1731 1813 } ··· 1760 1842 const url_ref = try writer.internString(pkg.tarball_url); 1761 1843 const prerelease_ref = if (pkg.version.prerelease) |pre| 1762 1844 try writer.internString(pre) 1763 - else lockfile.StringRef.empty; 1845 + else 1846 + lockfile.StringRef.empty; 1764 1847 const parent_ref = if (pkg.parent_path) |parent| 1765 1848 try writer.internString(parent) 1766 - else lockfile.StringRef.empty; 1849 + else 1850 + lockfile.StringRef.empty; 1767 1851 1768 1852 const deps_start: u32 = @intCast(writer.dependencies.items.len); 1769 1853 const pkg_install_path = try pkg.installPath(self.allocator); ··· 1790 1874 .dev = dep.flags.dev, 1791 1875 .optional = dep.flags.optional, 1792 1876 }, 1793 - }); deps_written += 1; 1877 + }); 1878 + deps_written += 1; 1794 1879 } 1795 1880 } 1796 1881
+121 -3
src/pkg/root.zig
··· 2637 2637 return .ok; 2638 2638 } 2639 2639 2640 + const BinSelection = struct { 2641 + err: PkgError, 2642 + name: []const u8 = "", 2643 + }; 2644 + 2645 + fn packageSimpleName(pkg_name: []const u8) []const u8 { 2646 + if (std.mem.lastIndexOfScalar(u8, pkg_name, '/')) |slash| { 2647 + return pkg_name[slash + 1 ..]; 2648 + } 2649 + return pkg_name; 2650 + } 2651 + 2652 + fn normalizedBinTarget(path: []const u8) []const u8 { 2653 + if (std.mem.startsWith(u8, path, "./")) return path[2..]; 2654 + return path; 2655 + } 2656 + 2657 + fn appendBinName(list: *std.ArrayList(u8), allocator: std.mem.Allocator, name: []const u8) !void { 2658 + if (list.items.len > 0) try list.appendSlice(allocator, ", "); 2659 + try list.appendSlice(allocator, name); 2660 + } 2661 + 2662 + fn selectPackageBinName( 2663 + c: *PkgContext, 2664 + allocator: std.mem.Allocator, 2665 + node_modules_path: []const u8, 2666 + pkg_name: []const u8, 2667 + ) BinSelection { 2668 + const simple_name = packageSimpleName(pkg_name); 2669 + 2670 + const pkg_json_path = std.fmt.allocPrint(allocator, "{s}/{s}/package.json", .{ 2671 + node_modules_path, 2672 + pkg_name, 2673 + }) catch return .{ .err = .out_of_memory }; 2674 + 2675 + const file = std.fs.cwd().openFile(pkg_json_path, .{}) catch { 2676 + c.setErrorFmt("Package '{s}' was installed, but its package.json could not be opened", .{pkg_name}); 2677 + return .{ .err = .io_error }; 2678 + }; 2679 + defer file.close(); 2680 + 2681 + const content = file.readToEndAlloc(allocator, 1024 * 1024) catch { 2682 + c.setErrorFmt("Package '{s}' was installed, but its package.json could not be read", .{pkg_name}); 2683 + return .{ .err = .io_error }; 2684 + }; 2685 + 2686 + var doc = json.JsonDoc.parse(content) catch { 2687 + c.setErrorFmt("Package '{s}' was installed, but its package.json could not be parsed", .{pkg_name}); 2688 + return .{ .err = .io_error }; 2689 + }; 2690 + defer doc.deinit(); 2691 + 2692 + const root_val = doc.root(); 2693 + 2694 + if (root_val.getString("bin")) |_| { 2695 + const selected = allocator.dupe(u8, simple_name) catch return .{ .err = .out_of_memory }; 2696 + return .{ .err = .ok, .name = selected }; 2697 + } 2698 + 2699 + const bin_obj = root_val.getObject("bin") orelse { 2700 + c.setErrorFmt("Package '{s}' does not declare any binaries", .{pkg_name}); 2701 + return .{ .err = .not_found }; 2702 + }; 2703 + 2704 + var iter = bin_obj.objectIterator() orelse { 2705 + c.setErrorFmt("Package '{s}' does not declare any usable binaries", .{pkg_name}); 2706 + return .{ .err = .not_found }; 2707 + }; 2708 + 2709 + var valid_count: usize = 0; 2710 + var first_name: []const u8 = ""; 2711 + var first_target: []const u8 = ""; 2712 + var package_named_bin: ?[]const u8 = null; 2713 + var all_targets_same = true; 2714 + var names: std.ArrayList(u8) = .empty; 2715 + 2716 + while (iter.next()) |entry| { 2717 + const target = entry.value.asString() orelse continue; 2718 + 2719 + if (valid_count == 0) { 2720 + first_name = entry.key; 2721 + first_target = normalizedBinTarget(target); 2722 + } else if (!std.mem.eql(u8, normalizedBinTarget(target), first_target)) { 2723 + all_targets_same = false; 2724 + } 2725 + 2726 + appendBinName(&names, allocator, entry.key) catch return .{ .err = .out_of_memory }; 2727 + valid_count += 1; 2728 + 2729 + if (std.mem.eql(u8, entry.key, simple_name)) { 2730 + package_named_bin = entry.key; 2731 + } 2732 + } 2733 + 2734 + const selected = package_named_bin orelse if (valid_count == 1 or all_targets_same) first_name else ""; 2735 + if (selected.len > 0) { 2736 + const selected_copy = allocator.dupe(u8, selected) catch return .{ .err = .out_of_memory }; 2737 + return .{ .err = .ok, .name = selected_copy }; 2738 + } 2739 + 2740 + if (valid_count == 0) { 2741 + c.setErrorFmt("Package '{s}' does not declare any usable binaries", .{pkg_name}); 2742 + } else { 2743 + c.setErrorFmt("Package '{s}' exposes multiple binaries ({s}) and no default could be inferred", .{ 2744 + pkg_name, 2745 + names.items, 2746 + }); 2747 + } 2748 + 2749 + return .{ .err = .not_found }; 2750 + } 2751 + 2640 2752 export fn pkg_exec_temp( 2641 2753 ctx: ?*PkgContext, 2642 2754 package_spec: [*:0]const u8, ··· 2776 2888 var resolved_iter = res.resolved.valueIterator(); 2777 2889 while (resolved_iter.next()) |pkg_ptr| { 2778 2890 const pkg = pkg_ptr.*; 2779 - if (db.hasIntegrity(&pkg.integrity)) { 2780 - const pkg_cache_path = db.getPackagePath(&pkg.integrity, arena_alloc) catch continue; 2891 + if (db.lookup(&pkg.integrity)) |cache_entry| { 2892 + var entry = cache_entry; 2893 + defer entry.deinit(); 2894 + const pkg_cache_path = arena_alloc.dupe(u8, entry.path) catch continue; 2781 2895 pkg_linker.linkPackage(.{ 2782 2896 .cache_path = pkg_cache_path, 2783 2897 .node_modules_path = temp_nm_dir, 2784 2898 .name = pkg.name.slice(), 2785 2899 .parent_path = pkg.parent_path, 2786 - .file_count = 0, 2900 + .file_count = entry.file_count, 2787 2901 .has_bin = pkg.has_bin, 2788 2902 }) catch continue; 2789 2903 } ··· 2797 2911 trusted.put(pkg_ptr.*.name.slice(), {}) catch continue; 2798 2912 } 2799 2913 runTrustedPostinstall(c, &trusted, temp_nm_dir, arena_alloc); 2914 + 2915 + const selected_bin = selectPackageBinName(c, arena_alloc, temp_nm_dir, pkg_name); 2916 + if (selected_bin.err != .ok) return selected_bin.err; 2917 + bin_name = selected_bin.name; 2800 2918 2801 2919 var bin_path_buf: [std.fs.max_path_bytes]u8 = undefined; 2802 2920 const bin_link_path = std.fmt.bufPrint(&bin_path_buf, "{s}/.bin/{s}", .{ temp_nm_dir, bin_name }) catch return .io_error;
+24 -3
src/silver/ast.c
··· 76 76 77 77 static sv_ast_t *parse_stmt(P); 78 78 static sv_ast_t *parse_expr(P); 79 + static sv_ast_t *parse_paren_expr(P); 79 80 static sv_ast_t *parse_assign(P); 80 81 static sv_ast_t *parse_ternary(P); 81 82 static sv_ast_t *parse_binary(P, int min_prec); ··· 409 410 } 410 411 sv_lexer_restore_state(&p->lx, &saved); 411 412 NEXT(); CONSUME(); 412 - sv_ast_t *expr = parse_expr(p); 413 + sv_ast_t *expr = parse_paren_expr(p); 413 414 expect(p, TOK_RPAREN); 414 415 if (LA() == TOK_ARROW) { 415 416 NEXT(); CONSUME(); ··· 534 535 n->src_off = paren_off; 535 536 return n; 536 537 } 537 - sv_ast_t *expr = parse_expr(p); 538 + sv_ast_t *expr = parse_paren_expr(p); 538 539 expect(p, TOK_RPAREN); 539 540 expr->flags |= FN_PAREN; 540 541 expr->src_off = paren_off; ··· 1248 1249 return left; 1249 1250 } 1250 1251 1252 + static sv_ast_t *parse_paren_expr(P) { 1253 + sv_ast_t *left = parse_assign(p); 1254 + while (NEXT() == TOK_COMMA) { 1255 + CONSUME(); 1256 + if (NEXT() == TOK_RPAREN && LA() == TOK_ARROW) break; 1257 + 1258 + sv_ast_t *n = mk(N_SEQUENCE); 1259 + n->left = left; 1260 + n->right = parse_assign(p); 1261 + left = n; 1262 + } 1263 + return left; 1264 + } 1265 + 1251 1266 bool ast_references_arguments(const sv_ast_t *node) { 1252 1267 if (!node) return false; 1253 1268 if (node->type == N_IDENT && node->len == 9 && memcmp(node->str, "arguments", 9) == 0) ··· 1336 1351 1337 1352 uint8_t flags = 0; 1338 1353 1339 - if (TOK == TOK_STATIC) { 1354 + if ( 1355 + TOK == TOK_STATIC && 1356 + LA() != TOK_LPAREN && 1357 + LA() != TOK_ASSIGN && 1358 + LA() != TOK_SEMICOLON && 1359 + LA() != TOK_RBRACE 1360 + ) { 1340 1361 flags |= FN_STATIC; 1341 1362 CONSUME(); 1342 1363 NEXT();
+18 -9
src/silver/compiler.c
··· 2158 2158 typedef enum { 2159 2159 SV_CALL_DIRECT = 0, 2160 2160 SV_CALL_METHOD = 1, 2161 + SV_CALL_SUPER = 2, 2161 2162 } sv_call_kind_t; 2163 + 2164 + static inline bool sv_call_kind_has_receiver(sv_call_kind_t kind) { 2165 + return kind == SV_CALL_METHOD || kind == SV_CALL_SUPER; 2166 + } 2162 2167 2163 2168 static void compile_receiver_property_get(sv_compiler_t *c, sv_ast_t *node) { 2164 2169 emit_op(c, OP_DUP); ··· 2177 2182 ) { 2178 2183 int argc = node->args.count; 2179 2184 if (has_spread) { 2180 - if (kind == SV_CALL_METHOD) emit_op(c, OP_SWAP); 2185 + if (sv_call_kind_has_receiver(kind)) emit_op(c, OP_SWAP); 2181 2186 else emit_op(c, OP_GLOBAL); 2182 2187 compile_call_args_array(c, node); 2183 - emit_op(c, OP_APPLY); 2188 + emit_op(c, kind == SV_CALL_SUPER ? OP_SUPER_APPLY : OP_APPLY); 2184 2189 emit_u16(c, 1); 2185 2190 return; 2186 2191 } 2187 2192 2188 2193 for (int i = 0; i < argc; i++) 2189 2194 compile_expr(c, node->args.items[i]); 2190 - emit_op(c, kind == SV_CALL_METHOD ? OP_CALL_METHOD : OP_CALL); 2195 + emit_op(c, sv_call_kind_has_receiver(kind) ? OP_CALL_METHOD : OP_CALL); 2191 2196 emit_u16(c, (uint16_t)argc); 2192 2197 } 2193 2198 ··· 2195 2200 if (is_ident_name(callee, "super")) { 2196 2201 emit_op(c, OP_THIS); 2197 2202 emit_get_var(c, "super", 5); 2198 - return SV_CALL_METHOD; 2203 + return SV_CALL_SUPER; 2199 2204 } 2200 2205 2201 2206 if (callee->type == N_MEMBER && is_ident_name(callee->left, "super")) { ··· 2247 2252 emit_op(c, OP_IS_UNDEF_OR_NULL); 2248 2253 int j_do_call = emit_jump(c, OP_JMP_FALSE); 2249 2254 emit_op(c, OP_POP); 2250 - if (kind == SV_CALL_METHOD) 2255 + if (sv_call_kind_has_receiver(kind)) 2251 2256 emit_op(c, OP_POP); 2252 2257 emit_op(c, OP_UNDEF); 2253 2258 int j_end = emit_jump(c, OP_JMP); ··· 3649 3654 emit_loop(c, loop_start); 3650 3655 patch_jump(c, exit_jump); 3651 3656 3652 - int skip_break_cleanup = -1; 3653 - if (break_close_slot >= 0) 3654 - skip_break_cleanup = emit_jump(c, OP_JMP); 3655 - 3656 3657 if (is_for_of) { 3657 3658 emit_op(c, OP_POP); 3658 3659 emit_op(c, OP_TRY_POP); 3660 + 3661 + int skip_break_cleanup = -1; 3662 + if (break_close_slot >= 0) 3663 + skip_break_cleanup = emit_jump(c, OP_JMP); 3659 3664 3660 3665 pop_loop(c); 3661 3666 if (break_close_slot >= 0) { ··· 3678 3683 patch_jump(c, end_jump); 3679 3684 c->try_depth--; 3680 3685 } else { 3686 + int skip_break_cleanup = -1; 3687 + if (break_close_slot >= 0) 3688 + skip_break_cleanup = emit_jump(c, OP_JMP); 3689 + 3681 3690 pop_loop(c); 3682 3691 if (break_close_slot >= 0) { 3683 3692 emit_op(c, OP_CLOSE_UPVAL);
+4 -3
src/silver/engine.c
··· 1229 1229 } 1230 1230 1231 1231 L_NEW: { VM_CHECK(sv_op_new(vm, js, ip)); NEXT(3); } 1232 - L_APPLY: { VM_CHECK(sv_op_apply(vm, js, ip)); NEXT(3); } 1233 - L_NEW_APPLY: { VM_CHECK(sv_op_new_apply(vm, js, ip)); NEXT(3); } 1234 - L_EVAL: { VM_CHECK(sv_op_eval(vm, js, frame, ip)); NEXT(5); } 1232 + L_APPLY: { VM_CHECK(sv_op_apply(vm, js, ip)); NEXT(3); } 1233 + L_SUPER_APPLY: { VM_CHECK(sv_op_super_apply(vm, js, frame, ip)); NEXT(3); } 1234 + L_NEW_APPLY: { VM_CHECK(sv_op_new_apply(vm, js, ip)); NEXT(3); } 1235 + L_EVAL: { VM_CHECK(sv_op_eval(vm, js, frame, ip)); NEXT(5); } 1235 1236 1236 1237 // TODO: make the methods below DRY 1237 1238 L_RETURN: {
+23
src/silver/ops/calls.h
··· 119 119 return result; 120 120 } 121 121 122 + static inline ant_value_t sv_op_super_apply(sv_vm_t *vm, ant_t *js, sv_frame_t *frame, uint8_t *ip) { 123 + uint16_t argc = sv_get_u16(ip + 1); 124 + ant_value_t *args = &vm->stack[vm->sp - argc]; 125 + ant_value_t this = vm->stack[vm->sp - argc - 1]; 126 + ant_value_t func = vm->stack[vm->sp - argc - 2]; 127 + sv_call_args_t call; 128 + 129 + sv_call_args_reset(&call, args, (int)argc); 130 + ant_value_t norm = sv_apply_normalize_args(js, &call); 131 + if (is_err(norm)) return norm; 132 + 133 + if (frame) js->new_target = frame->new_target; 134 + ant_value_t super_this = this; 135 + ant_value_t result = sv_vm_call( 136 + vm, js, func, this, call.args, call.argc, &super_this, true); 137 + sv_call_args_release(&call); 138 + vm->sp -= argc + 2; 139 + if (frame && !is_err(result)) 140 + frame->this = is_object_type(result) ? result : super_this; 141 + if (!is_err(result)) vm->stack[vm->sp++] = result; 142 + return result; 143 + } 144 + 122 145 static inline ant_value_t sv_op_new_apply(sv_vm_t *vm, ant_t *js, uint8_t *ip) { 123 146 uint16_t argc = sv_get_u16(ip + 1); 124 147 ant_value_t *args = &vm->stack[vm->sp - argc];
+7 -4
src/silver/ops/coercion.h
··· 101 101 return lookup_string_prop_meta(js, as_obj, name, len, &meta); 102 102 } 103 103 104 - static inline const char *sv_module_namespace_filename(ant_t *js, ant_value_t ns) { 104 + static inline const char *sv_module_namespace_display_name(ant_t *js, ant_value_t ns) { 105 105 if (!is_object_type(ns)) return NULL; 106 106 107 107 ant_value_t module_ctx = js_get_slot(ns, SLOT_MODULE_CTX); 108 108 if (!is_object_type(module_ctx)) return NULL; 109 + 110 + ant_value_t display_name = js_get(js, module_ctx, "displayName"); 111 + if (vtype(display_name) == T_STR) return js_getstr(js, display_name, NULL); 109 112 110 113 ant_value_t filename = js_get(js, module_ctx, "filename"); 111 114 if (vtype(filename) != T_STR) return NULL; ··· 119 122 const char *name, 120 123 size_t len 121 124 ) { 122 - const char *filename = sv_module_namespace_filename(js, ns); 123 - if (!filename) filename = "<unknown>"; 125 + const char *display_name = sv_module_namespace_display_name(js, ns); 126 + if (!display_name) display_name = "<unknown>"; 124 127 return js_mkerr_typed( 125 128 js, JS_ERR_SYNTAX, 126 129 "The requested module '%s' does not provide an export named '%.*s'", 127 - filename, (int)len, name 130 + display_name, (int)len, name 128 131 ); 129 132 } 130 133
+17
src/types/buffer.d.ts
··· 11 11 } 12 12 13 13 declare const Buffer: BufferStatic; 14 + 15 + declare module 'buffer' { 16 + const Buffer: BufferStatic; 17 + const constants: { 18 + MAX_LENGTH: number; 19 + MAX_STRING_LENGTH: number; 20 + }; 21 + const kMaxLength: number; 22 + const kStringMaxLength: number; 23 + const INSPECT_MAX_BYTES: number; 24 + function atob(data: string): string; 25 + function btoa(data: string): string; 26 + } 27 + 28 + declare module 'node:buffer' { 29 + export * from 'buffer'; 30 + }
+3 -1
src/types/modules/child_process.d.ts
··· 1 1 declare module 'child_process' { 2 + import { EventEmitter } from 'events'; 3 + 2 4 interface SpawnResult { 3 5 stdout: string; 4 6 stderr: string; ··· 7 9 pid: number; 8 10 } 9 11 10 - interface ChildProcess { 12 + class ChildProcess extends EventEmitter { 11 13 stdout: string; 12 14 stderr: string; 13 15 exitCode: number | null;
+12
src/types/modules/constants.d.ts
··· 1 + declare module 'constants' { 2 + const constants: Record<string, number>; 3 + export = constants; 4 + } 5 + 6 + declare module 'ant:constants' { 7 + export * from 'constants'; 8 + } 9 + 10 + declare module 'node:constants' { 11 + export * from 'constants'; 12 + }
+6
src/types/modules/fs.d.ts
··· 134 134 function existsSync(path: string): boolean; 135 135 function access(path: string, mode?: number): Promise<void>; 136 136 function accessSync(path: string, mode?: number): void; 137 + function chmod(path: string, mode: number | string): Promise<void>; 138 + function chmodSync(path: string, mode: number | string): void; 137 139 function readdir(path: string): Promise<string[]>; 138 140 function readdirSync(path: string): string[]; 139 141 function realpath(path: string): Promise<string>; 140 142 function realpathSync(path: string): string; 143 + function readlink(path: string): Promise<string>; 144 + function readlinkSync(path: string): string; 141 145 namespace realpathSync { 142 146 function native(path: string): string; 143 147 } ··· 235 239 function stat(path: string): Promise<Stats>; 236 240 function exists(path: string): Promise<boolean>; 237 241 function access(path: string, mode?: number): Promise<void>; 242 + function chmod(path: string, mode: number | string): Promise<void>; 238 243 function readdir(path: string): Promise<string[]>; 239 244 function realpath(path: string): Promise<string>; 245 + function readlink(path: string): Promise<string>; 240 246 } 241 247 242 248 declare module 'ant:fs/promises' {
+15 -1
src/types/modules/stream.d.ts
··· 56 56 57 57 function pipeline(...streams: Array<Stream | ((error?: Error | null) => void)>): Stream; 58 58 function finished(stream: Stream, callback?: (error?: Error | null) => void): Stream; 59 + function getDefaultHighWaterMark(objectMode: boolean): number; 60 + function setDefaultHighWaterMark(objectMode: boolean, value: number): void; 59 61 60 62 const promises: { 61 63 pipeline(...streams: Stream[]): Promise<void>; ··· 63 65 }; 64 66 65 67 export default Stream; 66 - export { Stream, Readable, Writable, Duplex, Transform, PassThrough, pipeline, finished, promises }; 68 + export { 69 + Stream, 70 + Readable, 71 + Writable, 72 + Duplex, 73 + Transform, 74 + PassThrough, 75 + pipeline, 76 + finished, 77 + getDefaultHighWaterMark, 78 + setDefaultHighWaterMark, 79 + promises 80 + }; 67 81 } 68 82 69 83 declare module 'ant:stream' {
+2
src/types/modules/util.d.ts
··· 55 55 function inherits(ctor: (...args: unknown[]) => unknown, superCtor: (...args: unknown[]) => unknown): void; 56 56 function parseEnv(content: string): Record<string, string>; 57 57 function promisify(fn: (...args: unknown[]) => unknown): (...args: unknown[]) => Promise<unknown>; 58 + function callbackify(fn: (...args: unknown[]) => Promise<unknown>): (...args: unknown[]) => void; 59 + function aborted(signal: AbortSignal, resource: object): Promise<void>; 58 60 function stripVTControlCharacters(str: string): string; 59 61 function styleText(format: StyleTextFormat, text: string, options?: StyleTextOptions): string; 60 62
+3
tests/builtin_missing_named_export_importer.mjs
··· 1 + import { __antMissingExportForDisplayNameTest } from 'node:url'; 2 + 3 + void __antMissingExportForDisplayNameTest;
+21
tests/test_builtin_missing_named_export.mjs
··· 1 + function assert(condition, message) { 2 + if (!condition) throw new Error(message); 3 + } 4 + 5 + const expectedMessage = 6 + "The requested module 'node:url' does not provide an export named '__antMissingExportForDisplayNameTest'"; 7 + 8 + let caught = null; 9 + 10 + try { 11 + await import('./builtin_missing_named_export_importer.mjs'); 12 + } catch (err) { 13 + caught = err; 14 + } 15 + 16 + assert(caught, 'expected import to fail'); 17 + assert(caught.name === 'SyntaxError', `expected SyntaxError, got ${caught && caught.name}`); 18 + assert(caught.message === expectedMessage, 19 + `unexpected message:\n${caught && caught.message}\n!=\n${expectedMessage}`); 20 + 21 + console.log('builtin missing named export test passed');
+40
tests/test_fs_chmod_sync.mjs
··· 1 + import fsDefault, { chmod as chmodFromFs, chmodSync as chmodSyncFromFs, statSync, writeFileSync, unlinkSync } from 'fs'; 2 + import { chmod as chmodFromNodeFs, chmodSync as chmodSyncFromNodeFs } from 'node:fs'; 3 + import { chmod as chmodFromPromises } from 'node:fs/promises'; 4 + 5 + function assert(condition, message) { 6 + if (!condition) throw new Error(message); 7 + } 8 + 9 + const testFile = 'tests/.fs_chmod_sync_tmp'; 10 + 11 + try { 12 + writeFileSync(testFile, 'chmod sync test'); 13 + 14 + chmodSyncFromFs(testFile, 0o600); 15 + assert((statSync(testFile).mode & 0o777) === 0o600, 'fs named chmodSync should set numeric mode'); 16 + 17 + chmodSyncFromNodeFs(testFile, '644'); 18 + assert((statSync(testFile).mode & 0o777) === 0o644, 'node:fs named chmodSync should set string mode'); 19 + 20 + fsDefault.chmodSync(testFile, '0o600'); 21 + assert((fsDefault.statSync(testFile).mode & 0o777) === 0o600, 'default chmodSync should set prefixed string mode'); 22 + 23 + await chmodFromPromises(testFile, 0o644); 24 + assert((statSync(testFile).mode & 0o777) === 0o644, 'fs/promises chmod should set numeric mode'); 25 + 26 + await chmodFromNodeFs(testFile, '600'); 27 + assert((statSync(testFile).mode & 0o777) === 0o600, 'node:fs named chmod should set string mode'); 28 + 29 + await new Promise((resolve, reject) => { 30 + chmodFromFs(testFile, 0o644, error => error ? reject(error) : resolve()); 31 + }); 32 + assert((statSync(testFile).mode & 0o777) === 0o644, 'fs callback chmod should set numeric mode'); 33 + } finally { 34 + try { 35 + fsDefault.chmodSync(testFile, 0o600); 36 + unlinkSync(testFile); 37 + } catch {} 38 + } 39 + 40 + console.log('fs chmodSync test passed');
+66
tests/test_process_emit_warning.cjs
··· 1 + const { spawnSync } = require('child_process'); 2 + 3 + function assertIncludes(haystack, needle, label) { 4 + if (!haystack.includes(needle)) { 5 + throw new Error(`${label}: expected ${JSON.stringify(haystack)} to include ${JSON.stringify(needle)}`); 6 + } 7 + } 8 + 9 + function assertNotIncludes(haystack, needle, label) { 10 + if (haystack.includes(needle)) { 11 + throw new Error(`${label}: expected ${JSON.stringify(haystack)} not to include ${JSON.stringify(needle)}`); 12 + } 13 + } 14 + 15 + function assertMatch(haystack, pattern, label) { 16 + if (!pattern.test(haystack)) { 17 + throw new Error(`${label}: expected ${JSON.stringify(haystack)} to match ${pattern}`); 18 + } 19 + } 20 + 21 + function run(source) { 22 + const result = spawnSync(process.execPath, ['-e', source]); 23 + if (result.error) throw result.error; 24 + if (result.status !== 0) { 25 + throw new Error(`child exited ${result.status}\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`); 26 + } 27 + return { 28 + stdout: String(result.stdout), 29 + stderr: String(result.stderr), 30 + }; 31 + } 32 + 33 + const withDetail = run(` 34 + process.emitWarning('Something happened!', { 35 + code: 'Custom_Warning', 36 + detail: 'Additional information about warning' 37 + }); 38 + `); 39 + 40 + assertMatch( 41 + withDetail.stderr, 42 + /\\(ant:\\d+\\) \\[Custom_Warning\\] Warning: Something happened!\\n/, 43 + 'emitWarning formats code, type, and message' 44 + ); 45 + assertIncludes( 46 + withDetail.stderr, 47 + 'Additional information about warning\n', 48 + 'emitWarning prints detail' 49 + ); 50 + assertNotIncludes( 51 + withDetail.stderr, 52 + '[Custom_Warning] : Something happened!', 53 + 'emitWarning avoids the old empty type formatting' 54 + ); 55 + 56 + const listenerPayload = run(` 57 + process.on('warning', warning => { 58 + console.log([warning.name, warning.message, warning.code, warning.detail].join('|')); 59 + }); 60 + process.emitWarning('msg', { code: 'C', detail: 'd' }); 61 + `); 62 + 63 + assertIncludes(listenerPayload.stderr, '[C] Warning: msg\n', 'emitWarning still writes default warning output'); 64 + assertIncludes(listenerPayload.stdout, 'Warning|msg|C|d\n', 'emitWarning emits a warning object'); 65 + 66 + console.log('process.emitWarning formatting ok');