char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
7325e8c
main
1# [ path = "common/registry.rs" ] 2mod fixture; 3 4use fixture::{ Registry , failure, manifest, success, write}; 5use std:: fs; 6use std:: path::{ Path , PathBuf }; 7use std:: process::{ Command , Output }; 8 9fn run ( dir : & Path , args : & [ & str ], entry : & str ) ->Output { 10Command :: new ( env! ( "CARGO_BIN_EXE_avril" )) 11. arg ( "run" ) 12. current_dir ( dir) 13. args ( args) 14. arg ( entry) 15. output () 16. unwrap () 17} 18 19# [ test ] 20fn local_cjs_executes_once_with_shared_identity () { 21let dir = tempfile:: tempdir (). unwrap (); 22write ( 23 dir. path (), 24"main.cjs" , 25r#" 26const a1 = require('./a.cjs'); 27const a2 = require('./a.cjs'); 28console.log(a1 === a2, globalThis.count, a1.foo); 29import('./a.cjs').then((ns) => console.log(ns.default === a1, globalThis.count)); 30"# , 31); 32write ( 33 dir. path (), 34"a.cjs" , 35"globalThis.count = (globalThis.count ?? 0) + 1; exports.foo = 7;" , 36); 37assert_eq! ( 38success ( & run ( dir. path (), & [], "main.cjs" )), 39"true 1 7\ntrue 1\n" 40); 41} 42 43# [ test ] 44fn esm_default_and_named_import_of_cjs () { 45let dir = tempfile:: tempdir (). unwrap (); 46write ( 47 dir. path (), 48"main.js" , 49r#" 50import def, { foo, bar } from './a.cjs'; 51import * as ns from './a.cjs'; 52console.log(def.foo, foo, bar, ns.bar, def === ns.default); 53"# , 54); 55write ( 56 dir. path (), 57"a.cjs" , 58"exports.foo = 1; module.exports.bar = 2; module.exports.foo = 10;" , 59); 60assert_eq! ( 61success ( & run ( dir. path (), & [], "main.js" )), 62"10 10 2 2 true\n" 63); 64} 65 66# [ test ] 67fn function_primitive_and_reassigned_exports () { 68let dir = tempfile:: tempdir (). unwrap (); 69write ( 70 dir. path (), 71"main.cjs" , 72r#" 73console.log(require('./prim.cjs')); 74const fn_ = require('./fn.cjs'); 75console.log(typeof fn_, fn_(), fn_.extra); 76console.log(require('./reassign.cjs').foo); 77"# , 78); 79write ( dir. path (), "prim.cjs" , "module.exports = 42;" ); 80write ( 81 dir. path (), 82"fn.cjs" , 83"module.exports = function() { return 3; }; module.exports.extra = 5;" , 84); 85write ( 86 dir. path (), 87"reassign.cjs" , 88"exports.a = 1; module.exports = { foo: 9 };" , 89); 90assert_eq! ( 91success ( & run ( dir. path (), & [], "main.cjs" )), 92"42\nfunction 3 5\n9\n" 93); 94write ( 95 dir. path (), 96"main2.js" , 97r#" 98import prim from './prim.cjs'; 99import fn_, { extra } from './fn.cjs'; 100console.log(prim, fn_(), extra); 101"# , 102); 103assert_eq! ( success ( & run ( dir. path (), & [], "main2.js" )), "42 3 5\n" ); 104} 105 106# [ test ] 107fn cycles_observe_partial_and_replaced_exports () { 108let dir = tempfile:: tempdir (). unwrap (); 109write ( 110 dir. path (), 111"main.cjs" , 112"console.log(JSON.stringify(require('./a.cjs')));" , 113); 114write ( 115 dir. path (), 116"a.cjs" , 117r#" 118exports.partial = 'a-partial'; 119const b = require('./b.cjs'); 120module.exports.done = 'a-done'; 121module.exports.bSeen = b.partial; 122"# , 123); 124write ( 125 dir. path (), 126"b.cjs" , 127r#" 128exports.partial = 'b-partial'; 129const a = require('./a.cjs'); 130module.exports = { done: 'b-done', aSeen: a.partial }; 131"# , 132); 133// `b` replaces its exports during the cycle, dropping `partial`; `a` 134// observes the replaced object, so `bSeen` is absent (undefined omitted). 135assert_eq! ( 136success ( & run ( dir. path (), & [], "main.cjs" )), 137"{\"partial\":\"a-partial\",\"done\":\"a-done\"}\n" 138); 139} 140 141# [ test ] 142fn lazy_conditional_trycatch_and_failed_retry () { 143let dir = tempfile:: tempdir (). unwrap (); 144write ( 145 dir. path (), 146"main.cjs" , 147r#" 148globalThis.executed = false; 149if (false) { require('./lazy.cjs'); } 150try { require('./missing.cjs'); } catch (e) { console.log('missing'); } 151try { require('./bad.cjs'); } catch (e) { console.log('bad1', e.message); } 152try { require('./bad.cjs'); } catch (e) { console.log('bad2', e.message); } 153console.log('lazy', globalThis.executed); 154"# , 155); 156write ( 157 dir. path (), 158"lazy.cjs" , 159"globalThis.executed = true; module.exports = 1;" , 160); 161write ( dir. path (), "bad.cjs" , "throw new Error('boom');" ); 162let out =success ( & run ( dir. path (), & [], "main.cjs" )); 163assert! ( out. contains ( "missing" ), "{out}" ); 164assert! ( 165 out. contains ( "bad1 boom" ) && out. contains ( "bad2 boom" ), 166"{out}" 167); 168assert! ( out. contains ( "lazy false" ), "{out}" ); 169} 170 171# [ test ] 172fn computed_and_aliased_require_and_resolve_has_no_side_effects () { 173let dir = tempfile:: tempdir (). unwrap (); 174write ( 175 dir. path (), 176"main.cjs" , 177r#" 178const r = require; 179const base = './a'; 180const mod = r(base + '.cjs'); 181const alias = r('./a.cjs'); 182console.log(mod === alias, mod.foo); 183const p = require.resolve('./lazy.cjs'); 184console.log(p.endsWith('lazy.cjs'), globalThis.lazy === undefined); 185"# , 186); 187write ( dir. path (), "a.cjs" , "exports.foo = 11;" ); 188write ( 189 dir. path (), 190"lazy.cjs" , 191"globalThis.lazy = true; module.exports = 1;" , 192); 193assert_eq! ( 194success ( & run ( dir. path (), & [], "main.cjs" )), 195"true 11\ntrue true\n" 196); 197} 198 199# [ test ] 200fn json_require_and_import_rejection () { 201let dir = tempfile:: tempdir (). unwrap (); 202write ( dir. path (), "data.json" , "{\"a\": 5}" ); 203write ( 204 dir. path (), 205"main.cjs" , 206"console.log(require('./data.json').a);" , 207); 208assert_eq! ( success ( & run ( dir. path (), & [], "main.cjs" )), "5\n" ); 209write ( dir. path (), "main2.js" , "import './data.json';" ); 210assert! ( 211failure ( & run ( dir. path (), & [], "main2.js" )). contains ( "JSON" ), 212"import JSON stays unsupported" 213); 214write ( dir. path (), "bad.json" , "{oops" ); 215write ( 216 dir. path (), 217"main3.cjs" , 218"try { require('./bad.json'); } catch (e) { console.log(e.name); }" , 219); 220assert_eq! ( success ( & run ( dir. path (), & [], "main3.cjs" )), "SyntaxError\n" ); 221} 222 223# [ test ] 224fn wrapper_this_return_filename_dirname_and_module_fields () { 225let dir = tempfile:: tempdir (). unwrap (); 226write ( 227 dir. path (), 228"main.cjs" , 229r#" 230console.log(this === exports, typeof require, module.id.endsWith('main.cjs')); 231console.log(module.filename.endsWith('main.cjs'), module.loaded === false); 232console.log(__filename.endsWith('main.cjs'), __dirname.length > 0); 233return; 234"# , 235); 236// Top-level `return` is legal inside the wrapper; reaching here means it parsed. 237assert_eq! ( 238success ( & run ( dir. path (), & [], "main.cjs" )), 239"true function true\ntrue true\ntrue true\n" 240); 241write ( 242 dir. path (), 243"shebang.cjs" , 244"#!/usr/bin/env node\nmodule.exports = 3;" , 245); 246write ( 247 dir. path (), 248"main2.cjs" , 249"console.log(require('./shebang.cjs'));" , 250); 251assert_eq! ( success ( & run ( dir. path (), & [], "main2.cjs" )), "3\n" ); 252} 253 254# [ test ] 255fn import_from_cjs_uses_correct_referrer () { 256let dir = tempfile:: tempdir (). unwrap (); 257write ( dir. path (), "esm.js" , "export const x = 9;" ); 258write ( dir. path (), "sub/inner.cjs" , "module.exports = 1;" ); 259write ( 260 dir. path (), 261"main.cjs" , 262r#" 263import('./esm.js').then((m) => console.log('dyn', m.x)); 264import('./sub/inner.cjs').then((m) => console.log('cjs-dyn', m.default)); 265"# , 266); 267let out =success ( & run ( dir. path (), & [], "main.cjs" )); 268assert! ( out. contains ( "dyn 9" ), "{out}" ); 269assert! ( out. contains ( "cjs-dyn 1" ), "{out}" ); 270} 271 272# [ test ] 273fn require_esm_is_explicitly_unsupported () { 274let dir = tempfile:: tempdir (). unwrap (); 275write ( dir. path (), "esm.js" , "export const x = 1;" ); 276write ( 277 dir. path (), 278"main.cjs" , 279"try { require('./esm.js'); } catch (e) { console.log(e.message.slice(0, 20)); }" , 280); 281let out =success ( & run ( dir. path (), & [], "main.cjs" )); 282assert! ( out. contains ( "require() of ESM" ), "{out}" ); 283} 284 285# [ test ] 286fn extension_and_directory_lookup () { 287let dir = tempfile:: tempdir (). unwrap (); 288write ( dir. path (), "sub/bar.json" , "{\"b\": 2}" ); 289write ( 290 dir. path (), 291"main.cjs" , 292"console.log(require('./sub/bar').b, require('./sub/bar.json').b);" , 293); 294assert_eq! ( success ( & run ( dir. path (), & [], "main.cjs" )), "2 2\n" ); 295write ( dir. path (), "sub/foo.js" , "export const x = 1;" ); 296write ( 297 dir. path (), 298"main2.cjs" , 299"try { require('./sub/foo'); } catch (e) { console.log(e.message.slice(0, 16)); }" , 300); 301assert! ( 302success ( & run ( dir. path (), & [], "main2.cjs" )). contains ( "require() of ESM" ), 303".js stays ESM when required" 304); 305 fs:: create_dir_all ( dir. path (). join ( "mydir" )). unwrap (); 306 fs:: write ( 307 dir. path (). join ( "mydir/package.json" ), 308"{\"main\": \"./entry.cjs\"}" , 309) 310. unwrap (); 311write ( dir. path (), "mydir/entry.cjs" , "module.exports = 99;" ); 312write ( dir. path (), "main3.cjs" , "console.log(require('./mydir'));" ); 313assert_eq! ( success ( & run ( dir. path (), & [], "main3.cjs" )), "99\n" ); 314 fs:: create_dir_all ( dir. path (). join ( "idx" )). unwrap (); 315write ( dir. path (), "idx/index.cjs" , "module.exports = 100;" ); 316write ( dir. path (), "main4.cjs" , "console.log(require('./idx'));" ); 317assert_eq! ( success ( & run ( dir. path (), & [], "main4.cjs" )), "100\n" ); 318 fs:: write ( dir. path (). join ( "bad.node" ), "binary" ). unwrap (); 319write ( 320 dir. path (), 321"main5.cjs" , 322"try { require('./bad'); } catch (e) { console.log(e.message.slice(0, 13)); }" , 323); 324assert! ( 325success ( & run ( dir. path (), & [], "main5.cjs" )). contains ( "Native addons" ), 326".node rejects" 327); 328} 329 330# [ test ] 331fn malformed_syntax_is_guest_error () { 332let dir = tempfile:: tempdir (). unwrap (); 333write ( dir. path (), "bad.cjs" , "const = =" ); 334write ( 335 dir. path (), 336"main.cjs" , 337"try { require('./bad.cjs'); } catch (e) { console.log(e.name); }" , 338); 339assert_eq! ( success ( & run ( dir. path (), & [], "main.cjs" )), "SyntaxError\n" ); 340} 341 342# [ test ] 343fn cached_require_respects_pledge_and_transitive_denial () { 344let dir = tempfile:: tempdir (). unwrap (); 345write ( dir. path (), "a.cjs" , "module.exports = 42;" ); 346write ( 347 dir. path (), 348"b.cjs" , 349"const a = require('./a.cjs'); module.exports = a + 1;" , 350); 351write ( 352 dir. path (), 353"main.js" , 354r#" 355import { pledge } from 'avril:std'; 356import b from './b.cjs'; 357console.log(b); 358pledge({jsExec:{deny:['./a.cjs']}}); 359try { await import('./b.cjs'); console.log('unexpected'); } 360catch (e) { console.log('denied'); } 361"# , 362); 363let output =Command :: new ( env! ( "CARGO_BIN_EXE_avril" )) 364. arg ( "run" ) 365. current_dir ( dir. path ()) 366. arg ( "main.js" ) 367. output () 368. unwrap (); 369let out =success ( & output); 370assert_eq! ( out, "43\ndenied\n" ); 371} 372 373# [ test ] 374fn npm_cjs_default_and_named_import_and_require () { 375let server =Registry :: new (); 376 server. publish_npm ( 377"cjslib" , 378"1.0.0" , 379& [ 380( 381"package.json" , 382& manifest ( "cjslib" , "1.0.0" , serde_json:: json!({ "type" : "commonjs" })), 383), 384( "index.js" , "exports.foo = 1; module.exports.bar = 2;" ), 385], 386); 387let dir = tempfile:: tempdir (). unwrap (); 388 fs:: write ( dir. path (). join ( "avril.conf" ), "deps: ['npm:cjslib@1']" ). unwrap (); 389write ( 390 dir. path (), 391"main.js" , 392r#" 393import def, { foo, bar } from 'npm:cjslib@1'; 394console.log(def.foo, foo, bar); 395"# , 396); 397assert_eq! ( 398success ( & server. run_npm ( dir. path (), & [ "run" ], "main.js" )), 399"1 1 2\n" 400); 401write ( 402 dir. path (), 403"main2.cjs" , 404r#" 405const lib = require('npm:cjslib@1'); 406console.log(lib.foo, lib.bar); 407const again = require('npm:cjslib@1'); 408console.log(lib === again); 409"# , 410); 411assert_eq! ( 412success ( & server. run_npm ( dir. path (), & [ "run" ], "main2.cjs" )), 413"1 2\ntrue\n" 414); 415// Offline frozen rerun uses the pinned tree without network. 416assert_eq! ( 417success ( & server. run_npm ( dir. path (), & [ "run" , "--offline" , "--frozen" ], "main2.cjs" )), 418"1 2\ntrue\n" 419); 420} 421 422# [ test ] 423fn npm_module_entry_and_commonjs_main_keep_distinct_formats () { 424let server =Registry :: new (); 425 server. publish_npm ( 426"dual-entry" , 427"1.0.0" , 428& [ 429( 430"package.json" , 431r#"{"name":"dual-entry","version":"1.0.0","module":"./esm.js","main":"./main.js"}"# , 432), 433( 434"esm.js" , 435"import main from './main.js'; export default { value: main.value + 1 };" , 436), 437( "main.js" , "exports.value = 41;" ), 438], 439); 440let dir = tempfile:: tempdir (). unwrap (); 441write ( dir. path (), "avril.conf" , "deps: ['npm:dual-entry@1']" ); 442write ( 443 dir. path (), 444"main.js" , 445r#" 446import direct from 'npm:dual-entry@1/esm.js'; 447import entry from 'npm:dual-entry@1'; 448import required from './require.cjs'; 449const dynamic = (await import('npm:dual-entry@1')).default; 450console.log(direct === entry, entry.value, required.value, required.denied, dynamic === entry); 451"# , 452); 453write ( 454 dir. path (), 455"require.cjs" , 456r#" 457exports.value = require('npm:dual-entry@1').value; 458try { 459require('npm:dual-entry@1/esm.js'); 460} catch (error) { 461exports.denied = error.message.startsWith('require() of ESM'); 462} 463"# , 464); 465for argsin [ vec! [ "run" ], vec! [ "run" , "--offline" , "--frozen" ]] { 466assert_eq! ( 467success ( & server. run_npm ( dir. path (), & args, "main.js" )), 468"true 42 41 true true\n" 469); 470} 471} 472 473# [ test ] 474fn npm_conditional_exports_choose_require_branch () { 475let server =Registry :: new (); 476 server. publish_npm ( 477"cond" , 478"1.0.0" , 479& [ 480( 481"package.json" , 482& manifest ( 483"cond" , 484"1.0.0" , 485 serde_json:: json!({ "exports" : { " . " : { "require" : "./c.cjs" , "import" : "./e.mjs" }}}), 486), 487), 488( "c.cjs" , "module.exports = 'cjs';" ), 489( "e.mjs" , "export default 'esm';" ), 490], 491); 492let dir = tempfile:: tempdir (). unwrap (); 493 fs:: write ( dir. path (). join ( "avril.conf" ), "deps: ['npm:cond@1']" ). unwrap (); 494write ( 495 dir. path (), 496"main.js" , 497"import def from 'npm:cond@1'; console.log(def);" , 498); 499assert_eq! ( 500success ( & server. run_npm ( dir. path (), & [ "run" ], "main.js" )), 501"esm\n" 502); 503write ( 504 dir. path (), 505"main2.cjs" , 506"console.log(require('npm:cond@1'));" , 507); 508assert_eq! ( 509success ( & server. run_npm ( dir. path (), & [ "run" ], "main2.cjs" )), 510"cjs\n" 511); 512} 513 514# [ test ] 515fn npm_nested_types_and_relative_lookup () { 516let server =Registry :: new (); 517 server. publish_npm ( 518"nested" , 519"1.0.0" , 520& [ 521( 522"package.json" , 523& manifest ( "nested" , "1.0.0" , serde_json:: json!({ "type" : "commonjs" })), 524), 525( "index.js" , "module.exports = require('./sub/util');" ), 526( "sub/util.js" , "module.exports = 5;" ), 527( "sub/data.json" , "{\"v\": 6}" ), 528( "esm/package.json" , "{\"type\": \"module\"}" ), 529( "esm/mod.js" , "export default 7;" ), 530], 531); 532let dir = tempfile:: tempdir (). unwrap (); 533 fs:: write ( dir. path (). join ( "avril.conf" ), "deps: ['npm:nested@1']" ). unwrap (); 534write ( 535 dir. path (), 536"main.cjs" , 537r#" 538console.log(require('npm:nested@1')); 539console.log(require('npm:nested@1/sub/data.json').v); 540"# , 541); 542assert_eq! ( 543success ( & server. run_npm ( dir. path (), & [ "run" ], "main.cjs" )), 544"5\n6\n" 545); 546write ( 547 dir. path (), 548"main2.js" , 549"import mod from 'npm:nested@1/esm/mod.js'; console.log(mod);" , 550); 551assert_eq! ( 552success ( & server. run_npm ( dir. path (), & [ "run" ], "main2.js" )), 553"7\n" 554); 555} 556 557# [ test ] 558fn npm_json_require_and_unprepared_require_fails_clearly () { 559let server =Registry :: new (); 560 server. publish_npm ( 561"data" , 562"1.0.0" , 563& [ 564( 565"package.json" , 566& manifest ( 567"data" , 568"1.0.0" , 569 serde_json:: json!({ "exports" : { " . " : "./data.json" }}), 570), 571), 572( "data.json" , "{\"a\": 8}" ), 573], 574); 575let dir = tempfile:: tempdir (). unwrap (); 576 fs:: write ( dir. path (). join ( "avril.conf" ), "deps: ['npm:data@1']" ). unwrap (); 577write ( 578 dir. path (), 579"main.cjs" , 580"console.log(require('npm:data@1').a);" , 581); 582assert_eq! ( 583success ( & server. run_npm ( dir. path (), & [ "run" ], "main.cjs" )), 584"8\n" 585); 586// Unprepared new npm acquisition via sync require fails without network. 587let dir2 = tempfile:: tempdir (). unwrap (); 588write ( 589 dir2. path (), 590"main.cjs" , 591"try { require('npm:data@1'); } catch (e) { console.log(e.message); }" , 592); 593let out =success ( & server. run_npm ( dir2. path (), & [ "run" ], "main.cjs" )); 594assert! ( out. contains ( "not prepared" ), "{out}" ); 595} 596 597# [ test ] 598fn npm_require_esm_is_unsupported_and_native_rejects () { 599let server =Registry :: new (); 600 server. publish_npm ( 601"esmlib" , 602"1.0.0" , 603& [ 604( 605"package.json" , 606& manifest ( "esmlib" , "1.0.0" , serde_json:: json!({ "type" : "module" })), 607), 608( "index.js" , "export default 1;" ), 609], 610); 611 server. publish_npm ( 612"nativelib" , 613"1.0.0" , 614& [ 615( 616"package.json" , 617& manifest ( 618"nativelib" , 619"1.0.0" , 620 serde_json:: json!({ "exports" : { " . " : "./binding.node" }}), 621), 622), 623( "binding.node" , "binary" ), 624], 625); 626let dir = tempfile:: tempdir (). unwrap (); 627 fs:: write ( 628 dir. path (). join ( "avril.conf" ), 629"deps: ['npm:esmlib@1', 'npm:nativelib@1']" , 630) 631. unwrap (); 632write ( 633 dir. path (), 634"main.cjs" , 635"try { require('npm:esmlib@1'); } catch (e) { console.log(e.message.slice(0, 16)); }" , 636); 637assert! ( 638success ( & server. run_npm ( dir. path (), & [ "run" ], "main.cjs" )). contains ( "require() of ESM" ), 639"require(ESM) is explicitly unsupported" 640); 641write ( 642 dir. path (), 643"main2.cjs" , 644"try { require('npm:nativelib@1'); } catch (e) { console.log(e.message.slice(0, 13)); }" , 645); 646assert! ( 647success ( & server. run_npm ( dir. path (), & [ "run" ], "main2.cjs" )). contains ( "Native addons" ), 648"native rejects" 649); 650} 651 652# [ test ] 653fn reentry_getter_no_panic_and_single_read () { 654let dir = tempfile:: tempdir (). unwrap (); 655write ( 656 dir. path (), 657"a.cjs" , 658r#" 659let n = 0; 660Object.defineProperty(module, 'exports', { get() { 661if (++n === 3) return require('./b.cjs'); 662return { n }; 663}}); 664"# , 665); 666write ( dir. path (), "b.cjs" , "module.exports = 42;" ); 667write ( 668 dir. path (), 669"main.cjs" , 670"require('./a.cjs'); console.log('second'); console.log(JSON.stringify(require('./a.cjs'))); console.log(require('./a.cjs'));" , 671); 672assert_eq! ( 673success ( & run ( dir. path (), & [], "main.cjs" )), 674"second\n{\"n\":2}\n42\n" 675); 676write ( 677 dir. path (), 678"single.cjs" , 679"let n = 0; Object.defineProperty(module, 'exports', { get() { return ++n; } });" , 680); 681write ( 682 dir. path (), 683"main2.cjs" , 684"console.log(require('./single.cjs'));" , 685); 686assert_eq! ( success ( & run ( dir. path (), & [], "main2.cjs" )), "1\n" ); 687} 688 689# [ test ] 690fn require_args_are_strings_not_coerced () { 691let dir = tempfile:: tempdir (). unwrap (); 692write ( dir. path (), "a.cjs" , "module.exports = 1;" ); 693write ( 694 dir. path (), 695"main.cjs" , 696r#" 697for (const bad of [42, {}, null]) { 698try { require(bad); console.log('coerced'); } 699catch (e) { console.log(e.name); } 700} 701try { require.resolve(); } catch (e) { console.log(e.name); } 702"# , 703); 704assert_eq! ( 705success ( & run ( dir. path (), & [], "main.cjs" )), 706"TypeError\nTypeError\nTypeError\nTypeError\n" 707); 708} 709 710# [ test ] 711fn prototype_pollution_does_not_fire_on_creation () { 712let dir = tempfile:: tempdir (). unwrap (); 713write ( dir. path (), "a.cjs" , "module.exports = 7;" ); 714write ( 715 dir. path (), 716"main.cjs" , 717r#" 718Object.defineProperty(Object.prototype, 'exports', { set(v) { throw new Error('setter fired'); } }); 719Object.defineProperty(Object.prototype, 'resolve', { set(v) { throw new Error('resolve setter'); } }); 720console.log(require('./a.cjs')); 721"# , 722); 723assert_eq! ( success ( & run ( dir. path (), & [], "main.cjs" )), "7\n" ); 724} 725 726# [ test ] 727fn json_semantics_match_v8_not_serde () { 728let dir = tempfile:: tempdir (). unwrap (); 729write ( dir. path (), "big.json" , "{\"n\":1e999}" ); 730write ( dir. path (), "bom.json" , "\u{FEFF}{\"a\":1}" ); 731 fs:: write ( 732 dir. path (). join ( "deep.json" ), 733format! ( "{{\"a\":{}}}" , "[" . repeat ( 50 ) +& "]" . repeat ( 50 )), 734) 735. unwrap (); 736write ( dir. path (), "bad.json" , "{oops" ); 737write ( 738 dir. path (), 739"main.cjs" , 740r#" 741console.log(require('./big.json').n); 742console.log(require('./bom.json').a); 743console.log(typeof require('./deep.json').a); 744try { require('./bad.json'); } catch (e) { console.log(e.name); } 745"# , 746); 747assert_eq! ( 748success ( & run ( dir. path (), & [], "main.cjs" )), 749"Infinity\n1\nobject\nSyntaxError\n" 750); 751} 752 753# [ test ] 754fn cjs_source_maps_preserve_throw_lines () { 755let dir = tempfile:: tempdir (). unwrap (); 756write ( 757 dir. path (), 758"a.cjs" , 759"exports.ok = 1;\nthrow new Error('boom');\n" , 760); 761write ( 762 dir. path (), 763"main.cjs" , 764"try { require('./a.cjs'); } catch (e) { console.log(e.stack.split('\\n')[1]); }" , 765); 766let out =success ( & run ( dir. path (), & [], "main.cjs" )); 767assert! ( out. contains ( "a.cjs:2:" ), "{out}" ); 768write ( 769 dir. path (), 770"b.cts" , 771"const x: number = 1;\nthrow new Error('cts-boom');\n" , 772); 773write ( 774 dir. path (), 775"main2.cjs" , 776"try { require('./b.cts'); } catch (e) { console.log(e.stack.split('\\n')[1]); }" , 777); 778let out2 =success ( & run ( dir. path (), & [], "main2.cjs" )); 779assert! ( out2. contains ( "b.cts:2:" ), "{out2}" ); 780} 781 782# [ test ] 783fn local_dotted_basename_and_trailing_slash () { 784let dir = tempfile:: tempdir (). unwrap (); 785write ( dir. path (), "a.b.json" , "{\"v\":3}" ); 786write ( dir. path (), "main.cjs" , "console.log(require('./a.b').v);" ); 787assert_eq! ( success ( & run ( dir. path (), & [], "main.cjs" )), "3\n" ); 788} 789 790# [ test ] 791fn esm_intermediary_does_not_hide_revoked_cjs () { 792let dir = tempfile:: tempdir (). unwrap (); 793write ( dir. path (), "a.cjs" , "module.exports = 1;" ); 794write ( 795 dir. path (), 796"mid.js" , 797"export { default } from './a.cjs'; export { default as v } from './a.cjs';" , 798); 799write ( 800 dir. path (), 801"main.js" , 802r#" 803import { pledge } from 'avril:std'; 804import { v } from './mid.js'; 805console.log(v); 806pledge({jsExec:{deny:['./a.cjs']}}); 807try { await import('./mid.js'); console.log('unexpected'); } 808catch (e) { console.log('denied'); } 809"# , 810); 811let output =Command :: new ( env! ( "CARGO_BIN_EXE_avril" )) 812. arg ( "run" ) 813. current_dir ( dir. path ()) 814. arg ( "main.js" ) 815. output () 816. unwrap (); 817assert_eq! ( success ( & output), "1\ndenied\n" ); 818} 819 820# [ test ] 821fn bare_and_aliased_npm_require () { 822let server =Registry :: new (); 823 server. publish_npm ( 824"cjslib" , 825"1.0.0" , 826& [ 827( 828"package.json" , 829& manifest ( "cjslib" , "1.0.0" , serde_json:: json!({ "type" : "commonjs" })), 830), 831( "index.js" , "module.exports = 21;" ), 832], 833); 834 server. publish_npm ( 835"alib" , 836"1.0.0" , 837& [ 838( 839"package.json" , 840& manifest ( "alib" , "1.0.0" , serde_json:: json!({ "type" : "commonjs" })), 841), 842( "index.js" , "module.exports = 22;" ), 843], 844); 845let dir = tempfile:: tempdir (). unwrap (); 846 fs:: write ( 847 dir. path (). join ( "avril.conf" ), 848"deps: ['npm:cjslib@1', {from: 'npm:alib@1', name: 'ui'}]" , 849) 850. unwrap (); 851write ( 852 dir. path (), 853"main.cjs" , 854"console.log(require('cjslib'), require('ui'));" , 855); 856assert_eq! ( 857success ( & server. run_npm ( dir. path (), & [ "run" ], "main.cjs" )), 858"21 22\n" 859); 860} 861 862# [ test ] 863fn fresh_cjs_only_offline_and_cache () { 864let server =Registry :: new (); 865 server. publish_npm ( 866"cjslib" , 867"1.0.0" , 868& [ 869( 870"package.json" , 871& manifest ( "cjslib" , "1.0.0" , serde_json:: json!({ "type" : "commonjs" })), 872), 873( "index.js" , "module.exports = 33;" ), 874], 875); 876let dir = tempfile:: tempdir (). unwrap (); 877 fs:: write ( dir. path (). join ( "avril.conf" ), "deps: ['npm:cjslib@1']" ). unwrap (); 878write ( 879 dir. path (), 880"main.cjs" , 881"console.log(require('npm:cjslib@1'));" , 882); 883assert_eq! ( 884success ( & server. run_npm ( dir. path (), & [ "run" ], "main.cjs" )), 885"33\n" 886); 887assert_eq! ( 888success ( & server. run_npm ( dir. path (), & [ "run" , "--offline" , "--frozen" ], "main.cjs" )), 889"33\n" 890); 891let cache_out = server. run_npm ( dir. path (), & [ "cache" ], "main.cjs" ); 892assert! ( 893 cache_out. status . success (), 894"{}" , 895String :: from_utf8_lossy ( & cache_out. stderr ) 896); 897assert_eq! ( 898success ( & server. run_npm ( dir. path (), & [ "run" , "--offline" , "--frozen" ], "main.cjs" )), 899"33\n" 900); 901} 902 903# [ test ] 904fn npm_legacy_main_without_extension_and_subdir () { 905let server =Registry :: new (); 906 server. publish_npm ( 907"leg" , 908"1.0.0" , 909& [ 910( 911"package.json" , 912& manifest ( 913"leg" , 914"1.0.0" , 915 serde_json:: json!({ "type" : "commonjs" , "main" : "lib/start" }), 916), 917), 918( "lib/start.js" , "module.exports = 44;" ), 919( "lib/index.js" , "module.exports = 45;" ), 920( "sub.js" , "module.exports = 46;" ), 921], 922); 923let dir = tempfile:: tempdir (). unwrap (); 924 fs:: write ( dir. path (). join ( "avril.conf" ), "deps: ['npm:leg@1']" ). unwrap (); 925write ( 926 dir. path (), 927"main.cjs" , 928"console.log(require('npm:leg@1')); console.log(require('npm:leg@1/sub'));" , 929); 930assert_eq! ( 931success ( & server. run_npm ( dir. path (), & [ "run" ], "main.cjs" )), 932"44\n46\n" 933); 934 server. publish_npm ( 935"legdir" , 936"1.0.0" , 937& [ 938( 939"package.json" , 940& manifest ( 941"legdir" , 942"1.0.0" , 943 serde_json:: json!({ "type" : "commonjs" , "main" : "lib" }), 944), 945), 946( "lib/index.js" , "module.exports = 47;" ), 947], 948); 949let dir2 = tempfile:: tempdir (). unwrap (); 950 fs:: write ( dir2. path (). join ( "avril.conf" ), "deps: ['npm:legdir@1']" ). unwrap (); 951write ( 952 dir2. path (), 953"main.cjs" , 954"console.log(require('npm:legdir@1'));" , 955); 956assert_eq! ( 957success ( & server. run_npm ( dir2. path (), & [ "run" ], "main.cjs" )), 958"47\n" 959); 960} 961 962# [ test ] 963fn cached_cjs_require_after_pledge_denies () { 964let dir = tempfile:: tempdir (). unwrap (); 965write ( dir. path (), "a.cjs" , "module.exports = 1;" ); 966write ( 967 dir. path (), 968"main.cjs" , 969r#" 970console.log(require('./a.cjs')); 971import('avril:std').then(({ pledge }) => { 972pledge({ jsExec: { deny: ['./a.cjs'] } }); 973try { console.log(require('./a.cjs')); } catch (e) { console.log('denied'); } 974}); 975"# , 976); 977let out =success ( & run ( dir. path (), & [], "main.cjs" )); 978assert_eq! ( out, "1\ndenied\n" ); 979} 980 981# [ test ] 982fn named_getter_transitive_denial () { 983let dir = tempfile:: tempdir (). unwrap (); 984write ( dir. path (), "b.cjs" , "module.exports = 42;" ); 985write ( 986 dir. path (), 987"a.cjs" , 988"Object.defineProperty(exports, 'foo', { enumerable: true, get() { return require('./b.cjs'); } });" , 989); 990write ( 991 dir. path (), 992"main.js" , 993r#" 994import { pledge } from 'avril:std'; 995import { foo } from './a.cjs'; 996console.log(foo); 997pledge({jsExec:{deny:['./b.cjs']}}); 998try { await import('./a.cjs'); console.log('unexpected'); } 999catch (e) { console.log('denied'); } 1000"# , 1001); 1002let output =Command :: new ( env! ( "CARGO_BIN_EXE_avril" )) 1003. arg ( "run" ) 1004. current_dir ( dir. path ()) 1005. arg ( "main.js" ) 1006. output () 1007. unwrap (); 1008assert_eq! ( success ( & output), "42\ndenied\n" ); 1009} 1010 1011# [ test ] 1012fn npm_cache_path_needs_virtual_authority () { 1013let server =Registry :: new (); 1014 server. publish_npm ( 1015"cjslib" , 1016"1.0.0" , 1017& [ 1018( 1019"package.json" , 1020& manifest ( "cjslib" , "1.0.0" , serde_json:: json!({ "type" : "commonjs" })), 1021), 1022( "index.js" , "module.exports = 1;" ), 1023], 1024); 1025let dir = tempfile:: tempdir (). unwrap (); 1026 fs:: write ( dir. path (). join ( "avril.conf" ), "deps: ['npm:cjslib@1']" ). unwrap (); 1027write ( 1028 dir. path (), 1029"main.cjs" , 1030"console.log(require('npm:cjslib@1'));" , 1031); 1032// Local-only grant cannot authorize npm virtual files. 1033let out =Command :: new ( env! ( "CARGO_BIN_EXE_avril" )) 1034. arg ( "run" ) 1035. current_dir ( dir. path ()) 1036. arg ( "-0" ) 1037. arg ( "--allow-js-exec=./" ) 1038. arg ( format! ( "--jsr-registry={}" , server. url )) 1039. arg ( format! ( "--npm-registry={}" , server. url )) 1040. arg ( format! ( 1041"--cache-dir={}" , 1042 dir. path (). join ( "cache" ). display () 1043)) 1044. arg ( "main.cjs" ) 1045. output () 1046. unwrap (); 1047assert! ( 1048 !out. status . success (), 1049"local grant must not authorize npm cache" 1050); 1051assert! ( 1052String :: from_utf8_lossy ( & out. stderr ). contains ( "PermissionDenied" ), 1053"{}" , 1054String :: from_utf8_lossy ( & out. stderr ) 1055); 1056} 1057 1058# [ test ] 1059fn npm_private_exports_and_imports_reject () { 1060let server =Registry :: new (); 1061 server. publish_npm ( 1062"priv" , 1063"1.0.0" , 1064& [ 1065( 1066"package.json" , 1067& manifest ( 1068"priv" , 1069"1.0.0" , 1070 serde_json:: json!({ 1071"type" : "commonjs" , 1072"exports" : { "." : "./index.js" }, 1073"imports" : { "#u" : "./utils.js" } 1074}), 1075), 1076), 1077( "index.js" , "module.exports = require('#u');" ), 1078( "utils.js" , "module.exports = 55;" ), 1079( "secret.js" , "module.exports = 99;" ), 1080], 1081); 1082let dir = tempfile:: tempdir (). unwrap (); 1083 fs:: write ( dir. path (). join ( "avril.conf" ), "deps: ['npm:priv@1']" ). unwrap (); 1084write ( 1085 dir. path (), 1086"main.cjs" , 1087"console.log(require('npm:priv@1'));" , 1088); 1089assert_eq! ( 1090success ( & server. run_npm ( dir. path (), & [ "run" ], "main.cjs" )), 1091"55\n" 1092); 1093write ( 1094 dir. path (), 1095"main2.cjs" , 1096"try { require('npm:priv@1/secret.js'); } catch (e) { console.log('blocked'); }" , 1097); 1098assert_eq! ( 1099success ( & server. run_npm ( dir. path (), & [ "run" ], "main2.cjs" )), 1100"blocked\n" 1101); 1102} 1103 1104# [ test ] 1105fn npm_imports_choose_require_branch () { 1106let server =Registry :: new (); 1107 server. publish_npm ( 1108"imp" , 1109"1.0.0" , 1110& [ 1111( 1112"package.json" , 1113& manifest ( 1114"imp" , 1115"1.0.0" , 1116 serde_json:: json!({ 1117"type" : "commonjs" , 1118"imports" : { "#x" : { "require" : "./c.cjs" , "import" : "./e.mjs" } } 1119}), 1120), 1121), 1122( "index.js" , "module.exports = require('#x');" ), 1123( "c.cjs" , "module.exports = 'c';" ), 1124( "e.mjs" , "export default 'e';" ), 1125], 1126); 1127let dir = tempfile:: tempdir (). unwrap (); 1128 fs:: write ( dir. path (). join ( "avril.conf" ), "deps: ['npm:imp@1']" ). unwrap (); 1129write ( dir. path (), "main.cjs" , "console.log(require('npm:imp@1'));" ); 1130assert_eq! ( 1131success ( & server. run_npm ( dir. path (), & [ "run" ], "main.cjs" )), 1132"c\n" 1133); 1134} 1135 1136# [ test ] 1137fn import_map_mapped_require () { 1138let dir = tempfile:: tempdir (). unwrap (); 1139 fs:: write ( 1140 dir. path (). join ( "avril.conf" ), 1141"imports: { a: './a.cjs', dir: './mydir', alias: './b.cjs' }" , 1142) 1143. unwrap (); 1144write ( dir. path (), "a.cjs" , "module.exports = 42;" ); 1145write ( dir. path (), "b.cjs" , "module.exports = 43;" ); 1146 fs:: create_dir_all ( dir. path (). join ( "mydir" )). unwrap (); 1147 fs:: write ( 1148 dir. path (). join ( "mydir/package.json" ), 1149"{\"main\":\"./entry.cjs\"}" , 1150) 1151. unwrap (); 1152write ( dir. path (), "mydir/entry.cjs" , "module.exports = 44;" ); 1153write ( 1154 dir. path (), 1155"main.cjs" , 1156"console.log(require('a'), require('dir'), require('alias'));" , 1157); 1158assert_eq! ( success ( & run ( dir. path (), & [], "main.cjs" )), "42 44 43\n" ); 1159// Symlink identity: symlinked file resolves to canonical target. 1160# [ cfg ( unix )] 1161{ 1162 std:: os:: unix:: fs:: symlink ( dir. path (). join ( "a.cjs" ), dir. path (). join ( "link.cjs" )). unwrap (); 1163write ( 1164 dir. path (), 1165"main2.cjs" , 1166"console.log(require('./link.cjs') === require('./a.cjs'));" , 1167); 1168assert_eq! ( success ( & run ( dir. path (), & [], "main2.cjs" )), "true\n" ); 1169} 1170let dir2 = tempfile:: tempdir (). unwrap (); 1171 fs:: write ( 1172 dir2. path (). join ( "avril.conf" ), 1173"imports: { bad: 'ext:foo', nd: 'node:fs' }" , 1174) 1175. unwrap (); 1176write ( 1177 dir2. path (), 1178"main.cjs" , 1179"try { require('bad'); } catch (e) { console.log('denied-ext'); } try { require('nd'); } catch (e) { console.log('denied-node'); }" , 1180); 1181assert_eq! ( 1182success ( & run ( dir2. path (), & [], "main.cjs" )), 1183"denied-ext\ndenied-node\n" 1184); 1185} 1186 1187# [ test ] 1188fn local_trailing_slash_directory () { 1189let dir = tempfile:: tempdir (). unwrap (); 1190 fs:: create_dir_all ( dir. path (). join ( "idx" )). unwrap (); 1191write ( dir. path (), "idx/index.cjs" , "module.exports = 100;" ); 1192write ( dir. path (), "main.cjs" , "console.log(require('./idx/'));" ); 1193assert_eq! ( success ( & run ( dir. path (), & [], "main.cjs" )), "100\n" ); 1194} 1195 1196# [ test ] 1197fn denied_getter_not_executed () { 1198let dir = tempfile:: tempdir (). unwrap (); 1199write ( dir. path (), "b.cjs" , "module.exports = 1;" ); 1200write ( 1201 dir. path (), 1202"a.cjs" , 1203r#" 1204const b = require('./b.cjs'); 1205globalThis.count = 0; 1206Object.defineProperty(module, 'exports', { enumerable: true, get() { globalThis.count++; return { b }; } }); 1207"# , 1208); 1209write ( dir. path (), "require.cjs" , "module.exports = require;" ); 1210write ( 1211 dir. path (), 1212"main.js" , 1213r#" 1214import { pledge } from 'avril:std'; 1215const r = (await import('./require.cjs')).default; 1216console.log(r('./a.cjs').b, globalThis.count); 1217pledge({jsExec:{deny:['./b.cjs']}}); 1218try { r('./a.cjs'); console.log('unexpected'); } 1219catch (e) { console.log('denied', globalThis.count); } 1220"# , 1221); 1222let output =Command :: new ( env! ( "CARGO_BIN_EXE_avril" )) 1223. arg ( "run" ) 1224. current_dir ( dir. path ()) 1225. arg ( "main.js" ) 1226. output () 1227. unwrap (); 1228assert_eq! ( success ( & output), "1 1\ndenied 1\n" ); 1229} 1230 1231# [ test ] 1232fn getter_invokes_pledge () { 1233let dir = tempfile:: tempdir (). unwrap (); 1234write ( dir. path (), "b.cjs" , "module.exports = 2;" ); 1235write ( 1236 dir. path (), 1237"a.cjs" , 1238r#" 1239const cached = require('./b.cjs'); 1240Object.defineProperty(module, 'exports', { enumerable: true, get() { 1241globalThis.pledge({ jsExec: { deny: ['./b.cjs'] } }); 1242return cached; 1243}}); 1244"# , 1245); 1246write ( 1247 dir. path (), 1248"named.cjs" , 1249r#" 1250const cached = require('./b.cjs'); 1251Object.defineProperty(exports, 'foo', { enumerable: true, get() { 1252globalThis.pledge({ jsExec: { deny: ['./b.cjs'] } }); 1253return cached; 1254}}); 1255"# , 1256); 1257write ( 1258 dir. path (), 1259"main.js" , 1260r#" 1261import { pledge } from 'avril:std'; 1262globalThis.pledge = pledge; 1263try { (await import('./a.cjs')).default; console.log('unexpected'); } 1264catch (e) { console.log('denied-module'); } 1265globalThis.pledge = pledge; 1266try { (await import('./named.cjs')).foo; console.log('unexpected'); } 1267catch (e) { console.log('denied-named'); } 1268"# , 1269); 1270let output =Command :: new ( env! ( "CARGO_BIN_EXE_avril" )) 1271. arg ( "run" ) 1272. current_dir ( dir. path ()) 1273. arg ( "main.js" ) 1274. output () 1275. unwrap (); 1276assert_eq! ( success ( & output), "denied-module\ndenied-named\n" ); 1277} 1278 1279# [ test ] 1280fn npm_root_and_trailing_require () { 1281let server =Registry :: new (); 1282 server. publish_npm ( 1283"rootpkg" , 1284"1.0.0" , 1285& [ 1286( 1287"package.json" , 1288& manifest ( 1289"rootpkg" , 1290"1.0.0" , 1291 serde_json:: json!({ "type" : "commonjs" , "main" : "./index.js" }), 1292), 1293), 1294( "index.js" , "module.exports = { value: 61 };" ), 1295( "helper.cjs" , "module.exports = require('./');" ), 1296], 1297); 1298let dir = tempfile:: tempdir (). unwrap (); 1299 fs:: write ( dir. path (). join ( "avril.conf" ), "deps: ['npm:rootpkg@1']" ). unwrap (); 1300write ( 1301 dir. path (), 1302"main.cjs" , 1303"const h = require('npm:rootpkg@1/helper.cjs'); const r = require('npm:rootpkg@1'); console.log(h === r, h.value);" , 1304); 1305assert_eq! ( 1306success ( & server. run_npm ( dir. path (), & [ "run" ], "main.cjs" )), 1307"true 61\n" 1308); 1309 server. publish_npm ( 1310"dirpkg" , 1311"1.0.0" , 1312& [ 1313( 1314"package.json" , 1315& manifest ( "dirpkg" , "1.0.0" , serde_json:: json!({ "type" : "commonjs" })), 1316), 1317( "index.js" , "module.exports = require('./sub/');" ), 1318( "sub/package.json" , "{\"main\": \"./inner.js\"}" ), 1319( "sub/inner.js" , "module.exports = 63;" ), 1320], 1321); 1322let dir2 = tempfile:: tempdir (). unwrap (); 1323 fs:: write ( dir2. path (). join ( "avril.conf" ), "deps: ['npm:dirpkg@1']" ). unwrap (); 1324write ( 1325 dir2. path (), 1326"main.cjs" , 1327"console.log(require('npm:dirpkg@1'));\n console.log(require('npm:dirpkg@1/sub/'));" , 1328); 1329assert_eq! ( 1330success ( & server. run_npm ( dir2. path (), & [ "run" ], "main.cjs" )), 1331"63\n63\n" 1332); 1333} 1334 1335# [ test ] 1336fn npm_malformed_and_tampered_metadata_fail () { 1337let server =Registry :: new (); 1338 server. publish_npm ( 1339"badmeta" , 1340"1.0.0" , 1341& [ 1342( 1343"package.json" , 1344& manifest ( "badmeta" , "1.0.0" , serde_json:: json!({ "type" : "commonjs" })), 1345), 1346( "index.js" , "module.exports = require('./sub');" ), 1347( "sub/package.json" , "{oops" ), 1348( "sub/index.js" , "module.exports = 1;" ), 1349], 1350); 1351let dir = tempfile:: tempdir (). unwrap (); 1352 fs:: write ( dir. path (). join ( "avril.conf" ), "deps: ['npm:badmeta@1']" ). unwrap (); 1353write ( 1354 dir. path (), 1355"main.cjs" , 1356"console.log(require('npm:badmeta@1'));" , 1357); 1358// Malformed nested metadata fails when consumed, not via fallback. 1359assert! ( 1360failure ( & server. run_npm ( dir. path (), & [ "run" ], "main.cjs" )). contains ( "Invalid nested" ), 1361"malformed nested package.json must propagate" 1362); 1363// Post-acquisition tampering fails verification on use. 1364let server2 =Registry :: new (); 1365 server2. publish_npm ( 1366"tamper" , 1367"1.0.0" , 1368& [ 1369( 1370"package.json" , 1371& manifest ( "tamper" , "1.0.0" , serde_json:: json!({ "type" : "commonjs" })), 1372), 1373( "index.js" , "module.exports = 1;" ), 1374], 1375); 1376let dir3 = tempfile:: tempdir (). unwrap (); 1377 fs:: write ( dir3. path (). join ( "avril.conf" ), "deps: ['npm:tamper@1']" ). unwrap (); 1378write ( 1379 dir3. path (), 1380"main.cjs" , 1381"console.log(require('npm:tamper@1'));" , 1382); 1383assert_eq! ( 1384success ( & server2. run_npm ( dir3. path (), & [ "run" ], "main.cjs" )), 1385"1\n" 1386); 1387// Corrupt one materialized file; next use must fail integrity, not fallback. 1388fn find_index ( dir : & Path ) ->Option < PathBuf > { 1389let mut stack =vec! [ dir. to_owned ()]; 1390while let Some ( d) = stack. pop () { 1391for ein fs:: read_dir ( & d). ok () ?{ 1392let e = e. ok () ?; 1393let path = e. path (); 1394if path. is_dir () { 1395 stack. push ( path); 1396} else if path. file_name (). and_then ( |n| n. to_str ()) ==Some ( "index.js" ) { 1397return Some ( path); 1398} 1399} 1400} 1401None 1402} 1403let tampered_path = 1404find_index ( & dir3. path (). join ( ".avril/npm" )). expect ( "expected materialized index.js" ); 1405 fs:: write ( & tampered_path, "module.exports = 2; // tampered" ). unwrap (); 1406assert! ( 1407failure ( & server2. run_npm ( dir3. path (), & [ "run" ], "main.cjs" )). contains ( "integrity" ), 1408"tampered npm file must fail verification" 1409); 1410} 1411 1412# [ test ] 1413fn npm_mapping_leakage_and_peer_cjs_identity () { 1414let server =Registry :: new (); 1415 server. publish_npm ( 1416"peer-dep" , 1417"1.0.0" , 1418& [ 1419( 1420"package.json" , 1421& manifest ( "peer-dep" , "1.0.0" , serde_json:: json!({ "type" : "commonjs" })), 1422), 1423( "index.js" , "module.exports = { tag: 'peer' };" ), 1424], 1425); 1426 server. publish_npm ( 1427"has-peer" , 1428"1.0.0" , 1429& [ 1430( 1431"package.json" , 1432& manifest ( 1433"has-peer" , 1434"1.0.0" , 1435 serde_json:: json!({ 1436"type" : "commonjs" , 1437"peerDependencies" : { "peer-dep" : "1" } 1438}), 1439), 1440), 1441( "index.js" , "module.exports = require('peer-dep');" ), 1442], 1443); 1444let dir = tempfile:: tempdir (). unwrap (); 1445 fs:: write ( 1446 dir. path (). join ( "avril.conf" ), 1447"deps: ['npm:has-peer@1', 'npm:peer-dep@1'], imports: { mapped: './local.cjs' }" , 1448) 1449. unwrap (); 1450write ( dir. path (), "local.cjs" , "module.exports = 72;" ); 1451write ( 1452 dir. path (), 1453"main.cjs" , 1454"console.log(require('npm:has-peer@1').tag, require('mapped')); console.log(require('npm:has-peer@1') === require('npm:peer-dep@1'));" , 1455); 1456assert_eq! ( 1457success ( & server. run_npm ( dir. path (), & [ "run" ], "main.cjs" )), 1458"peer 72\ntrue\n" 1459); 1460// npm-owned file must not use project import map: publish npm file requiring 'mapped' should fail (not a declared dep). 1461 server. publish_npm ( 1462"leaky" , 1463"1.0.0" , 1464& [ 1465( 1466"package.json" , 1467& manifest ( "leaky" , "1.0.0" , serde_json:: json!({ "type" : "commonjs" })), 1468), 1469( "index.js" , "module.exports = require('mapped');" ), 1470], 1471); 1472let dir2 = tempfile:: tempdir (). unwrap (); 1473 fs:: write ( 1474 dir2. path (). join ( "avril.conf" ), 1475"deps: ['npm:leaky@1'], imports: { mapped: './local.cjs' }" , 1476) 1477. unwrap (); 1478write ( dir2. path (), "local.cjs" , "module.exports = 1;" ); 1479write ( 1480 dir2. path (), 1481"main.cjs" , 1482"try { require('npm:leaky@1'); console.log('unexpected'); } catch (e) { console.log('blocked'); }" , 1483); 1484assert_eq! ( 1485success ( & server. run_npm ( dir2. path (), & [ "run" ], "main.cjs" )), 1486"blocked\n" 1487); 1488} 1489 1490# [ test ] 1491fn cts_erased_positions_and_shebang () { 1492let dir = tempfile:: tempdir (). unwrap (); 1493write ( 1494 dir. path (), 1495"a.cts" , 1496"#!/usr/bin/env node\ntype A = { x: number };\nenum E { A, B }\nconst v: A = { x: 1 };\nthrow new Error('cts-boom');\n" , 1497); 1498write ( 1499 dir. path (), 1500"main.cjs" , 1501"try { require('./a.cts'); } catch (e) { console.log(e.stack.split('\\n')[1]); }" , 1502); 1503let out =success ( & run ( dir. path (), & [], "main.cjs" )); 1504assert! ( out. contains ( "a.cts:5:" ), "{out}" ); 1505} 1506 1507# [ test ] 1508fn cjs_snapshot_stable_across_mutation () { 1509let dir = tempfile:: tempdir (). unwrap (); 1510write ( dir. path (), "a.cjs" , "exports.foo = 1;" ); 1511write ( 1512 dir. path (), 1513"mutate.js" , 1514r#" 1515import { writeTextFile } from 'avril:fs'; 1516await writeTextFile('./a.cjs', 'exports.bar = 2;'); 1517"# , 1518); 1519write ( 1520 dir. path (), 1521"main.js" , 1522r#" 1523import './mutate.js'; 1524import def, { foo } from './a.cjs'; 1525console.log(def.foo, foo, 'bar' in def); 1526"# , 1527); 1528// Link stores foo from prepared snapshot; mutate rewrites before CJS eval, 1529// but eval uses prepared snapshot + stored names, so no fatal and foo survives. 1530assert_eq! ( success ( & run ( dir. path (), & [], "main.js" )), "1 1 false\n" ); 1531} 1532 1533# [ test ] 1534fn fresh_cache_in_distinct_project_offline () { 1535let server =Registry :: new (); 1536 server. publish_npm ( 1537"cjslib2" , 1538"1.0.0" , 1539& [ 1540( 1541"package.json" , 1542& manifest ( "cjslib2" , "1.0.0" , serde_json:: json!({ "type" : "commonjs" })), 1543), 1544( "index.js" , "module.exports = 77;" ), 1545], 1546); 1547let dir = tempfile:: tempdir (). unwrap (); 1548 fs:: write ( dir. path (). join ( "avril.conf" ), "deps: ['npm:cjslib2@1']" ). unwrap (); 1549write ( 1550 dir. path (), 1551"main.cjs" , 1552"console.log(require('npm:cjslib2@1'));" , 1553); 1554let cache_out = server. run_npm ( dir. path (), & [ "cache" ], "main.cjs" ); 1555assert! ( 1556 cache_out. status . success (), 1557"{}" , 1558String :: from_utf8_lossy ( & cache_out. stderr ) 1559); 1560assert_eq! ( 1561success ( & server. run_npm ( dir. path (), & [ "run" , "--offline" , "--frozen" ], "main.cjs" )), 1562"77\n" 1563); 1564} 1565 1566# [ test ] 1567fn npm_absolute_paths_share_identity_and_block_escape () { 1568let server =Registry :: new (); 1569 server. publish_npm ( 1570"abspkg" , 1571"1.0.0" , 1572& [ 1573( 1574"package.json" , 1575& manifest ( "abspkg" , "1.0.0" , serde_json:: json!({ "type" : "commonjs" })), 1576), 1577( "index.js" , "module.exports = { v: 81 };" ), 1578( "data.js" , "module.exports = { v: 82 };" ), 1579( 1580"caller.js" , 1581"const rel = require('./data.js'); const viaResolve = require(require.resolve('./data.js')); const viaDir = require(__dirname + '/data.js'); let blocked = false; try { require('/etc/passwd'); } catch (_) { blocked = true; } console.log(rel === viaResolve, rel === viaDir, rel.v, blocked); module.exports = { rel, viaResolve };" , 1582), 1583], 1584); 1585let dir_a = tempfile:: tempdir (). unwrap (); 1586 fs:: write ( dir_a. path (). join ( "avril.conf" ), "deps: ['npm:abspkg@1']" ). unwrap (); 1587write ( 1588 dir_a. path (), 1589"main.cjs" , 1590"const a = require('npm:abspkg@1/data.js'); const p = require.resolve('npm:abspkg@1/data.js'); const b = require(p); console.log(a === b, a.v);" , 1591); 1592assert_eq! ( 1593success ( & server. run_npm ( dir_a. path (), & [ "run" ], "main.cjs" )), 1594"true 82\n" 1595); 1596write ( 1597 dir_a. path (), 1598"main2.cjs" , 1599"const c = require('npm:abspkg@1/caller.js'); console.log(c.rel.v);" , 1600); 1601// Within-instance require.resolve('./data') + __dirname absolute share identity; outside absolute blocked. 1602assert_eq! ( 1603success ( & server. run_npm ( dir_a. path (), & [ "run" ], "main2.cjs" )), 1604"true true 82 true\n82\n" 1605); 1606} 1607 1608# [ test ] 1609fn npm_cached_absolute_after_pledge () { 1610let server =Registry :: new (); 1611 server. publish_npm ( 1612"pledgeabs" , 1613"1.0.0" , 1614& [ 1615( 1616"package.json" , 1617& manifest ( 1618"pledgeabs" , 1619"1.0.0" , 1620 serde_json:: json!({ "type" : "commonjs" }), 1621), 1622), 1623( "index.js" , "module.exports = require('./data.js');" ), 1624( "data.js" , "module.exports = { v: 83 };" ), 1625], 1626); 1627let dir = tempfile:: tempdir (). unwrap (); 1628 fs:: write ( dir. path (). join ( "avril.conf" ), "deps: ['npm:pledgeabs@1']" ). unwrap (); 1629write ( dir. path (), "require.cjs" , "module.exports = require;" ); 1630write ( 1631 dir. path (), 1632"main.js" , 1633r#" 1634import { pledge } from 'avril:std'; 1635const r = (await import('./require.cjs')).default; 1636const first = r('npm:pledgeabs@1/data.js'); 1637const abs = r.resolve('npm:pledgeabs@1/data.js'); 1638console.log(first.v, first === r(abs)); 1639pledge({ jsExec: false }); 1640try { r(abs); console.log('unexpected'); } 1641catch (e) { console.log('denied'); } 1642"# , 1643); 1644assert_eq! ( 1645success ( & server. run_npm ( dir. path (), & [ "run" ], "main.js" )), 1646"83 true\ndenied\n" 1647); 1648} 1649 1650# [ test ] 1651fn npm_dot_main_and_nested_dot_fallback () { 1652let server =Registry :: new (); 1653 server. publish_npm ( 1654"dotmain" , 1655"1.0.0" , 1656& [ 1657( 1658"package.json" , 1659& manifest ( 1660"dotmain" , 1661"1.0.0" , 1662 serde_json:: json!({ "type" : "commonjs" , "main" : "." }), 1663), 1664), 1665( "index.js" , "module.exports = 91;" ), 1666], 1667); 1668 server. publish_npm ( 1669"dotdir" , 1670"1.0.0" , 1671& [ 1672( 1673"package.json" , 1674& manifest ( "dotdir" , "1.0.0" , serde_json:: json!({ "type" : "commonjs" })), 1675), 1676( "index.js" , "module.exports = require('./sub');" ), 1677( "sub/package.json" , "{\"main\": \".\"}" ), 1678( "sub/index.js" , "module.exports = 92;" ), 1679], 1680); 1681let dir = tempfile:: tempdir (). unwrap (); 1682 fs:: write ( 1683 dir. path (). join ( "avril.conf" ), 1684"deps: ['npm:dotmain@1', 'npm:dotdir@1']" , 1685) 1686. unwrap (); 1687write ( 1688 dir. path (), 1689"main.cjs" , 1690"console.log(require('npm:dotmain@1'), require('npm:dotdir@1'));" , 1691); 1692assert_eq! ( 1693success ( & server. run_npm ( dir. path (), & [ "run" ], "main.cjs" )), 1694"91 92\n" 1695); 1696} 1697 1698# [ test ] 1699fn npm_same_graph_tampered_metadata_fails_not_fallback () { 1700let server =Registry :: new (); 1701 server. publish_npm ( 1702"metagraph2" , 1703"1.0.0" , 1704& [ 1705( 1706"package.json" , 1707& manifest ( "metagraph2" , "1.0.0" , serde_json:: json!({ "type" : "commonjs" })), 1708), 1709( 1710"index.js" , 1711r#"module.exports.run = () => { const pkg = require.resolve('./sub/package.json'); globalThis.syncWrite(pkg, '{oops'); return require('./sub'); };"# , 1712), 1713( "sub/package.json" , "{\"main\": \"./inner.js\"}" ), 1714( "sub/inner.js" , "module.exports = 1;" ), 1715( "sub/index.js" , "module.exports = 2;" ), 1716], 1717); 1718let dir = tempfile:: tempdir (). unwrap (); 1719 fs:: write ( dir. path (). join ( "avril.conf" ), "deps: ['npm:metagraph2@1']" ). unwrap (); 1720write ( 1721 dir. path (), 1722"main.js" , 1723r#" 1724import { writeTextFile } from 'avril:fs/sync'; 1725globalThis.syncWrite = writeTextFile; 1726const pkg = (await import('npm:metagraph2@1')).default; 1727try { pkg.run(); console.log('unexpected'); } catch (e) { console.log(e.message.includes('Invalid nested') || e.message.includes('integrity') ? 'failed' : 'other:' + e.message); } 1728"# , 1729); 1730assert_eq! ( 1731success ( & server. run_npm ( dir. path (), & [ "run" ], "main.js" )), 1732"failed\n" 1733); 1734} 1735 1736# [ test ] 1737fn local_symlink_npm_metadata_denied_and_tamper_rejected () { 1738let server =Registry :: new (); 1739 server. publish_npm ( 1740"metapkg" , 1741"1.0.0" , 1742& [ 1743( 1744"package.json" , 1745& manifest ( 1746"metapkg" , 1747"1.0.0" , 1748 serde_json:: json!({ "type" : "commonjs" , "main" : "./entry.js" }), 1749), 1750), 1751( "entry.js" , "module.exports = 91;" ), 1752], 1753); 1754let dir = tempfile:: tempdir (). unwrap (); 1755 fs:: write ( dir. path (). join ( "avril.conf" ), "deps: ['npm:metapkg@1']" ). unwrap (); 1756write ( 1757 dir. path (), 1758"main.cjs" , 1759"console.log(require('npm:metapkg@1'));" , 1760); 1761assert_eq! ( 1762success ( & server. run_npm ( dir. path (), & [ "run" ], "main.cjs" )), 1763"91\n" 1764); 1765fn find_manifest ( root : & Path ) ->Option < PathBuf > { 1766let mut stack =vec! [ root. join ( ".avril/npm" )]; 1767while let Some ( d) = stack. pop () { 1768for ein fs:: read_dir ( & d). ok () ?{ 1769let e = e. ok () ?; 1770let path = e. path (); 1771if path. is_dir () { 1772 stack. push ( path); 1773} else if path. file_name (). and_then ( |n| n. to_str ()) ==Some ( "package.json" ) 1774 && fs:: read_to_string ( & path). ok () ?. contains ( "\"metapkg\"" ) 1775{ 1776return Some ( path); 1777} 1778} 1779} 1780None 1781} 1782let manifest_path =find_manifest ( dir. path ()). expect ( "expected materialized npm manifest" ); 1783# [ cfg ( unix )] 1784{ 1785 fs:: create_dir_all ( dir. path (). join ( "aliased" )). unwrap (); 1786 std:: os:: unix:: fs:: symlink ( & manifest_path, dir. path (). join ( "aliased/package.json" )) 1787. unwrap (); 1788write ( 1789 dir. path (), 1790"main2.cjs" , 1791"try { console.log(require('./aliased')); } catch (e) { console.log(e.message.includes('PermissionDenied') || e.message.includes('not owned') ? 'denied' : 'other:' + e.message); }" , 1792); 1793// No npm deps in config so prepare succeeds with local-only grant; 1794// aliased metadata read must still deny (no virtual authority / unowned). 1795 fs:: write ( dir. path (). join ( "avril.conf" ), "deps: []" ). unwrap (); 1796let out =Command :: new ( env! ( "CARGO_BIN_EXE_avril" )) 1797. arg ( "run" ) 1798. current_dir ( dir. path ()) 1799. arg ( "-0" ) 1800. arg ( "--allow-js-exec=./" ) 1801. arg ( format! ( "--jsr-registry={}" , server. url )) 1802. arg ( format! ( "--npm-registry={}" , server. url )) 1803. arg ( format! ( 1804"--cache-dir={}" , 1805 dir. path (). join ( "cache" ). display () 1806)) 1807. arg ( "main2.cjs" ) 1808. output () 1809. unwrap (); 1810assert_eq! ( success ( & out), "denied\n" ); 1811// Restore dep so npm instance is prepared; tampered manifest must fail integrity, not fallback. 1812 fs:: write ( dir. path (). join ( "avril.conf" ), "deps: ['npm:metapkg@1']" ). unwrap (); 1813 fs:: write ( & manifest_path, "{\"name\":\"metapkg\",\"version\":\"1.0.0\",\"main\":\"./entry.js\",\"tampered\":true}" ). unwrap (); 1814write ( 1815 dir. path (), 1816"main3.cjs" , 1817"console.log(require('./aliased'));" , 1818); 1819assert! ( 1820failure ( & server. run_npm ( dir. path (), & [ "run" ], "main3.cjs" )). contains ( "integrity" ), 1821"tampered aliased metadata must fail verification" 1822); 1823} 1824}