Evict apps/animate to kappa-store (M3): 15 blake3 objects
Browse files- .gitattributes +1 -0
- b/2f156fc728ed9576f68326bd8e342b7d9f7497fea8dba1cd941bfd94b582174d +1 -0
- b/432cde7e8423e4b3ce960802ed09f9d2def267dcee8a1118c956d803eac78ed8 +226 -0
- b/47fba521d83144ed4bfd40e803eec7e9d04814cd4c905acfbe456a1f6c6b458d +163 -0
- b/535ed9bd32c3c964ae2cd9dbf69406fc007db53501a6bdbefe33c70d949e4b90 +3 -0
- b/70255554e8d094619d355e66eb84d43727b00217c548422bb69de0aeb2005c76 +339 -0
- b/8d0e7f2b52c1ca5e44a55b25f70f7dfaaf6d1da04a859cec7593143ffb678efb +56 -0
- b/b55f686e982d5a4ce34e2194790338a73e75d57669bc27a6d88adc46856864e3 +150 -0
- b/bd107202590889391a58c57799da577a930f2a0f2c748480ca3cda91a04eddc6 +228 -0
- b/c23e5859d8ebacbe948e9084f45938ff25f459bb9da5b137c6525bc1271fba24 +39 -0
- b/c96aa7b602dd1542e195bc8d57aad812b68d99afda9d51a659d76d64a2421632 +69 -0
- b/dbe3e6ed0f585a4cd05dc636cedfa61910a9c00ca548340eee366f5ab906a8b9 +289 -0
- b/e08b35ffc16f9f2b45d93d51033a4a061db1c1d59edf76ecffc192c15fb2743b +93 -0
- b/e1881b19fc538fcfbf3294d25e81dc61006e50aa62ce04953f92076473054dd0 +84 -0
- b/e6201a82be15cf856eb6ceb7f48bb806f06013e6a0b7f92d22ddb5b8d03dcbfe +128 -0
- b/f4e2a6b63fe6250e0eb3126ee6cb5598ac4c372fd0deea7fe8ef7a2eec63eeb1 +22 -0
.gitattributes
CHANGED
|
@@ -62,3 +62,4 @@ b/c4b894de1e276ccb732d9e17ab407022a1fcfb3d415ce7ac5e48c0c22464ef4d filter=lfs di
|
|
| 62 |
b/cd4d521be3e2bdbc82e815d8411b0e92c31f935584d4bb5f4b9ec911434f90de filter=lfs diff=lfs merge=lfs -text
|
| 63 |
b/d63c7e2117883cf9ec8551a013d48695c4fc8183c81c1c704a1920060fcfe570 filter=lfs diff=lfs merge=lfs -text
|
| 64 |
b/ed23baff6c0015f9a92afba85a8e1043afe1ca57733c08ad8ff79273270ef861 filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 62 |
b/cd4d521be3e2bdbc82e815d8411b0e92c31f935584d4bb5f4b9ec911434f90de filter=lfs diff=lfs merge=lfs -text
|
| 63 |
b/d63c7e2117883cf9ec8551a013d48695c4fc8183c81c1c704a1920060fcfe570 filter=lfs diff=lfs merge=lfs -text
|
| 64 |
b/ed23baff6c0015f9a92afba85a8e1043afe1ca57733c08ad8ff79273270ef861 filter=lfs diff=lfs merge=lfs -text
|
| 65 |
+
b/535ed9bd32c3c964ae2cd9dbf69406fc007db53501a6bdbefe33c70d949e4b90 filter=lfs diff=lfs merge=lfs -text
|
b/2f156fc728ed9576f68326bd8e342b7d9f7497fea8dba1cd941bfd94b582174d
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"built":"2026-07-07T13:45:26.227Z","featured":[{"full_name":"jakesgordon/javascript-tetris","name":"Tetris","owner":"jakesgordon","description":"The classic falling-block puzzle — playable the instant it opens.","stars":800,"language":"JavaScript","registry":true,"tier":"R0","kappa":"did:holo:blake3:82c0a30659d9ba11302899e1323c0f78d96095d2891de3b3320546eddb4c6fa3","verifiedPlayable":false},{"full_name":"mdn/todo-react","name":"TodoMatic","owner":"mdn","description":"A clean, focused to-do list built with React.","stars":400,"language":"JavaScript","registry":true,"tier":"R1","kappa":"did:holo:blake3:b8a511f8852045c23495d8cad2886a0d461e65bd34e99a8e6ab5f987b7ad0782","verifiedPlayable":false},{"full_name":"ghosh/uiGradients","name":"uiGradients","owner":"ghosh","description":"Beautiful, ready-to-use colour gradients for designers.","stars":6300,"language":"JavaScript","registry":true,"tier":"R1","kappa":"did:holo:blake3:3098d262b317e38873b1e5d0364eb0e35f9457f1e771105c5c851d20c17fcd6e","verifiedPlayable":false},{"full_name":"hakimel/reveal.js","name":"reveal.js","owner":"hakimel","description":"Make gorgeous presentations that run in the browser.","stars":67000,"language":"JavaScript","registry":true,"tier":"R0","kappa":"did:holo:blake3:464b13937a3d27d1f760d1d807125d0a2fe1b0e106b95f1728dc10ee9b02b115","verifiedPlayable":false},{"full_name":"gabrielecirulli/2048","name":"2048","owner":"gabrielecirulli","description":"Join the numbers to reach 2048 — the original.","stars":13000,"language":"JavaScript","registry":true,"tier":"R0","kappa":"did:holo:blake3:bc15ef7eb1c8cd819c6f8c38ac1db17c57eceac886c1dc7e5fbdd9f0a24a4c4a","verifiedPlayable":true},{"full_name":"ellisonleao/clumsy-bird","name":"Clumsy Bird","owner":"ellisonleao","description":"A Flappy Bird port on the melonJS game engine.","stars":1600,"language":"JavaScript","registry":true,"tier":"R0","kappa":"did:holo:blake3:3a5ff2f7c341e4017671f708a83fa9fece50b7e59f7c9ddea649789a26412692","verifiedPlayable":true},{"full_name":"victorqribeiro/isocity","name":"IsoCity","owner":"victorqribeiro","description":"Build an isometric city, one tile at a time.","stars":3200,"language":"JavaScript","registry":true,"tier":"R0","kappa":"did:holo:blake3:a90aeb73486133c1d2d8c8b63d4f3efc2fc19f1900e94750aa56a2b421dbf532","verifiedPlayable":true},{"full_name":"epidemian/snake","name":"Snake","owner":"epidemian","description":"A silly snake game that lives in the browser URL.","stars":1400,"language":"JavaScript","registry":true,"tier":"R0","kappa":"did:holo:blake3:788257c50e3c10cc1cdacfca6dcea25577d4403f653dc93fe0d97c2d0ee3d02b","verifiedPlayable":true},{"full_name":"renzorlive/vimmaster","name":"VIM Master","owner":"renzorlive","description":"Learn core Vim motions through short in-browser challenges.","stars":1700,"language":"JavaScript","registry":true,"tier":"R0","kappa":"did:holo:blake3:f88353dc8eda21bedbcdda8f0006085591296703b81d0c53514d6073ce6094c3","verifiedPlayable":true},{"full_name":"iamkun/tower_game","name":"Tower Game","owner":"iamkun","description":"HTML5 canvas tower-stacking arcade game.","stars":1600,"language":"JavaScript","registry":true,"tier":"R0","kappa":"did:holo:blake3:530ade799a5273674c34530c6165374583d597ffadce7fe0b42c4cabcca9aa00","verifiedPlayable":true}],"categories":[{"id":"games","label":"🎮 Games","blurb":"Playable in one click."},{"id":"creative","label":"🎨 Creative coding","blurb":"Generative & interactive art."},{"id":"threed","label":"🧊 3D & WebGL","blurb":"Real-time 3D in the browser."},{"id":"dataviz","label":"📊 Data viz","blurb":"Charts, maps & dashboards."},{"id":"emulators","label":"🕹️ Emulators","blurb":"Retro machines, in a tab."},{"id":"tools","label":"🛠️ Tools","blurb":"Handy web apps & utilities."},{"id":"ai","label":"🤖 AI","blurb":"AI you can run yourself."}],"apps":{"games":[{"full_name":"gabrielecirulli/2048","name":"2048","owner":"gabrielecirulli","description":"The source code for 2048 ","stars":13315,"forks":17588,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/886011?v=4","updated":"2024-10-24T13:06:19Z","license":"MIT","topics":["2048","2048-game","game","javascript"],"verifiedPlayable":true},{"full_name":"ellisonleao/clumsy-bird","name":"clumsy-bird","owner":"ellisonleao","description":":bird: :video_game: A MelonJS port of the famous Flappy Bird Game","stars":1614,"forks":1086,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/178641?v=4","updated":"2018-04-13T23:19:54Z","license":"GPL-3.0","topics":["flappy-bird","game","game-development","javascript"],"verifiedPlayable":true},{"full_name":"victorqribeiro/isocity","name":"isocity","owner":"victorqribeiro","description":"A isometric city builder in JavaScript","stars":3242,"forks":198,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/3822139?v=4","updated":"2024-10-14T17:37:50Z","license":"MIT","topics":["city-builder","game","javascript"],"verifiedPlayable":true},{"full_name":"epidemian/snake","name":"snake","owner":"epidemian","description":"A silly snake game on the browser URL","stars":1392,"forks":114,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/722544?v=4","updated":"2025-09-30T04:31:25Z","license":"Unlicense","topics":["address-bar","game","snake-game","url"],"verifiedPlayable":true},{"full_name":"renzorlive/vimmaster","name":"vimmaster","owner":"renzorlive","description":"VIM Master: in-browser game that teaches core Vim motions and editing commands through short, focused levels. ","stars":1707,"forks":71,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/30356443?v=4","updated":"2025-09-02T18:11:17Z","license":"MIT","topics":["coding","education","game","indie-game"],"verifiedPlayable":true},{"full_name":"iamkun/tower_game","name":"tower_game","owner":"iamkun","description":"💒 HTML5 Canvas Tower Building Game 🏢🏬🏦🏯🏰","stars":1597,"forks":445,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/17680888?v=4","updated":"2025-08-29T09:43:42Z","license":"MIT","topics":["canvas","game","html5","javascript"],"verifiedPlayable":true},{"full_name":"4ian/GDevelop","name":"GDevelop","owner":"4ian","description":"🎮 Open-source, cross-platform 2D/3D/multiplayer game engine designed for everyone.","stars":24430,"forks":1434,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/1280130?v=4","updated":"2026-07-06T17:02:02Z","license":"NOASSERTION","topics":["2d-game","2d-game-engine","3d","3d-game"]},{"full_name":"thomaspark/flexboxfroggy","name":"flexboxfroggy","owner":"thomaspark","description":"A game for learning CSS flexbox 🐸","stars":7359,"forks":727,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/900407?v=4","updated":"2026-01-04T19:19:53Z","license":"MIT","topics":["css","flexbox","flexbox-froggy","game"]},{"full_name":"tobspr-games/shapez.io","name":"shapez.io","owner":"tobspr-games","description":"shapez is an open source base building game on Steam inspired by factorio!","stars":6906,"forks":1303,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/114693393?v=4","updated":"2026-04-28T06:56:37Z","license":"GPL-3.0","topics":["game","game-2d","game-development","gamedev"]},{"full_name":"pshenok/server-survival","name":"server-survival","owner":"pshenok","description":"Tower defense game that teaches cloud architecture. Build infrastructure, survive traffic, learn scaling.","stars":6128,"forks":686,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/42213409?v=4","updated":"2026-07-05T06:47:49Z","license":"MIT","topics":["aws","cloud","devops","education"]},{"full_name":"hiloteam/Hilo","name":"Hilo","owner":"hiloteam","description":"A Cross-end HTML5 Game development solution","stars":5933,"forks":847,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/13195604?v=4","updated":"2025-10-20T03:25:49Z","license":"MIT","topics":["canvas","game","hilo","webgl"]},{"full_name":"nicholas-ochoa/OpenSC2K","name":"OpenSC2K","owner":"nicholas-ochoa","description":"OpenSC2K - An Open Source remake of Sim City 2000 by Maxis","stars":5089,"forks":110,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/126299?v=4","updated":"2020-01-27T03:38:22Z","license":"GPL-3.0","topics":["2000","city","electron","game"]},{"full_name":"FormidableLabs/react-game-kit","name":"react-game-kit","owner":"FormidableLabs","description":"Component library for making games with React & React Native","stars":4605,"forks":311,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/5078602?v=4","updated":"2023-01-04T16:18:43Z","license":"MIT","topics":["component-tree","game","matter","physics-bodies"]},{"full_name":"thomaspark/gridgarden","name":"gridgarden","owner":"thomaspark","description":"A game for learning CSS grid layout 🥕","stars":3381,"forks":320,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/900407?v=4","updated":"2026-01-04T19:15:04Z","license":"MIT","topics":["css","cssgrid","game","learning"]},{"full_name":"deck-of-cards/deck-of-cards","name":"deck-of-cards","owner":"deck-of-cards","description":"Deck of Cards (old version)","stars":3144,"forks":471,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/29150664?v=4","updated":"2025-06-07T21:00:19Z","license":"NOASSERTION","topics":["card","css","deck","game"]},{"full_name":"prettymuchbryce/easystarjs","name":"easystarjs","owner":"prettymuchbryce","description":"An asynchronous A* pathfinding API written in Javascript.","stars":1941,"forks":170,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/709042?v=4","updated":"2024-01-23T23:07:02Z","license":"MIT","topics":["astar","asynchronous","easystar","game"]},{"full_name":"setube/vue-xiuxiangame","name":"vue-xiuxiangame","owner":"setube","description":"文字游戏: 我的文字修仙全靠刷","stars":1750,"forks":301,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/73606411?v=4","updated":"2026-03-02T09:42:06Z","license":"NOASSERTION","topics":["game","javascript","vue","xiuxian"]},{"full_name":"ovidiuch/flatris","name":"flatris","owner":"ovidiuch","description":"Fast-paced two-player web game","stars":1500,"forks":5669,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/250750?v=4","updated":"2024-08-01T22:48:25Z","license":"MIT","topics":["game","offline","pwa","react"]},{"full_name":"leandr0ck/phaser-es6-webpack","name":"phaser-es6-webpack","owner":"leandr0ck","description":"A bootstrap project for create games with Phaser + ES6 + Webpack.","stars":1286,"forks":304,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/1163315?v=4","updated":"2020-09-06T12:43:24Z","license":"MIT","topics":["bootstrap","cordova","es6","game"]},{"full_name":"michaljaz/webmc","name":"webmc","owner":"michaljaz","description":"PoC Minecraft client written in Javascript (1.16.5 offline mode working)","stars":1266,"forks":203,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/26044292?v=4","updated":"2024-08-05T19:09:07Z","license":"MIT","topics":["3d-game","browser","browser-game","browser-minecraft"]},{"full_name":"networked-aframe/networked-aframe","name":"networked-aframe","owner":"networked-aframe","description":"A web framework for building multi-user virtual reality experiences.","stars":1207,"forks":301,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/33105547?v=4","updated":"2026-06-24T06:38:27Z","license":"MIT","topics":["aframe","game","game-development","multi-user-vr"]},{"full_name":"floatinghotpot/casino-server","name":"casino-server","owner":"floatinghotpot","description":":fire: An online poker game server powered by Redis, node.js and socket.io","stars":1157,"forks":492,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/2339512?v=4","updated":"2020-04-26T10:41:29Z","license":"MIT","topics":["casino","game","javascript","poker"]}],"creative":[{"full_name":"processing/p5.js","name":"p5.js","owner":"processing","description":"p5.js is a client-side JS platform that empowers artists, designers, students, and anyone to learn to code and express themselves creatively on the web. It is based on the core principles of Processing. Looking for p5.js 2.0? http://beta.p5js.org","stars":23776,"forks":3766,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/1617169?v=4","updated":"2026-07-06T16:18:36Z","license":"LGPL-2.1","topics":["art","creative-coding","design","education"]},{"full_name":"css-doodle/css-doodle","name":"css-doodle","owner":"css-doodle","description":"A web component for visual art and creative coding","stars":6020,"forks":209,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/39110515?v=4","updated":"2026-04-19T13:29:13Z","license":"MIT","topics":["creative-coding","css","css-doodle","generative"]},{"full_name":"bradley/Blotter","name":"Blotter","owner":"bradley","description":"A JavaScript API for drawing unconventional text effects on the web.","stars":3075,"forks":202,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/806129?v=4","updated":"2020-07-28T12:49:10Z","license":"NOASSERTION","topics":["animation","creative-coding","css","design"]},{"full_name":"georgealways/lil-gui","name":"lil-gui","owner":"georgealways","description":"Makes a floating panel for controllers on the web. Works as a drop-in replacement for dat.gui in most projects.","stars":1601,"forks":66,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/238907?v=4","updated":"2025-10-12T17:36:24Z","license":"MIT","topics":["compact","creative-coding","dependency-free","gui"]},{"full_name":"liminalpurr/jizhi","name":"jizhi","owner":"liminalpurr","description":"A Chrome/Firefox extension with custom new tab page featuring Chinese poems and P5.js enabled noise waves in traditional Chinese colors","stars":1121,"forks":108,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/27838326?v=4","updated":"2023-07-28T02:02:22Z","license":null,"topics":["chrome-extension","creative-coding","newtabpage","p5js"]},{"full_name":"raphaelameaume/fragment","name":"fragment","owner":"raphaelameaume","description":"[alpha] A modern toolkit for creative coding","stars":932,"forks":25,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/3669192?v=4","updated":"2026-07-01T09:46:39Z","license":"MIT","topics":["creative-coding","generative-art","javascript","web"]},{"full_name":"shader-park/shader-park-core","name":"shader-park-core","owner":"shader-park","description":"A JavaScript library for creating real-time 2D and 3D shaders. JS -> Shader. https://shaderpark.com/ https://twitter.com/shaderpark","stars":823,"forks":29,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/51891331?v=4","updated":"2024-05-28T12:37:57Z","license":"MIT","topics":["computer-graphics","creative-coding","glsl","gpu"]},{"full_name":"nucliweb/People-You-Should-Follow-on-CodePen","name":"People-You-Should-Follow-on-CodePen","owner":"nucliweb","description":"People You Should Follow on CodePen","stars":710,"forks":77,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/1307927?v=4","updated":"2021-01-02T13:17:54Z","license":null,"topics":["animation","canvas","codepen","creative-coding"]},{"full_name":"charliegerard/gaze-detection","name":"gaze-detection","owner":"charliegerard","description":"👀 Use machine learning in JavaScript to detect eye movements and build gaze-controlled experiences.","stars":647,"forks":45,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/5985247?v=4","updated":"2021-06-24T17:01:37Z","license":"GPL-3.0","topics":["creative-coding","eye-detection","eye-tracking","frontend"]},{"full_name":"mmmerl/shader-doodle","name":"shader-doodle","owner":"mmmerl","description":"A friendly web-component for writing and rendering shaders.","stars":588,"forks":38,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/9411730?v=4","updated":"2026-06-25T05:35:14Z","license":"MIT","topics":["canvas","creative-coding","fragment-shader","glsl"]},{"full_name":"kousun12/eternal","name":"eternal","owner":"kousun12","description":"👾~ music, eternal ~ 👾","stars":579,"forks":35,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/1912669?v=4","updated":"2025-08-04T16:56:47Z","license":"MIT","topics":["3d-graphics","art","creative-coding","data-processing"]},{"full_name":"LingDong-/q5xjs","name":"q5xjs","owner":"LingDong-","description":"A small and fast alternative (experimental) implementation of p5.js","stars":572,"forks":24,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/7929704?v=4","updated":"2024-04-12T14:16:57Z","license":"Unlicense","topics":["2d","creative-coding","js-library","p5js"]},{"full_name":"klevron/threejs-toys","name":"threejs-toys","owner":"klevron","description":"✨ ThreeJS Toys ⚡","stars":531,"forks":54,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/4560725?v=4","updated":"2024-11-21T06:02:09Z","license":null,"topics":["creative-coding","interactive","threejs","webgl"]},{"full_name":"q5js/q5.js","name":"q5.js","owner":"q5js","description":"Beginner friendly graphics powered by WebGPU, optimized for interactive art!","stars":416,"forks":21,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/170815396?v=4","updated":"2026-06-26T19:18:37Z","license":"LGPL-3.0","topics":["art","creative-coding","design","education"]},{"full_name":"ektogamat/threejs-graces","name":"threejs-graces","owner":"ektogamat","description":"In this project, I wanted to show that creating a fancy design like this using #threejs is not as difficult as it looks. It has only 240 lines of code. The original design was created by Tom Bogner @dastom on Dribble: https://dribbble.com/shots/6767548-The-Three-Graces-Concept.","stars":304,"forks":79,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/86076434?v=4","updated":"2023-09-12T17:10:44Z","license":"NOASSERTION","topics":["creative-coding","threejs","threejs-example","webgl"]},{"full_name":"colejd/guify","name":"guify","owner":"colejd","description":"A simple GUI for inspecting and changing JavaScript variables","stars":299,"forks":17,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/702432?v=4","updated":"2024-07-18T00:46:03Z","license":"NOASSERTION","topics":["creative-coding","gui","js","prototyping"]},{"full_name":"Leonxlnx/lumenshaders","name":"lumenshaders","owner":"Leonxlnx","description":"LUMEN, a generative shader studio. 9 WebGL2 art modes plus synth style blending, perfect-loop animation, gradient sets, share codes and PNG/WebM/GIF export. Zero dependencies.","stars":298,"forks":34,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/219127460?v=4","updated":"2026-06-12T13:52:09Z","license":"MIT","topics":["creative-coding","generative-art","gif","glsl"]},{"full_name":"vibertthio/beact","name":"beact","owner":"vibertthio","description":":guitar::art: DJ and VJ all by yourself in seconds !","stars":294,"forks":25,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/19225067?v=4","updated":"2025-07-24T23:39:30Z","license":"MIT","topics":["creative-coding","dj","sequencer","vj"]},{"full_name":"gregtatum/sessions","name":"sessions","owner":"gregtatum","description":"A series of creative coding sessions","stars":277,"forks":21,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/1588648?v=4","updated":"2021-09-09T15:03:12Z","license":"NOASSERTION","topics":["canvas","creative-coding","javascript","regl"]},{"full_name":"jasonwebb/2d-differential-growth-experiments","name":"2d-differential-growth-experiments","owner":"jasonwebb","description":"Visual experiments exploring differential growth as a 2D morphogenesis tool.","stars":269,"forks":18,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/422783?v=4","updated":"2023-06-24T23:06:14Z","license":"NOASSERTION","topics":["2d","creative-coding","differential-growth","generative-art"]},{"full_name":"machineinteractive/skies-adsb","name":"skies-adsb","owner":"machineinteractive","description":"skies-adsb transforms your browser into a real-time 3D air traffic display. Using unfiltered ADS-B data from an RTL-SDR receiver, explore your local air traffic, surrounding airspace, and geography with custom 3D maps you can tailor to your needs.","stars":261,"forks":12,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/1178303?v=4","updated":"2026-05-31T14:42:41Z","license":"MIT","topics":["3d","adsb","adsb-b","creative-coding"]},{"full_name":"golanlevin/p5-single-line-font-resources","name":"p5-single-line-font-resources","owner":"golanlevin","description":"An archive of monoline vector fonts, and p5.js code to render them.","stars":245,"forks":11,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/290053?v=4","updated":"2026-06-22T02:58:22Z","license":null,"topics":["alphabets","computational-design","creative-coding","fonts"]},{"full_name":"jasonwebb/2d-space-colonization-experiments","name":"2d-space-colonization-experiments","owner":"jasonwebb","description":"Visual experiments exploring space colonization as a 2D morphogenesis tool.","stars":224,"forks":21,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/422783?v=4","updated":"2025-11-27T21:13:35Z","license":"NOASSERTION","topics":["2d","creative-coding","generative-art","hyphae"]},{"full_name":"urpflanze-org/urpflanze","name":"urpflanze","owner":"urpflanze-org","description":"A library for developers who want to approach to creative coding, artists who want to approach coding and for those who find it fun to play with math.","stars":221,"forks":10,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/79699638?v=4","updated":"2025-06-25T11:38:37Z","license":"GPL-3.0","topics":["2d-framework","2d-graphics","art","creative-coding"]}],"threed":[{"full_name":"aframevr/aframe","name":"aframe","owner":"aframevr","description":":a: Web framework for building virtual reality experiences.","stars":17583,"forks":4383,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/16024092?v=4","updated":"2026-06-29T23:58:34Z","license":"MIT","topics":["3d","aframe","augmented-reality","daydream"]},{"full_name":"pmndrs/drei","name":"drei","owner":"pmndrs","description":"🥉 useful helpers for react-three-fiber","stars":9727,"forks":819,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/45790596?v=4","updated":"2026-03-23T19:31:26Z","license":"MIT","topics":["helpers","hooks","react","react-three-fiber"]},{"full_name":"adrianhajdin/project_3D_developer_portfolio","name":"project_3D_developer_portfolio","owner":"adrianhajdin","description":"The most impressive websites in the world use 3D graphics and animations to bring their content to life. Learn how to build your own ThreeJS 3D Developer Portfolio today!","stars":7082,"forks":1688,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/24898559?v=4","updated":"2024-10-29T09:21:29Z","license":null,"topics":["3d","reactjs","threejs"]},{"full_name":"tengbao/vanta","name":"vanta","owner":"tengbao","description":"Animated 3D backgrounds for your website","stars":6600,"forks":1091,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/306044?v=4","updated":"2024-03-03T13:48:00Z","license":"MIT","topics":["3d","animation","animations","background"]},{"full_name":"pshenok/server-survival","name":"server-survival","owner":"pshenok","description":"Tower defense game that teaches cloud architecture. Build infrastructure, survive traffic, learn scaling.","stars":6128,"forks":686,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/42213409?v=4","updated":"2026-07-05T06:47:49Z","license":"MIT","topics":["aws","cloud","devops","education"]},{"full_name":"AR-js-org/AR.js","name":"AR.js","owner":"AR-js-org","description":"Image tracking, Location Based AR, Marker tracking. All on the Web.","stars":5958,"forks":995,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/59167113?v=4","updated":"2026-06-21T16:09:10Z","license":"MIT","topics":["aframe","ar","augmented-reality","hacktoberfest"]},{"full_name":"alexlenail/NN-SVG","name":"NN-SVG","owner":"alexlenail","description":"Publication-ready NN-architecture schematics. ","stars":5684,"forks":750,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/2761597?v=4","updated":"2026-06-02T12:07:58Z","license":"MIT","topics":["d3","deep-learning","diagrams","drawing"]},{"full_name":"tensorspace-team/tensorspace","name":"tensorspace","owner":"tensorspace-team","description":"Neural network 3D visualization framework, build interactive and intuitive model in browsers, support pre-trained deep learning models from TensorFlow, Keras, TensorFlow.js ","stars":5184,"forks":450,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/43156133?v=4","updated":"2022-12-05T22:21:36Z","license":"Apache-2.0","topics":["3d","deep-learning","keras","machine-learning"]},{"full_name":"moshang-ax/lottery","name":"lottery","owner":"moshang-ax","description":"🎉🌟✨🎈年会抽奖程序,基于 Express + Three.js的 3D 球体抽奖程序,奖品🧧🎁,文字,图片,抽奖规则均可配置,😜抽奖人员信息Excel一键导入😍,抽奖结果Excel导出😎,给你的抽奖活动带来全新酷炫体验🚀🚀🚀","stars":4547,"forks":1080,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/29499430?v=4","updated":"2025-03-02T03:21:12Z","license":"MIT","topics":["lottery","lucky","lucky-wheel","luckydraw"]},{"full_name":"kovacsv/Online3DViewer","name":"Online3DViewer","owner":"kovacsv","description":"A solution to visualize and explore 3D models in your browser.","stars":3582,"forks":756,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/2034315?v=4","updated":"2026-06-24T14:54:51Z","license":"MIT","topics":["3d","3dviewer","convert","export"]},{"full_name":"HamishMW/portfolio","name":"portfolio","owner":"HamishMW","description":"My personal portfolio website built using React and three js","stars":3464,"forks":722,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/6334501?v=4","updated":"2024-11-16T02:46:21Z","license":"MIT","topics":["cyberpunk","designer","developer","framer-motion"]},{"full_name":"gkjohnson/three-mesh-bvh","name":"three-mesh-bvh","owner":"gkjohnson","description":"A BVH implementation to speed up raycasting and enable spatial queries against three.js meshes.","stars":3411,"forks":328,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/734200?v=4","updated":"2026-07-04T04:22:56Z","license":"MIT","topics":["acceleration","bounds","bounds-hierarchy","bvh"]},{"full_name":"nidorx/matcaps","name":"matcaps","owner":"nidorx","description":"Huge library of matcap PNG textures organized by color","stars":3289,"forks":285,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/675430?v=4","updated":"2025-10-13T20:11:43Z","license":null,"topics":["blender-3d","envmap","lighting","litsphere"]},{"full_name":"jeeliz/jeelizFaceFilter","name":"jeelizFaceFilter","owner":"jeeliz","description":"🎭 Lightweight WebGL & JavaScript library for real-time multi-face detection, tracking and augmented-reality face filters. Supports rotation, mouth-opening and integrates with Three.js, Babylon.js, Canvas2D and CSS3D","stars":2911,"forks":547,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/19225471?v=4","updated":"2025-11-14T13:50:59Z","license":"Apache-2.0","topics":["3d","augmented-reality","camera","deep-learning"]},{"full_name":"dragonir/3d","name":"3d","owner":"dragonir","description":"Three.js 3D项目,包含冰墩墩🐼、数字城市🏙、3D人像👤、车模展示🚗、塞尔达传说🗡等一些3D趣味演示页面,持续优化中...访问链接如下👇","stars":2864,"forks":1007,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/21058931?v=4","updated":"2022-11-15T11:48:33Z","license":null,"topics":["3d","canvas","css","html"]},{"full_name":"pmndrs/react-three-next","name":"react-three-next","owner":"pmndrs","description":"React Three Fiber, Threejs, Nextjs starter","stars":2846,"forks":399,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/45790596?v=4","updated":"2024-06-21T00:17:05Z","license":"MIT","topics":["react","react-three-fiber","starter","tailwind"]},{"full_name":"mkkellogg/GaussianSplats3D","name":"GaussianSplats3D","owner":"mkkellogg","description":"Three.js-based implementation of 3D Gaussian splatting","stars":2805,"forks":372,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/6249005?v=4","updated":"2025-10-19T22:58:20Z","license":"MIT","topics":["3d-gaussian-splatting","gaussian-splatting","javascript","three-js"]},{"full_name":"hiukim/mind-ar-js","name":"mind-ar-js","owner":"hiukim","description":"Web Augmented Reality. Image Tracking, Face Tracking. Tensorflow.js","stars":2685,"forks":507,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/459126?v=4","updated":"2024-06-11T13:03:11Z","license":"MIT","topics":["aframe","augmented-reality","mediapipe","tensorflowjs"]},{"full_name":"ykob/sketch-threejs","name":"sketch-threejs","owner":"ykob","description":"Interactive sketches made with three.js.","stars":2534,"forks":329,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/5755225?v=4","updated":"2025-04-29T08:13:09Z","license":"MIT","topics":["gulp","threejs","webgl"]},{"full_name":"craftzdog/craftzdog-homepage","name":"craftzdog-homepage","owner":"craftzdog","description":"My homepage","stars":2459,"forks":707,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/1332805?v=4","updated":"2026-02-04T07:01:52Z","license":"NOASSERTION","topics":["chakra-ui","nextjs","react","threejs"]},{"full_name":"mlt131220/Astral3D","name":"Astral3D","owner":"mlt131220","description":"基于Vue3 + THREE.JS 免费开源的三维引擎及配套编辑器,包含BIM轻量化、CAD解析预览、粒子系统、插件系统等功能。 A free and open-source 3D engine based on Vue3 + THREE.JS and its accompanying editor, featuring functions such as BIM lightweighting, CAD parsing preview, particle system, and plugin system.","stars":2390,"forks":482,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/48863081?v=4","updated":"2026-06-21T14:54:36Z","license":"Apache-2.0","topics":["3d","bim","cad","editor"]},{"full_name":"NASA-AMMOS/3DTilesRendererJS","name":"3DTilesRendererJS","owner":"NASA-AMMOS","description":"Renderer for 3D Tiles in Javascript using three.js, Babylon.js, and r3f","stars":2376,"forks":405,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/37851411?v=4","updated":"2026-07-07T06:47:22Z","license":"Apache-2.0","topics":["3d-tiles","3dtiles","b3dm","babylonjs"]},{"full_name":"spite/THREE.MeshLine","name":"THREE.MeshLine","owner":"spite","description":"Mesh replacement for THREE.Line","stars":2331,"forks":380,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/731885?v=4","updated":"2024-03-22T08:31:48Z","license":"MIT","topics":["glsl","line","shader","threejs"]},{"full_name":"arscan/encom-boardroom","name":"encom-boardroom","owner":"arscan","description":"📈 Web tribute to the Tron: Legacy Boardroom Scene","stars":2314,"forks":300,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/412901?v=4","updated":"2017-10-10T12:47:27Z","license":null,"topics":["data-viz","threejs","tron","webgl"]}],"dataviz":[{"full_name":"algorithm-visualizer/algorithm-visualizer","name":"algorithm-visualizer","owner":"algorithm-visualizer","description":":fireworks:Interactive Online Platform that Visualizes Algorithms from Code","stars":48624,"forks":7581,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/38487505?v=4","updated":"2024-06-09T06:54:22Z","license":"MIT","topics":["algorithm","animation","data-structure","visualization"]},{"full_name":"NaiboWang/EasySpider","name":"EasySpider","owner":"NaiboWang","description":"A visual no-code/code-free web crawler/spider易采集:一个可视化浏览器自动化测试/数据采集/网页爬虫软件,可以无代码图形化的设计和执行爬虫任务。别名:ServiceWrapper面向Web应用的智能化服务封装系统。","stars":44203,"forks":5381,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/30287768?v=4","updated":"2026-07-03T09:39:50Z","license":"AGPL-3.0","topics":["batch-processing","batch-script","code-free","crawler"]},{"full_name":"plotly/plotly.js","name":"plotly.js","owner":"plotly","description":"Open-source JavaScript charting library behind Plotly and Dash","stars":18243,"forks":2007,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/5997976?v=4","updated":"2026-07-06T23:51:07Z","license":"MIT","topics":["charting-library","charts","d3","data-visualization"]},{"full_name":"apexcharts/apexcharts.js","name":"apexcharts.js","owner":"apexcharts","description":"📊 Interactive JavaScript Charts built on SVG","stars":15103,"forks":1374,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/37190687?v=4","updated":"2026-07-06T11:38:48Z","license":"NOASSERTION","topics":["charts","data-visualization","graphs","interactive"]},{"full_name":"vega/vega","name":"vega","owner":"vega","description":"A visualization grammar.","stars":11914,"forks":1571,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/11796929?v=4","updated":"2026-07-01T13:06:57Z","license":"BSD-3-Clause","topics":["canvas","d3","svg","vega"]},{"full_name":"cytoscape/cytoscape.js","name":"cytoscape.js","owner":"cytoscape","description":"Graph theory (network) library for visualisation and analysis","stars":11080,"forks":1667,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/956141?v=4","updated":"2026-06-30T16:15:14Z","license":"MIT","topics":["analysis","cytoscapejs","graph-theory","javascript"]},{"full_name":"rawgraphs/rawgraphs-app","name":"rawgraphs-app","owner":"rawgraphs","description":"A web interface to create custom vector-based visualizations on top of RAWGraphs core","stars":9010,"forks":1846,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/24813346?v=4","updated":"2025-11-18T16:10:52Z","license":"Apache-2.0","topics":["d3js","data","data-visualization","ddj"]},{"full_name":"poloclub/cnn-explainer","name":"cnn-explainer","owner":"poloclub","description":"Learning Convolutional Neural Networks with Interactive Visualization.","stars":9000,"forks":1347,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/19315506?v=4","updated":"2023-10-14T15:56:25Z","license":"MIT","topics":["deep-learning","interactive-visualizations","machine-learning","visual-learning"]},{"full_name":"uber/react-vis","name":"react-vis","owner":"uber","description":"Data Visualization Components","stars":8786,"forks":831,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/538264?v=4","updated":"2024-12-18T22:24:34Z","license":"MIT","topics":["chart","charting-library","data-visualization","react"]},{"full_name":"poloclub/transformer-explainer","name":"transformer-explainer","owner":"poloclub","description":"Transformer Explained Visually: Learn How LLM Transformer Models Work with Interactive Visualization","stars":8136,"forks":897,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/19315506?v=4","updated":"2026-06-06T12:08:04Z","license":"MIT","topics":["deep-learning","generative-ai","gpt","langauge-model"]},{"full_name":"antvis/F2","name":"F2","owner":"antvis","description":"📱📈An elegant, interactive and flexible charting library for mobile.","stars":7991,"forks":640,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/19199542?v=4","updated":"2026-05-19T10:57:41Z","license":"MIT","topics":["antv","charts","data-visualisation","data-visualization"]},{"full_name":"ecomfe/echarts-for-weixin","name":"echarts-for-weixin","owner":"ecomfe","description":"基于 Apache ECharts 的微信小程序图表库","stars":7505,"forks":1598,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/2268460?v=4","updated":"2024-06-09T03:51:09Z","license":"BSD-3-Clause","topics":["chart","echarts","visualization","weixin"]},{"full_name":"dc-js/dc.js","name":"dc.js","owner":"dc-js","description":"Multi-Dimensional charting built to work natively with crossfilter rendered with d3.js","stars":7430,"forks":1769,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/6378965?v=4","updated":"2024-07-31T16:01:22Z","license":"Apache-2.0","topics":["charting","crossfilter","visualization"]},{"full_name":"jwilber/roughViz","name":"roughViz","owner":"jwilber","description":"Reusable JavaScript library for creating sketchy/hand-drawn styled charts in the browser.","stars":7121,"forks":239,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/8595819?v=4","updated":"2024-04-26T11:54:48Z","license":"MIT","topics":["charting-library","d3v5","dashboard","data-science"]},{"full_name":"sbstjn/timesheet.js","name":"timesheet.js","owner":"sbstjn","description":"JavaScript library for HTML5 & CSS3 time sheets","stars":6979,"forks":535,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/248965?v=4","updated":"2018-05-24T21:40:05Z","license":"MIT","topics":["chart","charting-library","css","html"]},{"full_name":"covid19india/covid19india.github.io","name":"covid19india.github.io","owner":"covid19india","description":"Tracking the impact of COVID-19 in India","stars":6806,"forks":3340,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/62393982?v=4","updated":"2026-02-03T22:14:59Z","license":"MIT","topics":["analytics","coronavirus","covid-19","covid19"]},{"full_name":"Bogdan-Lyashenko/Under-the-hood-ReactJS","name":"Under-the-hood-ReactJS","owner":"Bogdan-Lyashenko","description":"Entire React code base explanation by visual block schemes (Stack version) ","stars":6779,"forks":615,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/2098309?v=4","updated":"2022-04-25T11:06:33Z","license":"MIT","topics":["debugging","fiber","flow","hood"]},{"full_name":"pa7/heatmap.js","name":"heatmap.js","owner":"pa7","description":"🔥 JavaScript Library for HTML5 canvas based heatmaps","stars":6373,"forks":1320,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/481662?v=4","updated":"2023-10-31T03:05:44Z","license":"MIT","topics":["angularjs","canvas","data-visualization","dataviz"]},{"full_name":"JuliaPluto/Pluto.jl","name":"Pluto.jl","owner":"JuliaPluto","description":"🎈 Simple reactive notebooks for Julia","stars":5350,"forks":344,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/74617459?v=4","updated":"2026-07-07T08:24:48Z","license":"MIT","topics":["designed-for-teachers","education","exploration","interactive"]},{"full_name":"clientIO/joint","name":"joint","owner":"clientIO","description":"A proven SVG-based JavaScript diagramming library powering exceptional UIs","stars":5278,"forks":883,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/882337?v=4","updated":"2026-07-06T12:00:33Z","license":"MPL-2.0","topics":["charts","diagramming","html5","interactive-diagrams"]},{"full_name":"tensorspace-team/tensorspace","name":"tensorspace","owner":"tensorspace-team","description":"Neural network 3D visualization framework, build interactive and intuitive model in browsers, support pre-trained deep learning models from TensorFlow, Keras, TensorFlow.js ","stars":5184,"forks":450,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/43156133?v=4","updated":"2022-12-05T22:21:36Z","license":"Apache-2.0","topics":["3d","deep-learning","keras","machine-learning"]},{"full_name":"plotly/falcon","name":"falcon","owner":"plotly","description":"Free, open-source SQL client for Windows and Mac 🦅","stars":5101,"forks":273,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/5997976?v=4","updated":"2023-04-17T17:59:03Z","license":"MIT","topics":["electron","falcon","mysql-client","plotly"]},{"full_name":"Jannchie/Historical-ranking-data-visualization-based-on-d3.js","name":"Historical-ranking-data-visualization-based-on-d3.js","owner":"Jannchie","description":"[Deprecated!] This is a data visualization project that converts historical data rankings into dynamic bar charts.","stars":4654,"forks":1078,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/29743310?v=4","updated":"2020-10-07T06:52:37Z","license":"GPL-3.0","topics":["bargraph","d3","data-visualization","visualization"]},{"full_name":"Netflix/vizceral","name":"vizceral","owner":"Netflix","description":"WebGL visualization for displaying animated traffic graphs","stars":4094,"forks":400,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/913567?v=4","updated":"2023-11-28T04:23:58Z","license":"Apache-2.0","topics":["graph","monitoring","traffic","visualization"]}],"emulators":[{"full_name":"copy/v86","name":"v86","owner":"copy","description":"x86 PC emulator and x86-to-wasm JIT, running in the browser","stars":23205,"forks":1850,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/1816456?v=4","updated":"2026-07-05T07:17:05Z","license":"BSD-2-Clause","topics":["emulator","virtualization","wasm","x86"]},{"full_name":"felixrieseberg/macintosh.js","name":"macintosh.js","owner":"felixrieseberg","description":"🖥 A virtual Apple Macintosh with System 8, running in Electron. I'm sorry.","stars":8484,"forks":334,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/1426799?v=4","updated":"2026-05-05T16:37:17Z","license":null,"topics":["electron","emulator","javascript","macintosh"]},{"full_name":"bfirsh/jsnes","name":"jsnes","owner":"bfirsh","description":"A JavaScript NES emulator.","stars":6399,"forks":852,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/40906?v=4","updated":"2026-07-07T06:23:55Z","license":"Apache-2.0","topics":["6502","emulation","emulator","javascript"]},{"full_name":"EmulatorJS/EmulatorJS","name":"EmulatorJS","owner":"EmulatorJS","description":"A web-based frontend for RetroArch","stars":3968,"forks":941,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/113546769?v=4","updated":"2026-06-27T19:39:20Z","license":"GPL-3.0","topics":["atari-lynx","atari2600","atari7800","ds"]},{"full_name":"s-macke/jor1k","name":"jor1k","owner":"s-macke","description":"Online OR1K Emulator running Linux","stars":1799,"forks":216,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/3176016?v=4","updated":"2025-11-01T14:53:27Z","license":"BSD-2-Clause","topics":["emulator","javascript","linux","openrisc"]},{"full_name":"1j01/98","name":"98","owner":"1j01","description":"💿 Web-based Windows 98 desktop recreation █████▓█▓▓▒▓▒▒░▒░░░🗕︎🗗︎🗙︎","stars":1398,"forks":216,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/3630663?v=4","updated":"2025-05-23T15:26:00Z","license":null,"topics":["classic","desktop","emulator","html"]},{"full_name":"Emupedia/emupedia.github.io","name":"emupedia.github.io","owner":"Emupedia","description":"The purpose of Emupedia is to serve as a nonprofit meta-resource, hub and community for those interested mainly in video game preservation which aims to digitally collect, archive and preserve games and software to make them available online accessible by a user-friendly UI that simulates several retro operating systems for educational purposes.","stars":1307,"forks":215,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/12756090?v=4","updated":"2026-07-03T17:43:01Z","license":"NOASSERTION","topics":["desktop","dosbox","dosgame","emularity"]},{"full_name":"jeffpar/pcjs","name":"pcjs","owner":"jeffpar","description":"The original IBM PC and other machine emulations in JavaScript","stars":1171,"forks":179,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/645749?v=4","updated":"2026-03-18T21:54:04Z","license":"NOASSERTION","topics":["6502","8080","8088","dos"]},{"full_name":"Schweigi/assembler-simulator","name":"assembler-simulator","owner":"Schweigi","description":"Simple 8-bit Assembler Simulator with Angular.js","stars":989,"forks":299,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/1357884?v=4","updated":"2021-09-25T21:15:26Z","license":null,"topics":["assembler","computer","emulator","javascript"]},{"full_name":"morgan3d/quadplay","name":"quadplay","owner":"morgan3d","description":"The quadplay✜ fantasy console","stars":958,"forks":45,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/3970018?v=4","updated":"2026-04-20T07:13:35Z","license":"NOASSERTION","topics":["2d-game-engine","browser","emulator","fantasy-console"]},{"full_name":"JohnEarnest/Octo","name":"Octo","owner":"JohnEarnest","description":"A Chip8 IDE","stars":854,"forks":57,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/400682?v=4","updated":"2025-08-27T03:19:54Z","license":"MIT","topics":["assembler","chip-8","chip8","chip8-emulator"]},{"full_name":"googlearchive/cloud-functions-emulator","name":"cloud-functions-emulator","owner":"googlearchive","description":"A local emulator for deploying, running, and debugging Google Cloud Functions.","stars":822,"forks":111,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/12502296?v=4","updated":"2019-05-15T16:32:16Z","license":"Apache-2.0","topics":["cloud-functions-emulator","emulator","google-cloud","google-cloud-functions"]},{"full_name":"bfirsh/jsnes-web","name":"jsnes-web","owner":"bfirsh","description":"A browser UI for JSNES, a JavaScript NES emulator","stars":633,"forks":171,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/40906?v=4","updated":"2026-02-14T16:12:38Z","license":"Apache-2.0","topics":["canvas","emulation","emulator","nes"]},{"full_name":"taniarascia/chip8","name":"chip8","owner":"taniarascia","description":"🎮 A Chip-8 emulator written in JavaScript for web, CLI, and native UI.","stars":473,"forks":44,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/11951801?v=4","updated":"2022-05-11T17:50:45Z","license":"MIT","topics":["chip-8","chip-8-emulator","emulator","javascript"]},{"full_name":"jeffpar/pcjs.v1","name":"pcjs.v1","owner":"jeffpar","description":"PCjs Machines 1.0 (archived)","stars":473,"forks":88,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/645749?v=4","updated":"2020-05-01T17:43:03Z","license":"GPL-3.0","topics":["6502","80286","80386","8080"]},{"full_name":"hackwaly/jswqx","name":"jswqx","owner":"hackwaly","description":"文曲星模拟器","stars":423,"forks":83,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/540298?v=4","updated":"2015-08-28T05:55:03Z","license":null,"topics":["emulator"]},{"full_name":"stipsan/ioredis-mock","name":"ioredis-mock","owner":"stipsan","description":"Emulates ioredis by performing all operations in-memory.","stars":388,"forks":134,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/81981?v=4","updated":"2026-06-22T17:51:56Z","license":"MIT","topics":["emulator","ioredis","mocking","node"]},{"full_name":"mattgodbolt/jsbeeb","name":"jsbeeb","owner":"mattgodbolt","description":"Javascript BBC micro emulator","stars":387,"forks":76,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/633973?v=4","updated":"2026-06-19T20:24:07Z","license":"GPL-3.0","topics":["6502","bbc","bbc-micro-emulator","emulator"]},{"full_name":"ayvacs/gba.js.org","name":"gba.js.org","owner":"ayvacs","description":"👾 Emulate GBA games for free, right in your browser.","stars":367,"forks":193,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/74518502?v=4","updated":"2026-01-15T19:17:50Z","license":"NOASSERTION","topics":["emulator","gameboy","gameboy-advance","gameboy-advance-emulator"]},{"full_name":"Berny23/LD-ToyPad-Emulator","name":"LD-ToyPad-Emulator","owner":"Berny23","description":"Toy Pad Emulator for Lego Dimensions (Unofficial Fan Project)","stars":364,"forks":53,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/36038743?v=4","updated":"2026-06-17T06:25:33Z","license":"MIT","topics":["emulation","emulator","html","javascript"]},{"full_name":"oldweb-today/oldweb-today","name":"oldweb-today","owner":"oldweb-today","description":"Browse emulated browsers connected to old web sites in your browser!","stars":353,"forks":37,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/23365253?v=4","updated":"2024-10-31T05:19:35Z","license":"AGPL-3.0","topics":["emulation","emulator","oldweb-today","wayback-machine"]},{"full_name":"afska/emudevz","name":"emudevz","owner":"afska","description":"🕹️ A game about building emulators!","stars":292,"forks":28,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/1631752?v=4","updated":"2026-07-06T02:32:19Z","license":"NOASSERTION","topics":["development","emudev","emulator","game"]},{"full_name":"taisel/IodineGBA","name":"IodineGBA","owner":"taisel","description":"JavaScript GameBoy Advance emulator.","stars":290,"forks":169,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/13305116?v=4","updated":"2023-08-17T14:28:53Z","license":null,"topics":["emulator","gameboy-advance-emulator","javascript"]},{"full_name":"quantastica/quantum-circuit","name":"quantum-circuit","owner":"quantastica","description":"Quantum Circuit Simulator implemented in JavaScript","stars":273,"forks":56,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/58999167?v=4","updated":"2026-02-09T22:19:57Z","license":"MIT","topics":["cirq","emulator","pyquil","qasm"]}],"tools":[{"full_name":"GoogleChrome/lighthouse","name":"lighthouse","owner":"GoogleChrome","description":"Automated auditing, performance metrics, and best practices for the web.","stars":30476,"forks":9733,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/1778935?v=4","updated":"2026-07-05T12:06:16Z","license":"Apache-2.0","topics":["audit","best-practices","chrome-devtools","developer-tools"]},{"full_name":"quasarframework/quasar","name":"quasar","owner":"quasarframework","description":"Quasar Framework - Build high-performance VueJS user interfaces in record time","stars":27187,"forks":3686,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/23064371?v=4","updated":"2026-07-06T09:08:49Z","license":"MIT","topics":["android","browser-extension","electron","ios"]},{"full_name":"SnapDrop/snapdrop","name":"snapdrop","owner":"SnapDrop","description":"A Progressive Web App for local file sharing ","stars":19713,"forks":1877,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/88061056?v=4","updated":"2025-02-10T10:35:43Z","license":"GPL-3.0","topics":["pwa","snapdrop","webrtc"]},{"full_name":"schlagmichdoch/PairDrop","name":"PairDrop","owner":"schlagmichdoch","description":"PairDrop: Transfer Files Cross-Platform. No Setup, No Signup.","stars":10757,"forks":637,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/52242352?v=4","updated":"2026-04-22T20:27:50Z","license":"GPL-3.0","topics":["file-sharing","indexeddb","nodejs","pwa"]},{"full_name":"OnsenUI/OnsenUI","name":"OnsenUI","owner":"OnsenUI","description":"Mobile app development framework and SDK using HTML5 and JavaScript. Create beautiful and performant cross-platform mobile apps. Based on Web Components, and provides bindings for Angular 1, 2, React and Vue.js.","stars":8865,"forks":1000,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/5972822?v=4","updated":"2026-06-17T03:34:27Z","license":null,"topics":["android","angular","cordova","customelements"]},{"full_name":"alibaba/rax","name":"rax","owner":"alibaba","description":"🐰 Rax is a progressive framework for building universal application. https://rax.js.org","stars":8035,"forks":612,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/1961952?v=4","updated":"2023-03-27T06:49:28Z","license":"NOASSERTION","topics":["framework","javascript","miniapp","mobile"]},{"full_name":"andreasbm/web-skills","name":"web-skills","owner":"andreasbm","description":"A visual overview of useful skills to learn as a web developer","stars":7565,"forks":712,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/6267397?v=4","updated":"2024-04-19T03:22:28Z","license":"NOASSERTION","topics":["accessibility","algorithms","architecture","backend"]},{"full_name":"angus-c/just","name":"just","owner":"angus-c","description":"A library of dependency-free JavaScript utilities that do just one thing.","stars":6204,"forks":209,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/604030?v=4","updated":"2024-02-11T12:23:37Z","license":"MIT","topics":["dependency-free","javascript","library","pwa"]},{"full_name":"TalAter/UpUp","name":"UpUp","owner":"TalAter","description":"✈️ Easily create sites that work offline as well as online","stars":4921,"forks":260,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/926653?v=4","updated":"2022-12-06T21:57:24Z","license":"MIT","topics":["hacktoberfest","offline","offline-first","progressive-web-app"]},{"full_name":"hemanth/awesome-pwa","name":"awesome-pwa","owner":"hemanth","description":"Awesome list of progressive web apps!","stars":4862,"forks":555,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/18315?v=4","updated":"2026-05-01T14:02:45Z","license":null,"topics":["modern-web","pwa","service-worker"]},{"full_name":"preactjs/preact-cli","name":"preact-cli","owner":"preactjs","description":"😺 Your next Preact PWA starts in 30 seconds.","stars":4670,"forks":369,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/26872990?v=4","updated":"2024-03-27T18:12:14Z","license":"MIT","topics":["cli","hacktoberfest","http2","preact"]},{"full_name":"NekR/offline-plugin","name":"offline-plugin","owner":"NekR","description":"Offline plugin (ServiceWorker, AppCache) for webpack (https://webpack.js.org/)","stars":4510,"forks":289,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/1189265?v=4","updated":"2021-11-08T05:42:32Z","license":"MIT","topics":["appcache","application-cache","caching","javascript"]},{"full_name":"shadowwalker/next-pwa","name":"next-pwa","owner":"shadowwalker","description":"Zero config PWA plugin for Next.js, with workbox 🧰","stars":4084,"forks":338,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/9603375?v=4","updated":"2024-07-27T00:29:09Z","license":"MIT","topics":["cache","nextjs","pwa","service-worker"]},{"full_name":"vuejs-templates/pwa","name":"pwa","owner":"vuejs-templates","description":"PWA template for vue-cli based on the webpack template","stars":3036,"forks":494,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/16445995?v=4","updated":"2018-08-13T10:11:54Z","license":"MIT","topics":["code-splitting","offline-capable","offline-first","progressive-web-apps"]},{"full_name":"filips123/PWAsForFirefox","name":"PWAsForFirefox","owner":"filips123","description":"A tool to install, manage and use Progressive Web Apps (PWAs) in Mozilla Firefox","stars":3035,"forks":69,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/16626308?v=4","updated":"2026-06-23T10:47:05Z","license":"MPL-2.0","topics":["firefox","firefox-addon","firefox-extension","hacktoberfest"]},{"full_name":"padloc/padloc","name":"padloc","owner":"padloc","description":"A modern, open source password manager for individuals and teams.","stars":2923,"forks":264,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/38265889?v=4","updated":"2025-03-29T04:40:17Z","license":"AGPL-3.0","topics":["cryptography","end-to-end-encryption","javascript","lit-element"]},{"full_name":"le0pard/pgtune","name":"pgtune","owner":"le0pard","description":"Pgtune - tuning PostgreSQL config by your hardware","stars":2722,"forks":244,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/98444?v=4","updated":"2026-06-26T15:13:05Z","license":"MIT","topics":["database","hardware","javascript","performance"]},{"full_name":"GoogleChromeLabs/lighthousebot","name":"lighthousebot","owner":"GoogleChromeLabs","description":"Run Lighthouse in CI, as a web service, using Docker. Pass/Fail GH pull requests.","stars":2221,"forks":125,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/31970254?v=4","updated":"2020-07-07T09:41:34Z","license":"Apache-2.0","topics":["ci","docker","headless-chrome","lighthouse"]},{"full_name":"f2e-awesome/knowledge","name":"knowledge","owner":"f2e-awesome","description":"文档着重构建一个完整的「前端技术架构图谱」,方便 F2E(Front End Engineering又称FEE、F2E) 学习与进阶。","stars":1963,"forks":371,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/39408867?v=4","updated":"2023-02-28T13:50:04Z","license":null,"topics":["awesome","canvas","css","dns"]},{"full_name":"JLiscom/OpenNote","name":"OpenNote","owner":"JLiscom","description":"OpenNote was built to be an open web-based alternative to Microsoft OneNote (T) and EverNote.","stars":1665,"forks":168,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/886680?v=4","updated":"2023-02-27T20:56:07Z","license":"MIT","topics":["angularjs","couchdb","mobile-first","note-taking"]},{"full_name":"hanford/next-offline","name":"next-offline","owner":"hanford","description":"make your Next.js application work offline using service workers via Google's workbox","stars":1599,"forks":111,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/2148168?v=4","updated":"2023-01-03T23:02:48Z","license":null,"topics":["cache","nextjs","offline","pwa"]},{"full_name":"generativefm/generative.fm","name":"generative.fm","owner":"generativefm","description":"A platform for playing generative music in the browser.","stars":1592,"forks":81,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/60299987?v=4","updated":"2021-02-13T02:49:57Z","license":"MIT","topics":["art","generative-art","generative-music","music"]},{"full_name":"kefranabg/bento-starter","name":"bento-starter","owner":"kefranabg","description":":bento: Full-Stack solution to quickly build PWA applications with Vue.js and Firebase","stars":1511,"forks":128,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/9840435?v=4","updated":"2021-02-12T03:50:15Z","license":"MIT","topics":["circleci","firebase","firebase-auth","firebase-firestore"]},{"full_name":"ovidiuch/flatris","name":"flatris","owner":"ovidiuch","description":"Fast-paced two-player web game","stars":1500,"forks":5669,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/250750?v=4","updated":"2024-08-01T22:48:25Z","license":"MIT","topics":["game","offline","pwa","react"]}],"ai":[{"full_name":"asgeirtj/system_prompts_leaks","name":"system_prompts_leaks","owner":"asgeirtj","description":"Extracted system prompts from Anthropic - Claude Fable 5, Opus 4.8, Claude Code, Claude Design. OpenAI - ChatGPT 5.5 Thinking, GPT 5.5 Instant, Codex. Google - Gemini 3.5 Flash, 3.1 Pro, Antigravity. xAI - Grok, Cursor, Copilot, VS Code, Perplexity, and more. Updated regularly.","stars":52187,"forks":8494,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/27446620?v=4","updated":"2026-07-07T02:16:35Z","license":"CC0-1.0","topics":["ai","ai-agents","anthropic","awesome"]},{"full_name":"lutzroeder/netron","name":"netron","owner":"lutzroeder","description":"Visualizer for neural network, deep learning and machine learning models","stars":33189,"forks":3148,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/438516?v=4","updated":"2026-07-07T03:04:30Z","license":"MIT","topics":["ai","coreml","deep-learning","deeplearning"]},{"full_name":"AMAI-GmbH/AI-Expert-Roadmap","name":"AI-Expert-Roadmap","owner":"AMAI-GmbH","description":"Roadmap to becoming an Artificial Intelligence Expert in 2022","stars":31115,"forks":2578,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/54247845?v=4","updated":"2025-09-12T14:59:30Z","license":"MIT","topics":["ai","ai-roadmap","artificial-intelligence","data-analysis"]},{"full_name":"amark/gun","name":"gun","owner":"amark","description":"An open source cybersecurity protocol for syncing decentralized graph data.","stars":19071,"forks":1238,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/433412?v=4","updated":"2026-03-01T01:57:28Z","license":"NOASSERTION","topics":["artificial-intelligence","big-data","blockchain","crdt"]},{"full_name":"GetStream/Winds","name":"Winds","owner":"GetStream","description":"A Beautiful Open Source RSS & Podcast App Powered by Getstream.io","stars":9120,"forks":629,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/8597527?v=4","updated":"2021-10-11T20:32:23Z","license":"BSD-3-Clause","topics":["activity-stream","algolia-api","create-react-app","electron"]},{"full_name":"poloclub/cnn-explainer","name":"cnn-explainer","owner":"poloclub","description":"Learning Convolutional Neural Networks with Interactive Visualization.","stars":9000,"forks":1347,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/19315506?v=4","updated":"2023-10-14T15:56:25Z","license":"MIT","topics":["deep-learning","interactive-visualizations","machine-learning","visual-learning"]},{"full_name":"ml5js/ml5-library","name":"ml5-library","owner":"ml5js","description":"Friendly machine learning for the web! 🤖 ","stars":6585,"forks":899,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/36965392?v=4","updated":"2024-10-11T02:28:01Z","license":"NOASSERTION","topics":["deep-learning","imagenet","javascript","lstm"]},{"full_name":"paperswithcode/ai-deadlines","name":"ai-deadlines","owner":"paperswithcode","description":":alarm_clock: AI conference deadline countdowns ","stars":5991,"forks":1059,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/40305508?v=4","updated":"2024-09-15T11:41:58Z","license":"MIT","topics":["artificial-intelligence","computer-vision","deep-learning","deep-neural-networks"]},{"full_name":"alexlenail/NN-SVG","name":"NN-SVG","owner":"alexlenail","description":"Publication-ready NN-architecture schematics. ","stars":5684,"forks":750,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/2761597?v=4","updated":"2026-06-02T12:07:58Z","license":"MIT","topics":["d3","deep-learning","diagrams","drawing"]},{"full_name":"tensorspace-team/tensorspace","name":"tensorspace","owner":"tensorspace-team","description":"Neural network 3D visualization framework, build interactive and intuitive model in browsers, support pre-trained deep learning models from TensorFlow, Keras, TensorFlow.js ","stars":5184,"forks":450,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/43156133?v=4","updated":"2022-12-05T22:21:36Z","license":"Apache-2.0","topics":["3d","deep-learning","keras","machine-learning"]},{"full_name":"transcranial/keras-js","name":"keras-js","owner":"transcranial","description":"Run Keras models in the browser, with GPU support using WebGL","stars":4964,"forks":493,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/6182852?v=4","updated":"2022-06-15T12:11:24Z","license":"MIT","topics":["deep-learning","javascript","keras","machine-learning"]},{"full_name":"janhuenermann/neurojs","name":"neurojs","owner":"janhuenermann","description":"A JavaScript deep learning and reinforcement learning library.","stars":4372,"forks":362,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/10101036?v=4","updated":"2023-10-10T12:55:04Z","license":"MIT","topics":["deep-learning","javascript","machine-learning","neural-network"]},{"full_name":"iterative/cml","name":"cml","owner":"iterative","description":"♾️ CML - Continuous Machine Learning | CI/CD for ML","stars":4184,"forks":345,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/39572954?v=4","updated":"2025-06-02T04:40:57Z","license":"Apache-2.0","topics":["bitbucket-pipelines","ci","ci-cd","cicd"]},{"full_name":"xviniette/FlappyLearning","name":"FlappyLearning","owner":"xviniette","description":"Program learning to play Flappy Bird by machine learning (Neuroevolution)","stars":3993,"forks":501,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/4215051?v=4","updated":"2023-11-23T01:04:40Z","license":"MIT","topics":["flappybird","machine-learning","neuroevolution"]},{"full_name":"googlecreativelab/teachable-machine-v1","name":"teachable-machine-v1","owner":"googlecreativelab","description":" Explore how machine learning works, live in the browser. No coding required.","stars":3870,"forks":603,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/4741861?v=4","updated":"2021-09-01T22:38:21Z","license":"Apache-2.0","topics":["machine-learning","teachable-machine"]},{"full_name":"pashpashpash/vault-ai","name":"vault-ai","owner":"pashpashpash","description":"OP Vault ChatGPT: Give ChatGPT long-term memory using the OP Stack (OpenAI + Pinecone Vector Database). Upload your own custom knowledge base files (PDF, txt, epub, etc) using a simple React frontend.","stars":3389,"forks":297,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/20898225?v=4","updated":"2025-07-08T16:02:56Z","license":"MIT","topics":["ai","artificial-intelligence","chatgpt","generative"]},{"full_name":"SharpAI/DeepCamera","name":"DeepCamera","owner":"SharpAI","description":"Open-Source AI Camera Skills Platform, AI NVR & CCTV Surveillance. Local VLM video analysis with Qwen, DeepSeek, SmolVLM, LLaVA, YOLO26. LLM-powered agentic security camera agent — watches, understands, remembers & guards your home via Telegram, Discord or Slack. Pluggable AI skills. OpenAI, Google, Anthropic or local AI. Runs on Mac Mini & AI PC.","stars":2886,"forks":456,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/36420545?v=4","updated":"2026-06-18T04:37:54Z","license":"MIT","topics":["ai","ai-camera","ai-nvr","camera"]},{"full_name":"saharmor/dalle-playground","name":"dalle-playground","owner":"saharmor","description":"A playground to generate images from any text prompt using Stable Diffusion (past: using DALL-E Mini)","stars":2741,"forks":576,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/6180201?v=4","updated":"2024-06-03T22:19:37Z","license":"MIT","topics":["artificial","artificial-intelligence","dall-e","dalle"]},{"full_name":"mljs/ml","name":"ml","owner":"mljs","description":"Machine learning tools in JavaScript","stars":2712,"forks":210,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/9335229?v=4","updated":"2024-10-21T07:02:43Z","license":"MIT","topics":["javascript","machine-learning","ml"]},{"full_name":"microsoft/pai","name":"pai","owner":"microsoft","description":"Resource scheduling and cluster management for AI","stars":2684,"forks":549,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/6154722?v=4","updated":"2024-06-06T07:56:07Z","license":"MIT","topics":["ai","artificial-intelligence","chainer","cloud"]},{"full_name":"apachecn/fe4ml-zh","name":"fe4ml-zh","owner":"apachecn","description":":book: [译] 面向机器学习的特征工程","stars":2559,"forks":670,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/24802038?v=4","updated":"2023-08-25T17:00:02Z","license":"NOASSERTION","topics":["book","feature-engineering","machine-learning","python"]},{"full_name":"ErickWendel/semana-javascript-expert07","name":"semana-javascript-expert07","owner":"ErickWendel","description":"JS Expert Week 7.0 - 🙅🤏🏻 Controlling Streaming Platforms using Eye and Hand Detection 👁🖐","stars":2364,"forks":508,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/8060102?v=4","updated":"2023-12-12T13:50:14Z","license":null,"topics":["canvas","eye-detection","hand-detection","javascript"]},{"full_name":"trekhleb/nano-neuron","name":"nano-neuron","owner":"trekhleb","description":"🤖 NanoNeuron is 7 simple JavaScript functions that will give you a feeling of how machines can actually \"learn\"","stars":2271,"forks":123,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/3000285?v=4","updated":"2025-11-23T04:27:56Z","license":"MIT","topics":["artificial-intelligence","javascript","machine-learning","machine-learning-algorithms"]},{"full_name":"vaaaaanquish/Awesome-Rust-MachineLearning","name":"Awesome-Rust-MachineLearning","owner":"vaaaaanquish","description":"This repository is a list of machine learning libraries written in Rust. It's a compilation of GitHub repositories, blogs, books, movies, discussions, papers, etc. 🦀","stars":2258,"forks":126,"language":"JavaScript","avatar":"https://avatars.githubusercontent.com/u/47341845?v=4","updated":"2023-09-25T15:36:33Z","license":"MIT","topics":["awasome","deep-learning","image-processing","machine-learning"]}]}}
|
b/432cde7e8423e4b3ce960802ed09f9d2def267dcee8a1118c956d803eac78ed8
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// holo-animate-core.mjs — the ONE per-request animate core (HOLO-ANIMATE-SERVERLESS, SL-P1): everything
|
| 2 |
+
// that turns a fetched repo file-tree into a servable app tree, shared VERBATIM by BOTH hosts —
|
| 3 |
+
// · the Node store daemon (holo-apps/apps/tauri/holo-animate-serve.mjs)
|
| 4 |
+
// · the in-browser animator (holo-apps/apps/tauri/animate-web/animate-client.mjs, via /lib/)
|
| 5 |
+
// One codebase, two hosts: parity is DEFINED by the standing gates (holo-animate-conformance L1–L4 +
|
| 6 |
+
// holo-animate-fidelity F1–F5) running green against either origin, because both serve THIS logic.
|
| 7 |
+
//
|
| 8 |
+
// PURE + ISOMORPHIC (no Node globals, no DOM): bytes are Uint8Array (Node Buffers are Uint8Arrays),
|
| 9 |
+
// text via TextEncoder/TextDecoder. Every effect is INJECTED at the boundary (Law L4):
|
| 10 |
+
// · the COMPILER (native esbuild in Node · κ-pinned esbuild-wasm in the tab — same API, same κ-transform)
|
| 11 |
+
// · the VENDOR store + fetch (disk in Node · Cache-API in the tab), hash = blake3hex passed in
|
| 12 |
+
// The fit/fidelity engine (rewriteIndex) is the FID-P0/P1/P4 result — fit=contain faithful default,
|
| 13 |
+
// fill/actual opt-in, backdrop fills letterbox bars, mobile zoom-to-fit. Change it HERE only.
|
| 14 |
+
|
| 15 |
+
export const VERSION = "holo-animate-core/0.2.0";
|
| 16 |
+
|
| 17 |
+
import { b64encode, b64decode } from "./holo-dotholo.mjs"; // isomorphic base64 (Node Buffer · browser atob)
|
| 18 |
+
|
| 19 |
+
const enc = (s) => new TextEncoder().encode(s);
|
| 20 |
+
const dec = (b) => new TextDecoder().decode(b);
|
| 21 |
+
export const asText = (v) => v == null ? "" : (typeof v === "string" ? v : dec(v));
|
| 22 |
+
export const asBytes = (v) => typeof v === "string" ? enc(v) : v;
|
| 23 |
+
|
| 24 |
+
// ── tree .holo wire format: a built site is a multi-file TREE (Map<path,bytes>); its .holo result is the
|
| 25 |
+
// JSON {path: base64} — ONE format packed/unpacked identically by the daemon and the tab (SS-P0). ──────
|
| 26 |
+
export const packTree = (files) => JSON.stringify(Object.fromEntries([...files].map(([k, v]) => [k, b64encode(asBytes(v))])));
|
| 27 |
+
export const unpackTree = (s) => new Map(Object.entries(JSON.parse(typeof s === "string" ? s : dec(s))).map(([k, v]) => [k, b64decode(v)]));
|
| 28 |
+
|
| 29 |
+
// ── MIME — engines (melonJS/Phaser/three) reject wrong content-types; audio/video/game-data included ──────
|
| 30 |
+
export const MIME = { html: "text/html", htm: "text/html", js: "text/javascript", mjs: "text/javascript", cjs: "text/javascript", css: "text/css", json: "application/json", svg: "image/svg+xml", png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", gif: "image/gif", webp: "image/webp", avif: "image/avif", bmp: "image/bmp", ico: "image/x-icon", cur: "image/x-icon", woff: "font/woff", woff2: "font/woff2", ttf: "font/ttf", otf: "font/otf", eot: "application/vnd.ms-fontobject", map: "application/json", wasm: "application/wasm", txt: "text/plain", xml: "application/xml", webmanifest: "application/manifest+json",
|
| 31 |
+
mp3: "audio/mpeg", ogg: "audio/ogg", oga: "audio/ogg", wav: "audio/wav", m4a: "audio/mp4", aac: "audio/aac", flac: "audio/flac", weba: "audio/webm",
|
| 32 |
+
mp4: "video/mp4", webm: "video/webm", ogv: "video/ogg", m4v: "video/mp4", mov: "video/quicktime",
|
| 33 |
+
tmx: "application/xml", tsx: "application/xml", fnt: "application/xml", atlas: "text/plain", csv: "text/csv",
|
| 34 |
+
glb: "model/gltf-binary", gltf: "model/gltf+json", obj: "text/plain", mtl: "text/plain", glsl: "text/plain", vert: "text/plain", frag: "text/plain",
|
| 35 |
+
bin: "application/octet-stream", dat: "application/octet-stream", mem: "application/octet-stream", data: "application/octet-stream", md: "text/markdown" };
|
| 36 |
+
export const mimeOf = (p) => MIME[(p.split(".").pop() || "").toLowerCase()] || "application/octet-stream";
|
| 37 |
+
|
| 38 |
+
// ── rewriteIndex(html, id, prefix) — <base> under <prefix>run/<id>/ + viewport + the FIDELITY-FIRST fit
|
| 39 |
+
// engine (FID-P0 contain+backdrop · FID-P1 pixel-art crisp · FID-P4 Fit/Fill/Actual · mobile zoom-to-fit).
|
| 40 |
+
// NON-DESTRUCTIVE: the app's canvas render/aspect/input mapping stay byte-identical.
|
| 41 |
+
// SUBPATH RELOCATION (SS-P3): a sealed .holo is PREFIX-NEUTRAL (its importmap says "/vendor/<κ>" by
|
| 42 |
+
// convention); the serving host passes its scope prefix ("/" for the daemon, the SW scope for a Pages
|
| 43 |
+
// subpath) and THIS rewrite adapts the doc at serve time — the κ never bakes in an origin layout.
|
| 44 |
+
export function rewriteIndex(html, id, prefix = "/") {
|
| 45 |
+
html = html.replace(/((?:src|href)\s*=\s*["'])\/(?!\/)/gi, "$1"); // "/assets/x" → "assets/x" (strip FIRST)
|
| 46 |
+
if (prefix !== "/") html = html.split('"/vendor/').join('"' + prefix + 'vendor/').split("'/vendor/").join("'" + prefix + "vendor/"); // importmap κ paths → scoped
|
| 47 |
+
const hasVp = /<meta[^>]+name\s*=\s*["']viewport["']/i.test(html);
|
| 48 |
+
const fill = '<script>(function(){function S(e,k,v){try{e.style.setProperty(k,v,"important")}catch(_){}}'
|
| 49 |
+
+ 'var bd,alive=true;'
|
| 50 |
+
+ 'function mfit(){try{if(mode!=="fit")return;var de=document.documentElement;de.style.removeProperty("zoom");var need=Math.max(de.scrollWidth,(document.body||de).scrollWidth||0),have=de.clientWidth||innerWidth;if(need>have+8){var z=have/need;if(z>0.3&&z<0.999)de.style.setProperty("zoom",z.toFixed(4),"important")}}catch(_){}}'
|
| 51 |
+
+ 'function lone(){var cs=[].filter.call(document.querySelectorAll("canvas"),function(x){return x.id!=="__holo_bd"});if(cs.length!==1)return null;if(((document.body||{}).innerText||"").trim().length>400)return null;return cs[0];}'
|
| 52 |
+
+ 'var mode="fit";'
|
| 53 |
+
+ 'function grad(){alive=false;if(!bd)return;S(bd,"filter","none");S(bd,"transform","none");S(bd,"background","radial-gradient(140% 140% at 50% 34%, #171b26 0%, #0b0e16 55%, #05070c 100%)");}'
|
| 54 |
+
+ 'function mk(c){bd=document.createElement("canvas");bd.id="__holo_bd";bd.width=96;bd.height=54;'
|
| 55 |
+
+ 'S(bd,"position","fixed");S(bd,"inset","0");S(bd,"z-index","-1");S(bd,"pointer-events","none");S(bd,"width","100vw");S(bd,"height","100vh");S(bd,"object-fit","cover");S(bd,"filter","blur(38px) saturate(1.35) brightness(.6)");S(bd,"transform","scale(1.25)");'
|
| 56 |
+
+ 'document.body.insertBefore(bd,document.body.firstChild);'
|
| 57 |
+
+ 'var bx=bd.getContext("2d"),lt=0,ok=false;var lp=function(t){if(!alive)return;if(t-lt>80){lt=t;try{bx.drawImage(c,0,0,bd.width,bd.height);if(!ok){var s=bx.getImageData(0,0,3,3).data,q=0;for(var i=0;i<s.length;i++)q+=s[i];if(q>18)ok=true;}}catch(_){}}requestAnimationFrame(lp)};requestAnimationFrame(lp);'
|
| 58 |
+
+ 'setTimeout(function(){if(!ok)grad();},800);}' // engine frame unreadable (offscreen/webgl) → clean gradient
|
| 59 |
+
+ 'function run(){try{var c=lone();if(!c)return;if(!bd||!bd.parentNode)mk(c);'
|
| 60 |
+
+ 'S(document.documentElement,"background-color","transparent");S(document.body,"background-color","transparent");'
|
| 61 |
+
+ 'var p=c.parentElement;while(p&&p!==document.body&&p!==document.documentElement){S(p,"background-color","transparent");p=p.parentElement;}'
|
| 62 |
+
+ 'var iw=c.width||c.getBoundingClientRect().width||1,ih=c.height||c.getBoundingClientRect().height||1,vw=innerWidth,vh=innerHeight;'
|
| 63 |
+
+ 'var s=mode==="fill"?Math.max(vw/iw,vh/ih):(mode==="actual"?1:Math.min(vw/iw,vh/ih));'
|
| 64 |
+
+ 'var dw=Math.round(iw*s),dh=Math.round(ih*s);'
|
| 65 |
+
+ 'S(c,"position","fixed");S(c,"left","50%");S(c,"top","50%");S(c,"transform","translate(-50%,-50%)");S(c,"width",dw+"px");S(c,"height",dh+"px");S(c,"max-width","none");S(c,"max-height","none");S(c,"margin","0");S(c,"z-index","1");'
|
| 66 |
+
+ 'if(s>1.05&&Math.max(iw,ih)<=512)S(c,"image-rendering","pixelated");else c.style.removeProperty("image-rendering");'
|
| 67 |
+
+ 'if(mode==="fill")S(document.documentElement,"overflow","hidden");else document.documentElement.style.removeProperty("overflow");'
|
| 68 |
+
+ '}catch(_){}}'
|
| 69 |
+
+ 'function announce(){try{parent.postMessage({holoFit:{canvasApp:!!lone()}},"*")}catch(_){}}'
|
| 70 |
+
+ 'addEventListener("message",function(e){if(e&&e.data&&e.data.holoMode){mode=e.data.holoMode;run()}});'
|
| 71 |
+
+ 'function b(){run();mfit();announce();setTimeout(function(){run();mfit();announce()},300);setTimeout(function(){run();mfit()},1200)}if(document.readyState!=="loading")b();else addEventListener("DOMContentLoaded",b);addEventListener("load",b);addEventListener("resize",function(){run();mfit()})})();<\/script>';
|
| 72 |
+
const inject = '<base href="' + prefix + 'run/' + id + '/">'
|
| 73 |
+
+ (hasVp ? '' : '<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">')
|
| 74 |
+
+ '<style>html,body{margin:0;min-height:100%}</style>' + fill;
|
| 75 |
+
return /<head[^>]*>/i.test(html) ? html.replace(/<head[^>]*>/i, (h) => h + "\n" + inject) : inject + html;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
// ── module resolution shared by findEntry + buildSPA ──────────────────────────────────────────────────────
|
| 79 |
+
export const TRY = ["", ".tsx", ".ts", ".jsx", ".js", ".mjs", ".json", ".css", "/index.tsx", "/index.ts", "/index.jsx", "/index.js"];
|
| 80 |
+
export const clean = (p) => String(p).replace(/^\.?\//, "").split(/[?#]/)[0];
|
| 81 |
+
export const dirOf = (p) => { const i = p.lastIndexOf("/"); return i < 0 ? "" : p.slice(0, i); };
|
| 82 |
+
export const joinRel = (d, s) => { const parts = (d ? d.split("/") : []).concat(clean(s).split("/")); const o = []; for (const x of parts) { if (x === "" || x === ".") continue; if (x === "..") o.pop(); else o.push(x); } return o.join("/"); };
|
| 83 |
+
// .js/.mjs/.cjs → the `jsx` loader (a superset that also parses plain JS): many React repos put JSX in .js.
|
| 84 |
+
export const loaderOf = (p) => ({ js: "jsx", mjs: "jsx", cjs: "jsx", jsx: "jsx", ts: "ts", tsx: "tsx", json: "json", css: "css", svg: "dataurl", png: "dataurl", jpg: "dataurl", jpeg: "dataurl", gif: "dataurl", webp: "dataurl", woff: "dataurl", woff2: "dataurl", ttf: "dataurl" }[(p.split(".").pop() || "").toLowerCase()] || "text");
|
| 85 |
+
export const PEERS = ["react", "react-dom", "vue", "preact", "solid-js", "@vue/runtime-dom"];
|
| 86 |
+
|
| 87 |
+
export function findEntry(files) {
|
| 88 |
+
const idx = ["index.html", "public/index.html", "src/index.html"].find((p) => files.has(p));
|
| 89 |
+
let html = idx ? asText(files.get(idx)) : null;
|
| 90 |
+
if (html) { const m = html.match(/<script\b[^>]*\btype\s*=\s*["']module["'][^>]*\bsrc\s*=\s*["']([^"']+)["']/i) || html.match(/<script\b[^>]*\bsrc\s*=\s*["']([^"']+\.[jt]sx?)["']/i); if (m && !/^(?:[a-z]+:)?\/\//i.test(m[1])) { const c = clean(m[1]); for (const e of TRY) if (files.has(c + e)) return { entry: c + e, html, idx }; } }
|
| 91 |
+
let pkg = null; try { pkg = JSON.parse(asText(files.get("package.json"))); } catch {}
|
| 92 |
+
const guesses = [pkg && pkg.module, pkg && pkg.main, "src/main.tsx", "src/main.jsx", "src/main.ts", "src/main.js", "src/index.tsx", "src/index.jsx", "src/index.js"].filter(Boolean).map(clean);
|
| 93 |
+
for (const g of guesses) for (const e of TRY) if (files.has(g + e)) return { entry: g + e, html, idx };
|
| 94 |
+
return { entry: null, html, idx };
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
// ── κ-vendoring (SR-P0): fetch a runtime graph ONCE at build time, follow esm.sh's thin re-export shims
|
| 98 |
+
// TRANSITIVELY, rewrite each import to /vendor/<κ>, address every module by hash. The store/index/fetch
|
| 99 |
+
// are injected; the graph walk + rewrite are identical in Node and the tab. ───────────────────────────
|
| 100 |
+
export function makeVendor({ fetchText, store, exists, hash, index }) {
|
| 101 |
+
async function vendorGraph(full, seen) {
|
| 102 |
+
if (seen.has(full)) return seen.get(full);
|
| 103 |
+
seen.set(full, "PENDING"); // cycle guard
|
| 104 |
+
let src = await fetchText(full);
|
| 105 |
+
const origin = new URL(full).origin;
|
| 106 |
+
const specs = new Set();
|
| 107 |
+
for (const m of src.matchAll(/(?:from|import|export)\s*(?:\(\s*)?["']([^"']+)["']/g)) { const s = m[1]; if (s.startsWith("/") || s.includes("esm.sh")) specs.add(s); }
|
| 108 |
+
for (const s of specs) {
|
| 109 |
+
const childUrl = s.startsWith("http") ? s : origin + s;
|
| 110 |
+
const childK = await vendorGraph(childUrl, seen);
|
| 111 |
+
// SIBLING-relative rewrite ("./<κ>"): a vendor module at <scope>vendor/<a> imports <scope>vendor/<b>
|
| 112 |
+
// with NO prefix knowledge — the κ-addressed bytes work at any origin layout (root, Pages subpath).
|
| 113 |
+
src = src.split('"' + s + '"').join('"./' + childK + '"').split("'" + s + "'").join("'./" + childK + "'");
|
| 114 |
+
}
|
| 115 |
+
const hex = hash(enc(src));
|
| 116 |
+
await store(hex, src);
|
| 117 |
+
seen.set(full, hex);
|
| 118 |
+
return hex;
|
| 119 |
+
}
|
| 120 |
+
return async function vendorKappa(esmUrl) { // esm.sh url → { kappa(hex), reused } — fully vendored (transitive)
|
| 121 |
+
const known = await index.get(esmUrl);
|
| 122 |
+
if (known && (await exists(known))) return { kappa: known, reused: true };
|
| 123 |
+
const hex = await vendorGraph(esmUrl, new Map());
|
| 124 |
+
await index.set(esmUrl, hex);
|
| 125 |
+
return { kappa: hex, reused: false };
|
| 126 |
+
};
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
// ── buildSPA — compile the ORIGINAL source with the injected esbuild (native or κ-pinned wasm), bare deps
|
| 130 |
+
// → κ-vendored importmap (honest esm.sh fallback if vendoring fails). Same κ-transform on both hosts. ──
|
| 131 |
+
export async function buildSPA(files, { esbuild, vendorKappa, onStatus = () => {} }) {
|
| 132 |
+
const { entry, html, idx } = findEntry(files);
|
| 133 |
+
if (!entry) throw new Error("no SPA entry found (index.html module script / src/main.*)");
|
| 134 |
+
onStatus("compiling " + entry);
|
| 135 |
+
const bare = new Set();
|
| 136 |
+
const plugin = { name: "holo", setup(b) {
|
| 137 |
+
b.onResolve({ filter: /.*/ }, (a) => {
|
| 138 |
+
if (a.kind === "entry-point") return { path: clean(a.path), namespace: "v" };
|
| 139 |
+
const s = a.path;
|
| 140 |
+
if (/^(?:[a-z]+:)?\/\//i.test(s) || s.startsWith("data:")) return { external: true };
|
| 141 |
+
if (s.startsWith(".") || s.startsWith("/")) { const base = joinRel(dirOf(a.importer === "<stdin>" ? entry : a.importer), s); for (const e of TRY) if (files.has(base + e)) return { path: base + e, namespace: "v" }; return { external: true }; }
|
| 142 |
+
bare.add(s); return { path: s, external: true }; // npm dep → external → importmap → κ-vendor
|
| 143 |
+
});
|
| 144 |
+
b.onLoad({ filter: /.*/, namespace: "v" }, (a) => { const f = files.get(a.path); return { contents: asText(f), loader: loaderOf(a.path), resolveDir: dirOf(a.path) }; });
|
| 145 |
+
} };
|
| 146 |
+
const res = await esbuild.build({ entryPoints: [entry], bundle: true, write: false, outdir: "dist", format: "esm", jsx: "automatic", target: "es2020", logLevel: "silent",
|
| 147 |
+
define: { "process.env.NODE_ENV": '"production"', "import.meta.env.MODE": '"production"', "import.meta.env.PROD": "true", "import.meta.env.DEV": "false", "import.meta.env.BASE_URL": '"/"', "import.meta.env.SSR": "false", "import.meta.env": "{}" },
|
| 148 |
+
plugins: [plugin] });
|
| 149 |
+
let js = "", css = ""; for (const o of res.outputFiles) { if (o.path.endsWith(".css")) css += o.text; else js += o.text; }
|
| 150 |
+
const peers = PEERS.filter((p) => bare.has(p));
|
| 151 |
+
const imports = {}; let vNovel = 0, vReused = 0;
|
| 152 |
+
for (const s of bare) {
|
| 153 |
+
let u = "https://esm.sh/" + s + "?bundle"; const ext = peers.filter((p) => p !== s); if (ext.length && !peers.includes(s)) u += "&external=" + ext.join(",");
|
| 154 |
+
onStatus("κ-vendoring " + s);
|
| 155 |
+
try { const v = await vendorKappa(u); imports[s] = "/vendor/" + v.kappa; if (v.reused) vReused++; else vNovel++; } // κ-served vendor, verified — NOT esm.sh at runtime
|
| 156 |
+
catch { imports[s] = "https://esm.sh/" + s; } // honest fallback if κ-vendoring fails
|
| 157 |
+
}
|
| 158 |
+
const importmap = Object.keys(imports).length ? '<script type="importmap">' + JSON.stringify({ imports }) + "</script>\n" : "";
|
| 159 |
+
// assemble the runnable HTML: keep the repo's index body/mount, inject importmap + css + our module bundle
|
| 160 |
+
let body = "<div id=\"root\"></div><div id=\"app\"></div>";
|
| 161 |
+
if (html) {
|
| 162 |
+
const bm = html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
|
| 163 |
+
if (bm) {
|
| 164 |
+
// strip ONLY the scripts we replace: module scripts (bundled) + the entry's own <script src=ENTRY>.
|
| 165 |
+
// KEEP the app's other global <script src> (jQuery, engine libs) — they load from the tree.
|
| 166 |
+
const eb = String(entry).split("/").pop().replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
| 167 |
+
const entryRe = new RegExp('<script\\b[^>]*\\bsrc\\s*=\\s*["\'][^"\']*' + eb + '["\'][^>]*>\\s*<\\/script>', "gi");
|
| 168 |
+
body = bm[1]
|
| 169 |
+
.replace(/<script\b[^>]*\btype\s*=\s*["']module["'][\s\S]*?<\/script>/gi, "") // module (bundled)
|
| 170 |
+
.replace(entryRe, ""); // the entry itself (bundled)
|
| 171 |
+
}
|
| 172 |
+
}
|
| 173 |
+
const doc = "<!doctype html><html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n"
|
| 174 |
+
+ importmap + (css ? "<style>\n" + css + "\n</style>\n" : "") + "</head><body>\n" + body + "\n<script type=\"module\">\n" + js + "\n</script></body></html>";
|
| 175 |
+
return { html: doc, deps: [...bare], entry, idx, vendor: { novel: vNovel, reused: vReused, kappas: Object.values(imports).filter((u) => u.startsWith("/vendor/")).map((u) => u.slice(8)) } };
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
// ── tree assembly (the R0 / classic / R1 shapes — the serving-law-critical logic) ─────────────────────────
|
| 179 |
+
const TREE_SKIP = /(^|\/)(node_modules|\.git)\//;
|
| 180 |
+
|
| 181 |
+
// R0: the WHOLE repo as a binary-safe file tree rebased to the entry's dir (runtime-loaded sprites/audio/
|
| 182 |
+
// tilemaps resolve at /run/<id>/<path>; an inlined single doc broke every runtime asset — the blank-canvas bug).
|
| 183 |
+
export function assembleR0Tree(files, entry) {
|
| 184 |
+
const entryDir = entry.includes("/") ? entry.slice(0, entry.lastIndexOf("/") + 1) : "";
|
| 185 |
+
const tree = new Map();
|
| 186 |
+
for (const [fp, v] of files) { let tk = fp; if (entryDir) { if (!fp.startsWith(entryDir)) continue; tk = fp.slice(entryDir.length); } tree.set(tk, asBytes(v)); }
|
| 187 |
+
if (!tree.has("index.html")) { const b = entry.split("/").pop(); if (tree.has(b)) tree.set("index.html", tree.get(b)); } // entry in a subdir → surface it at the tree root
|
| 188 |
+
return tree;
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
// FAITHFUL-AS-IS: a repo whose index has only classic <script src=".js"> (no module/TS/JSX entry) must be
|
| 192 |
+
// served AS-IS as a tree — module-bundling a global-script app scopes away its globals ("Foo is not defined").
|
| 193 |
+
export function classicIndexPath(files) {
|
| 194 |
+
const idxPath = ["index.html", "public/index.html", "src/index.html", "docs/index.html"].find((p) => files.has(p));
|
| 195 |
+
if (!idxPath) return null;
|
| 196 |
+
const idxHtml = asText(files.get(idxPath));
|
| 197 |
+
const hasModule = /<script[^>]*\btype\s*=\s*["']module["']/i.test(idxHtml);
|
| 198 |
+
const localSrcs = [...idxHtml.matchAll(/<script\b[^>]*\bsrc\s*=\s*["']([^"']+)["']/gi)].map((m) => m[1]).filter((s) => !/^(?:[a-z]+:)?\/\//i.test(s));
|
| 199 |
+
const classic = !hasModule && localSrcs.length > 0 && localSrcs.every((s) => /\.(?:js|mjs|cjs)(?:[?#]|$)/i.test(s));
|
| 200 |
+
return classic ? idxPath : null;
|
| 201 |
+
}
|
| 202 |
+
export function assembleClassicTree(files, idxPath) {
|
| 203 |
+
const entryDir = idxPath.includes("/") ? idxPath.slice(0, idxPath.lastIndexOf("/") + 1) : "";
|
| 204 |
+
const tree = new Map();
|
| 205 |
+
for (const [fp, v] of files) { if (TREE_SKIP.test(fp)) continue; let tk = fp; if (entryDir) { if (!fp.startsWith(entryDir)) continue; tk = fp.slice(entryDir.length); } tree.set(tk, asBytes(v)); }
|
| 206 |
+
if (!tree.has("index.html")) { const b = idxPath.split("/").pop(); if (tree.has(b)) tree.set("index.html", tree.get(b)); }
|
| 207 |
+
return tree;
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
// R1: the bundled index at the tree root PLUS the repo's static assets (images/audio/fonts/json the app
|
| 211 |
+
// loads at runtime by path — the JS bundle does NOT inline those), rebased to the index's dir.
|
| 212 |
+
export function assembleR1Tree(files, built) {
|
| 213 |
+
const idxDir = built.idx && built.idx.includes("/") ? built.idx.slice(0, built.idx.lastIndexOf("/") + 1) : "";
|
| 214 |
+
const tree = new Map();
|
| 215 |
+
for (const [fp, v] of files) { if (TREE_SKIP.test(fp)) continue; let tk = fp; if (idxDir) { if (!fp.startsWith(idxDir)) continue; tk = fp.slice(idxDir.length); } tree.set(tk, asBytes(v)); }
|
| 216 |
+
tree.set("index.html", enc(built.html)); // the bundled doc overrides the repo's raw index
|
| 217 |
+
return tree;
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
export function describeAnimateCore() {
|
| 221 |
+
return { is: "the shared per-request animate core — MIME, fidelity rewriteIndex, entry resolution, buildSPA (esbuild injected), κ-vendor graph, R0/classic/R1 tree assembly",
|
| 222 |
+
hosts: "the Node store daemon AND the in-browser service-worker animator consume this file verbatim",
|
| 223 |
+
parity: "the conformance (L1–L4) + fidelity (F1–F5) gates define parity: both origins serve this one logic" };
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
export default { VERSION, MIME, mimeOf, rewriteIndex, findEntry, makeVendor, buildSPA, assembleR0Tree, classicIndexPath, assembleClassicTree, assembleR1Tree, TRY, clean, dirOf, joinRel, loaderOf, PEERS, asText, asBytes, describeAnimateCore };
|
b/47fba521d83144ed4bfd40e803eec7e9d04814cd4c905acfbe456a1f6c6b458d
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// holo-dotholo.mjs — the .holo app package (HOLO-ANIMATE-PLATFORM, M3). An animated app is sealed as ONE
|
| 2 |
+
// content-addressed object: a manifest (source repo@commit + provider + tier + recipe κ + result κ + caps)
|
| 3 |
+
// bound to the compiled result bytes. Its identity IS its content (BLAKE3, §1.2), so the SAME repo@commit
|
| 4 |
+
// compiles to the SAME .holo κ on every machine → build ONCE per planet; everyone after streams the κ and
|
| 5 |
+
// verifies it before render (Law L5). Pure + isomorphic. This is the store's unit of distribution.
|
| 6 |
+
|
| 7 |
+
import { blake3hex } from "./holo-blake3.mjs";
|
| 8 |
+
import { verifyMembers as verifyClosure } from "./holo-l5.mjs"; // the ONE L5 re-derivation primitive
|
| 9 |
+
const enc = (s) => new TextEncoder().encode(s);
|
| 10 |
+
// RFC 8785 JCS (sorted keys) — one κ across every engine (identical to holo-import/holo-forge).
|
| 11 |
+
export const jcs = (v) => Array.isArray(v) ? "[" + v.map(jcs).join(",") + "]"
|
| 12 |
+
: (v && typeof v === "object") ? "{" + Object.keys(v).sort().map((k) => JSON.stringify(k) + ":" + jcs(v[k])).join(",") + "}"
|
| 13 |
+
: JSON.stringify(v);
|
| 14 |
+
const b3 = (bytesOrStr) => "did:holo:blake3:" + blake3hex(typeof bytesOrStr === "string" ? enc(bytesOrStr) : bytesOrStr);
|
| 15 |
+
|
| 16 |
+
export const VERSION = "holo-dotholo/0.1.0";
|
| 17 |
+
|
| 18 |
+
// holoManifest(...) → the sealed descriptor (no bytes; the result is referenced by resultKappa). ───────────
|
| 19 |
+
export function holoManifest({ repo, commit = null, provider, tier, recipeKappa = null, resultKappa, name = null, contentType = "text/html", deps = null, capabilities = null }) {
|
| 20 |
+
return {
|
| 21 |
+
"@type": "holo:App", "holo:version": 1,
|
| 22 |
+
"holo:source": repo ? (repo.replace(/^https?:\/\//, "") + (commit ? "@" + commit : "")) : null,
|
| 23 |
+
"holo:provider": provider, "holo:tier": tier,
|
| 24 |
+
...(recipeKappa ? { "holo:recipe": recipeKappa } : {}),
|
| 25 |
+
"holo:result": resultKappa, "holo:contentType": contentType,
|
| 26 |
+
...(name ? { "schema:name": name } : {}),
|
| 27 |
+
...(deps && deps.length ? { "holo:deps": deps } : {}),
|
| 28 |
+
...(capabilities ? { "holo:capabilities": capabilities } : {}),
|
| 29 |
+
};
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
// sealHolo({ repo, commit, provider, tier, recipeKappa, result, ... }) → { kappa, manifest, resultKappa, result }.
|
| 33 |
+
// `result` = the compiled app bytes/string (R0/R1: one self-contained HTML). kappa = κ of the manifest;
|
| 34 |
+
// resultKappa = κ of the bytes. Same source → same κ (Law L5 content-convergence).
|
| 35 |
+
export function sealHolo({ repo, commit = null, provider, tier, recipeKappa = null, result, name = null, contentType = "text/html", deps = null, capabilities = null }) {
|
| 36 |
+
if (result == null) throw new Error("sealHolo needs the compiled result bytes");
|
| 37 |
+
const resultKappa = b3(result);
|
| 38 |
+
const manifest = holoManifest({ repo, commit, provider, tier, recipeKappa, resultKappa, name, contentType, deps, capabilities });
|
| 39 |
+
const kappa = b3(jcs(manifest));
|
| 40 |
+
return { kappa, manifest, resultKappa, result };
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
// ── machine .holo (R3): the result is a FROZEN MACHINE (disk κ ⊕ device-state κ + boot meta), not bytes to
|
| 44 |
+
// render. Open = RESUME, not reboot. Sealed + verified exactly like a content .holo (its result is the JCS
|
| 45 |
+
// of the snapshot descriptor), so one cache + one open path serve both kinds. ─────────────────────────────
|
| 46 |
+
// snapshot = { disk, dev, engine, mem, smp } — the descriptor holo-vm.vmSnapshot writes (disk manifest κ +
|
| 47 |
+
// device-state κ + boot meta). port = the app's forwarded/guest port so the resumed machine re-projects.
|
| 48 |
+
export function sealMachineHolo({ repo, commit = null, provider, tier = "R3", recipeKappa = null, snapshot, name = null, port = 0, capabilities = null }) {
|
| 49 |
+
if (!snapshot || !snapshot.dev) throw new Error("sealMachineHolo needs a snapshot descriptor { disk, dev, ... }");
|
| 50 |
+
const result = jcs(snapshot); // the machine descriptor IS the result bytes
|
| 51 |
+
const resultKappa = b3(result);
|
| 52 |
+
const manifest = { ...holoManifest({ repo, commit, provider, tier, recipeKappa, resultKappa, name, contentType: "application/holo-machine", capabilities }), "holo:kind": "machine", "holo:port": port };
|
| 53 |
+
const kappa = b3(jcs(manifest));
|
| 54 |
+
return { kappa, manifest, resultKappa, result, snapshot };
|
| 55 |
+
}
|
| 56 |
+
// openMachineHolo({ kappa?, manifest, result }) → { ok, snapshot?, port?, error? }. VERIFY BEFORE RESUME (L5):
|
| 57 |
+
// the descriptor bytes must hash to resultKappa (and manifest to kappa) — a tampered machine κ is refused
|
| 58 |
+
// before the guest is resumed. Returns the snapshot descriptor for holo-vm.vmResume.
|
| 59 |
+
export function openMachineHolo({ kappa = null, manifest, result }) {
|
| 60 |
+
if (!manifest || result == null) return { ok: false, error: "incomplete machine .holo" };
|
| 61 |
+
if (manifest["holo:kind"] !== "machine") return { ok: false, error: "not a machine .holo (use openHolo)" };
|
| 62 |
+
if (kappa && !verifyHolo(kappa, manifest)) return { ok: false, error: "manifest-mismatch (tampered machine κ)" };
|
| 63 |
+
if (b3(result) !== manifest["holo:result"]) return { ok: false, error: "snapshot-mismatch (tampered machine, L5 refused)" };
|
| 64 |
+
return { ok: true, snapshot: JSON.parse(typeof result === "string" ? result : new TextDecoder().decode(result)), port: manifest["holo:port"] || 0, manifest };
|
| 65 |
+
}
|
| 66 |
+
// isMachine(manifest) — the store branches open→resume vs open→render on this. ─────────────────────────────
|
| 67 |
+
export const isMachine = (manifest) => !!(manifest && manifest["holo:kind"] === "machine");
|
| 68 |
+
|
| 69 |
+
// ── closure .holo (κ-Portal, P0): ONE κ that transitively names an ENTIRE runtime — the entry document + every
|
| 70 |
+
// subresource + the delivery Service Worker — as a Merkle root. This is the single self-verifying κ that is
|
| 71 |
+
// "sufficient to stream the whole experience anywhere": a cold, foreign browser fetches this one .holo, opens
|
| 72 |
+
// it (L5), then streams each member by κ (content-blind transport) and refuses any byte that does not
|
| 73 |
+
// re-derive. Unifies today's scattered holospace.lock.json `closure` + os-closure.json under one sealed κ.
|
| 74 |
+
// members: { "path": "did:holo:<algo>:<hex>" | { kappa } } — the closure map (path→κ). The SORTED map's JCS
|
| 75 |
+
// IS the result bytes (same trick as sealMachineHolo), so one cache + one verify path serve every kind. ─────
|
| 76 |
+
const kappaOf = (v) => (typeof v === "string" ? v : v && v.kappa);
|
| 77 |
+
const algoHexOf = (kappa) => { const m = /^did:holo:([a-z0-9]+):([0-9a-f]+)$/.exec(kappa || ""); return m ? { algo: m[1], hex: m[2] } : null; };
|
| 78 |
+
|
| 79 |
+
export function sealClosureHolo({ repo, commit = null, provider = "closure", tier = "R1", entry, members, worker = null, name = null, capabilities = null, deps = null }) {
|
| 80 |
+
if (!entry || !members || typeof members !== "object") throw new Error("sealClosureHolo needs { entry, members }");
|
| 81 |
+
if (!members[entry]) throw new Error("sealClosureHolo: entry must be a member of the closure");
|
| 82 |
+
const map = {}; // path → κ, sorted → deterministic (build once per planet)
|
| 83 |
+
for (const p of Object.keys(members).sort()) { const k = kappaOf(members[p]); if (!algoHexOf(k)) throw new Error("sealClosureHolo: member '" + p + "' has no κ"); map[p] = k; }
|
| 84 |
+
const wk = worker ? kappaOf(worker) : null;
|
| 85 |
+
if (worker && !algoHexOf(wk)) throw new Error("sealClosureHolo: worker κ malformed");
|
| 86 |
+
const result = jcs(map); // the closure map IS the result bytes
|
| 87 |
+
const resultKappa = b3(result);
|
| 88 |
+
const manifest = {
|
| 89 |
+
...holoManifest({ repo, commit, provider, tier, resultKappa, name, contentType: "application/holo-closure", deps, capabilities }),
|
| 90 |
+
"holo:kind": "closure", "holo:entry": entry, ...(wk ? { "holo:worker": wk } : {}), "holo:files": Object.keys(map).length,
|
| 91 |
+
};
|
| 92 |
+
const kappa = b3(jcs(manifest));
|
| 93 |
+
return { kappa, manifest, resultKappa, result, members: map };
|
| 94 |
+
}
|
| 95 |
+
export const isClosure = (manifest) => !!(manifest && manifest["holo:kind"] === "closure");
|
| 96 |
+
|
| 97 |
+
// openClosureHolo({ kappa?, manifest, result }) → { ok, entry?, worker?, members?, error? }. STRUCTURAL L5:
|
| 98 |
+
// manifest re-derives to κ, the closure-map bytes re-derive to resultKappa, and the entry is a listed member.
|
| 99 |
+
// (Byte-completeness of the members is a separate async pass — verifyClosure — since it needs the bytes.)
|
| 100 |
+
export function openClosureHolo({ kappa = null, manifest, result }) {
|
| 101 |
+
if (!manifest || result == null) return { ok: false, error: "incomplete closure .holo" };
|
| 102 |
+
if (manifest["holo:kind"] !== "closure") return { ok: false, error: "not a closure .holo (use openHolo/openMachineHolo)" };
|
| 103 |
+
if (kappa && !verifyHolo(kappa, manifest)) return { ok: false, error: "manifest-mismatch (tampered closure κ)" };
|
| 104 |
+
if (b3(result) !== manifest["holo:result"]) return { ok: false, error: "closure-mismatch (tampered map, L5 refused)" };
|
| 105 |
+
let members; try { members = JSON.parse(typeof result === "string" ? result : new TextDecoder().decode(result)); } catch { return { ok: false, error: "closure map is not JSON" }; }
|
| 106 |
+
const entry = manifest["holo:entry"];
|
| 107 |
+
if (!members[entry]) return { ok: false, error: "entry '" + entry + "' not in closure (L5 refused)" };
|
| 108 |
+
return { ok: true, entry, worker: manifest["holo:worker"] || null, members, manifest };
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
// verifyClosure — the closure COMPLETENESS gate. It IS the ONE L5 primitive (holo-l5.verifyMembers, imported at
|
| 112 |
+
// top as verifyClosure) so the re-derivation rule lives in a single place (shared with the delivery Service
|
| 113 |
+
// Worker). Re-derive EVERY member or refuse (missing / tampered / unverifiable). Same signature + result as before.
|
| 114 |
+
export { verifyClosure };
|
| 115 |
+
|
| 116 |
+
// verifyHolo(kappa, manifest) → the manifest re-derives to the .holo κ (identity intact). ──────────────────
|
| 117 |
+
export function verifyHolo(kappa, manifest) { return b3(jcs(manifest)) === kappa; }
|
| 118 |
+
|
| 119 |
+
// openHolo({ kappa?, manifest, result }) → { ok, html?, error? }. VERIFY BEFORE RENDER (L5): the bytes must
|
| 120 |
+
// hash to the manifest's resultKappa, and (if kappa given) the manifest must hash to it — tamper → refused.
|
| 121 |
+
export function openHolo({ kappa = null, manifest, result }) {
|
| 122 |
+
if (!manifest || result == null) return { ok: false, error: "incomplete .holo (manifest + result required)" };
|
| 123 |
+
if (manifest["holo:kind"] === "machine") return { ok: false, error: "machine .holo (use openMachineHolo)" };
|
| 124 |
+
if (manifest["holo:kind"] === "closure") return { ok: false, error: "closure .holo (use openClosureHolo)" };
|
| 125 |
+
if (kappa && !verifyHolo(kappa, manifest)) return { ok: false, error: "manifest-mismatch (tampered .holo κ)" };
|
| 126 |
+
if (b3(result) !== manifest["holo:result"]) return { ok: false, error: "result-mismatch (tampered bytes, L5 refused)" };
|
| 127 |
+
return { ok: true, html: typeof result === "string" ? result : new TextDecoder().decode(result), manifest };
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
// packHolo(sealed) / unpackHolo(bytes) → a single self-describing wire blob (manifest + result) for the
|
| 131 |
+
// κ-cache / fabric to store + stream as one object. JSON envelope (result base64) — simple + verifiable.
|
| 132 |
+
// ISOMORPHIC base64 (SS-P0): `Buffer` is a Node global — in the browser/service-worker the .holo must pack
|
| 133 |
+
// and unpack identically (OPFS warm tier, static κ-registry), so base64 goes via btoa/atob there. Chunked:
|
| 134 |
+
// String.fromCharCode.apply blows the arg limit on multi-MB results.
|
| 135 |
+
export const b64encode = (bytes) => {
|
| 136 |
+
if (typeof Buffer !== "undefined") return Buffer.from(bytes).toString("base64");
|
| 137 |
+
let s = ""; const CH = 0x8000;
|
| 138 |
+
for (let i = 0; i < bytes.length; i += CH) s += String.fromCharCode.apply(null, bytes.subarray(i, i + CH));
|
| 139 |
+
return btoa(s);
|
| 140 |
+
};
|
| 141 |
+
export const b64decode = (b64) => {
|
| 142 |
+
if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(b64, "base64"));
|
| 143 |
+
const s = atob(b64); const u = new Uint8Array(s.length);
|
| 144 |
+
for (let i = 0; i < s.length; i++) u[i] = s.charCodeAt(i);
|
| 145 |
+
return u;
|
| 146 |
+
};
|
| 147 |
+
export function packHolo({ kappa, manifest, result }) {
|
| 148 |
+
const b64 = b64encode(typeof result === "string" ? enc(result) : result);
|
| 149 |
+
return JSON.stringify({ kappa, manifest, result_b64: b64 });
|
| 150 |
+
}
|
| 151 |
+
export function unpackHolo(bytes) {
|
| 152 |
+
const o = JSON.parse(typeof bytes === "string" ? bytes : new TextDecoder().decode(bytes));
|
| 153 |
+
const result = new TextDecoder().decode(b64decode(o.result_b64));
|
| 154 |
+
return { kappa: o.kappa, manifest: o.manifest, result };
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
export function describeDotHolo() {
|
| 158 |
+
return { is: "the .holo app package — a content-addressed app (manifest ⊕ compiled result), the store's unit of distribution",
|
| 159 |
+
once: "same repo@commit → same .holo κ everywhere → build once per planet; re-open streams the κ (0 rebuild)",
|
| 160 |
+
l5: "verify before render — bytes must hash to resultKappa, manifest to the .holo κ; tamper → refused" };
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
export default { VERSION, holoManifest, sealHolo, sealMachineHolo, openMachineHolo, isMachine, sealClosureHolo, openClosureHolo, verifyClosure, isClosure, verifyHolo, openHolo, packHolo, unpackHolo, jcs, describeDotHolo };
|
b/535ed9bd32c3c964ae2cd9dbf69406fc007db53501a6bdbefe33c70d949e4b90
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:711d62484385c29d64ddcdc4c0beb3fb7903635a41bafffeb652938ee8480587
|
| 3 |
+
size 11655844
|
b/70255554e8d094619d355e66eb84d43727b00217c548422bb69de0aeb2005c76
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// holo-import.mjs — Holo Import (ADR-0092): paste a GitHub URL, get a running, content-addressed
|
| 2 |
+
// Holo app. The repo address goes in; a live, governed, interoperable holospace comes out — 100%
|
| 3 |
+
// serverless, in-browser. This module is the PURE, isomorphic CORE (Node + the browser run identical
|
| 4 |
+
// logic; the witness drives it headless). It NEVER fetches and NEVER hashes by itself — the network is
|
| 5 |
+
// an INGEST BOUNDARY (Law L4: a `fetchRepo` thunk is injected, brokered through holo-gov in the browser)
|
| 6 |
+
// and the address layer is injected (`hash` — WebCrypto in the browser, the pure sha256 of the repo in
|
| 7 |
+
// Node), exactly like holo-forge.mjs and holo-scene.mjs.
|
| 8 |
+
//
|
| 9 |
+
// THE LOAD-BEARING FINDING (why this is small, not a new runtime): once a repo is reduced to ONE
|
| 10 |
+
// self-contained HTML source string, it seals through the SAME `publishSource` UOR envelope the Create
|
| 11 |
+
// studio already uses (holo-blocks-repo.mjs) — so an imported app's κ is BYTE-IDENTICAL to a hand-built
|
| 12 |
+
// app's κ for the same source. Import is therefore NOT a new kind of object; it is a DETERMINISTIC
|
| 13 |
+
// ENCODER that produces a source the substrate already renders · links · remixes · shares · traces.
|
| 14 |
+
// Faithful by construction (no model, no hallucination — Law L5: same repo@commit → same app κ).
|
| 15 |
+
//
|
| 16 |
+
// HONEST SCOPE (the build-locus, decided per class — never papered over):
|
| 17 |
+
// • static / client-SPA → DIRECT here: inline assets into one HTML, seal. Runs now.
|
| 18 |
+
// • needs a bundler → FORGE: a re-derivable κ-transform (esbuild-wasm) — STAGED, classified
|
| 19 |
+
// here, not executed (named, not faked).
|
| 20 |
+
// • SSR / backend / DB / CLI → OUT OF SCOPE v1: classified + the reason SURFACED in the result, so
|
| 21 |
+
// "we don't run this yet" is visible, never a silently-empty app.
|
| 22 |
+
//
|
| 23 |
+
// Composition: Holo Q (ADR-0091, the import rides Q.create as task "import") · Holo Forge (ADR-0051, the
|
| 24 |
+
// staged bundler transform) · Holo Scene (ADR-0089, the app is one scene κ) · Holo Prov (ADR-0082, the
|
| 25 |
+
// receipt's wasDerivedFrom walks to GitHub forever) · holo-gov / Holo Q Remote (ADR-0090, the governed
|
| 26 |
+
// ingest fetch) · Holo Cosmos (ADR-0080, κ-streamed heavy assets). Grounded in Laws L1/L3/L4/L5.
|
| 27 |
+
|
| 28 |
+
export const VERSION = "holo-import/0.1.0";
|
| 29 |
+
|
| 30 |
+
// ── the UOR app envelope — mirrors holo-blocks-repo.mjs publishSource() EXACTLY so an imported app's κ
|
| 31 |
+
// equals a studio-built app's κ for identical source bytes (the κ-parity that makes import first-class). ──
|
| 32 |
+
const UOR_CONTEXT = ["https://www.w3.org/ns/did/v1", "https://w3id.org/security/data-integrity/v2",
|
| 33 |
+
{ schema: "https://schema.org/", prov: "http://www.w3.org/ns/prov#", dcterms: "http://purl.org/dc/terms/",
|
| 34 |
+
rel: "schema:additionalType", links: { "@id": "schema:hasPart", "@container": "@set" } }];
|
| 35 |
+
|
| 36 |
+
// RFC 8785 JCS (sorted keys) — identical to holo-blocks-repo / holo-forge so one κ, every engine.
|
| 37 |
+
export const jcs = (v) => Array.isArray(v) ? "[" + v.map(jcs).join(",") + "]"
|
| 38 |
+
: (v && typeof v === "object") ? "{" + Object.keys(v).sort().map((k) => JSON.stringify(k) + ":" + jcs(v[k])).join(",") + "}"
|
| 39 |
+
: JSON.stringify(v);
|
| 40 |
+
|
| 41 |
+
import { ensureMobileHead } from "./holo-mobile-defaults.mjs"; // every imported app is mobile + PWA-ready at birth
|
| 42 |
+
import { blake3hex } from "./holo-blake3.mjs"; // the ONE canonical κ hash (§1.2) — mint IDENTICALLY to holo-blocks-repo.addressOf
|
| 43 |
+
const enc = (s) => new TextEncoder().encode(s); // byte-for-byte the same enc as holo-blocks-repo (preserves κ-parity)
|
| 44 |
+
|
| 45 |
+
// appEnvelope(name, source) → the exact object publishSource seals (minus its `id`). Sealing it with the
|
| 46 |
+
// injected `hash` over jcs() yields the SAME did:holo the studio would. derivedFrom (lineage) is metadata
|
| 47 |
+
// — it does not change the content address (Law L5 content-convergence, ADR-0088), matching publishSource.
|
| 48 |
+
export function appEnvelope(name, source, derivedFrom = null) {
|
| 49 |
+
return { "@context": UOR_CONTEXT, "@type": ["schema:SoftwareSourceCode", "schema:WebApplication"],
|
| 50 |
+
"schema:name": name, "schema:programmingLanguage": "HTML", "schema:text": source,
|
| 51 |
+
...(derivedFrom ? { "prov:wasDerivedFrom": derivedFrom } : {}) };
|
| 52 |
+
}
|
| 53 |
+
// sealApp({ name, source, derivedFrom? }) → { id, bytes } — id is byte-identical to holo-blocks-repo.addressOf
|
| 54 |
+
// (publishSource): "did:holo:blake3:" + blake3hex(enc(jcs(env))). The canonical κ hash is BLAKE3, computed the
|
| 55 |
+
// same way in both modules, so an imported app's κ still equals a studio-built app's κ (§1.2 + the parity intact).
|
| 56 |
+
// `hash` is accepted for caller back-compat but no longer used (the axis is fixed canonical, not injected).
|
| 57 |
+
export function sealApp({ name, source, hash, derivedFrom = null }) {
|
| 58 |
+
const env = appEnvelope(name, source, derivedFrom);
|
| 59 |
+
return { id: "did:holo:blake3:" + blake3hex(enc(jcs(env))), bytes: jcs(env) };
|
| 60 |
+
}
|
| 61 |
+
const kappaOf = (bytesOrStr) => "did:holo:blake3:" + blake3hex(typeof bytesOrStr === "string" ? enc(bytesOrStr) : bytesOrStr);
|
| 62 |
+
|
| 63 |
+
// ── parse a GitHub reference — accepts a full URL, owner/repo, or a /tree/<ref> deep link. PURE. ────────
|
| 64 |
+
// The COMMIT is NOT resolved here (that is the fetcher's job — the pin happens at the ingest boundary):
|
| 65 |
+
// a bare branch ref is mutable, so `fetchRepo` MUST return the resolved commit SHA and we address THAT.
|
| 66 |
+
export function parseRepoRef(input) {
|
| 67 |
+
const s = String(input || "").trim();
|
| 68 |
+
let m = s.match(/^(?:https?:\/\/)?(?:www\.)?github\.com\/([^/\s]+)\/([^/\s#?]+)(?:\.git)?(?:\/tree\/([^/\s#?]+))?/i);
|
| 69 |
+
if (!m) m = s.match(/^(?:git@github\.com:)?([\w.-]+)\/([\w.-]+?)(?:\.git)?(?:@([\w./-]+))?$/);
|
| 70 |
+
if (!m) return null;
|
| 71 |
+
const owner = m[1], repo = m[2].replace(/\.git$/, ""), ref = m[3] || null;
|
| 72 |
+
if (!owner || !repo) return null;
|
| 73 |
+
return { owner, repo, ref, host: "github.com", url: `https://github.com/${owner}/${repo}` };
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
// ── classify the repo by CONTENT (package.json + the file listing), not guesswork. Returns the class, the
|
| 77 |
+
// build-locus PATH, an honest reason, and whether it runs in v1. This is the gate that makes the feature
|
| 78 |
+
// honest: every repo gets a truthful verdict, and the non-runnable ones say so. ──────────────────────
|
| 79 |
+
const BUNDLERS = ["vite", "webpack", "rollup", "parcel", "esbuild", "snowpack", "@vitejs/plugin-react"];
|
| 80 |
+
const SSR_FRAMEWORKS = ["next", "nuxt", "@remix-run/dev", "@remix-run/node", "astro", "@sveltejs/kit", "gatsby"];
|
| 81 |
+
const SERVER_DEPS = ["express", "fastify", "koa", "hapi", "@nestjs/core", "socket.io", "ws", "http-server-only"];
|
| 82 |
+
const DB_DEPS = ["pg", "mysql", "mysql2", "mongodb", "mongoose", "prisma", "@prisma/client", "sqlite3", "better-sqlite3", "redis", "ioredis", "typeorm", "sequelize"];
|
| 83 |
+
|
| 84 |
+
// HEADLINES — the one human line a person sees first (the machinery rides in `reason`, one tap deeper).
|
| 85 |
+
const HEADLINES = {
|
| 86 |
+
static: "This one runs here — making it mine.",
|
| 87 |
+
"client-bundle": "It needs building first — that path is coming.",
|
| 88 |
+
ssr: "It renders on a server — that path is coming.",
|
| 89 |
+
backend: "It needs a server. Hologram runs only what's truly mine.",
|
| 90 |
+
cli: "A command-line tool — there's no app to open.",
|
| 91 |
+
library: "A library, not an app — there's nothing to open.",
|
| 92 |
+
monorepo: "Many apps in here — point me at the one I want.",
|
| 93 |
+
unknown: "I couldn't find an app to open in here.",
|
| 94 |
+
};
|
| 95 |
+
|
| 96 |
+
export function classify(probe = {}) {
|
| 97 |
+
const files = (probe.files || []).map((f) => String(f).replace(/^\.?\//, ""));
|
| 98 |
+
const has = (re) => files.some((f) => re.test(f));
|
| 99 |
+
const pkg = probe.pkg || null;
|
| 100 |
+
const deps = pkg ? { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) } : {};
|
| 101 |
+
const depNames = Object.keys(deps);
|
| 102 |
+
const dep = (list) => depNames.some((d) => list.includes(d));
|
| 103 |
+
const scripts = (pkg && pkg.scripts) || {};
|
| 104 |
+
const scriptText = Object.values(scripts).join(" ; ");
|
| 105 |
+
const rootIndex = files.includes("index.html") || files.includes("public/index.html") || files.includes("docs/index.html") || files.includes("dist/index.html");
|
| 106 |
+
|
| 107 |
+
const verdict = (cls, path, runs, reason, coverage = null) => ({ class: cls, headline: HEADLINES[cls] || null, path, runs, reason, coverage, entry: probe.entry || (rootIndex ? files.find((f) => /(^|\/)index\.html$/.test(f)) : null) });
|
| 108 |
+
|
| 109 |
+
// 1) a non-JS / backend stack is out of scope v1 — a serverless device can't host a server or a DB.
|
| 110 |
+
const nonJsServer = has(/(^|\/)(requirements\.txt|Pipfile|pyproject\.toml|go\.mod|Cargo\.toml|Gemfile|composer\.json|pom\.xml)$/i);
|
| 111 |
+
if (dep(DB_DEPS)) return verdict("backend", "out-of-scope-v1", false, "depends on a database (" + depNames.filter((d) => DB_DEPS.includes(d)).join(", ") + ") — a serverless device has no server-side DB; translate persistence to the κ-store (ADR follow-on) or it cannot run faithfully.");
|
| 112 |
+
if (dep(SERVER_DEPS)) return verdict("backend", "out-of-scope-v1", false, "runs an application server (" + depNames.filter((d) => SERVER_DEPS.includes(d)).join(", ") + ") — there is no server to run it on; the serverless line ends here in v1.");
|
| 113 |
+
if (nonJsServer && !rootIndex) return verdict("backend", "out-of-scope-v1", false, "non-JS runtime (Python/Go/Rust/Ruby/PHP/Java) with no static entry — out of scope for the in-browser serverless target in v1.");
|
| 114 |
+
|
| 115 |
+
// 2) SSR frameworks render on a server by default — staged (a static-export path may exist, but not faked here).
|
| 116 |
+
if (dep(SSR_FRAMEWORKS)) return verdict("ssr", "translate-or-forge", false, "SSR framework (" + depNames.filter((d) => SSR_FRAMEWORKS.includes(d)).join(", ") + ") — renders server-side by default; a static-export/forge path is STAGED, not run in v1.");
|
| 117 |
+
|
| 118 |
+
// 3) a client bundle (a bundler + a browser app, no server) → FORGE: bundle-in-browser as a re-derivable
|
| 119 |
+
// κ-transform (ADR-0051). Classified + named here; the transform execution is the staged follow-on.
|
| 120 |
+
if (dep(BUNDLERS) || /\b(vite|webpack|rollup|parcel|esbuild)\b/.test(scriptText)) {
|
| 121 |
+
return verdict("client-bundle", "forge", false, "a client-side bundle (" + (depNames.filter((d) => BUNDLERS.includes(d))[0] || "bundler in scripts") + ") — runs serverless AFTER an in-browser bundle (Holo Forge κ-transform, ADR-0051); STAGED as the next class up.", "the largest runnable class beyond static — esbuild-wasm in-browser, cached as a κ");
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
// 4) static / client SPA with a real HTML entry and no build step → DIRECT, runs NOW.
|
| 125 |
+
if (rootIndex) return verdict("static", "direct", true, "a static site / client SPA with an HTML entry and no build step — inlined into one self-contained app κ and run directly.", "~static sites, hand-rolled SPAs, CDN-only apps");
|
| 126 |
+
|
| 127 |
+
// 5) a library or CLI with no browser entry — nothing to render serverlessly in v1.
|
| 128 |
+
if (pkg && (pkg.bin || (pkg.main && !rootIndex))) return verdict(pkg.bin ? "cli" : "library", "out-of-scope-v1", false, (pkg.bin ? "a CLI (bin entry)" : "a library (no app entry)") + " — no browser-renderable surface; nothing to run as a holospace in v1.");
|
| 129 |
+
|
| 130 |
+
// 6) unknown / monorepo — needs a target chosen before it can be classified.
|
| 131 |
+
if (pkg && (pkg.workspaces || has(/(^|\/)(pnpm-workspace\.yaml|lerna\.json|turbo\.json)$/))) return verdict("monorepo", "needs-target", false, "a monorepo (workspaces) — pick a single package/app to import; the repo root is not one app.");
|
| 132 |
+
return verdict("unknown", "out-of-scope-v1", false, "no HTML entry and no recognized app manifest — cannot classify a runnable serverless surface; surfaced rather than guessed.");
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
// ── encodeStatic — faithfully inline a static/SPA repo into ONE self-contained HTML. FIDELITY-FIRST:
|
| 136 |
+
// relative same-origin <link rel=stylesheet> and <script src> are inlined VERBATIM (byte-preserved);
|
| 137 |
+
// ABSOLUTE / cross-origin / API refs are NOT inlined — they are recorded in `externalRefs` and SURFACED
|
| 138 |
+
// (the "log what we don't run" rule: a backend call can't be served serverlessly, so we never pretend it
|
| 139 |
+
// is — we show the gap). Pure: `files` is a Map<path, {text}|{bytes→text}>; nothing is fetched here. ──
|
| 140 |
+
const isRelative = (u) => u && !/^(?:[a-z][\w+.-]*:)?\/\//i.test(u) && !/^data:/i.test(u) && !/^#/.test(u) && !/^mailto:/i.test(u);
|
| 141 |
+
const normPath = (base, u) => String(u).replace(/^\.?\//, "").split(/[?#]/)[0]; // drop ./ and query/hash
|
| 142 |
+
const dirOf = (p) => { const i = String(p).lastIndexOf("/"); return i < 0 ? "" : String(p).slice(0, i); };
|
| 143 |
+
// resolve a relative ref against the entry's directory — so a repo whose index.html lives in public/ or
|
| 144 |
+
// dist/ and references "style.css" looks up "public/style.css" (handles "./", "../", and root-"/" refs).
|
| 145 |
+
function resolveRel(baseDir, u) {
|
| 146 |
+
let s = String(u).split(/[?#]/)[0];
|
| 147 |
+
if (s.startsWith("/")) return s.replace(/^\/+/, ""); // root-relative → repo root
|
| 148 |
+
const parts = (baseDir ? baseDir.split("/") : []).concat(s.split("/"));
|
| 149 |
+
const out = []; for (const seg of parts) { if (seg === "" || seg === ".") continue; if (seg === "..") out.pop(); else out.push(seg); }
|
| 150 |
+
return out.join("/");
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
export function encodeStatic({ name = "app", entry = "index.html", files, hash, baseDir = null }) {
|
| 154 |
+
if (!files || typeof files.get !== "function") throw new Error("encodeStatic needs a files Map");
|
| 155 |
+
const entryFile = files.get(entry) || files.get(normPath("", entry));
|
| 156 |
+
if (!entryFile) throw new Error("encodeStatic: entry '" + entry + "' not in the repo files");
|
| 157 |
+
const base = baseDir != null ? baseDir : dirOf(entry); // refs resolve against the entry's dir
|
| 158 |
+
let html = entryFile.text != null ? entryFile.text : "";
|
| 159 |
+
const inlined = [], externalRefs = [], missing = [], assetKappas = {};
|
| 160 |
+
const lookup = (p) => files.get(p) || files.get(normPath("", p)) || null;
|
| 161 |
+
|
| 162 |
+
// <link rel="stylesheet" href="X"> (rel/href in either order) → <style>…</style>
|
| 163 |
+
html = html.replace(/<link\b[^>]*>/gi, (tag) => {
|
| 164 |
+
if (!/rel\s*=\s*["']?stylesheet/i.test(tag)) return tag;
|
| 165 |
+
const m = tag.match(/href\s*=\s*["']([^"']+)["']/i); if (!m) return tag;
|
| 166 |
+
const href = m[1];
|
| 167 |
+
if (!isRelative(href)) { externalRefs.push({ kind: "stylesheet", ref: href }); return tag; }
|
| 168 |
+
const p = resolveRel(base, href); const f = lookup(p);
|
| 169 |
+
if (!f) { missing.push({ kind: "stylesheet", ref: href }); return tag; }
|
| 170 |
+
const css = f.text != null ? f.text : "";
|
| 171 |
+
assetKappas[p] = kappaOf(css);
|
| 172 |
+
inlined.push({ kind: "stylesheet", ref: href });
|
| 173 |
+
return '<style data-holo-inlined="' + href + '">\n' + css + '\n</style>';
|
| 174 |
+
});
|
| 175 |
+
|
| 176 |
+
// <script src="X" …></script> → <script …>…</script> (preserve type=module + other attrs, drop src)
|
| 177 |
+
html = html.replace(/<script\b([^>]*)\bsrc\s*=\s*["']([^"']+)["']([^>]*)>\s*<\/script>/gi, (tag, pre, src, post) => {
|
| 178 |
+
if (!isRelative(src)) { externalRefs.push({ kind: "script", ref: src }); return tag; }
|
| 179 |
+
const p = resolveRel(base, src); const f = lookup(p);
|
| 180 |
+
if (!f) { missing.push({ kind: "script", ref: src }); return tag; }
|
| 181 |
+
const js = f.text != null ? f.text : "";
|
| 182 |
+
assetKappas[p] = kappaOf(js);
|
| 183 |
+
inlined.push({ kind: "script", ref: src });
|
| 184 |
+
const attrs = (pre + " " + post).replace(/\s+/g, " ").trim();
|
| 185 |
+
return "<script" + (attrs ? " " + attrs : "") + ' data-holo-inlined="' + src + '">\n' + js + "\n</script>";
|
| 186 |
+
});
|
| 187 |
+
|
| 188 |
+
// surface remaining same-origin asset refs (img/href/fetch targets) WITHOUT inlining them — they become
|
| 189 |
+
// κ-streamed objects later (Holo Cosmos LOD), but we record them so coverage is honest, never silent.
|
| 190 |
+
const assetRefs = [];
|
| 191 |
+
for (const m of html.matchAll(/(?:src|href)\s*=\s*["']([^"']+)["']/gi)) {
|
| 192 |
+
const u = m[1];
|
| 193 |
+
if (isRelative(u) && !/\.(css|js|mjs)(\?|#|$)/i.test(u) && lookup(resolveRel(base, u))) assetRefs.push(u);
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
// Mobile + PWA at birth: ensure the imported app carries viewport-fit, the mobile layer, the container
|
| 197 |
+
// surface marker, and PWA-standalone metas — only adds what the repo lacked (idempotent → κ stays deterministic).
|
| 198 |
+
html = ensureMobileHead(html);
|
| 199 |
+
return { name, html, inlined, externalRefs, missing, assetRefs, assetKappas,
|
| 200 |
+
selfContained: externalRefs.length === 0 && missing.length === 0 };
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
// ── the import receipt — a PROV-O activity linking the GitHub source (repo@commit) → the app κ, so a remote
|
| 204 |
+
// import carries honest lineage FOREVER and slots into Holo Prov walkLineage (ADR-0082). Mirrors the
|
| 205 |
+
// forgeReceipt shape; the SOURCE is the pinned commit (git+url@sha), the GENERATED is the sealed app. ──
|
| 206 |
+
export function importReceipt({ repoUrl, commit, classification, sourceKappa, appKappa, name = "app", externalRefs = [] }) {
|
| 207 |
+
return {
|
| 208 |
+
"@context": ["https://www.w3.org/ns/did/v1",
|
| 209 |
+
{ schema: "https://schema.org/", prov: "http://www.w3.org/ns/prov#", hosc: "https://hologram.os/ns/conformance#" }],
|
| 210 |
+
"@type": ["prov:Activity", "hosc:Import", "schema:CreateAction"],
|
| 211 |
+
"schema:name": "Holo Import — " + name,
|
| 212 |
+
"hosc:tool": { "schema:name": "holo-import", "schema:softwareVersion": VERSION },
|
| 213 |
+
"hosc:class": classification && classification.class,
|
| 214 |
+
"hosc:buildLocus": classification && classification.path,
|
| 215 |
+
"prov:used": { "@id": "git+" + repoUrl + (commit ? "@" + commit : ""), "@type": ["prov:Entity", "schema:SoftwareSourceCode"],
|
| 216 |
+
"schema:codeRepository": repoUrl, "schema:version": commit || null },
|
| 217 |
+
"prov:generated": { "@id": appKappa, "@type": ["prov:Entity", "schema:WebApplication"] },
|
| 218 |
+
...(sourceKappa ? { "hosc:sourceManifest": sourceKappa } : {}),
|
| 219 |
+
// honest: anything we could NOT serve serverlessly is named in the receipt, not hidden.
|
| 220 |
+
...(externalRefs.length ? { "hosc:unservedExternalRefs": externalRefs.map((r) => r.ref) } : {}),
|
| 221 |
+
};
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
// ── makeGovernedFetch — the network as a GOVERNED INGEST BOUNDARY (Law L4). Every outbound fetch the
|
| 225 |
+
// import does (the GitHub repo bytes, a CDN dependency, the build tool) passes the conscience BEFORE it
|
| 226 |
+
// happens, and every accepted fetch mints a re-derivable PROV-O ingest receipt (who · url · host ·
|
| 227 |
+
// purpose · contentκ). FAIL-CLOSED: with a conscience present, a red-line verdict (P5 PII / P6 / P7,
|
| 228 |
+
// ADR-0033) REFUSES the fetch; with NO conscience and no explicit host opt-in, an ingest is denied by
|
| 229 |
+
// default (the same shape as Q.agent). `conscience` may be an object ({evaluate?,evaluateText?}) or a
|
| 230 |
+
// thunk (resolved per call → load-order safe); `fetch` + `hash` injected → Node-witnessable. ──
|
| 231 |
+
export function makeGovernedFetch({ conscience = null, fetch: rawFetch, allowUngoverned = false, hash = null, caller = "holo-import", now = () => 0 } = {}) {
|
| 232 |
+
if (typeof rawFetch !== "function") throw new Error("makeGovernedFetch needs an injected fetch");
|
| 233 |
+
const gate = () => (typeof conscience === "function" ? conscience() : conscience);
|
| 234 |
+
return async function governedFetch(url, opts = {}) {
|
| 235 |
+
const u = String(url); const host = (u.match(/^[a-z]+:\/\/([^/]+)/i) || [])[1] || "";
|
| 236 |
+
const C = gate();
|
| 237 |
+
if (C && (typeof C.evaluate === "function" || typeof C.evaluateText === "function")) {
|
| 238 |
+
const decision = { verb: "import.ingest", caller, host, purpose: opts.purpose || "import" };
|
| 239 |
+
let verdict;
|
| 240 |
+
try { verdict = typeof C.evaluateText === "function" ? C.evaluateText(u, { decision }) : C.evaluate(decision); }
|
| 241 |
+
catch (e) { return { ok: false, refused: true, reason: "gate error: " + ((e && e.message) || e), host }; }
|
| 242 |
+
if (verdict && verdict.outcome === "block") return { ok: false, refused: true, reason: verdict.reason || (verdict.blocked || []).join(",") || "refused by conscience", host, blocked: verdict.blocked };
|
| 243 |
+
} else if (!allowUngoverned) {
|
| 244 |
+
return { ok: false, refused: true, reason: "no conscience gate — ingest is fail-closed (Law L4)", host };
|
| 245 |
+
}
|
| 246 |
+
const response = await rawFetch(u, opts.init || undefined);
|
| 247 |
+
let receipt = null;
|
| 248 |
+
if (typeof hash === "function") {
|
| 249 |
+
let contentKappa = null;
|
| 250 |
+
if (opts.body != null) contentKappa = "did:holo:blake3:" + blake3hex(enc(typeof opts.body === "string" ? opts.body : jcs(opts.body)));
|
| 251 |
+
receipt = { "@context": { prov: "http://www.w3.org/ns/prov#", hosc: "https://hologram.os/ns/conformance#" },
|
| 252 |
+
"@type": ["prov:Activity", "hosc:Ingest"], "hosc:caller": caller, "prov:used": u, "hosc:host": host,
|
| 253 |
+
"hosc:purpose": opts.purpose || "import", "hosc:at": now(), ...(contentKappa ? { "prov:generated": contentKappa } : {}) };
|
| 254 |
+
}
|
| 255 |
+
return { ok: true, response, receipt, host };
|
| 256 |
+
};
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
// ── importRepo — the SPINE. URL → (governed) fetch → classify → encode → seal → receipt. The ONE async
|
| 260 |
+
// entry the studio/Q call. `fetchRepo({owner,repo,ref}) → { commit, files: Map, entry? }` is INJECTED
|
| 261 |
+
// (the ingest boundary; browser = a holo-gov-brokered fetch to raw.github / a κ-mirror / IPFS, witness =
|
| 262 |
+
// a mock). `hash(str)->hex` is injected. For a non-static class it returns a runnable:false verdict with
|
| 263 |
+
// the honest reason — never a fake app. ───────────────────────────────────────────────────────────────
|
| 264 |
+
export async function importRepo({ input, fetchRepo, hash, bundle = null }) {
|
| 265 |
+
const ref = parseRepoRef(input);
|
| 266 |
+
if (!ref) return { ok: false, reason: "not a GitHub repo reference: " + input };
|
| 267 |
+
if (typeof fetchRepo !== "function") throw new Error("importRepo needs an injected fetchRepo (Law L4: the network is a boundary)");
|
| 268 |
+
if (typeof hash !== "function") throw new Error("importRepo needs an injected hash");
|
| 269 |
+
|
| 270 |
+
const fetched = await fetchRepo({ owner: ref.owner, repo: ref.repo, ref: ref.ref });
|
| 271 |
+
const files = fetched.files, commit = fetched.commit || null;
|
| 272 |
+
const fileList = [...files.keys()];
|
| 273 |
+
let pkg = null; const pf = files.get("package.json"); if (pf && pf.text) { try { pkg = JSON.parse(pf.text); } catch { /* malformed → treated as no manifest */ } }
|
| 274 |
+
|
| 275 |
+
const classification = classify({ files: fileList, pkg, entry: fetched.entry });
|
| 276 |
+
const base = { ok: true, repo: ref, commit, classification };
|
| 277 |
+
const sourceManifestKappa = kappaOf(jcs({ commit, files: fileList.sort() }), hash); // a manifest of WHAT was imported
|
| 278 |
+
|
| 279 |
+
// FORGE class — a client-bundle repo runs ONLY if a bundler (esbuild) is injected (holo-forge-bundle).
|
| 280 |
+
// No bundler → the honest staged notice (runnable:false). The bundle is the re-derivable κ-transform.
|
| 281 |
+
if (classification.path === "forge") {
|
| 282 |
+
if (typeof bundle !== "function") return { ...base, runnable: false, reason: classification.reason };
|
| 283 |
+
let built; try { built = await bundle({ files, name: ref.repo, sourceKappa: sourceManifestKappa }); }
|
| 284 |
+
catch (e) { return { ...base, runnable: false, reason: "forge bundle failed: " + ((e && e.message) || e), buildError: String((e && e.message) || e) }; }
|
| 285 |
+
const app = sealApp({ name: ref.repo, source: built.html, hash });
|
| 286 |
+
const receipt = importReceipt({ repoUrl: ref.url, commit, classification, sourceKappa: sourceManifestKappa,
|
| 287 |
+
appKappa: app.id, name: ref.repo, externalRefs: (built.unresolved || []).map((s) => ({ kind: "dependency", ref: s })) });
|
| 288 |
+
return { ...base, runnable: true, app, html: built.html, receipt, built: { entry: built.entry, receipt: built.receipt },
|
| 289 |
+
externalRefs: (built.unresolved || []).map((s) => ({ kind: "dependency", ref: s })), selfContained: !!built.selfContained };
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
if (!classification.runs) {
|
| 293 |
+
// honest stop: classified, reason surfaced, NOTHING faked.
|
| 294 |
+
return { ...base, runnable: false, reason: classification.reason };
|
| 295 |
+
}
|
| 296 |
+
|
| 297 |
+
// the runnable (static/SPA) path — faithful encode → seal exactly like publishSource → mint the receipt.
|
| 298 |
+
const enc = encodeStatic({ name: ref.repo, entry: classification.entry || fetched.entry || "index.html", files, hash });
|
| 299 |
+
const app = sealApp({ name: ref.repo, source: enc.html, hash });
|
| 300 |
+
const receipt = importReceipt({ repoUrl: ref.url, commit, classification, sourceKappa: sourceManifestKappa,
|
| 301 |
+
appKappa: app.id, name: ref.repo, externalRefs: enc.externalRefs });
|
| 302 |
+
|
| 303 |
+
return { ...base, runnable: true, app, html: enc.html, encode: enc, receipt,
|
| 304 |
+
externalRefs: enc.externalRefs, selfContained: enc.selfContained };
|
| 305 |
+
}
|
| 306 |
+
|
| 307 |
+
// ── bindImporter — wire import onto the Holo Q spine as a DETERMINISTIC specialist (task "import"), so
|
| 308 |
+
// `Q.create(repoUrl, { task: "import" })` rides the SAME κ-memo path as every other build: a repeat is
|
| 309 |
+
// O(1) (cached "hot") and the encoder/fetcher is NOT re-run (the ingest happens once — Law L3). The
|
| 310 |
+
// studio adopts the yielded HTML through its existing publishSource flow, so the studio app κ === the
|
| 311 |
+
// import app κ. Returns the importer for direct calls too. `mux.bindSpecialist(task, {id, generate})`. ──
|
| 312 |
+
export function bindImporter(mux, { fetchRepo, hash, onResult = null, bundle = null } = {}) {
|
| 313 |
+
const id = "holo-import";
|
| 314 |
+
const generate = async function* (prompt) {
|
| 315 |
+
const r = await importRepo({ input: prompt, fetchRepo, hash, bundle }); // bundle (esbuild) enables the FORGE class
|
| 316 |
+
if (onResult) { try { onResult(r); } catch { /* observer must not break the build */ } }
|
| 317 |
+
if (!r.ok) { yield { replace: noticeCard("I couldn't read that repo.", r.reason) }; return; }
|
| 318 |
+
if (!r.runnable) { yield { replace: noticeCard((r.classification && r.classification.headline) || "Imported — but it can't run here yet.", r.classification.class + " · " + r.reason) }; return; }
|
| 319 |
+
yield { replace: r.html };
|
| 320 |
+
};
|
| 321 |
+
mux.bindSpecialist("import", { id, generate });
|
| 322 |
+
return { importRepo: (input) => importRepo({ input, fetchRepo, hash, bundle }) };
|
| 323 |
+
}
|
| 324 |
+
const notice = (msg) => '<!doctype html><meta charset="utf-8"><body style="margin:0;font:15px/1.5 system-ui;background:#0b0d12;color:#e7e9ee;display:grid;place-items:center;height:100vh"><div style="max-width:42ch;padding:2rem;white-space:pre-wrap;opacity:.92">' + msg.replace(/[<>&]/g, (c) => ({ "<": "<", ">": ">", "&": "&" }[c])) + "</div></body>";
|
| 325 |
+
|
| 326 |
+
// noticeCard — two-tier disclosure: the human headline leads; the technical detail rides behind "Why".
|
| 327 |
+
const escNotice = (s) => String(s).replace(/[<>&]/g, (c) => ({ "<": "<", ">": ">", "&": "&" }[c]));
|
| 328 |
+
const noticeCard = (headline, detail) => '<!doctype html><meta charset="utf-8"><body style="margin:0;font:15px/1.6 system-ui;background:#0b0d12;color:#e7e9ee;display:grid;place-items:center;height:100vh"><div style="max-width:44ch;padding:2rem;text-align:center"><div style="font-size:1.18rem;font-weight:600;line-height:1.4">' + escNotice(headline) + '</div>' + (detail ? '<details style="margin-top:1.15rem;font-size:.85rem"><summary style="cursor:pointer;opacity:.6;list-style:none">Why</summary><div style="margin-top:.7rem;opacity:.72;white-space:pre-wrap;line-height:1.55;text-align:left">' + escNotice(detail) + '</div></details>' : '') + '</div></body>';
|
| 329 |
+
|
| 330 |
+
export function describeImport() {
|
| 331 |
+
return {
|
| 332 |
+
is: "paste a GitHub URL → a running, content-addressed Holo app; import is a DETERMINISTIC encoder onto the existing publishSource/Q spine, not a new runtime",
|
| 333 |
+
fidelity: "static/SPA repos are inlined VERBATIM into one app κ (byte-preserved, no model); same repo@commit → same κ (Law L5)",
|
| 334 |
+
honest: "only the static/SPA class runs in v1; bundle→Forge, SSR/backend/DB/CLI are classified + their reason SURFACED, never faked; unserved external refs are named in the receipt",
|
| 335 |
+
governance: "the fetch is an injected ingest boundary (holo-gov-brokered, Law L4); imported code is untrusted and stays sandboxed + conscience-gated (ADR-0033)",
|
| 336 |
+
};
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
export default { VERSION, parseRepoRef, classify, encodeStatic, appEnvelope, sealApp, importReceipt, importRepo, bindImporter, makeGovernedFetch, describeImport, jcs };
|
b/8d0e7f2b52c1ca5e44a55b25f70f7dfaaf6d1da04a859cec7593143ffb678efb
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// holo-mobile-defaults.mjs — the ONE pure, isomorphic helper that guarantees an app's HTML carries the
|
| 2 |
+
// mobile + PWA-standalone essentials, however it was produced (imported from GitHub, Forge-bundled, …).
|
| 3 |
+
//
|
| 4 |
+
// Pure string transforms (Node + browser run identical logic — the witnesses drive it headless), and
|
| 5 |
+
// IDEMPOTENT: it only ADDS what is missing, never rewrites authored markup, so re-running is a no-op and
|
| 6 |
+
// the app's κ is deterministic (same input → same output, Law L5). The standard it ensures is the vendored
|
| 7 |
+
// holo-mobile.css (WCAG 2.2 §1.4.10 Reflow / §2.5.8 Target Size + MD3 48dp) + the container-query
|
| 8 |
+
// window-size classes + PWA installability (display:standalone via the metas below). The build path
|
| 9 |
+
// (scaffold) links these directly; the share paths (import · Forge) route their HTML through here, so
|
| 10 |
+
// every app is mobile-correct AND installable at birth, with zero per-app effort.
|
| 11 |
+
|
| 12 |
+
const MOBILE_CSS = "/_shared/holo-mobile.css";
|
| 13 |
+
|
| 14 |
+
export function ensureMobileHead(html, opts = {}) {
|
| 15 |
+
if (typeof html !== "string" || !html) return html;
|
| 16 |
+
let out = html;
|
| 17 |
+
const cssHref = opts.cssHref || MOBILE_CSS;
|
| 18 |
+
|
| 19 |
+
// 1) the <html> element carries data-holo-surface → the `holoapp` container, so the app reflows to its
|
| 20 |
+
// WINDOW width (Compact on a phone OR in a small desktop window), not the device viewport.
|
| 21 |
+
if (/<html\b/i.test(out)) {
|
| 22 |
+
out = out.replace(/<html\b([^>]*)>/i, (m, attrs) =>
|
| 23 |
+
/data-holo-surface/i.test(attrs) ? m : "<html" + attrs + " data-holo-surface>");
|
| 24 |
+
} else {
|
| 25 |
+
out = "<html data-holo-surface>" + out;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
// ensure a <head> to inject into (degenerate fragments get one)
|
| 29 |
+
if (!/<head\b/i.test(out)) {
|
| 30 |
+
out = /<html\b[^>]*>/i.test(out)
|
| 31 |
+
? out.replace(/(<html\b[^>]*>)/i, "$1<head></head>")
|
| 32 |
+
: "<head></head>" + out;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
// 2) viewport-fit=cover (honor the notch / home-indicator, incl. installed standalone)
|
| 36 |
+
const vp = out.match(/<meta\s+name=["']?viewport[^>]*>/i);
|
| 37 |
+
if (!vp) {
|
| 38 |
+
// appended below with the rest
|
| 39 |
+
} else if (!/viewport-fit/i.test(vp[0])) {
|
| 40 |
+
out = out.replace(/(<meta\s+name=["']?viewport[^>]*content=["'])([^"']*)(["'])/i,
|
| 41 |
+
(m, a, c, z) => a + c + (c ? ", " : "") + "viewport-fit=cover" + z);
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
// 3) collect the still-missing head pieces and inject them once
|
| 45 |
+
const add = [];
|
| 46 |
+
if (!vp) add.push('<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />');
|
| 47 |
+
if (!/holo-mobile\.css/i.test(out)) add.push('<link rel="stylesheet" href="' + cssHref + '" data-holo-injected="mobile" />');
|
| 48 |
+
if (!/name=["']?theme-color/i.test(out)) add.push('<meta name="theme-color" content="#0d0d0f" />');
|
| 49 |
+
if (!/apple-mobile-web-app-capable/i.test(out))
|
| 50 |
+
add.push('<meta name="apple-mobile-web-app-capable" content="yes" /><meta name="mobile-web-app-capable" content="yes" /><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />');
|
| 51 |
+
|
| 52 |
+
if (add.length) out = out.replace(/<head\b([^>]*)>/i, (m) => m + add.join(""));
|
| 53 |
+
return out;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
export default ensureMobileHead;
|
b/b55f686e982d5a4ce34e2194790338a73e75d57669bc27a6d88adc46856864e3
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// holo-providers.mjs — the Provider Registry (HOLO-ANIMATE-PLATFORM, M2). Nixpacks-shaped, but grounded on the
|
| 2 |
+
// Realizer Ladder: each provider knows a language/framework and emits the LIGHTEST tier that runs it +
|
| 3 |
+
// a deterministic recipe (build/install/start/port/output). `detectProvider(files,pkg)` walks providers
|
| 4 |
+
// most-specific → least and returns the first match — so "how to run this repo" is one decision, extensible
|
| 5 |
+
// by adding a provider (not editing a giant if-tree). The tier feeds holo-animate's ladder; the recipe feeds
|
| 6 |
+
// holo-provision. Pure + isomorphic (Node + browser identical). This is what makes "any repo just works".
|
| 7 |
+
//
|
| 8 |
+
// Tiers: R0 inline (static, instant) · R1 build-then-inline (client SPA/SSG, in-tab) · R3 vm (server/native).
|
| 9 |
+
// (R2 wasm-runtime is reserved; providers may target it later for pure-compute runtimes.)
|
| 10 |
+
|
| 11 |
+
export const VERSION = "holo-providers/0.2.0";
|
| 12 |
+
|
| 13 |
+
// ── ctx helpers over a files Map<path,{text}|Buffer|string> + parsed package.json ─────────────────────────
|
| 14 |
+
function makeCtx(files, pkg) {
|
| 15 |
+
const paths = [...files.keys()].map((p) => p.replace(/^\.?\//, ""));
|
| 16 |
+
const set = new Set(paths);
|
| 17 |
+
// truly isomorphic: `Buffer` is a Node global — guard it (a bare `Buffer.isBuffer` is a ReferenceError in
|
| 18 |
+
// a browser/service-worker, which broke the claimed Node+browser parity); bytes decode via TextDecoder.
|
| 19 |
+
const isBuf = (f) => typeof Buffer !== "undefined" && Buffer.isBuffer(f);
|
| 20 |
+
const dec = (b) => { try { return new TextDecoder().decode(b); } catch { return null; } };
|
| 21 |
+
const text = (p) => { const f = files.get(p) || files.get(p.replace(/^/, "")); if (f == null) return null; if (typeof f === "string") return f; if (isBuf(f)) return f.toString("utf8"); if (f instanceof Uint8Array) return dec(f); return f.text != null ? f.text : (f.bytes ? dec(f.bytes) : null); };
|
| 22 |
+
const has = (re) => paths.some((p) => re.test(p));
|
| 23 |
+
const deps = pkg ? { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) } : {};
|
| 24 |
+
const depNames = Object.keys(deps);
|
| 25 |
+
const dep = (list) => depNames.some((d) => list.includes(d));
|
| 26 |
+
const scripts = (pkg && pkg.scripts) || {};
|
| 27 |
+
const scriptText = Object.values(scripts).join(" ; ");
|
| 28 |
+
const rootHtml = ["index.html", "public/index.html", "dist/index.html", "docs/index.html", "build/index.html", "_site/index.html"].find((p) => set.has(p)) || null;
|
| 29 |
+
return { paths, set, has, text, pkg, deps, depNames, dep, scripts, scriptText, rootHtml };
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
const BUNDLERS = ["vite", "webpack", "rollup", "parcel", "esbuild", "snowpack", "@vitejs/plugin-react", "react-scripts", "@angular/cli", "@builder.io/qwik"];
|
| 33 |
+
const CLIENT_FW = ["react", "react-dom", "vue", "preact", "svelte", "solid-js", "@angular/core", "lit", "alpinejs"];
|
| 34 |
+
const SSR_FW = { next: "next", nuxt: "nuxt", "@remix-run/dev": "remix", astro: "astro", "@sveltejs/kit": "sveltekit", gatsby: "gatsby" };
|
| 35 |
+
const SERVER = ["express", "fastify", "koa", "hapi", "@nestjs/core", "socket.io", "ws", "@hapi/hapi", "polka", "restify"];
|
| 36 |
+
|
| 37 |
+
// each provider: { name, tier, detect(ctx)→bool, recipe(ctx)→{...} }. Order = priority (first match wins).
|
| 38 |
+
// Order note: CLIENT app providers (next/ssr/node-spa/static) are evaluated BEFORE the container providers
|
| 39 |
+
// (docker-compose/dockerfile) on purpose. A great many client games ship a Dockerfile/compose.yml for CI or
|
| 40 |
+
// self-hosting even though the app itself is a pure in-browser bundle — letting Docker win there needlessly
|
| 41 |
+
// forces a whole VM (R3) for something that runs as R1/R0 in a tab. So we detect the runnable client surface
|
| 42 |
+
// first and only fall to a container when there is NO lighter way to run it (genuine polyglot/server apps).
|
| 43 |
+
export const PROVIDERS = [
|
| 44 |
+
{ name: "next", tier: "R3", detect: (c) => c.dep(["next"]),
|
| 45 |
+
recipe: (c) => { const exp = /output\s*:\s*["']export["']/.test(c.text("next.config.js") || c.text("next.config.mjs") || "") || /next export/.test(c.scriptText);
|
| 46 |
+
return exp ? { tier: "R1", install: ["npm ci"], build: "npm run build", output: "out", start: null, port: 0 } : { install: ["npm ci"], build: "npm run build", start: "npm start", port: 3000 }; } },
|
| 47 |
+
|
| 48 |
+
{ name: "ssr-framework", tier: "R3", detect: (c) => Object.keys(SSR_FW).some((d) => c.dep([d])),
|
| 49 |
+
recipe: (c) => { const fw = Object.entries(SSR_FW).find(([d]) => c.dep([d]))[1];
|
| 50 |
+
const staticBuild = fw === "astro" || fw === "gatsby"; // these have a first-class static build
|
| 51 |
+
return staticBuild ? { tier: "R1", install: ["npm ci"], build: "npm run build", output: fw === "gatsby" ? "public" : "dist", start: null, port: 0 } : { install: ["npm ci"], build: "npm run build", start: "npm start", port: 3000, framework: fw }; } },
|
| 52 |
+
|
| 53 |
+
{ name: "node-spa", tier: "R1", detect: (c) => c.pkg && (c.dep(BUNDLERS) || c.dep(CLIENT_FW) || /\b(vite|webpack|rollup|parcel)\b/.test(c.scriptText) || c.has(/^src\/(main|index|App)\.[jt]sx?$/)),
|
| 54 |
+
recipe: (c) => ({ install: [c.set.has("package-lock.json") ? "npm ci" : "npm install"], build: "esbuild", output: "dist", start: null, port: 0, bundler: (BUNDLERS.find((b) => c.dep([b])) || "esbuild") }) },
|
| 55 |
+
|
| 56 |
+
// container providers sit BELOW the client providers (node-spa/static — see order note) so a client game that
|
| 57 |
+
// merely ships a Dockerfile/compose for CI still animates as R1/R0, but ABOVE node-server + the language
|
| 58 |
+
// providers so a genuine (possibly multi-service) server app runs via its own compose/Dockerfile recipe.
|
| 59 |
+
{ name: "docker-compose", tier: "R3", detect: (c) => c.has(/^(docker-)?compose\.ya?ml$/),
|
| 60 |
+
recipe: () => ({ base: "docker-in-guest", needsDocker: true, install: [], build: null, start: "docker compose up --build", port: 0 }) },
|
| 61 |
+
|
| 62 |
+
{ name: "dockerfile", tier: "R3", detect: (c) => c.set.has("Dockerfile"),
|
| 63 |
+
recipe: (c) => { const t = c.text("Dockerfile") || ""; const port = (t.match(/EXPOSE\s+(\d+)/i) || t.match(/ENV\s+PORT[=\s]+(\d+)/i) || t.match(/-p\s+(\d{2,5})\b/) || t.match(/PORT[=:\s]+(\d{2,5})\b/) || [])[1] * 1 || 8080;
|
| 64 |
+
return { base: "docker-in-guest", needsDocker: true, install: [], build: "docker build -t holo/app .", start: "docker run holo/app", port }; } },
|
| 65 |
+
|
| 66 |
+
{ name: "node-server", tier: "R3", detect: (c) => c.dep(SERVER) && !c.rootHtml,
|
| 67 |
+
recipe: (c) => ({ install: [c.set.has("package-lock.json") ? "npm ci" : "npm install"], build: c.scripts.build ? "npm run build" : null, start: c.scripts.start ? "npm start" : ("node " + ((c.pkg && c.pkg.main) || (c.set.has("server.js") ? "server.js" : c.set.has("app.js") ? "app.js" : "index.js"))), port: 3000 }) },
|
| 68 |
+
|
| 69 |
+
{ name: "jekyll", tier: "R1", detect: (c) => c.set.has("_config.yml") || c.set.has("_config.yaml"),
|
| 70 |
+
recipe: () => ({ base: "ruby", install: ["bundle install"], build: "bundle exec jekyll build", output: "_site", start: null, port: 0 }) },
|
| 71 |
+
|
| 72 |
+
{ name: "hugo", tier: "R1", detect: (c) => c.set.has("config.toml") || c.set.has("hugo.toml") || c.has(/^(archetypes|content)\//),
|
| 73 |
+
recipe: () => ({ base: "hugo", install: [], build: "hugo --minify", output: "public", start: null, port: 0 }) },
|
| 74 |
+
|
| 75 |
+
// require a real Python app marker (deps manifest or a root app entry) — NOT merely "some .py exists", which
|
| 76 |
+
// false-matched JS/native repos that ship Python build/tooling scripts (e.g. a game engine's helper scripts).
|
| 77 |
+
{ name: "python", tier: "R3", detect: (c) => c.has(/^(requirements\.txt|pyproject\.toml|Pipfile|manage\.py|setup\.py)$/) || ["app.py", "main.py", "wsgi.py", "server.py", "application.py"].some((f) => c.set.has(f)),
|
| 78 |
+
recipe: (c) => { const pip = "python3 -m pip install --break-system-packages"; // Alpine PEP668 + python3 (no `python`/`pip` on PATH)
|
| 79 |
+
const reqs = c.text("requirements.txt") || ""; const mod = (["app.py", "main.py", "wsgi.py", "server.py", "application.py"].find((f) => c.set.has(f)) || "app.py").replace(/\.py$/, "");
|
| 80 |
+
// the Procfile `web:` line is the app author's authoritative start (as every PaaS honors) — force the bind.
|
| 81 |
+
const proc = (c.text("Procfile") || "").match(/^web:\s*(.+)$/m)?.[1]?.trim();
|
| 82 |
+
const forceBind = (cmd) => /gunicorn/.test(cmd) ? cmd + " --bind 0.0.0.0:8000" : /uvicorn/.test(cmd) ? cmd + " --host 0.0.0.0 --port 8000" : cmd;
|
| 83 |
+
// force 0.0.0.0:8000 (12-factor: apps must bind the forwarded port) — the universal deploy constraint.
|
| 84 |
+
const start = proc ? forceBind(proc)
|
| 85 |
+
: c.set.has("manage.py") ? "python3 manage.py runserver 0.0.0.0:8000"
|
| 86 |
+
: /uvicorn|fastapi/i.test(reqs) ? "python3 -m uvicorn " + mod + ":app --host 0.0.0.0 --port 8000"
|
| 87 |
+
: /flask/i.test(reqs) ? "FLASK_APP=" + mod + " python3 -m flask run --host 0.0.0.0 --port 8000"
|
| 88 |
+
: /gunicorn/i.test(reqs) ? "gunicorn " + mod + ":app --bind 0.0.0.0:8000"
|
| 89 |
+
: "python3 " + mod + ".py";
|
| 90 |
+
return { base: "python", install: c.set.has("requirements.txt") ? [pip + " -r requirements.txt"] : c.set.has("pyproject.toml") ? [pip + " ."] : [pip + " pipenv && pipenv install"], build: null, start, port: 8000 }; } },
|
| 91 |
+
|
| 92 |
+
{ name: "deno", tier: "R3", detect: (c) => c.set.has("deno.json") || c.set.has("deno.jsonc") || c.has(/^deps\.ts$/),
|
| 93 |
+
recipe: (c) => ({ base: "deno", install: [], build: null, start: "deno run -A " + (c.set.has("main.ts") ? "main.ts" : c.set.has("server.ts") ? "server.ts" : "mod.ts"), port: 8000 }) },
|
| 94 |
+
|
| 95 |
+
{ name: "bun", tier: "R3", detect: (c) => c.set.has("bun.lockb") || c.set.has("bunfig.toml"),
|
| 96 |
+
recipe: (c) => ({ base: "bun", install: ["bun install"], build: c.scripts.build ? "bun run build" : null, start: c.scripts.start ? "bun run start" : "bun " + ((c.pkg && c.pkg.main) || "index.ts"), port: 3000 }) },
|
| 97 |
+
|
| 98 |
+
{ name: "elixir-phoenix", tier: "R3", detect: (c) => c.set.has("mix.exs"),
|
| 99 |
+
recipe: () => ({ base: "elixir", install: ["mix deps.get"], build: "mix compile", start: "mix phx.server", port: 4000 }) },
|
| 100 |
+
|
| 101 |
+
{ name: "dotnet", tier: "R3", detect: (c) => c.has(/\.csproj$/) || c.has(/\.sln$/),
|
| 102 |
+
recipe: () => ({ base: "dotnet", install: ["dotnet restore"], build: "dotnet build -c Release", start: "dotnet run -c Release", port: 5000 }) },
|
| 103 |
+
|
| 104 |
+
{ name: "go", tier: "R3", detect: (c) => c.set.has("go.mod"),
|
| 105 |
+
recipe: () => ({ base: "go", install: ["go mod download"], build: "go build -o /app/server .", start: "/app/server", port: 8080 }) },
|
| 106 |
+
|
| 107 |
+
{ name: "rust", tier: "R3", detect: (c) => c.set.has("Cargo.toml"),
|
| 108 |
+
recipe: () => ({ base: "rust", install: [], build: "cargo build --release", start: "cargo run --release", port: 8080 }) },
|
| 109 |
+
|
| 110 |
+
{ name: "ruby", tier: "R3", detect: (c) => c.set.has("Gemfile"),
|
| 111 |
+
recipe: (c) => ({ base: "ruby", install: ["bundle install"], build: null, start: /rails/.test(c.text("Gemfile") || "") ? "bundle exec rails server -b 0.0.0.0 -p 3000" : "bundle exec ruby app.rb", port: 3000 }) },
|
| 112 |
+
|
| 113 |
+
{ name: "php", tier: "R3", detect: (c) => c.set.has("composer.json") || c.set.has("index.php"),
|
| 114 |
+
recipe: (c) => ({ base: "php", install: c.set.has("composer.json") ? ["composer install"] : [], build: null, start: "php -S 0.0.0.0:8000 -t .", port: 8000 }) },
|
| 115 |
+
|
| 116 |
+
{ name: "java", tier: "R3", detect: (c) => c.set.has("pom.xml") || c.set.has("build.gradle") || c.set.has("build.gradle.kts"),
|
| 117 |
+
recipe: (c) => ({ base: "java", install: [], build: c.set.has("pom.xml") ? "mvn -q package -DskipTests" : "gradle build -x test", start: "java -jar $(ls target/*.jar build/libs/*.jar 2>/dev/null | head -1)", port: 8080 }) },
|
| 118 |
+
|
| 119 |
+
{ name: "static", tier: "R0", detect: (c) => !!c.rootHtml && !(c.pkg && (c.dep(BUNDLERS) || c.has(/^src\/(main|index|App)\.[jt]sx?$/))),
|
| 120 |
+
recipe: (c) => ({ install: [], build: null, start: null, entry: c.rootHtml, port: 0 }) },
|
| 121 |
+
|
| 122 |
+
{ name: "library", tier: "none", detect: (c) => c.pkg && (c.pkg.bin || (c.pkg.main && !c.rootHtml)) && !c.dep(CLIENT_FW),
|
| 123 |
+
recipe: () => ({ reason: "a library/CLI — no browser-renderable surface to run" }) },
|
| 124 |
+
];
|
| 125 |
+
|
| 126 |
+
// detectProvider(files, pkg?) → { provider, tier, recipe, why }. pkg is parsed if not passed.
|
| 127 |
+
export function detectProvider(files, pkg = undefined) {
|
| 128 |
+
if (pkg === undefined) { try { const pf = files.get("package.json"); const t = pf && (typeof pf === "string" ? pf : pf.text); pkg = t ? JSON.parse(t) : null; } catch { pkg = null; } }
|
| 129 |
+
const c = makeCtx(files, pkg);
|
| 130 |
+
for (const p of PROVIDERS) {
|
| 131 |
+
let ok = false; try { ok = !!p.detect(c); } catch { ok = false; }
|
| 132 |
+
if (ok) { const r = p.recipe(c) || {}; const tier = r.tier || p.tier; return { provider: p.name, tier, recipe: { kind: p.name, ...r, tier }, why: describe(p.name, tier) }; }
|
| 133 |
+
}
|
| 134 |
+
return { provider: "unknown", tier: "none", recipe: { kind: "unknown", reason: "no recognized language/framework or app entry" }, why: "unclassifiable — surfaced, never guessed" };
|
| 135 |
+
}
|
| 136 |
+
function describe(name, tier) {
|
| 137 |
+
const t = { R0: "static — inline, instant", R1: "client app — compile in-tab", R3: "server/native — run in a VM", none: "not a runnable app" }[tier];
|
| 138 |
+
return name + " → " + t;
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
// coverage() → the provider list (for the store to show "we understand N stacks") ─────────────────────────
|
| 142 |
+
export const coverage = () => PROVIDERS.map((p) => ({ name: p.name, tier: p.tier }));
|
| 143 |
+
|
| 144 |
+
export function describeProviders() {
|
| 145 |
+
return { is: "the Provider Registry — Nixpacks-shaped detection grounded on the Realizer Ladder; each provider emits the lightest tier + a deterministic recipe",
|
| 146 |
+
extensible: "add a language/framework by adding one provider entry; detection is ordered most-specific → least, first match wins",
|
| 147 |
+
tiers: "R0 static/instant · R1 client compile in-tab · R3 server/native in a VM (R2 wasm reserved)" };
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
export default { VERSION, PROVIDERS, detectProvider, coverage, describeProviders };
|
b/bd107202590889391a58c57799da577a930f2a0f2c748480ca3cda91a04eddc6
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
|
| 6 |
+
<title>Hologram Apps</title>
|
| 7 |
+
<style>
|
| 8 |
+
:root{color-scheme:dark;--bg:#0a0c11;--panel:#12151d;--line:#1e2430;--ink:#eef1f6;--dim:#98a2b3;--dim2:#6b7488;--acc:#5b8cff;--ok:#3ddc97;--warn:#ffbf6b}
|
| 9 |
+
*{box-sizing:border-box}html,body{margin:0;height:100%}
|
| 10 |
+
body{font:15px/1.55 -apple-system,system-ui,"Segoe UI",sans-serif;background:var(--bg);color:var(--ink);min-height:100vh}
|
| 11 |
+
header{position:sticky;top:0;z-index:5;backdrop-filter:blur(14px);background:rgba(10,12,17,.82);border-bottom:1px solid var(--line)}
|
| 12 |
+
.hd{max-width:1160px;margin:0 auto;padding:14px 22px;display:flex;gap:16px;align-items:center}
|
| 13 |
+
.logo{font-weight:750;font-size:16px;white-space:nowrap;letter-spacing:.2px}.logo b{color:var(--acc)}
|
| 14 |
+
.sb{flex:1;position:relative;max-width:560px}
|
| 15 |
+
.sb svg{position:absolute;left:13px;top:50%;transform:translateY(-50%);opacity:.5}
|
| 16 |
+
#q{width:100%;background:var(--panel);border:1px solid var(--line);color:var(--ink);border-radius:11px;padding:10px 14px 10px 38px;font:15px system-ui}
|
| 17 |
+
#q:focus{outline:none;border-color:var(--acc);box-shadow:0 0 0 3px rgba(91,140,255,.15)}
|
| 18 |
+
.nav{max-width:1160px;margin:0 auto;padding:0 22px 12px;display:flex;gap:8px;overflow-x:auto;scrollbar-width:none}.nav::-webkit-scrollbar{display:none}
|
| 19 |
+
.chip{white-space:nowrap;padding:7px 14px;border-radius:999px;border:1px solid var(--line);background:var(--panel);color:var(--dim);cursor:pointer;font-size:13px;font-weight:550;transition:.12s}
|
| 20 |
+
.chip:hover{color:var(--ink);border-color:#33405c}.chip.on{background:var(--acc);border-color:var(--acc);color:#fff}
|
| 21 |
+
.wrap{max-width:1160px;margin:0 auto;padding:22px 22px 90px}
|
| 22 |
+
.lead{font-size:22px;font-weight:700;letter-spacing:-.01em;margin:6px 0 3px}
|
| 23 |
+
.sub{color:var(--dim);margin:0 0 22px;font-size:14.5px}
|
| 24 |
+
.sect{margin:22px 2px 12px;font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:var(--dim2);font-weight:650}
|
| 25 |
+
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(300px,100%),1fr));gap:14px}
|
| 26 |
+
.nm,.own,.desc{overflow-wrap:anywhere}
|
| 27 |
+
.card{background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:16px 17px;cursor:pointer;transition:transform .13s,border-color .13s,box-shadow .13s;display:flex;flex-direction:column;min-height:150px}
|
| 28 |
+
.card:hover{transform:translateY(-3px);border-color:#3a465f;box-shadow:0 14px 34px -16px rgba(0,0,0,.75)}
|
| 29 |
+
.top{display:flex;align-items:center;gap:11px}
|
| 30 |
+
.ic{width:38px;height:38px;border-radius:10px;object-fit:cover;background:#222a38;flex:none}
|
| 31 |
+
.nm{font-weight:680;font-size:15.5px;line-height:1.2}
|
| 32 |
+
.own{color:var(--dim);font-size:12.5px;margin-top:1px}
|
| 33 |
+
.run{margin-left:auto;flex:none;font-size:11.5px;font-weight:650;padding:4px 9px;border-radius:999px;display:flex;align-items:center;gap:4px}
|
| 34 |
+
.run.fast{background:rgba(61,220,151,.12);color:var(--ok)}.run.vm{background:rgba(255,191,107,.12);color:var(--warn)}
|
| 35 |
+
.desc{color:#c3cad6;font-size:13.5px;margin:11px 0 0;flex:1;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
|
| 36 |
+
.foot{display:flex;align-items:center;gap:13px;margin-top:13px;font-size:12.5px;color:var(--dim)}
|
| 37 |
+
.lang{display:flex;align-items:center;gap:6px}.dot{width:9px;height:9px;border-radius:50%;background:#6ea8fe;flex:none}
|
| 38 |
+
.foot .st{display:flex;align-items:center;gap:4px}.foot .play{color:var(--ok)}
|
| 39 |
+
.row-h{display:flex;align-items:baseline;justify-content:space-between;margin:26px 2px 4px}
|
| 40 |
+
.seeall{color:var(--acc);font-size:13px;font-weight:600;cursor:pointer;white-space:nowrap}.seeall:hover{text-decoration:underline}
|
| 41 |
+
.rowblurb{color:var(--dim2);font-size:13px;margin:0 2px 12px}
|
| 42 |
+
.empty{color:var(--dim);padding:48px 0;text-align:center}.empty b{color:var(--ink)}
|
| 43 |
+
.skl{height:150px;border-radius:16px;background:linear-gradient(100deg,#12151d 30%,#171b26 50%,#12151d 70%);background-size:200% 100%;animation:sh 1.2s infinite}
|
| 44 |
+
@keyframes sh{to{background-position:-200% 0}}
|
| 45 |
+
/* run overlay — full-bleed immersive projection; chrome floats + auto-hides */
|
| 46 |
+
#ov{position:fixed;inset:0;z-index:20;background:#000;display:none}
|
| 47 |
+
#ov.on{display:block}
|
| 48 |
+
#ovframe{position:absolute;inset:0;border:0;width:100%;height:100%;background:#000;display:block;touch-action:manipulation}
|
| 49 |
+
#ovveil{position:absolute;inset:0;z-index:2;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;background:#05070c;transition:opacity .3s}
|
| 50 |
+
#ovveil.gone{opacity:0;pointer-events:none}
|
| 51 |
+
.spin{width:34px;height:34px;border-radius:50%;border:3px solid #1e2430;border-top-color:var(--acc);animation:rot .8s linear infinite}
|
| 52 |
+
@keyframes rot{to{transform:rotate(1turn)}}
|
| 53 |
+
#ovmsg{color:var(--dim);font-size:13.5px;max-width:520px;text-align:center;padding:0 20px}
|
| 54 |
+
#ovbar{position:absolute;top:0;left:0;right:0;z-index:3;display:flex;gap:10px;align-items:center;padding:11px 16px;
|
| 55 |
+
font-size:13px;color:#eef1f6;background:linear-gradient(180deg,rgba(6,8,12,.9) 0%,rgba(6,8,12,.5) 60%,rgba(6,8,12,0) 100%);
|
| 56 |
+
opacity:0;transform:translateY(-6px);transition:opacity .22s ease,transform .22s ease;pointer-events:none;user-select:none}
|
| 57 |
+
#ov.chrome #ovbar{opacity:1;transform:none;pointer-events:auto}
|
| 58 |
+
#ovbar .t{font-weight:700}#ovbar .s{color:#9aa4b6;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
| 59 |
+
#ovbar .s .ok{color:var(--ok)}#ovbar .s .err{color:#ff8f8f}
|
| 60 |
+
#ovbar button{background:rgba(22,28,42,.72);border:1px solid rgba(255,255,255,.16);color:#eef1f6;border-radius:10px;padding:8px 13px;cursor:pointer;font-size:13px;font-weight:600;backdrop-filter:blur(8px);transition:.12s;white-space:nowrap}
|
| 61 |
+
#ovbar button:hover{border-color:#5b8cff;background:rgba(34,44,68,.85)}
|
| 62 |
+
#ovmode{display:inline-flex;border:1px solid rgba(255,255,255,.16);border-radius:10px;overflow:hidden;background:rgba(22,28,42,.72);backdrop-filter:blur(8px)}
|
| 63 |
+
#ovmode[hidden]{display:none}
|
| 64 |
+
#ovmode button{border:0;border-radius:0;padding:8px 11px;background:transparent}
|
| 65 |
+
#ovmode button.on{background:rgba(91,140,255,.35)}
|
| 66 |
+
</style>
|
| 67 |
+
</head>
|
| 68 |
+
<body>
|
| 69 |
+
<header>
|
| 70 |
+
<div class="hd">
|
| 71 |
+
<div class="logo">⬡ Hologram <b>Apps</b></div>
|
| 72 |
+
<div class="sb"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#98a2b3" stroke-width="2.4"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
|
| 73 |
+
<input id="q" placeholder="Search apps on GitHub, or paste a repo link" autocomplete="off"></div>
|
| 74 |
+
</div>
|
| 75 |
+
<nav class="nav" id="chips"></nav>
|
| 76 |
+
</header>
|
| 77 |
+
<div class="wrap" id="main"></div>
|
| 78 |
+
|
| 79 |
+
<div id="ov">
|
| 80 |
+
<iframe id="ovframe" allow="autoplay; fullscreen; gamepad; accelerometer; gyroscope; magnetometer; xr-spatial-tracking" sandbox="allow-scripts allow-same-origin allow-pointer-lock allow-orientation-lock allow-forms allow-modals allow-popups"></iframe>
|
| 81 |
+
<div id="ovveil"><div class="spin"></div><div id="ovs" style="font-weight:650">Opening…</div><div id="ovmsg"></div></div>
|
| 82 |
+
<div id="ovbar">
|
| 83 |
+
<img id="ovic" width="22" height="22" style="border-radius:6px" alt="">
|
| 84 |
+
<span class="t" id="ovt"></span><span class="s" id="ovstat"></span>
|
| 85 |
+
<span id="ovmode" hidden><button data-m="fit" class="on">Fit</button><button data-m="fill">Fill</button><button data-m="actual">1:1</button></span>
|
| 86 |
+
<button id="fs">⛶ Fullscreen</button><button id="x">✕ Close</button>
|
| 87 |
+
</div>
|
| 88 |
+
</div>
|
| 89 |
+
|
| 90 |
+
<script type="module">
|
| 91 |
+
import { animate, probeLadder, _opfsTamper, _opfsWipe, registryIndex } from "./animate-client.mjs";
|
| 92 |
+
window.__opfsTamper = _opfsTamper; window.__opfsWipe = _opfsWipe; window.__registryIndex = registryIndex;
|
| 93 |
+
|
| 94 |
+
// ── service worker: the app server. Wait for control (claim → controllerchange on first load). ────────────
|
| 95 |
+
async function swReady() {
|
| 96 |
+
await navigator.serviceWorker.register("./sw.js", { type: "module" });
|
| 97 |
+
await navigator.serviceWorker.ready;
|
| 98 |
+
if (!navigator.serviceWorker.controller) await new Promise((res) => navigator.serviceWorker.addEventListener("controllerchange", res, { once: true }));
|
| 99 |
+
}
|
| 100 |
+
const ready = swReady().then(() => { window.__swReady = true; }).catch((e) => { document.getElementById("main").innerHTML = '<div class="empty">Service worker failed: ' + e.message + "</div>"; throw e; });
|
| 101 |
+
|
| 102 |
+
// ── catalog: STATIC file shipped beside the page — browsing costs ZERO GitHub API calls. ──────────────────
|
| 103 |
+
let CATALOG = { featured: [], categories: [], apps: {} };
|
| 104 |
+
const catalogReady = fetch("catalog.json").then((r) => r.ok ? r.json() : CATALOG).then((c) => { CATALOG = c; }).catch(() => {});
|
| 105 |
+
|
| 106 |
+
// language → how-it-runs pill (same heuristic as the daemon store: client langs run instantly in the tab;
|
| 107 |
+
// anything else needs its own machine — labeled honestly, wired in a later milestone)
|
| 108 |
+
const CLIENT_LANGS = new Set(["JavaScript", "TypeScript", "HTML", "CSS", "Vue", "Svelte", "Astro", "CoffeeScript"]);
|
| 109 |
+
const runPill = (a) => a.registry || CLIENT_LANGS.has(a.language) ? '<span class="run fast">⚡ Instant</span>' : '<span class="run vm">🖥️ Needs a machine</span>';
|
| 110 |
+
const fmtK = (n) => n >= 1000 ? (n / 1000).toFixed(n >= 10000 ? 0 : 1) + "k" : String(n || 0);
|
| 111 |
+
const esc = (s) => String(s || "").replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c]));
|
| 112 |
+
const avatar = (a) => a.avatar || ("https://github.com/" + (a.owner || a.full_name.split("/")[0]) + ".png?size=76");
|
| 113 |
+
|
| 114 |
+
function card(a) {
|
| 115 |
+
const el = document.createElement("div"); el.className = "card";
|
| 116 |
+
el.innerHTML = '<div class="top"><img class="ic" loading="lazy" src="' + avatar(a) + '" onerror="this.style.visibility=\'hidden\'">'
|
| 117 |
+
+ '<div><div class="nm">' + esc(a.name || a.full_name.split("/")[1]) + '</div><div class="own">' + esc(a.owner || a.full_name.split("/")[0]) + "</div></div>" + runPill(a) + "</div>"
|
| 118 |
+
+ '<p class="desc">' + esc(a.description || "") + "</p>"
|
| 119 |
+
+ '<div class="foot"><span class="lang"><span class="dot"></span>' + esc(a.language || "app") + "</span>"
|
| 120 |
+
+ (a.stars ? '<span class="st">★ ' + fmtK(a.stars) + "</span>" : "")
|
| 121 |
+
+ (a.verifiedPlayable ? '<span class="play">✓ Plays</span>' : "") + "</div>";
|
| 122 |
+
el.onclick = () => openApp(a.full_name, a);
|
| 123 |
+
return el;
|
| 124 |
+
}
|
| 125 |
+
function section(title, blurb, apps, catId) {
|
| 126 |
+
const frag = document.createDocumentFragment();
|
| 127 |
+
const h = document.createElement("div"); h.className = "row-h";
|
| 128 |
+
h.innerHTML = '<div class="sect">' + title + "</div>" + (catId ? '<span class="seeall" data-cat="' + catId + '">See all →</span>' : "");
|
| 129 |
+
frag.appendChild(h);
|
| 130 |
+
if (blurb) { const b = document.createElement("p"); b.className = "rowblurb"; b.textContent = blurb; frag.appendChild(b); }
|
| 131 |
+
const g = document.createElement("div"); g.className = "grid";
|
| 132 |
+
for (const a of apps) g.appendChild(card(a));
|
| 133 |
+
frag.appendChild(g);
|
| 134 |
+
return frag;
|
| 135 |
+
}
|
| 136 |
+
function renderBrowse(catId) {
|
| 137 |
+
const main = document.getElementById("main"); main.innerHTML = "";
|
| 138 |
+
document.querySelectorAll(".chip").forEach((c) => c.classList.toggle("on", c.dataset.cat === (catId || "featured")));
|
| 139 |
+
if (!catId || catId === "featured") {
|
| 140 |
+
const lead = document.createElement("div");
|
| 141 |
+
lead.innerHTML = '<div class="lead">Open any app on GitHub. No install, no account.</div>'
|
| 142 |
+
+ '<p class="sub">Pick one — it streams into this tab as a verified κ, built once for the whole planet. Apps you\'ve opened work offline.</p>';
|
| 143 |
+
main.appendChild(lead);
|
| 144 |
+
main.appendChild(section("Featured", null, CATALOG.featured || []));
|
| 145 |
+
for (const c of CATALOG.categories || []) { const apps = (CATALOG.apps[c.id] || []).slice(0, 6); if (apps.length) main.appendChild(section(c.label, c.blurb, apps, c.id)); }
|
| 146 |
+
} else {
|
| 147 |
+
const c = (CATALOG.categories || []).find((x) => x.id === catId);
|
| 148 |
+
main.appendChild(section(c ? c.label : catId, c && c.blurb, CATALOG.apps[catId] || []));
|
| 149 |
+
}
|
| 150 |
+
}
|
| 151 |
+
function renderChips() {
|
| 152 |
+
const nav = document.getElementById("chips"); nav.innerHTML = "";
|
| 153 |
+
for (const c of [{ id: "featured", label: "Featured" }, ...(CATALOG.categories || [])]) {
|
| 154 |
+
const b = document.createElement("button"); b.className = "chip" + (c.id === "featured" ? " on" : ""); b.dataset.cat = c.id; b.textContent = c.label.replace(/^\S+\s/, (m) => m);
|
| 155 |
+
b.onclick = () => renderBrowse(c.id); nav.appendChild(b);
|
| 156 |
+
}
|
| 157 |
+
}
|
| 158 |
+
document.addEventListener("click", (e) => { const s = e.target.closest(".seeall"); if (s) renderBrowse(s.dataset.cat); });
|
| 159 |
+
|
| 160 |
+
// ── search: live GitHub search (CORS-open) — rate limit surfaced honestly; a pasted repo link runs directly.
|
| 161 |
+
let _t;
|
| 162 |
+
document.getElementById("q").addEventListener("input", (e) => {
|
| 163 |
+
clearTimeout(_t); const q = e.target.value.trim();
|
| 164 |
+
if (!q) return renderBrowse("featured");
|
| 165 |
+
if (/github\.com\/[^/]+\/[^/]+/.test(q) || /^[\w.-]+\/[\w.-]+$/.test(q)) return; // a repo ref — Enter runs it
|
| 166 |
+
_t = setTimeout(async () => {
|
| 167 |
+
const main = document.getElementById("main"); main.innerHTML = '<div class="grid">' + '<div class="skl"></div>'.repeat(6) + "</div>";
|
| 168 |
+
try {
|
| 169 |
+
const r = await fetch("https://api.github.com/search/repositories?per_page=24&sort=stars&order=desc&q=" + encodeURIComponent(q + " stars:>30"));
|
| 170 |
+
if (r.status === 403) { main.innerHTML = '<div class="empty"><b>GitHub search is catching its breath</b><br>Unauthenticated search allows 10 queries/minute — try again shortly, or paste a repo link directly.</div>'; return; }
|
| 171 |
+
const j = await r.json();
|
| 172 |
+
const apps = (j.items || []).map((x) => ({ full_name: x.full_name, name: x.name, owner: x.owner && x.owner.login, description: x.description, stars: x.stargazers_count, language: x.language, avatar: x.owner && x.owner.avatar_url }));
|
| 173 |
+
main.innerHTML = "";
|
| 174 |
+
main.appendChild(section('Results for "' + esc(q) + '"', null, apps));
|
| 175 |
+
if (!apps.length) main.innerHTML = '<div class="empty">Nothing found for <b>' + esc(q) + "</b></div>";
|
| 176 |
+
} catch { main.innerHTML = '<div class="empty">Search needs the network — browsing and apps you\'ve opened still work.</div>'; }
|
| 177 |
+
}, 350);
|
| 178 |
+
});
|
| 179 |
+
document.getElementById("q").addEventListener("keydown", (e) => {
|
| 180 |
+
if (e.key !== "Enter") return; const q = e.target.value.trim();
|
| 181 |
+
if (/github\.com\/[^/]+\/[^/]+/.test(q) || /^[\w.-]+\/[\w.-]+$/.test(q)) openApp(q);
|
| 182 |
+
});
|
| 183 |
+
|
| 184 |
+
// ── open an app: overlay + veil + animate() → the SW serves it from /run. Witness hook = window.__animate. ─
|
| 185 |
+
const ov = document.getElementById("ov"), ovframe = document.getElementById("ovframe"), ovveil = document.getElementById("ovveil");
|
| 186 |
+
const ovs = document.getElementById("ovs"), ovmsg = document.getElementById("ovmsg"), ovstat = document.getElementById("ovstat");
|
| 187 |
+
let lastStatus = "";
|
| 188 |
+
async function runApp(input, meta, opts) {
|
| 189 |
+
await ready;
|
| 190 |
+
ov.classList.add("on"); document.documentElement.style.overflow = "hidden";
|
| 191 |
+
ovveil.classList.remove("gone"); ovs.textContent = "Opening " + (meta && meta.name || input) + "…"; ovmsg.textContent = "";
|
| 192 |
+
document.getElementById("ovt").textContent = (meta && meta.name) || String(input).split("/").pop();
|
| 193 |
+
document.getElementById("ovic").src = meta ? avatar(meta) : "";
|
| 194 |
+
const t0 = performance.now();
|
| 195 |
+
const r = await animate(input, (m) => { lastStatus = m; ovmsg.textContent = m; }, opts);
|
| 196 |
+
ovframe.onload = () => { ovveil.classList.add("gone"); try { ovframe.contentWindow.focus(); } catch {} showChrome(4200); };
|
| 197 |
+
ovframe.src = new URL("run/" + r.id + "/", location.href).pathname; // scope-relative (Pages subpath safe)
|
| 198 |
+
const how = r.meta.source === "live" ? "🔨 built from source" : "⚡ streamed (" + r.meta.source + ")";
|
| 199 |
+
ovstat.innerHTML = '<span class="ok">' + how + "</span> · verified ✓ · " + ((performance.now() - t0) / 1000).toFixed(1) + "s";
|
| 200 |
+
return r;
|
| 201 |
+
}
|
| 202 |
+
window.__animate = (input, opts) => runApp(input, null, opts).catch((e) => { ovs.textContent = "Couldn't open this one"; ovmsg.textContent = e.message; throw e; });
|
| 203 |
+
window.__probe = async (slug, ref) => { await ready; return probeLadder(slug, ref); };
|
| 204 |
+
function openApp(input, meta) { runApp(input, meta).catch((e) => { ovs.textContent = "Couldn't open this one"; ovmsg.textContent = e.message; }); }
|
| 205 |
+
|
| 206 |
+
function closeOv() { try { if (document.fullscreenElement) document.exitFullscreen(); } catch {} ov.classList.remove("on", "chrome"); document.documentElement.style.overflow = ""; ovframe.removeAttribute("src"); }
|
| 207 |
+
document.getElementById("x").onclick = closeOv;
|
| 208 |
+
// floating chrome + fullscreen + Fit/Fill/1:1 (announced by the app's injected fit engine via postMessage)
|
| 209 |
+
const ovmode = document.getElementById("ovmode"); let curMode = "fit", _chromeT;
|
| 210 |
+
const sendMode = () => { try { ovframe.contentWindow.postMessage({ holoMode: curMode }, "*"); } catch {} };
|
| 211 |
+
ovmode.querySelectorAll("button").forEach((btn) => { btn.onclick = (e) => { e.stopPropagation(); curMode = btn.dataset.m; ovmode.querySelectorAll("button").forEach((b) => b.classList.toggle("on", b === btn)); sendMode(); showChrome(); }; });
|
| 212 |
+
addEventListener("message", (e) => { if (ovframe && e.source === ovframe.contentWindow && e.data && e.data.holoFit) { ovmode.hidden = !e.data.holoFit.canvasApp; if (!ovmode.hidden) sendMode(); } });
|
| 213 |
+
function showChrome(ms) { ov.classList.add("chrome"); clearTimeout(_chromeT); _chromeT = setTimeout(() => ov.classList.remove("chrome"), ms || 2600); }
|
| 214 |
+
ov.addEventListener("mousemove", () => showChrome()); ov.addEventListener("touchstart", () => showChrome(), { passive: true });
|
| 215 |
+
const fsBtn = document.getElementById("fs");
|
| 216 |
+
fsBtn.onclick = (e) => { e.stopPropagation(); try { document.fullscreenElement ? document.exitFullscreen() : ov.requestFullscreen(); } catch {} setTimeout(() => { try { ovframe.contentWindow.focus(); } catch {} }, 60); };
|
| 217 |
+
document.addEventListener("fullscreenchange", () => { fsBtn.textContent = document.fullscreenElement ? "⤢ Exit full" : "⛶ Fullscreen"; showChrome(); });
|
| 218 |
+
addEventListener("keydown", (e) => { if (!ov.classList.contains("on")) return; if (e.key === "Escape" && !document.fullscreenElement) closeOv(); else if ((e.key === "f" || e.key === "F") && document.activeElement !== document.getElementById("q")) fsBtn.onclick(e); });
|
| 219 |
+
|
| 220 |
+
// boot
|
| 221 |
+
Promise.all([catalogReady]).then(() => { renderChips(); renderBrowse("featured");
|
| 222 |
+
// deep-link: ?run=<owner/repo> auto-opens that app (the unified Holo Hub portal streams repos this way).
|
| 223 |
+
const run = new URLSearchParams(location.search).get("run");
|
| 224 |
+
if (run) openApp(run);
|
| 225 |
+
});
|
| 226 |
+
</script>
|
| 227 |
+
</body>
|
| 228 |
+
</html>
|
b/c23e5859d8ebacbe948e9084f45938ff25f459bb9da5b137c6525bc1271fba24
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// holo-l5.mjs — THE verify-by-re-derivation primitive (Law L5), in one place. "Does this byte blob re-derive to
|
| 2 |
+
// this κ?" is the whole trust model of κ-Portal (source is irrelevant; the math admits). It was copied into the
|
| 3 |
+
// closure completeness check (holo-dotholo.verifyClosure) AND the delivery Service Worker (portal-sw) — now both
|
| 4 |
+
// call HERE. (The single-file portal inlines an equivalent check on purpose: that file must be dependency-free.)
|
| 5 |
+
// Pure + isomorphic; blake3 (the σ-axis) is wired by default, inject others to admit more axes. ────────────────
|
| 6 |
+
import { blake3hex } from "./holo-blake3.mjs";
|
| 7 |
+
|
| 8 |
+
const enc = (s) => new TextEncoder().encode(s);
|
| 9 |
+
const kappaOf = (v) => (typeof v === "string" ? v : v && v.kappa);
|
| 10 |
+
// parse did:holo:<algo>:<hex>, or a bare 64-hex (assumed blake3 — the canonical σ-axis)
|
| 11 |
+
const algoHexOf = (k) => { const s = String(k || ""); const m = /^did:holo:([a-z0-9]+):([0-9a-f]+)$/.exec(s); if (m) return { algo: m[1], hex: m[2] }; return /^[0-9a-f]{64}$/i.test(s) ? { algo: "blake3", hex: s.toLowerCase() } : null; };
|
| 12 |
+
|
| 13 |
+
// reDerives(bytes, kappa, {digests}) → bool. The atomic L5 check: re-hash the bytes on the κ's axis and compare.
|
| 14 |
+
// A κ whose axis has no digest → false (fail-closed; an unverifiable byte is never "trusted").
|
| 15 |
+
export async function reDerives(bytes, kappa, { digests = { blake3: blake3hex } } = {}) {
|
| 16 |
+
const ah = algoHexOf(kappa); if (!ah) return false;
|
| 17 |
+
const d = digests[ah.algo]; if (!d || bytes == null) return false;
|
| 18 |
+
return (await d(typeof bytes === "string" ? enc(bytes) : bytes)) === ah.hex;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
// verifyMembers(members, resolve, {digests}) → the closure COMPLETENESS gate (fail-closed): re-derive EVERY member
|
| 22 |
+
// or refuse. members: path → κ (or {kappa}). resolve(path) → bytes|null (content-blind). Returns
|
| 23 |
+
// { ok, files, verified, missing, tampered, unverifiable }. (Exposed as holo-dotholo.verifyClosure.)
|
| 24 |
+
export async function verifyMembers(members, resolve, { digests = { blake3: blake3hex } } = {}) {
|
| 25 |
+
const missing = [], tampered = [], unverifiable = []; let verified = 0;
|
| 26 |
+
const paths = Object.keys(members);
|
| 27 |
+
for (const p of paths) {
|
| 28 |
+
const ah = algoHexOf(kappaOf(members[p]));
|
| 29 |
+
if (!ah || !digests[ah.algo]) { unverifiable.push(p); continue; }
|
| 30 |
+
let bytes; try { bytes = await resolve(p); } catch { bytes = null; }
|
| 31 |
+
if (bytes == null) { missing.push(p); continue; }
|
| 32 |
+
const hex = await digests[ah.algo](typeof bytes === "string" ? enc(bytes) : bytes);
|
| 33 |
+
if (hex !== ah.hex) { tampered.push(p); continue; }
|
| 34 |
+
verified++;
|
| 35 |
+
}
|
| 36 |
+
return { ok: missing.length === 0 && tampered.length === 0 && unverifiable.length === 0, files: paths.length, verified, missing, tampered, unverifiable };
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
export default { reDerives, verifyMembers };
|
b/c96aa7b602dd1542e195bc8d57aad812b68d99afda9d51a659d76d64a2421632
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// holo-forge-esbuild.mjs — the BROWSER binding that wires the R1 rung (HOLO-ANIMATE-GITHUB, P1). The
|
| 2 |
+
// deterministic bundler (holo-forge-bundle.mjs) keeps esbuild INJECTED; in Node the witness injects the
|
| 3 |
+
// native `esbuild`, and in a CEF holospace tab THIS module injects the vendored `esbuild-wasm`. Same API
|
| 4 |
+
// surface, same κ-transform, no divergence.
|
| 5 |
+
//
|
| 6 |
+
// L5 ON THE TOOL ITSELF: the build re-derives only if the BUILDER is fixed too. So the vendored toolchain
|
| 7 |
+
// is content-addressed (esbuild-wasm.pin.json), and this module REFUSES to initialize esbuild-wasm unless
|
| 8 |
+
// its bytes hash to the pinned κ — a swapped/tampered compiler can never silently change a bundle κ. This
|
| 9 |
+
// is the recipe side of "content-address BOTH the recipe and the result": κ(sources)⊕κ(esbuild)⊕κ(flags).
|
| 10 |
+
//
|
| 11 |
+
// PURE + injected: the script-load and the wasm-fetch are the shell's ingest boundary (passed in); the
|
| 12 |
+
// verify + initialize core is isomorphic and witnessable. No Node-only imports at module top.
|
| 13 |
+
|
| 14 |
+
import { sha256hex } from "./holo-uor.mjs"; // the sha256 axis the vendored pin is written in (bridge alias)
|
| 15 |
+
|
| 16 |
+
// the pinned toolchain (mirrors vendor/esbuild-wasm/esbuild-wasm.pin.json — kept here so verify needs no fetch)
|
| 17 |
+
export const ESBUILD_PIN = {
|
| 18 |
+
tool: "esbuild-wasm",
|
| 19 |
+
version: "0.21.5",
|
| 20 |
+
files: {
|
| 21 |
+
"browser.min.js": "did:holo:sha256:6525467654c98b3cc9edcda44dd2fd6859bb9e00cd771cce85a431c5d2057ef3",
|
| 22 |
+
"esbuild.wasm": "did:holo:sha256:711d62484385c29d64ddcdc4c0beb3fb7903635a41bafffeb652938ee8480587",
|
| 23 |
+
},
|
| 24 |
+
sizes: { "browser.min.js": 50593, "esbuild.wasm": 11655844 },
|
| 25 |
+
};
|
| 26 |
+
|
| 27 |
+
const kOf = (bytes) => "did:holo:sha256:" + sha256hex(bytes);
|
| 28 |
+
|
| 29 |
+
// verifyToolchain(bytesByName, { sha256? }) → { ok, checked, mismatches } — assert each vendored file hashes
|
| 30 |
+
// to its pinned κ. FAIL-CLOSED: any mismatch (or a missing file) → ok:false, and the loader must refuse.
|
| 31 |
+
// `sha256` may be injected (WebCrypto in the browser); defaults to the pure sha256hex (Node + witness).
|
| 32 |
+
export function verifyToolchain(bytesByName, { sha256 = null } = {}) {
|
| 33 |
+
const hash = typeof sha256 === "function" ? sha256 : (b) => "did:holo:sha256:" + sha256hex(b);
|
| 34 |
+
const checked = [], mismatches = [];
|
| 35 |
+
for (const [name, want] of Object.entries(ESBUILD_PIN.files)) {
|
| 36 |
+
const bytes = bytesByName && bytesByName[name];
|
| 37 |
+
if (bytes == null) { mismatches.push({ name, reason: "missing" }); continue; }
|
| 38 |
+
const got = hash(bytes);
|
| 39 |
+
checked.push(name);
|
| 40 |
+
if (got !== want) mismatches.push({ name, want, got });
|
| 41 |
+
}
|
| 42 |
+
return { ok: mismatches.length === 0 && checked.length === Object.keys(ESBUILD_PIN.files).length, checked, mismatches };
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
// loadBrowserEsbuild({ toolchainBytes, loadScript, initialize, sha256? }) → the esbuild API, or throws.
|
| 46 |
+
// toolchainBytes: { "browser.min.js": Uint8Array, "esbuild.wasm": Uint8Array } — fetched by the shell (ingest boundary)
|
| 47 |
+
// loadScript(jsBytes) → the esbuild-wasm module/global (the shell decides how: <script> inject or import)
|
| 48 |
+
// initialize(api, wasmBytes) → await api.initialize({ wasmModule|wasmURL }) — the shell owns the WASM handoff
|
| 49 |
+
// The GATE: verifyToolchain must pass before we touch the compiler. Then it's the same `esbuild` object the
|
| 50 |
+
// witness feeds makeBundler — R1 realizes identically in the tab and in Node.
|
| 51 |
+
export async function loadBrowserEsbuild({ toolchainBytes, loadScript, initialize, sha256 = null } = {}) {
|
| 52 |
+
const v = verifyToolchain(toolchainBytes, { sha256 });
|
| 53 |
+
if (!v.ok) throw new Error("esbuild-wasm toolchain failed κ-verify (L5) — refusing to build: " + JSON.stringify(v.mismatches));
|
| 54 |
+
if (typeof loadScript !== "function") throw new Error("loadBrowserEsbuild needs an injected loadScript (the shell's script boundary)");
|
| 55 |
+
const api = await loadScript(toolchainBytes["browser.min.js"]);
|
| 56 |
+
if (!api || typeof api.build !== "function") throw new Error("esbuild-wasm did not expose a build() API");
|
| 57 |
+
if (typeof initialize === "function") await initialize(api, toolchainBytes["esbuild.wasm"]);
|
| 58 |
+
return api;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
export function describeEsbuildBinding() {
|
| 62 |
+
return {
|
| 63 |
+
is: "the browser binding for R1 — injects the vendored, κ-pinned esbuild-wasm into holo-forge-bundle's deterministic κ-transform",
|
| 64 |
+
l5: "the compiler itself is content-addressed (esbuild-wasm.pin.json); a tampered/swapped esbuild is refused before any build — the recipe κ is honest",
|
| 65 |
+
parity: "same API as the native esbuild the witness uses → R1 bundles are byte-identical in the tab and in Node (Law L5)",
|
| 66 |
+
};
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
export default { ESBUILD_PIN, verifyToolchain, loadBrowserEsbuild, describeEsbuildBinding };
|
b/dbe3e6ed0f585a4cd05dc636cedfa61910a9c00ca548340eee366f5ab906a8b9
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// animate-client.mjs — the ANIMATOR, IN THE BROWSER (HOLO-ANIMATE-SERVERLESS, SL-P0/P1). Everything the
|
| 2 |
+
// local Node daemon (holo-animate-serve.mjs) did per repo now happens in the page — and since SL-P1 the
|
| 3 |
+
// per-request logic (MIME, fidelity rewriteIndex, entry resolution, buildSPA, κ-vendor graph, tree assembly)
|
| 4 |
+
// is the ONE SHARED CORE at /lib/holo-animate-core.mjs, consumed VERBATIM by both hosts. This file only owns
|
| 5 |
+
// what is genuinely browser-side: the CORS fetch ladder, the Cache-API κ-store, and the κ-pinned esbuild-wasm
|
| 6 |
+
// loading (verified against ESBUILD_PIN before it may build — L5 on the compiler).
|
| 7 |
+
//
|
| 8 |
+
// FETCH LADDER (empirical, from a real page origin — SL-P0a, witnessed):
|
| 9 |
+
// codeload tarball → BLOCKED (ACAO locked to render.githubusercontent.com, incl. via the
|
| 10 |
+
// api.github.com/…/tarball redirect — the browser checks the FINAL response)
|
| 11 |
+
// data.jsdelivr.com list → OPEN (ACAO *) — the file listing, no GitHub rate limit
|
| 12 |
+
// cdn.jsdelivr.net files → OPEN (ACAO *) — per-file, CDN-cached planet-wide
|
| 13 |
+
// raw.githubusercontent → OPEN (ACAO *) — per-file fallback
|
| 14 |
+
// api.github.com trees → OPEN (ACAO *) — listing fallback (60/hr unauth — last resort)
|
| 15 |
+
// NOTE (SL-P2): the κ-registry becomes the PRIMARY rung — anyone who animated repo@commit before you means
|
| 16 |
+
// zero GitHub traffic at all. GitHub is only for first-animation of a novel repo.
|
| 17 |
+
|
| 18 |
+
import { parseRepoRef } from "./lib/holo-import.mjs";
|
| 19 |
+
import { detectProvider } from "./lib/holo-providers.mjs";
|
| 20 |
+
import { blake3hex } from "./lib/holo-blake3.mjs";
|
| 21 |
+
import { loadBrowserEsbuild } from "./lib/holo-forge-esbuild.mjs";
|
| 22 |
+
import { sealHolo, openHolo, packHolo, unpackHolo } from "./lib/holo-dotholo.mjs";
|
| 23 |
+
import { mimeOf, rewriteIndex, makeVendor, buildSPA as coreBuildSPA, assembleR0Tree, classicIndexPath, assembleClassicTree, assembleR1Tree, asText, packTree, unpackTree } from "./lib/holo-animate-core.mjs";
|
| 24 |
+
|
| 25 |
+
const enc = (s) => new TextEncoder().encode(s);
|
| 26 |
+
const dec = (b) => new TextDecoder().decode(b);
|
| 27 |
+
export const RUN_CACHE = "holo-run-v1";
|
| 28 |
+
// SUBPATH RELOCATION (SS-P3): everything keys off the page's directory (== the SW scope), NOT the origin
|
| 29 |
+
// root — so the same bundle serves at "/" locally and at "/<repo>/" on a Pages origin, unchanged.
|
| 30 |
+
const SCOPE_URL = new URL("./", location.href); // e.g. https://x.github.io/animate/
|
| 31 |
+
const SCOPE = SCOPE_URL.pathname; // e.g. /animate/
|
| 32 |
+
const scoped = (p) => new URL(p, SCOPE_URL).href; // "run/<id>/x" → absolute under the scope
|
| 33 |
+
|
| 34 |
+
// ── SL-P0a: the empirical ladder probe — run each rung FROM THIS ORIGIN and report what the browser lets
|
| 35 |
+
// through. This is the witnessed answer to "can a page fetch GitHub without our server?" ───────────────
|
| 36 |
+
export async function probeLadder(slug = "mdn/todo-react", ref = "main") {
|
| 37 |
+
const out = {};
|
| 38 |
+
const attempt = async (name, fn) => { try { const r = await fn(); out[name] = { ok: true, note: r }; } catch (e) { out[name] = { ok: false, note: String(e && e.message || e).slice(0, 120) }; } };
|
| 39 |
+
await attempt("codeload-tarball", async () => { const r = await fetch(`https://codeload.github.com/${slug}/tar.gz/${ref}`); return "HTTP " + r.status + " " + (await r.blob()).size + "B"; });
|
| 40 |
+
await attempt("api-tarball-redirect", async () => { const r = await fetch(`https://api.github.com/repos/${slug}/tarball/${ref}`); return "HTTP " + r.status + " " + (await r.blob()).size + "B"; });
|
| 41 |
+
await attempt("jsdelivr-list", async () => { const r = await fetch(`https://data.jsdelivr.com/v1/packages/gh/${slug}@${ref}`); if (!r.ok) throw new Error("HTTP " + r.status); const j = await r.json(); return (j.files || []).length + " top-level entries"; });
|
| 42 |
+
await attempt("jsdelivr-file", async () => { const r = await fetch(`https://cdn.jsdelivr.net/gh/${slug}@${ref}/package.json`); if (!r.ok) throw new Error("HTTP " + r.status); return (await r.text()).length + " chars"; });
|
| 43 |
+
await attempt("raw-file", async () => { const r = await fetch(`https://raw.githubusercontent.com/${slug}/${ref}/package.json`); if (!r.ok) throw new Error("HTTP " + r.status); return (await r.text()).length + " chars"; });
|
| 44 |
+
await attempt("api-trees", async () => { const r = await fetch(`https://api.github.com/repos/${slug}/git/trees/${ref}?recursive=1`); if (!r.ok) throw new Error("HTTP " + r.status); const j = await r.json(); return (j.tree || []).length + " entries"; });
|
| 45 |
+
await attempt("esm-sh", async () => { const r = await fetch("https://esm.sh/react?bundle"); if (!r.ok) throw new Error("HTTP " + r.status); return (await r.text()).length + " chars"; });
|
| 46 |
+
return out;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
// ── repo fetch over the working rungs: jsdelivr list → parallel per-file (jsdelivr, raw fallback) ─────────
|
| 50 |
+
const SKIP_RE = /(^|\/)(node_modules|\.git|\.github)\//;
|
| 51 |
+
const TEXT_EXT = new Set(["js", "mjs", "cjs", "jsx", "ts", "tsx", "json", "html", "htm", "css", "md", "txt", "yml", "yaml", "toml", "xml", "svg", "lock", "cfg", "ini", "py", "rb", "go", "rs", "php", "java", "sh", "vue", "svelte", "astro", "env", "csv", "map", "webmanifest", "gitignore", "npmrc", "nvmrc", "editorconfig", "csproj", "sln", "gradle", "kts", "procfile"]);
|
| 52 |
+
const looksText = (p, size) => { const base = p.split("/").pop(); const ext = base.includes(".") ? base.split(".").pop().toLowerCase() : ""; return ext ? TEXT_EXT.has(ext) : size <= 512 * 1024; };
|
| 53 |
+
|
| 54 |
+
function flattenJsdelivr(entries, prefix = "") {
|
| 55 |
+
const out = [];
|
| 56 |
+
for (const e of entries || []) {
|
| 57 |
+
if (e.type === "file") out.push({ path: prefix + e.name, size: e.size || 0 });
|
| 58 |
+
else if (e.type === "directory") out.push(...flattenJsdelivr(e.files, prefix + e.name + "/"));
|
| 59 |
+
}
|
| 60 |
+
return out;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
async function listRepo(slug, ref) {
|
| 64 |
+
// rung 1: jsdelivr's data API (no GitHub rate limit)
|
| 65 |
+
try {
|
| 66 |
+
const r = await fetch(`https://data.jsdelivr.com/v1/packages/gh/${slug}@${ref}`);
|
| 67 |
+
if (r.ok) { const j = await r.json(); const files = flattenJsdelivr(j.files); if (files.length) return { files, via: "jsdelivr" }; }
|
| 68 |
+
} catch {}
|
| 69 |
+
// rung 2: GitHub trees API (rate-limited — last resort)
|
| 70 |
+
const r = await fetch(`https://api.github.com/repos/${slug}/git/trees/${ref}?recursive=1`, { headers: { accept: "application/vnd.github+json" } });
|
| 71 |
+
if (!r.ok) throw new Error("could not list " + slug + "@" + ref + " (jsdelivr + api both failed, HTTP " + r.status + ")");
|
| 72 |
+
const j = await r.json();
|
| 73 |
+
return { files: (j.tree || []).filter((t) => t.type === "blob").map((t) => ({ path: t.path, size: t.size || 0 })), via: "api-trees" };
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
async function fetchFile(slug, ref, p) {
|
| 77 |
+
for (const url of [`https://cdn.jsdelivr.net/gh/${slug}@${ref}/${p}`, `https://raw.githubusercontent.com/${slug}/${ref}/${p}`]) {
|
| 78 |
+
try { const r = await fetch(url); if (r.ok) return new Uint8Array(await r.arrayBuffer()); } catch {}
|
| 79 |
+
}
|
| 80 |
+
throw new Error("fetch failed: " + p);
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
const MAX_FILES = 1200, MAX_FILE_BYTES = 12 * 1024 * 1024;
|
| 84 |
+
export async function fetchRepo(slug, ref, onStatus = () => {}) {
|
| 85 |
+
let listing = null, used = ref;
|
| 86 |
+
for (const r of [ref, ref === "main" ? "master" : "main"]) {
|
| 87 |
+
try { listing = await listRepo(slug, r); used = r; break; } catch {}
|
| 88 |
+
}
|
| 89 |
+
if (!listing) throw new Error("could not fetch " + slug + " (tried " + ref + ")");
|
| 90 |
+
let entries = listing.files.filter((f) => !SKIP_RE.test(f.path));
|
| 91 |
+
const skippedBig = entries.filter((f) => f.size > MAX_FILE_BYTES).map((f) => f.path);
|
| 92 |
+
entries = entries.filter((f) => f.size <= MAX_FILE_BYTES);
|
| 93 |
+
if (entries.length > MAX_FILES) throw new Error("repo too large for the in-tab spike: " + entries.length + " files (cap " + MAX_FILES + ") — named honestly, not truncated");
|
| 94 |
+
onStatus("fetching " + entries.length + " files via " + listing.via + (skippedBig.length ? " (skipped " + skippedBig.length + " >12MB)" : ""));
|
| 95 |
+
const files = new Map();
|
| 96 |
+
let done = 0;
|
| 97 |
+
const queue = entries.slice();
|
| 98 |
+
const workers = Array.from({ length: 24 }, async () => {
|
| 99 |
+
for (;;) { const e = queue.shift(); if (!e) return;
|
| 100 |
+
files.set(e.path, await fetchFile(slug, used, e.path));
|
| 101 |
+
if (++done % 25 === 0) onStatus("fetched " + done + "/" + entries.length);
|
| 102 |
+
}
|
| 103 |
+
});
|
| 104 |
+
await Promise.all(workers);
|
| 105 |
+
return { files, commit: used, via: listing.via, skippedBig };
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
// ── provider detection over the fetched bytes: the canonical registry wants text values for the files it
|
| 109 |
+
// inspects — decode texty files, mark the rest {text:null} (keys still count for path detection). ──────
|
| 110 |
+
export function detectFromBytes(files) {
|
| 111 |
+
const dmap = new Map();
|
| 112 |
+
for (const [p, bytes] of files) dmap.set(p, looksText(p, bytes.length) ? { text: dec(bytes) } : { text: null });
|
| 113 |
+
let pkg = null; try { pkg = JSON.parse(dec(files.get("package.json") || new Uint8Array())); } catch {}
|
| 114 |
+
return { det: detectProvider(dmap, pkg), pkg };
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
// ── the Cache-API κ-store the SW serves from: /run/<id>/<path> + /vendor/<κ> ──────────────────────────────
|
| 118 |
+
async function storeTree(id, tree) {
|
| 119 |
+
const cache = await caches.open(RUN_CACHE);
|
| 120 |
+
for (const [p, v] of tree) {
|
| 121 |
+
const bytes = typeof v === "string" ? enc(v) : v;
|
| 122 |
+
const body = p === "index.html" ? enc(rewriteIndex(dec(bytes), id, SCOPE)) : bytes; // prefix-neutral .holo → scoped doc at serve time
|
| 123 |
+
await cache.put(scoped("run/" + id + "/" + p), new Response(body, { headers: { "content-type": mimeOf(p) } }));
|
| 124 |
+
}
|
| 125 |
+
}
|
| 126 |
+
async function storeVendor(hex, text) {
|
| 127 |
+
const cache = await caches.open(RUN_CACHE);
|
| 128 |
+
await cache.put(scoped("vendor/" + hex), new Response(enc(text), { headers: { "content-type": "text/javascript" } }));
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
// ── R1: esbuild-wasm, κ-VERIFIED before it may build (holo-forge-esbuild's pin gate = L5 on the compiler) ──
|
| 132 |
+
let _esbuild = null;
|
| 133 |
+
export async function loadEsbuild(onStatus = () => {}) {
|
| 134 |
+
if (_esbuild) return _esbuild;
|
| 135 |
+
onStatus("fetching κ-pinned esbuild-wasm toolchain");
|
| 136 |
+
const [js, wasm] = await Promise.all([
|
| 137 |
+
fetch("/vendor-toolchain/browser.min.js").then((r) => { if (!r.ok) throw new Error("toolchain js HTTP " + r.status); return r.arrayBuffer(); }),
|
| 138 |
+
fetch("/vendor-toolchain/esbuild.wasm").then((r) => { if (!r.ok) throw new Error("toolchain wasm HTTP " + r.status); return r.arrayBuffer(); }),
|
| 139 |
+
]);
|
| 140 |
+
onStatus("verifying toolchain against ESBUILD_PIN (L5)");
|
| 141 |
+
_esbuild = await loadBrowserEsbuild({
|
| 142 |
+
toolchainBytes: { "browser.min.js": new Uint8Array(js), "esbuild.wasm": new Uint8Array(wasm) },
|
| 143 |
+
loadScript: (jsBytes) => new Promise((res, rej) => {
|
| 144 |
+
const url = URL.createObjectURL(new Blob([jsBytes], { type: "text/javascript" }));
|
| 145 |
+
const s = document.createElement("script"); s.src = url;
|
| 146 |
+
s.onload = () => res(globalThis.esbuild); s.onerror = () => rej(new Error("esbuild-wasm script failed to load"));
|
| 147 |
+
document.head.appendChild(s);
|
| 148 |
+
}),
|
| 149 |
+
initialize: async (api, wasmBytes) => { await api.initialize({ wasmModule: await WebAssembly.compile(wasmBytes.buffer ? wasmBytes : new Uint8Array(wasmBytes)) }); },
|
| 150 |
+
});
|
| 151 |
+
return _esbuild;
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
// ── κ-vendoring: the graph walk + rewrite are the CORE's (makeVendor); this host injects Cache-API
|
| 155 |
+
// boundaries (the daemon injects disk). Same κs out on both hosts. ─────────────────────────────────────
|
| 156 |
+
const vendorIndexKey = "holo-vendor-index-v1";
|
| 157 |
+
const vendorIndex = (() => { try { return JSON.parse(localStorage.getItem(vendorIndexKey) || "{}"); } catch { return {}; } })();
|
| 158 |
+
const vendorKappa = makeVendor({
|
| 159 |
+
fetchText: async (url) => { const r = await fetch(url); if (!r.ok) throw new Error("vendor fetch HTTP " + r.status + " " + url); return r.text(); },
|
| 160 |
+
store: storeVendor,
|
| 161 |
+
exists: async (hex) => !!(await (await caches.open(RUN_CACHE)).match(scoped("vendor/" + hex))),
|
| 162 |
+
hash: blake3hex,
|
| 163 |
+
index: { get: async (u) => vendorIndex[u], set: async (u, hex) => { vendorIndex[u] = hex; try { localStorage.setItem(vendorIndexKey, JSON.stringify(vendorIndex)); } catch {} } },
|
| 164 |
+
});
|
| 165 |
+
|
| 166 |
+
// ── the OPFS warm tier (SS-P0): a sealed .holo per slug@ref — the browser's κ-cache. Warm reopen = open the
|
| 167 |
+
// .holo from OPFS, VERIFY (L5), unpack, serve — zero network. Same key discipline as the daemon's disk cache.
|
| 168 |
+
const holoKey = (slug, ref) => (slug + "@" + ref).replace(/[^\w.@-]/g, "_");
|
| 169 |
+
async function opfsDir() { const root = await navigator.storage.getDirectory(); return root.getDirectoryHandle("holo-store", { create: true }); }
|
| 170 |
+
async function opfsLoad(key) { try { const dir = await opfsDir(); const fh = await dir.getFileHandle(key + ".holo"); return await (await fh.getFile()).text(); } catch { return null; } }
|
| 171 |
+
async function opfsSave(key, packed) { try { const dir = await opfsDir(); const fh = await dir.getFileHandle(key + ".holo", { create: true }); const w = await fh.createWritable(); await w.write(packed); await w.close(); } catch {} }
|
| 172 |
+
async function opfsDelete(key) { try { const dir = await opfsDir(); await dir.removeEntry(key + ".holo"); } catch {} }
|
| 173 |
+
|
| 174 |
+
// unpack + VERIFY a packed tree .holo (L5 before render — manifest→κ, bytes→resultκ, else refused)
|
| 175 |
+
function openSealedTree(packed) {
|
| 176 |
+
let u; try { u = unpackHolo(packed); } catch (e) { return { ok: false, error: "unreadable .holo: " + e.message }; }
|
| 177 |
+
const o = openHolo({ kappa: u.kappa, manifest: u.manifest, result: u.result });
|
| 178 |
+
if (!o.ok) return { ok: false, error: o.error, kappa: u.kappa };
|
| 179 |
+
if (u.manifest["holo:contentType"] !== "application/holo-tree") return { ok: false, error: "not a tree .holo (" + u.manifest["holo:contentType"] + ")", kappa: u.kappa };
|
| 180 |
+
let tree; try { tree = unpackTree(o.html); } catch (e) { return { ok: false, error: "tree unpack failed: " + e.message, kappa: u.kappa }; }
|
| 181 |
+
return { ok: true, kappa: u.kappa, manifest: u.manifest, tree };
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
// ── the static κ-REGISTRY rung (SS-P1): pre-sealed .holos shipped as files beside this page — built once
|
| 185 |
+
// per planet, streamed + verified. Relative fetches so the origin can live under a subpath. ────────────
|
| 186 |
+
let _registryIndex;
|
| 187 |
+
export async function registryIndex() {
|
| 188 |
+
if (_registryIndex !== undefined) return _registryIndex;
|
| 189 |
+
try { const r = await fetch("registry/index.json", { cache: "no-cache" }); _registryIndex = r.ok ? await r.json() : null; } catch { _registryIndex = null; }
|
| 190 |
+
return _registryIndex;
|
| 191 |
+
}
|
| 192 |
+
async function registryLoad(slugRef, onStatus) {
|
| 193 |
+
const idx = await registryIndex();
|
| 194 |
+
const e = idx && idx.apps && idx.apps[slugRef];
|
| 195 |
+
if (!e) return null;
|
| 196 |
+
try {
|
| 197 |
+
onStatus("streaming from the κ-registry (built once per planet)");
|
| 198 |
+
const r = await fetch("registry/" + e.file); if (!r.ok) return null;
|
| 199 |
+
const packed = await r.text();
|
| 200 |
+
// seed the app's κ-vendored runtime modules (importmap points at <scope>vendor/<κ>) — verify before caching
|
| 201 |
+
for (const hex of e.vendors || []) {
|
| 202 |
+
const cache = await caches.open(RUN_CACHE);
|
| 203 |
+
if (await cache.match(scoped("vendor/" + hex))) continue;
|
| 204 |
+
const vr = await fetch("registry/vendor/" + hex + ".js"); if (!vr.ok) continue;
|
| 205 |
+
const bytes = new Uint8Array(await vr.arrayBuffer());
|
| 206 |
+
if (blake3hex(bytes) !== hex) { onStatus("registry vendor κ mismatch — refused " + hex.slice(0, 8)); continue; } // L5
|
| 207 |
+
await cache.put(scoped("vendor/" + hex), new Response(bytes, { headers: { "content-type": "text/javascript" } }));
|
| 208 |
+
}
|
| 209 |
+
return { packed, expectKappa: e.kappa };
|
| 210 |
+
} catch { return null; }
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
// ── animate — the SAME rungs as the daemon (R0 tree · classic→R0 · R1 esbuild), all in-tab, all via core.
|
| 214 |
+
// Open ladder: OPFS .holo → static κ-registry → live animate (GitHub only for a NOVEL repo). ───────────
|
| 215 |
+
let seq = 0;
|
| 216 |
+
async function realize(id, tree, rung, meta) { await storeTree(id, tree); return { rung, id, meta: { ...meta, treeFiles: tree.size, paths: [...tree.keys()] } }; }
|
| 217 |
+
|
| 218 |
+
export async function animate(input, onStatus = () => {}, { noCache = false } = {}) {
|
| 219 |
+
const ref = parseRepoRef(input); if (!ref) throw new Error("not a GitHub URL: " + input);
|
| 220 |
+
const slug = ref.owner + "/" + ref.repo, gitref = ref.ref || "main", key = holoKey(slug, gitref);
|
| 221 |
+
const id = "r" + (++seq) + Date.now().toString(36);
|
| 222 |
+
|
| 223 |
+
// 1) OPFS warm tier — verify before render; tampered → discard (self-heal online, REFUSE offline).
|
| 224 |
+
// noCache skips the read rungs (fresh animate — the seal still writes, so the next open is warm).
|
| 225 |
+
const cached = noCache ? null : await opfsLoad(key);
|
| 226 |
+
if (cached) {
|
| 227 |
+
const o = openSealedTree(cached);
|
| 228 |
+
if (o.ok) { onStatus("⚡ streamed from OPFS (0 net), verified κ:" + o.kappa.slice(-8)); return realize(id, o.tree, o.manifest["holo:tier"], { repo: slug, provider: o.manifest["holo:provider"], tier: o.manifest["holo:tier"], fetchMs: 0, buildMs: 0, cached: true, source: "opfs", kappa: o.kappa }); }
|
| 229 |
+
await opfsDelete(key);
|
| 230 |
+
if (!navigator.onLine) throw new Error("cached .holo failed verification (" + o.error + ") — offline, refusing to render (L5)");
|
| 231 |
+
onStatus("cached .holo failed verify (" + o.error + ") — discarded, re-animating live");
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
// 2) the static κ-registry — someone (the build) animated this for the planet already
|
| 235 |
+
const reg = noCache ? null : await registryLoad(slug + "@" + gitref, onStatus);
|
| 236 |
+
if (reg) {
|
| 237 |
+
const o = openSealedTree(reg.packed);
|
| 238 |
+
if (o.ok && (!reg.expectKappa || reg.expectKappa === o.kappa)) {
|
| 239 |
+
await opfsSave(key, reg.packed); // next open = OPFS, offline-capable
|
| 240 |
+
return realize(id, o.tree, o.manifest["holo:tier"], { repo: slug, provider: o.manifest["holo:provider"], tier: o.manifest["holo:tier"], fetchMs: 0, buildMs: 0, cached: true, source: "registry", kappa: o.kappa });
|
| 241 |
+
}
|
| 242 |
+
onStatus("registry .holo REFUSED (" + (o.ok ? "κ != index (" + reg.expectKappa + ")" : o.error) + ") — falling to live animate"); // L5: never render refused bytes
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
// 3) live animate (first animation of a novel repo)
|
| 246 |
+
const t0 = performance.now();
|
| 247 |
+
onStatus("fetching " + slug);
|
| 248 |
+
const { files, commit, via } = await fetchRepo(slug, gitref, onStatus);
|
| 249 |
+
const fetchMs = Math.round(performance.now() - t0);
|
| 250 |
+
const { det } = detectFromBytes(files);
|
| 251 |
+
onStatus("provider: " + det.provider + " → " + det.tier);
|
| 252 |
+
const base = { repo: slug, commit, via, provider: det.provider, tier: det.tier, fetchMs, files: files.size, source: "live" };
|
| 253 |
+
const sealSave = async (tree, provider, tier) => { // seal + OPFS = build once, reopen offline
|
| 254 |
+
const sealed = sealHolo({ repo: "https://github.com/" + slug, commit, provider, tier, result: packTree(tree), name: ref.repo, contentType: "application/holo-tree" });
|
| 255 |
+
await opfsSave(key, packHolo(sealed));
|
| 256 |
+
return sealed.kappa;
|
| 257 |
+
};
|
| 258 |
+
|
| 259 |
+
if (det.tier === "R0") {
|
| 260 |
+
const tree = assembleR0Tree(files, det.recipe.entry || "index.html");
|
| 261 |
+
const kappa = await sealSave(tree, det.provider, "R0");
|
| 262 |
+
return realize(id, tree, "R0", { ...base, buildMs: 0, kappa });
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
if (det.tier === "R1" && det.provider === "node-spa") {
|
| 266 |
+
const idxPath = classicIndexPath(files); // FAITHFUL-AS-IS: classic global-script index → tree, not bundle
|
| 267 |
+
if (idxPath) {
|
| 268 |
+
const tree = assembleClassicTree(files, idxPath);
|
| 269 |
+
const kappa = await sealSave(tree, "static", "R0");
|
| 270 |
+
return realize(id, tree, "R0", { ...base, provider: "static", tier: "R0", buildMs: 0, kappa });
|
| 271 |
+
}
|
| 272 |
+
const b0 = performance.now();
|
| 273 |
+
const esbuild = await loadEsbuild(onStatus);
|
| 274 |
+
const built = await coreBuildSPA(files, { esbuild, vendorKappa, onStatus });
|
| 275 |
+
const tree = assembleR1Tree(files, built);
|
| 276 |
+
const kappa = await sealSave(tree, det.provider, "R1");
|
| 277 |
+
return realize(id, tree, "R1", { ...base, buildMs: Math.round(performance.now() - b0), deps: built.deps, entry: built.entry, vendor: built.vendor, kappa });
|
| 278 |
+
}
|
| 279 |
+
|
| 280 |
+
if (det.tier === "R3") throw new Error("R3 (" + det.provider + ") — a server app: in-tab VM (SL-P4) or a Hologram host (SL-P5); named honestly, not faked");
|
| 281 |
+
throw new Error("could not classify a runnable app (" + det.provider + ") — " + (det.recipe.reason || "no app entry"));
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
// witness/debug hooks: OPFS tamper + wipe (drive the L5 refusal path deterministically)
|
| 285 |
+
export async function _opfsTamper(slug, ref = "main") { const key = holoKey(slug, ref); const packed = await opfsLoad(key); if (!packed) return false; const j = JSON.parse(packed); j.result_b64 = b64tamper(j.result_b64); await opfsSave(key, JSON.stringify(j)); return true; }
|
| 286 |
+
const b64tamper = (b64) => (b64.slice(0, 10) + (b64[10] === "A" ? "B" : "A") + b64.slice(11));
|
| 287 |
+
export async function _opfsWipe() { try { const root = await navigator.storage.getDirectory(); await root.removeEntry("holo-store", { recursive: true }); } catch {} }
|
| 288 |
+
|
| 289 |
+
export { mimeOf, rewriteIndex, asText }; // re-exported for the page/witness (single source: the core)
|
b/e08b35ffc16f9f2b45d93d51033a4a061db1c1d59edf76ecffc192c15fb2743b
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// sw.js — the service worker that IS the app server (HOLO-ANIMATE-SERVERLESS, SL-P0). It serves what the
|
| 2 |
+
// in-page animator stored in the Cache-API κ-store:
|
| 3 |
+
// /run/<id>/<path> → the animated app's file tree (exact asset or HONEST 404 — never page-HTML for an
|
| 4 |
+
// asset; the L2/L3 serving laws of holo-animate-conformance, host-invariant)
|
| 5 |
+
// /vendor/<κ> → a κ-vendored runtime module, BLAKE3-VERIFIED against its own name BEFORE serving
|
| 6 |
+
// (L5: tampered bytes → 409, refused — trustless because verification, not trust)
|
| 7 |
+
// Everything else falls through to the network (the static origin).
|
| 8 |
+
//
|
| 9 |
+
// Module SW: imports the CANONICAL blake3 (served unmodified from /lib/) — the same bytes the OS uses.
|
| 10 |
+
|
| 11 |
+
import { blake3hex } from "./lib/holo-blake3.mjs";
|
| 12 |
+
|
| 13 |
+
const RUN_CACHE = "holo-run-v1";
|
| 14 |
+
// SUBPATH RELOCATION (SS-P3): routes key off the REGISTRATION SCOPE, not the origin root — the same SW
|
| 15 |
+
// serves at "/" locally and at "/<repo>/" on a Pages origin. Cache keys are scope-absolute URLs (the
|
| 16 |
+
// client stores them the same way), so scope and keys always agree.
|
| 17 |
+
const SCOPE = new URL(self.registration.scope).pathname; // e.g. "/" or "/animate/"
|
| 18 |
+
|
| 19 |
+
self.addEventListener("install", () => self.skipWaiting());
|
| 20 |
+
self.addEventListener("activate", (e) => e.waitUntil(self.clients.claim()));
|
| 21 |
+
|
| 22 |
+
// vm-fetch RPC correlation: reqId → resolve (answers arrive as messages from the page owning the machine)
|
| 23 |
+
const vmPending = new Map();
|
| 24 |
+
self.addEventListener("message", (e) => {
|
| 25 |
+
const r = e.data && e.data.vmFetchResult;
|
| 26 |
+
if (!r) return;
|
| 27 |
+
const resolve = vmPending.get(r.reqId);
|
| 28 |
+
if (resolve) { vmPending.delete(r.reqId); resolve(r); }
|
| 29 |
+
});
|
| 30 |
+
|
| 31 |
+
const honest404 = (p) => new Response("not found: " + p, { status: 404, headers: { "content-type": "text/plain" } });
|
| 32 |
+
|
| 33 |
+
self.addEventListener("fetch", (e) => {
|
| 34 |
+
const url = new URL(e.request.url);
|
| 35 |
+
if (url.origin !== location.origin) return; // cross-origin (jsdelivr/raw/esm.sh build-time ingest) → network
|
| 36 |
+
|
| 37 |
+
if (url.pathname.startsWith(SCOPE + "run/")) {
|
| 38 |
+
e.respondWith((async () => {
|
| 39 |
+
const cache = await caches.open(RUN_CACHE);
|
| 40 |
+
let p = url.pathname;
|
| 41 |
+
if (p.endsWith("/")) p += "index.html";
|
| 42 |
+
let hit = await cache.match(url.origin + p);
|
| 43 |
+
if (!hit) { try { hit = await cache.match(url.origin + decodeURIComponent(p)); } catch {} }
|
| 44 |
+
// a bare <scope>run/<id> (no trailing slash) opens the app's index
|
| 45 |
+
if (!hit && new RegExp("^" + SCOPE.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "run/[^/]+$").test(p)) hit = await cache.match(url.origin + p + "/index.html");
|
| 46 |
+
return hit || honest404(p); // L3: a missing asset 404s honestly — NEVER echoes HTML
|
| 47 |
+
})());
|
| 48 |
+
return;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
// ── VT-P2: <scope>vm/<vmid>/<path> — the in-tab MACHINE as a URL. The SW does not own the emulator (it
|
| 52 |
+
// lives in a page), so this is an RPC: broadcast {vmFetch} to window clients; the page owning that
|
| 53 |
+
// vmid answers with the guest's real HTTP response (status/headers/body bytes, binary-faithful).
|
| 54 |
+
// No owner / guest down → honest 404/502, never a phantom page (the L2/L3 laws hold for machines too).
|
| 55 |
+
if (url.pathname.startsWith(SCOPE + "vm/")) {
|
| 56 |
+
e.respondWith((async () => {
|
| 57 |
+
const rest = url.pathname.slice((SCOPE + "vm/").length);
|
| 58 |
+
const slash = rest.indexOf("/");
|
| 59 |
+
const vmid = slash < 0 ? rest : rest.slice(0, slash);
|
| 60 |
+
const path = (slash < 0 ? "/" : rest.slice(slash)) + (url.search || "");
|
| 61 |
+
if (!vmid) return honest404(url.pathname);
|
| 62 |
+
const reqId = Math.random().toString(36).slice(2);
|
| 63 |
+
const clientsList = await self.clients.matchAll({ type: "window", includeUncontrolled: true });
|
| 64 |
+
if (!clientsList.length) return new Response("no page owns machine '" + vmid + "'", { status: 404, headers: { "content-type": "text/plain" } });
|
| 65 |
+
const answer = new Promise((resolve) => {
|
| 66 |
+
vmPending.set(reqId, resolve);
|
| 67 |
+
setTimeout(() => { if (vmPending.delete(reqId)) resolve(null); }, 15000);
|
| 68 |
+
});
|
| 69 |
+
for (const c of clientsList) c.postMessage({ vmFetch: { reqId, vmid, path } });
|
| 70 |
+
const r = await answer;
|
| 71 |
+
if (!r) return new Response("machine '" + vmid + "' did not answer (no owning page, or guest hung)", { status: 502, headers: { "content-type": "text/plain" } });
|
| 72 |
+
if (!r.ok) return new Response("machine error: " + r.error, { status: 502, headers: { "content-type": "text/plain" } });
|
| 73 |
+
const headers = { "content-type": (r.headers && r.headers["content-type"]) || "application/octet-stream" };
|
| 74 |
+
if (r.headers && r.headers["content-length"]) headers["content-length"] = r.headers["content-length"];
|
| 75 |
+
return new Response(r.body, { status: r.status, statusText: r.statusText || "", headers });
|
| 76 |
+
})());
|
| 77 |
+
return;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
if (url.pathname.startsWith(SCOPE + "vendor/")) {
|
| 81 |
+
e.respondWith((async () => {
|
| 82 |
+
const hex = url.pathname.slice((SCOPE + "vendor/").length).split("?")[0];
|
| 83 |
+
if (!/^[0-9a-f]{64}$/.test(hex)) return honest404(url.pathname);
|
| 84 |
+
const cache = await caches.open(RUN_CACHE);
|
| 85 |
+
const hit = await cache.match(url.origin + SCOPE + "vendor/" + hex);
|
| 86 |
+
if (!hit) return honest404(url.pathname);
|
| 87 |
+
const bytes = new Uint8Array(await hit.arrayBuffer());
|
| 88 |
+
if (blake3hex(bytes) !== hex) return new Response("vendor κ mismatch (tamper) — refused", { status: 409, headers: { "content-type": "text/plain" } }); // L5
|
| 89 |
+
return new Response(bytes, { headers: { "content-type": "text/javascript", "cache-control": "immutable, max-age=31536000" } });
|
| 90 |
+
})());
|
| 91 |
+
return;
|
| 92 |
+
}
|
| 93 |
+
});
|
b/e1881b19fc538fcfbf3294d25e81dc61006e50aa62ce04953f92076473054dd0
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// holo-uor.mjs — THE one canonical content-addressing primitive for Hologram OS, a downstream
|
| 2 |
+
// of UOR-ADDR (uor-foundation/uor-addr): a κ-label is `<axis>:<hex>` = H(canonical_form). The
|
| 3 |
+
// engine's Law L2 ("canonical forms only — canonicalize at the ingest boundary, hold κ") is
|
| 4 |
+
// made literal here: canonicalization + the σ-axis hashes + the multibase/SRI digests live in
|
| 5 |
+
// ONE module, never re-derived per file. Every product path — the holospace descriptor, the
|
| 6 |
+
// UOR object envelope, the witnesses — imports these. ISOMORPHIC (Node · browser · service
|
| 7 |
+
// worker): the SHA-256 is a pure-JS synchronous implementation (no node:crypto, no Buffer), so
|
| 8 |
+
// the same module loads and re-derives κ identically wherever it runs — the substrate gateway
|
| 9 |
+
// Service Worker IS this primitive, byte-for-byte equal to node:crypto's sha256 (witnessed by
|
| 10 |
+
// every κ re-derivation in the gate, and self-tested against node:crypto on the block boundaries).
|
| 11 |
+
//
|
| 12 |
+
// Equivalence to the engine's σ-axis is witnessed against its cc1 hash-KATs; that is what makes
|
| 13 |
+
// us a CANONICAL downstream, not a lookalike. Authorities: UOR-ADDR (κ-label = H(canonical_form));
|
| 14 |
+
// IETF RFC 8785 (JCS); multiformats (multihash); W3C Subresource Integrity / VC Data Integrity
|
| 15 |
+
// (digestSRI/Multibase); FIPS 180-4 (SHA-256).
|
| 16 |
+
|
| 17 |
+
// ── FIPS 180-4 SHA-256 — pure-JS, synchronous, isomorphic (bytes in → 32-byte digest out) ───────
|
| 18 |
+
const SHA_K = new Uint32Array([0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2]);
|
| 19 |
+
const _rotr = (x, n) => (x >>> n) | (x << (32 - n));
|
| 20 |
+
function sha256u8(msg) {
|
| 21 |
+
const h = new Uint32Array([0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19]);
|
| 22 |
+
const len = msg.length, bitLen = len * 8, withOne = len + 1, k = (56 - (withOne % 64) + 64) % 64, total = withOne + k + 8;
|
| 23 |
+
const m = new Uint8Array(total); m.set(msg); m[len] = 0x80;
|
| 24 |
+
const hi = Math.floor(bitLen / 0x100000000), lo = bitLen >>> 0;
|
| 25 |
+
m[total-8]=(hi>>>24)&255; m[total-7]=(hi>>>16)&255; m[total-6]=(hi>>>8)&255; m[total-5]=hi&255;
|
| 26 |
+
m[total-4]=(lo>>>24)&255; m[total-3]=(lo>>>16)&255; m[total-2]=(lo>>>8)&255; m[total-1]=lo&255;
|
| 27 |
+
const w = new Uint32Array(64);
|
| 28 |
+
for (let off = 0; off < total; off += 64) {
|
| 29 |
+
for (let i = 0; i < 16; i++) w[i] = (m[off+i*4]<<24)|(m[off+i*4+1]<<16)|(m[off+i*4+2]<<8)|(m[off+i*4+3]);
|
| 30 |
+
for (let i = 16; i < 64; i++) { const s0=_rotr(w[i-15],7)^_rotr(w[i-15],18)^(w[i-15]>>>3); const s1=_rotr(w[i-2],17)^_rotr(w[i-2],19)^(w[i-2]>>>10); w[i]=(w[i-16]+s0+w[i-7]+s1)|0; }
|
| 31 |
+
let a=h[0],b=h[1],c=h[2],d=h[3],e=h[4],f=h[5],g=h[6],hh=h[7];
|
| 32 |
+
for (let i = 0; i < 64; i++) { const S1=_rotr(e,6)^_rotr(e,11)^_rotr(e,25); const ch=(e&f)^((~e)&g); const t1=(hh+S1+ch+SHA_K[i]+w[i])|0; const S0=_rotr(a,2)^_rotr(a,13)^_rotr(a,22); const maj=(a&b)^(a&c)^(b&c); const t2=(S0+maj)|0; hh=g;g=f;f=e;e=(d+t1)|0;d=c;c=b;b=a;a=(t1+t2)|0; }
|
| 33 |
+
h[0]=(h[0]+a)|0;h[1]=(h[1]+b)|0;h[2]=(h[2]+c)|0;h[3]=(h[3]+d)|0;h[4]=(h[4]+e)|0;h[5]=(h[5]+f)|0;h[6]=(h[6]+g)|0;h[7]=(h[7]+hh)|0;
|
| 34 |
+
}
|
| 35 |
+
const out = new Uint8Array(32);
|
| 36 |
+
for (let i = 0; i < 8; i++) { out[i*4]=(h[i]>>>24)&255; out[i*4+1]=(h[i]>>>16)&255; out[i*4+2]=(h[i]>>>8)&255; out[i*4+3]=h[i]&255; }
|
| 37 |
+
return out;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
// ── isomorphic byte helpers (no Buffer) ─────────────────────────────────────────────────────────
|
| 41 |
+
const _B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
| 42 |
+
const _enc = new TextEncoder();
|
| 43 |
+
const utf8Bytes = (s) => _enc.encode(s);
|
| 44 |
+
const toHex = (u8) => { let s = ""; for (let i = 0; i < u8.length; i++) s += u8[i].toString(16).padStart(2, "0"); return s; };
|
| 45 |
+
const hexToBytes = (hex) => { const u = new Uint8Array(hex.length / 2); for (let i = 0; i < u.length; i++) u[i] = parseInt(hex.substr(i * 2, 2), 16); return u; };
|
| 46 |
+
const concatBytes = (...arrs) => { let n = 0; for (const a of arrs) n += a.length; const o = new Uint8Array(n); let p = 0; for (const a of arrs) { o.set(a, p); p += a.length; } return o; };
|
| 47 |
+
// base64 (urlsafe=false → standard, padded; urlsafe=true → base64url, unpadded) — matches Node's
|
| 48 |
+
// Buffer.toString("base64") and .toString("base64url") byte-for-byte.
|
| 49 |
+
function toBase64(u8, urlsafe) {
|
| 50 |
+
const alpha = _B64 + (urlsafe ? "-_" : "+/");
|
| 51 |
+
let out = "";
|
| 52 |
+
for (let i = 0; i < u8.length; i += 3) {
|
| 53 |
+
const b0 = u8[i], b1 = u8[i + 1], b2 = u8[i + 2], has1 = i + 1 < u8.length, has2 = i + 2 < u8.length;
|
| 54 |
+
out += alpha[b0 >> 2];
|
| 55 |
+
out += alpha[((b0 & 3) << 4) | ((has1 ? b1 : 0) >> 4)];
|
| 56 |
+
out += has1 ? alpha[((b1 & 15) << 2) | ((has2 ? b2 : 0) >> 6)] : (urlsafe ? "" : "=");
|
| 57 |
+
out += has2 ? alpha[b2 & 63] : (urlsafe ? "" : "=");
|
| 58 |
+
}
|
| 59 |
+
return out;
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
// RFC 8785 JSON Canonicalization Scheme — the canonical_form for JSON-LD objects. Sufficient
|
| 63 |
+
// for string/number/array/object descriptors (sorted keys, arrays in order).
|
| 64 |
+
export const jcs = (v) => Array.isArray(v) ? "[" + v.map(jcs).join(",") + "]"
|
| 65 |
+
: (v && typeof v === "object") ? "{" + Object.keys(v).sort().map((k) => JSON.stringify(k) + ":" + jcs(v[k])).join(",") + "}"
|
| 66 |
+
: JSON.stringify(v);
|
| 67 |
+
|
| 68 |
+
const buf = (x) => typeof x === "string" ? utf8Bytes(x) : (x instanceof Uint8Array ? x : new Uint8Array(x));
|
| 69 |
+
|
| 70 |
+
// the σ-axis: SHA-256 over a canonical form → the open-web κ axis (Web Crypto / SRI speak it).
|
| 71 |
+
export const sha256bytes = (x) => sha256u8(buf(x));
|
| 72 |
+
export const sha256hex = (x) => toHex(sha256bytes(x));
|
| 73 |
+
|
| 74 |
+
// W3C Subresource Integrity / VC Data Integrity digest (a browser verifies it; L5 = SRI).
|
| 75 |
+
export const sriOf = (x) => "sha256-" + toBase64(sha256bytes(x), false);
|
| 76 |
+
|
| 77 |
+
// multibase(base64url) multihash: sha2-256 = 0x12, blake3 = 0x1e (the native fast axis).
|
| 78 |
+
const multihash = (code, digest) => "u" + toBase64(concatBytes(new Uint8Array([code, digest.length]), digest), true);
|
| 79 |
+
export const mbSha256 = (x) => multihash(0x12, sha256bytes(x));
|
| 80 |
+
export const mbBlake3 = (hex) => multihash(0x1e, hexToBytes(hex));
|
| 81 |
+
|
| 82 |
+
// a κ-label / content-derived DID. axis ∈ {sha256, blake3}; hex = H_axis(canonical_form).
|
| 83 |
+
export const kappa = (axis, hex) => `${axis}:${hex}`;
|
| 84 |
+
export const didHolo = (axis, hex) => `did:holo:${axis}:${hex}`;
|
b/e6201a82be15cf856eb6ceb7f48bb806f06013e6a0b7f92d22ddb5b8d03dcbfe
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// holo-blake3.mjs — BLAKE3 (the hologram substrate's σ-axis, ADR-052), pure JS, lean +
|
| 2 |
+
// browser-native. This is the convergence seam made real: the substrate addresses content as
|
| 3 |
+
// `blake3:<hex>` = standard BLAKE3 of the canonical bytes (proven: its own differential test
|
| 4 |
+
// asserts the blake3 axis == the reference `blake3` crate). Reproducing standard BLAKE3 here —
|
| 5 |
+
// witnessed byte-identical against the substrate's own `kappa()` wasm across chunk boundaries —
|
| 6 |
+
// makes OS2 κ byte-identical to upstream WITHOUT a 6.5 MB wasm or restating substrate internals
|
| 7 |
+
// (BLAKE3 is the public standard the substrate itself consumes). Implements the official spec:
|
| 8 |
+
// 1024-byte chunks, 64-byte blocks, 7-round compression, binary tree of chaining values.
|
| 9 |
+
|
| 10 |
+
const IV = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];
|
| 11 |
+
const MSG = [2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8];
|
| 12 |
+
const CHUNK_START = 1, CHUNK_END = 2, PARENT = 4, ROOT = 8, BLOCK = 64, CHUNK = 1024;
|
| 13 |
+
const rotr = (x, n) => ((x >>> n) | (x << (32 - n))) >>> 0;
|
| 14 |
+
|
| 15 |
+
function g(v, a, b, c, d, mx, my) {
|
| 16 |
+
v[a] = (v[a] + v[b] + mx) >>> 0; v[d] = rotr(v[d] ^ v[a], 16);
|
| 17 |
+
v[c] = (v[c] + v[d]) >>> 0; v[b] = rotr(v[b] ^ v[c], 12);
|
| 18 |
+
v[a] = (v[a] + v[b] + my) >>> 0; v[d] = rotr(v[d] ^ v[a], 8);
|
| 19 |
+
v[c] = (v[c] + v[d]) >>> 0; v[b] = rotr(v[b] ^ v[c], 7);
|
| 20 |
+
}
|
| 21 |
+
function roundFn(v, m) {
|
| 22 |
+
g(v, 0, 4, 8, 12, m[0], m[1]); g(v, 1, 5, 9, 13, m[2], m[3]);
|
| 23 |
+
g(v, 2, 6, 10, 14, m[4], m[5]); g(v, 3, 7, 11, 15, m[6], m[7]);
|
| 24 |
+
g(v, 0, 5, 10, 15, m[8], m[9]); g(v, 1, 6, 11, 12, m[10], m[11]);
|
| 25 |
+
g(v, 2, 7, 8, 13, m[12], m[13]); g(v, 3, 4, 9, 14, m[14], m[15]);
|
| 26 |
+
}
|
| 27 |
+
// compress(cv[8], m[16], counter, blockLen, flags) → out[16]
|
| 28 |
+
function compress(cv, m0, counter, blockLen, flags) {
|
| 29 |
+
const cl = counter >>> 0, ch = Math.floor(counter / 4294967296) >>> 0;
|
| 30 |
+
const v = [cv[0], cv[1], cv[2], cv[3], cv[4], cv[5], cv[6], cv[7],
|
| 31 |
+
IV[0], IV[1], IV[2], IV[3], cl, ch, blockLen >>> 0, flags >>> 0];
|
| 32 |
+
let m = m0.slice();
|
| 33 |
+
for (let r = 0; r < 7; r++) { roundFn(v, m); if (r < 6) { const p = new Array(16); for (let i = 0; i < 16; i++) p[i] = m[MSG[i]]; m = p; } }
|
| 34 |
+
const out = new Array(16);
|
| 35 |
+
for (let i = 0; i < 8; i++) { out[i] = (v[i] ^ v[i + 8]) >>> 0; out[i + 8] = (v[i + 8] ^ cv[i]) >>> 0; }
|
| 36 |
+
return out;
|
| 37 |
+
}
|
| 38 |
+
// read a 64-byte block (zero-padded) into 16 LE u32 words
|
| 39 |
+
function words(bytes, off, len) {
|
| 40 |
+
const m = new Array(16).fill(0);
|
| 41 |
+
for (let i = 0; i < len; i++) m[i >> 2] |= bytes[off + i] << ((i & 3) * 8);
|
| 42 |
+
for (let i = 0; i < 16; i++) m[i] >>>= 0;
|
| 43 |
+
return m;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
// An "output" node: its chaining value, or (with ROOT) its 32-byte hash.
|
| 47 |
+
function nodeChainingValue(o) { return compress(o.cv, o.m, o.counter, o.blockLen, o.flags).slice(0, 8); }
|
| 48 |
+
function nodeRootBytes(o) {
|
| 49 |
+
const out = compress(o.cv, o.m, 0, o.blockLen, o.flags | ROOT); // output block counter 0 → first 32 bytes
|
| 50 |
+
const b = new Uint8Array(32);
|
| 51 |
+
for (let i = 0; i < 8; i++) { const w = out[i]; b[i * 4] = w & 255; b[i * 4 + 1] = (w >>> 8) & 255; b[i * 4 + 2] = (w >>> 16) & 255; b[i * 4 + 3] = (w >>> 24) & 255; }
|
| 52 |
+
return b;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
// One chunk (≤1024 bytes) → its output node (its last block carries CHUNK_END).
|
| 56 |
+
function chunkNode(bytes, start, len, counter) {
|
| 57 |
+
let cv = IV.slice();
|
| 58 |
+
const nBlocks = Math.max(1, Math.ceil(len / BLOCK));
|
| 59 |
+
let flags = CHUNK_START;
|
| 60 |
+
for (let i = 0; i < nBlocks - 1; i++) { cv = compress(cv, words(bytes, start + i * BLOCK, BLOCK), counter, BLOCK, flags).slice(0, 8); flags = 0; }
|
| 61 |
+
const lastOff = start + (nBlocks - 1) * BLOCK;
|
| 62 |
+
const lastLen = len - (nBlocks - 1) * BLOCK; // 0..64 (0 only when len==0)
|
| 63 |
+
return { cv, m: words(bytes, lastOff, lastLen), counter, blockLen: lastLen, flags: flags | CHUNK_END };
|
| 64 |
+
}
|
| 65 |
+
function parentNode(leftCV, rightCV) {
|
| 66 |
+
const m = leftCV.concat(rightCV);
|
| 67 |
+
return { cv: IV.slice(), m, counter: 0, blockLen: BLOCK, flags: PARENT };
|
| 68 |
+
}
|
| 69 |
+
// Hash a subtree [start, start+len) starting at chunk index `counter` → an output node.
|
| 70 |
+
function subtree(bytes, start, len, counter) {
|
| 71 |
+
if (len <= CHUNK) return chunkNode(bytes, start, len, counter);
|
| 72 |
+
let left = CHUNK; while (left * 2 < len) left *= 2; // largest power-of-two chunk span < len
|
| 73 |
+
const leftCV = nodeChainingValue(subtree(bytes, start, left, counter));
|
| 74 |
+
const rightCV = nodeChainingValue(subtree(bytes, start + left, len - left, counter + left / CHUNK));
|
| 75 |
+
return parentNode(leftCV, rightCV);
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
export function blake3(bytes) {
|
| 79 |
+
const b = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
| 80 |
+
return nodeRootBytes(subtree(b, 0, b.length, 0));
|
| 81 |
+
}
|
| 82 |
+
export function blake3hex(bytes) {
|
| 83 |
+
const d = blake3(bytes); let s = "";
|
| 84 |
+
for (let i = 0; i < 32; i++) s += d[i].toString(16).padStart(2, "0");
|
| 85 |
+
return s;
|
| 86 |
+
}
|
| 87 |
+
// the hologram σ-axis κ-label: "blake3:" + 64 hex (the 71-byte ContentLabel).
|
| 88 |
+
export function kappaBlake3(bytes) { return "blake3:" + blake3hex(bytes); }
|
| 89 |
+
|
| 90 |
+
// ── tree internals (the σ-axis Merkle structure) ─────────────────────────────────────
|
| 91 |
+
// Exposed so holo-bao.mjs can build verified-streaming (Bao) proofs against the SAME ONE
|
| 92 |
+
// implementation (Law L2 — no second hash). These are the standard BLAKE3 tree pieces:
|
| 93 |
+
// a chunk → output node, two CVs → parent node, a node → its chaining value or ROOT bytes.
|
| 94 |
+
export const CHUNK_BYTES = CHUNK;
|
| 95 |
+
export { chunkNode, parentNode, nodeChainingValue, nodeRootBytes, subtree };
|
| 96 |
+
|
| 97 |
+
// ── incremental hasher (stream-mint without a tail pass) ─────────────────────────────
|
| 98 |
+
// BLAKE3's streaming Hasher: feed chunks via update(), get the SAME digest as one-shot blake3() at
|
| 99 |
+
// digest()/hex(). Buffers at most ONE 1024-byte chunk; each completed chunk's CV folds into a stack via the
|
| 100 |
+
// standard add-chunk-cv merge (collapse while the chunk count is even), and digest() merges the final chunk
|
| 101 |
+
// down the stack with the ROOT flag — the canonical left-balanced tree, byte-identical to blake3() across
|
| 102 |
+
// chunk boundaries. Lets the streaming seam mint a κ AS bytes arrive instead of re-hashing the whole buffer.
|
| 103 |
+
export function createBlake3() {
|
| 104 |
+
const stack = []; // CVs of completed left subtrees (bottom = leftmost)
|
| 105 |
+
let counter = 0; // index of the current chunk
|
| 106 |
+
const buf = new Uint8Array(CHUNK);
|
| 107 |
+
let bufLen = 0;
|
| 108 |
+
const add = (cv, totalChunks) => { let t = totalChunks, c = cv; while ((t & 1) === 0) { c = nodeChainingValue(parentNode(stack.pop(), c)); t >>= 1; } stack.push(c); };
|
| 109 |
+
const api = {
|
| 110 |
+
update(bytes) {
|
| 111 |
+
const b = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
| 112 |
+
let off = 0; const n = b.length;
|
| 113 |
+
while (off < n) {
|
| 114 |
+
if (bufLen === CHUNK) { const cv = nodeChainingValue(chunkNode(buf, 0, CHUNK, counter)); counter++; add(cv, counter); bufLen = 0; }
|
| 115 |
+
const take = Math.min(CHUNK - bufLen, n - off);
|
| 116 |
+
buf.set(b.subarray(off, off + take), bufLen); bufLen += take; off += take;
|
| 117 |
+
}
|
| 118 |
+
return api;
|
| 119 |
+
},
|
| 120 |
+
digest() {
|
| 121 |
+
let node = chunkNode(buf, 0, bufLen, counter);
|
| 122 |
+
for (let i = stack.length - 1; i >= 0; i--) node = parentNode(stack[i], nodeChainingValue(node));
|
| 123 |
+
return nodeRootBytes(node);
|
| 124 |
+
},
|
| 125 |
+
hex() { const d = api.digest(); let s = ""; for (let i = 0; i < 32; i++) s += d[i].toString(16).padStart(2, "0"); return s; },
|
| 126 |
+
};
|
| 127 |
+
return api;
|
| 128 |
+
}
|
b/f4e2a6b63fe6250e0eb3126ee6cb5598ac4c372fd0deea7fe8ef7a2eec63eeb1
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
(module=>{
|
| 2 |
+
"use strict";var xe=Object.defineProperty;var _e=Object.getOwnPropertyDescriptor;var Ve=Object.getOwnPropertyNames;var Ye=Object.prototype.hasOwnProperty;var Je=(e,t)=>{for(var n in t)xe(e,n,{get:t[n],enumerable:!0})},Ge=(e,t,n,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of Ve(t))!Ye.call(e,a)&&a!==n&&xe(e,a,{get:()=>t[a],enumerable:!(i=_e(t,a))||i.enumerable});return e};var Qe=e=>Ge(xe({},"__esModule",{value:!0}),e);var ne=(e,t,n)=>new Promise((i,a)=>{var l=f=>{try{s(n.next(f))}catch(h){a(h)}},g=f=>{try{s(n.throw(f))}catch(h){a(h)}},s=f=>f.done?i(f.value):Promise.resolve(f.value).then(l,g);s((n=n.apply(e,t)).next())});var Re={};Je(Re,{analyzeMetafile:()=>mt,analyzeMetafileSync:()=>wt,build:()=>ft,buildSync:()=>yt,context:()=>dt,default:()=>Ot,formatMessages:()=>gt,formatMessagesSync:()=>bt,initialize:()=>Rt,stop:()=>vt,transform:()=>pt,transformSync:()=>ht,version:()=>ct});module.exports=Qe(Re);function Se(e){let t=i=>{if(i===null)n.write8(0);else if(typeof i=="boolean")n.write8(1),n.write8(+i);else if(typeof i=="number")n.write8(2),n.write32(i|0);else if(typeof i=="string")n.write8(3),n.write(Z(i));else if(i instanceof Uint8Array)n.write8(4),n.write(i);else if(i instanceof Array){n.write8(5),n.write32(i.length);for(let a of i)t(a)}else{let a=Object.keys(i);n.write8(6),n.write32(a.length);for(let l of a)n.write(Z(l)),t(i[l])}},n=new pe;return n.write32(0),n.write32(e.id<<1|+!e.isRequest),t(e.value),Ee(n.buf,n.len-4,0),n.buf.subarray(0,n.len)}function $e(e){let t=()=>{switch(n.read8()){case 0:return null;case 1:return!!n.read8();case 2:return n.read32();case 3:return ie(n.read());case 4:return n.read();case 5:{let g=n.read32(),s=[];for(let f=0;f<g;f++)s.push(t());return s}case 6:{let g=n.read32(),s={};for(let f=0;f<g;f++)s[ie(n.read())]=t();return s}default:throw new Error("Invalid packet")}},n=new pe(e),i=n.read32(),a=(i&1)===0;i>>>=1;let l=t();if(n.ptr!==e.length)throw new Error("Invalid packet");return{id:i,isRequest:a,value:l}}var pe=class{constructor(t=new Uint8Array(1024)){this.buf=t;this.len=0;this.ptr=0}_write(t){if(this.len+t>this.buf.length){let n=new Uint8Array((this.len+t)*2);n.set(this.buf),this.buf=n}return this.len+=t,this.len-t}write8(t){let n=this._write(1);this.buf[n]=t}write32(t){let n=this._write(4);Ee(this.buf,t,n)}write(t){let n=this._write(4+t.length);Ee(this.buf,t.length,n),this.buf.set(t,n+4)}_read(t){if(this.ptr+t>this.buf.length)throw new Error("Invalid packet");return this.ptr+=t,this.ptr-t}read8(){return this.buf[this._read(1)]}read32(){return ke(this.buf,this._read(4))}read(){let t=this.read32(),n=new Uint8Array(t),i=this._read(n.length);return n.set(this.buf.subarray(i,i+t)),n}},Z,ie,Oe;if(typeof TextEncoder!="undefined"&&typeof TextDecoder!="undefined"){let e=new TextEncoder,t=new TextDecoder;Z=n=>e.encode(n),ie=n=>t.decode(n),Oe='new TextEncoder().encode("")'}else if(typeof Buffer!="undefined")Z=e=>Buffer.from(e),ie=e=>{let{buffer:t,byteOffset:n,byteLength:i}=e;return Buffer.from(t,n,i).toString()},Oe='Buffer.from("")';else throw new Error("No UTF-8 codec found");if(!(Z("")instanceof Uint8Array))throw new Error(`Invariant violation: "${Oe} instanceof Uint8Array" is incorrectly false
|
| 3 |
+
|
| 4 |
+
This indicates that your JavaScript environment is broken. You cannot use
|
| 5 |
+
esbuild in this environment because esbuild relies on this invariant. This
|
| 6 |
+
is not a problem with esbuild. You need to fix your environment instead.
|
| 7 |
+
`);function ke(e,t){return e[t++]|e[t++]<<8|e[t++]<<16|e[t++]<<24}function Ee(e,t,n){e[n++]=t,e[n++]=t>>8,e[n++]=t>>16,e[n++]=t>>24}var _=JSON.stringify,Me="warning",Ce="silent";function Ae(e){if(J(e,"target"),e.indexOf(",")>=0)throw new Error(`Invalid target: ${e}`);return e}var ye=()=>null,I=e=>typeof e=="boolean"?null:"a boolean",y=e=>typeof e=="string"?null:"a string",he=e=>e instanceof RegExp?null:"a RegExp object",se=e=>typeof e=="number"&&e===(e|0)?null:"an integer",Pe=e=>typeof e=="function"?null:"a function",z=e=>Array.isArray(e)?null:"an array",ee=e=>typeof e=="object"&&e!==null&&!Array.isArray(e)?null:"an object",Xe=e=>typeof e=="object"&&e!==null?null:"an array or an object",Ze=e=>e instanceof WebAssembly.Module?null:"a WebAssembly.Module",Fe=e=>typeof e=="object"&&!Array.isArray(e)?null:"an object or null",De=e=>typeof e=="string"||typeof e=="boolean"?null:"a string or a boolean",et=e=>typeof e=="string"||typeof e=="object"&&e!==null&&!Array.isArray(e)?null:"a string or an object",tt=e=>typeof e=="string"||Array.isArray(e)?null:"a string or an array",Be=e=>typeof e=="string"||e instanceof Uint8Array?null:"a string or a Uint8Array",nt=e=>typeof e=="string"||e instanceof URL?null:"a string or a URL";function r(e,t,n,i){let a=e[n];if(t[n+""]=!0,a===void 0)return;let l=i(a);if(l!==null)throw new Error(`${_(n)} must be ${l}`);return a}function V(e,t,n){for(let i in e)if(!(i in t))throw new Error(`Invalid option ${n}: ${_(i)}`)}function Ue(e){let t=Object.create(null),n=r(e,t,"wasmURL",nt),i=r(e,t,"wasmModule",Ze),a=r(e,t,"worker",I);return V(e,t,"in initialize() call"),{wasmURL:n,wasmModule:i,worker:a}}function je(e){let t;if(e!==void 0){t=Object.create(null);for(let n in e){let i=e[n];if(typeof i=="string"||i===!1)t[n]=i;else throw new Error(`Expected ${_(n)} in mangle cache to map to either a string or false`)}}return t}function be(e,t,n,i,a){let l=r(t,n,"color",I),g=r(t,n,"logLevel",y),s=r(t,n,"logLimit",se);l!==void 0?e.push(`--color=${l}`):i&&e.push("--color=true"),e.push(`--log-level=${g||a}`),e.push(`--log-limit=${s||0}`)}function J(e,t,n){if(typeof e!="string")throw new Error(`Expected value for ${t}${n!==void 0?" "+_(n):""} to be a string, got ${typeof e} instead`);return e}function Le(e,t,n){let i=r(t,n,"legalComments",y),a=r(t,n,"sourceRoot",y),l=r(t,n,"sourcesContent",I),g=r(t,n,"target",tt),s=r(t,n,"format",y),f=r(t,n,"globalName",y),h=r(t,n,"mangleProps",he),w=r(t,n,"reserveProps",he),M=r(t,n,"mangleQuoted",I),L=r(t,n,"minify",I),D=r(t,n,"minifySyntax",I),q=r(t,n,"minifyWhitespace",I),G=r(t,n,"minifyIdentifiers",I),U=r(t,n,"lineLimit",se),K=r(t,n,"drop",z),Y=r(t,n,"dropLabels",z),v=r(t,n,"charset",y),m=r(t,n,"treeShaking",I),d=r(t,n,"ignoreAnnotations",I),o=r(t,n,"jsx",y),R=r(t,n,"jsxFactory",y),E=r(t,n,"jsxFragment",y),C=r(t,n,"jsxImportSource",y),T=r(t,n,"jsxDev",I),u=r(t,n,"jsxSideEffects",I),c=r(t,n,"define",ee),b=r(t,n,"logOverride",ee),S=r(t,n,"supported",ee),F=r(t,n,"pure",z),$=r(t,n,"keepNames",I),O=r(t,n,"platform",y),A=r(t,n,"tsconfigRaw",et);if(i&&e.push(`--legal-comments=${i}`),a!==void 0&&e.push(`--source-root=${a}`),l!==void 0&&e.push(`--sources-content=${l}`),g&&(Array.isArray(g)?e.push(`--target=${Array.from(g).map(Ae).join(",")}`):e.push(`--target=${Ae(g)}`)),s&&e.push(`--format=${s}`),f&&e.push(`--global-name=${f}`),O&&e.push(`--platform=${O}`),A&&e.push(`--tsconfig-raw=${typeof A=="string"?A:JSON.stringify(A)}`),L&&e.push("--minify"),D&&e.push("--minify-syntax"),q&&e.push("--minify-whitespace"),G&&e.push("--minify-identifiers"),U&&e.push(`--line-limit=${U}`),v&&e.push(`--charset=${v}`),m!==void 0&&e.push(`--tree-shaking=${m}`),d&&e.push("--ignore-annotations"),K)for(let x of K)e.push(`--drop:${J(x,"drop")}`);if(Y&&e.push(`--drop-labels=${Array.from(Y).map(x=>J(x,"dropLabels")).join(",")}`),h&&e.push(`--mangle-props=${h.source}`),w&&e.push(`--reserve-props=${w.source}`),M!==void 0&&e.push(`--mangle-quoted=${M}`),o&&e.push(`--jsx=${o}`),R&&e.push(`--jsx-factory=${R}`),E&&e.push(`--jsx-fragment=${E}`),C&&e.push(`--jsx-import-source=${C}`),T&&e.push("--jsx-dev"),u&&e.push("--jsx-side-effects"),c)for(let x in c){if(x.indexOf("=")>=0)throw new Error(`Invalid define: ${x}`);e.push(`--define:${x}=${J(c[x],"define",x)}`)}if(b)for(let x in b){if(x.indexOf("=")>=0)throw new Error(`Invalid log override: ${x}`);e.push(`--log-override:${x}=${J(b[x],"log override",x)}`)}if(S)for(let x in S){if(x.indexOf("=")>=0)throw new Error(`Invalid supported: ${x}`);let k=S[x];if(typeof k!="boolean")throw new Error(`Expected value for supported ${_(x)} to be a boolean, got ${typeof k} instead`);e.push(`--supported:${x}=${k}`)}if(F)for(let x of F)e.push(`--pure:${J(x,"pure")}`);$&&e.push("--keep-names")}function rt(e,t,n,i,a){var ae;let l=[],g=[],s=Object.create(null),f=null,h=null;be(l,t,s,n,i),Le(l,t,s);let w=r(t,s,"sourcemap",De),M=r(t,s,"bundle",I),L=r(t,s,"splitting",I),D=r(t,s,"preserveSymlinks",I),q=r(t,s,"metafile",I),G=r(t,s,"outfile",y),U=r(t,s,"outdir",y),K=r(t,s,"outbase",y),Y=r(t,s,"tsconfig",y),v=r(t,s,"resolveExtensions",z),m=r(t,s,"nodePaths",z),d=r(t,s,"mainFields",z),o=r(t,s,"conditions",z),R=r(t,s,"external",z),E=r(t,s,"packages",y),C=r(t,s,"alias",ee),T=r(t,s,"loader",ee),u=r(t,s,"outExtension",ee),c=r(t,s,"publicPath",y),b=r(t,s,"entryNames",y),S=r(t,s,"chunkNames",y),F=r(t,s,"assetNames",y),$=r(t,s,"inject",z),O=r(t,s,"banner",ee),A=r(t,s,"footer",ee),x=r(t,s,"entryPoints",Xe),k=r(t,s,"absWorkingDir",y),P=r(t,s,"stdin",ee),j=(ae=r(t,s,"write",I))!=null?ae:a,B=r(t,s,"allowOverwrite",I),H=r(t,s,"mangleCache",ee);if(s.plugins=!0,V(t,s,`in ${e}() call`),w&&l.push(`--sourcemap${w===!0?"":`=${w}`}`),M&&l.push("--bundle"),B&&l.push("--allow-overwrite"),L&&l.push("--splitting"),D&&l.push("--preserve-symlinks"),q&&l.push("--metafile"),G&&l.push(`--outfile=${G}`),U&&l.push(`--outdir=${U}`),K&&l.push(`--outbase=${K}`),Y&&l.push(`--tsconfig=${Y}`),E&&l.push(`--packages=${E}`),v){let p=[];for(let N of v){if(J(N,"resolve extension"),N.indexOf(",")>=0)throw new Error(`Invalid resolve extension: ${N}`);p.push(N)}l.push(`--resolve-extensions=${p.join(",")}`)}if(c&&l.push(`--public-path=${c}`),b&&l.push(`--entry-names=${b}`),S&&l.push(`--chunk-names=${S}`),F&&l.push(`--asset-names=${F}`),d){let p=[];for(let N of d){if(J(N,"main field"),N.indexOf(",")>=0)throw new Error(`Invalid main field: ${N}`);p.push(N)}l.push(`--main-fields=${p.join(",")}`)}if(o){let p=[];for(let N of o){if(J(N,"condition"),N.indexOf(",")>=0)throw new Error(`Invalid condition: ${N}`);p.push(N)}l.push(`--conditions=${p.join(",")}`)}if(R)for(let p of R)l.push(`--external:${J(p,"external")}`);if(C)for(let p in C){if(p.indexOf("=")>=0)throw new Error(`Invalid package name in alias: ${p}`);l.push(`--alias:${p}=${J(C[p],"alias",p)}`)}if(O)for(let p in O){if(p.indexOf("=")>=0)throw new Error(`Invalid banner file type: ${p}`);l.push(`--banner:${p}=${J(O[p],"banner",p)}`)}if(A)for(let p in A){if(p.indexOf("=")>=0)throw new Error(`Invalid footer file type: ${p}`);l.push(`--footer:${p}=${J(A[p],"footer",p)}`)}if($)for(let p of $)l.push(`--inject:${J(p,"inject")}`);if(T)for(let p in T){if(p.indexOf("=")>=0)throw new Error(`Invalid loader extension: ${p}`);l.push(`--loader:${p}=${J(T[p],"loader",p)}`)}if(u)for(let p in u){if(p.indexOf("=")>=0)throw new Error(`Invalid out extension: ${p}`);l.push(`--out-extension:${p}=${J(u[p],"out extension",p)}`)}if(x)if(Array.isArray(x))for(let p=0,N=x.length;p<N;p++){let X=x[p];if(typeof X=="object"&&X!==null){let te=Object.create(null),Q=r(X,te,"in",y),ce=r(X,te,"out",y);if(V(X,te,"in entry point at index "+p),Q===void 0)throw new Error('Missing property "in" for entry point at index '+p);if(ce===void 0)throw new Error('Missing property "out" for entry point at index '+p);g.push([ce,Q])}else g.push(["",J(X,"entry point at index "+p)])}else for(let p in x)g.push([p,J(x[p],"entry point",p)]);if(P){let p=Object.create(null),N=r(P,p,"contents",Be),X=r(P,p,"resolveDir",y),te=r(P,p,"sourcefile",y),Q=r(P,p,"loader",y);V(P,p,'in "stdin" object'),te&&l.push(`--sourcefile=${te}`),Q&&l.push(`--loader=${Q}`),X&&(h=X),typeof N=="string"?f=Z(N):N instanceof Uint8Array&&(f=N)}let W=[];if(m)for(let p of m)p+="",W.push(p);return{entries:g,flags:l,write:j,stdinContents:f,stdinResolveDir:h,absWorkingDir:k,nodePaths:W,mangleCache:je(H)}}function it(e,t,n,i){let a=[],l=Object.create(null);be(a,t,l,n,i),Le(a,t,l);let g=r(t,l,"sourcemap",De),s=r(t,l,"sourcefile",y),f=r(t,l,"loader",y),h=r(t,l,"banner",y),w=r(t,l,"footer",y),M=r(t,l,"mangleCache",ee);return V(t,l,`in ${e}() call`),g&&a.push(`--sourcemap=${g===!0?"external":g}`),s&&a.push(`--sourcefile=${s}`),f&&a.push(`--loader=${f}`),h&&a.push(`--banner=${h}`),w&&a.push(`--footer=${w}`),{flags:a,mangleCache:je(M)}}function qe(e){let t={},n={didClose:!1,reason:""},i={},a=0,l=0,g=new Uint8Array(16*1024),s=0,f=v=>{let m=s+v.length;if(m>g.length){let o=new Uint8Array(m*2);o.set(g),g=o}g.set(v,s),s+=v.length;let d=0;for(;d+4<=s;){let o=ke(g,d);if(d+4+o>s)break;d+=4,q(g.subarray(d,d+o)),d+=o}d>0&&(g.copyWithin(0,d,s),s-=d)},h=v=>{n.didClose=!0,v&&(n.reason=": "+(v.message||v));let m="The service was stopped"+n.reason;for(let d in i)i[d](m,null);i={}},w=(v,m,d)=>{if(n.didClose)return d("The service is no longer running"+n.reason,null);let o=a++;i[o]=(R,E)=>{try{d(R,E)}finally{v&&v.unref()}},v&&v.ref(),e.writeToStdin(Se({id:o,isRequest:!0,value:m}))},M=(v,m)=>{if(n.didClose)throw new Error("The service is no longer running"+n.reason);e.writeToStdin(Se({id:v,isRequest:!1,value:m}))},L=(v,m)=>ne(this,null,function*(){try{if(m.command==="ping"){M(v,{});return}if(typeof m.key=="number"){let d=t[m.key];if(!d)return;let o=d[m.command];if(o){yield o(v,m);return}}throw new Error("Invalid command: "+m.command)}catch(d){let o=[le(d,e,null,void 0,"")];try{M(v,{errors:o})}catch(R){}}}),D=!0,q=v=>{if(D){D=!1;let d=String.fromCharCode(...v);if(d!=="0.21.5")throw new Error(`Cannot start service: Host version "0.21.5" does not match binary version ${_(d)}`);return}let m=$e(v);if(m.isRequest)L(m.id,m.value);else{let d=i[m.id];delete i[m.id],m.value.error?d(m.value.error,{}):d(null,m.value)}};return{readFromStdout:f,afterClose:h,service:{buildOrContext:({callName:v,refs:m,options:d,isTTY:o,defaultWD:R,callback:E})=>{let C=0,T=l++,u={},c={ref(){++C===1&&m&&m.ref()},unref(){--C===0&&(delete t[T],m&&m.unref())}};t[T]=u,c.ref(),lt(v,T,w,M,c,e,u,d,o,R,(b,S)=>{try{E(b,S)}finally{c.unref()}})},transform:({callName:v,refs:m,input:d,options:o,isTTY:R,fs:E,callback:C})=>{let T=Ne(),u=c=>{try{if(typeof d!="string"&&!(d instanceof Uint8Array))throw new Error('The input to "transform" must be a string or a Uint8Array');let{flags:b,mangleCache:S}=it(v,o,R,Ce),F={command:"transform",flags:b,inputFS:c!==null,input:c!==null?Z(c):typeof d=="string"?Z(d):d};S&&(F.mangleCache=S),w(m,F,($,O)=>{if($)return C(new Error($),null);let A=ue(O.errors,T),x=ue(O.warnings,T),k=1,P=()=>{if(--k===0){let j={warnings:x,code:O.code,map:O.map,mangleCache:void 0,legalComments:void 0};"legalComments"in O&&(j.legalComments=O==null?void 0:O.legalComments),O.mangleCache&&(j.mangleCache=O==null?void 0:O.mangleCache),C(null,j)}};if(A.length>0)return C(fe("Transform failed",A,x),null);O.codeFS&&(k++,E.readFile(O.code,(j,B)=>{j!==null?C(j,null):(O.code=B,P())})),O.mapFS&&(k++,E.readFile(O.map,(j,B)=>{j!==null?C(j,null):(O.map=B,P())})),P()})}catch(b){let S=[];try{be(S,o,{},R,Ce)}catch($){}let F=le(b,e,T,void 0,"");w(m,{command:"error",flags:S,error:F},()=>{F.detail=T.load(F.detail),C(fe("Transform failed",[F],[]),null)})}};if((typeof d=="string"||d instanceof Uint8Array)&&d.length>1024*1024){let c=u;u=()=>E.writeFile(d,c)}u(null)},formatMessages:({callName:v,refs:m,messages:d,options:o,callback:R})=>{if(!o)throw new Error(`Missing second argument in ${v}() call`);let E={},C=r(o,E,"kind",y),T=r(o,E,"color",I),u=r(o,E,"terminalWidth",se);if(V(o,E,`in ${v}() call`),C===void 0)throw new Error(`Missing "kind" in ${v}() call`);if(C!=="error"&&C!=="warning")throw new Error(`Expected "kind" to be "error" or "warning" in ${v}() call`);let c={command:"format-msgs",messages:re(d,"messages",null,"",u),isWarning:C==="warning"};T!==void 0&&(c.color=T),u!==void 0&&(c.terminalWidth=u),w(m,c,(b,S)=>{if(b)return R(new Error(b),null);R(null,S.messages)})},analyzeMetafile:({callName:v,refs:m,metafile:d,options:o,callback:R})=>{o===void 0&&(o={});let E={},C=r(o,E,"color",I),T=r(o,E,"verbose",I);V(o,E,`in ${v}() call`);let u={command:"analyze-metafile",metafile:d};C!==void 0&&(u.color=C),T!==void 0&&(u.verbose=T),w(m,u,(c,b)=>{if(c)return R(new Error(c),null);R(null,b.result)})}}}}function lt(e,t,n,i,a,l,g,s,f,h,w){let M=Ne(),L=e==="context",D=(U,K)=>{let Y=[];try{be(Y,s,{},f,Me)}catch(m){}let v=le(U,l,M,void 0,K);n(a,{command:"error",flags:Y,error:v},()=>{v.detail=M.load(v.detail),w(fe(L?"Context failed":"Build failed",[v],[]),null)})},q;if(typeof s=="object"){let U=s.plugins;if(U!==void 0){if(!Array.isArray(U))return D(new Error('"plugins" must be an array'),"");q=U}}if(q&&q.length>0){if(l.isSync)return D(new Error("Cannot use plugins in synchronous API calls"),"");st(t,n,i,a,l,g,s,q,M).then(U=>{if(!U.ok)return D(U.error,U.pluginName);try{G(U.requestPlugins,U.runOnEndCallbacks,U.scheduleOnDisposeCallbacks)}catch(K){D(K,"")}},U=>D(U,""));return}try{G(null,(U,K)=>K([],[]),()=>{})}catch(U){D(U,"")}function G(U,K,Y){let v=l.hasFS,{entries:m,flags:d,write:o,stdinContents:R,stdinResolveDir:E,absWorkingDir:C,nodePaths:T,mangleCache:u}=rt(e,s,f,Me,v);if(o&&!l.hasFS)throw new Error('The "write" option is unavailable in this environment');let c={command:"build",key:t,entries:m,flags:d,write:o,stdinContents:R,stdinResolveDir:E,absWorkingDir:C||h,nodePaths:T,context:L};U&&(c.plugins=U),u&&(c.mangleCache=u);let b=($,O)=>{let A={errors:ue($.errors,M),warnings:ue($.warnings,M),outputFiles:void 0,metafile:void 0,mangleCache:void 0},x=A.errors.slice(),k=A.warnings.slice();$.outputFiles&&(A.outputFiles=$.outputFiles.map(at)),$.metafile&&(A.metafile=JSON.parse($.metafile)),$.mangleCache&&(A.mangleCache=$.mangleCache),$.writeToStdout!==void 0&&console.log(ie($.writeToStdout).replace(/\n$/,"")),K(A,(P,j)=>{if(x.length>0||P.length>0){let B=fe("Build failed",x.concat(P),k.concat(j));return O(B,null,P,j)}O(null,A,P,j)})},S,F;L&&(g["on-end"]=($,O)=>new Promise(A=>{b(O,(x,k,P,j)=>{let B={errors:P,warnings:j};F&&F(x,k),S=void 0,F=void 0,i($,B),A()})})),n(a,c,($,O)=>{if($)return w(new Error($),null);if(!L)return b(O,(k,P)=>(Y(),w(k,P)));if(O.errors.length>0)return w(fe("Context failed",O.errors,O.warnings),null);let A=!1,x={rebuild:()=>(S||(S=new Promise((k,P)=>{let j;F=(H,W)=>{j||(j=()=>H?P(H):k(W))};let B=()=>{n(a,{command:"rebuild",key:t},(W,ae)=>{W?P(new Error(W)):j?j():B()})};B()})),S),watch:(k={})=>new Promise((P,j)=>{if(!l.hasFS)throw new Error('Cannot use the "watch" API in this environment');V(k,{},"in watch() call"),n(a,{command:"watch",key:t},W=>{W?j(new Error(W)):P(void 0)})}),serve:(k={})=>new Promise((P,j)=>{if(!l.hasFS)throw new Error('Cannot use the "serve" API in this environment');let B={},H=r(k,B,"port",se),W=r(k,B,"host",y),ae=r(k,B,"servedir",y),p=r(k,B,"keyfile",y),N=r(k,B,"certfile",y),X=r(k,B,"fallback",y),te=r(k,B,"onRequest",Pe);V(k,B,"in serve() call");let Q={command:"serve",key:t,onRequest:!!te};H!==void 0&&(Q.port=H),W!==void 0&&(Q.host=W),ae!==void 0&&(Q.servedir=ae),p!==void 0&&(Q.keyfile=p),N!==void 0&&(Q.certfile=N),X!==void 0&&(Q.fallback=X),n(a,Q,(ce,We)=>{if(ce)return j(new Error(ce));te&&(g["serve-request"]=(ze,Ke)=>{te(Ke.args),i(ze,{})}),P(We)})}),cancel:()=>new Promise(k=>{if(A)return k();n(a,{command:"cancel",key:t},()=>{k()})}),dispose:()=>new Promise(k=>{if(A)return k();A=!0,n(a,{command:"dispose",key:t},()=>{k(),Y(),a.unref()})})};a.ref(),w(null,x)})}}var st=(e,t,n,i,a,l,g,s,f)=>ne(void 0,null,function*(){let h=[],w=[],M={},L={},D=[],q=0,G=0,U=[],K=!1;s=[...s];for(let m of s){let d={};if(typeof m!="object")throw new Error(`Plugin at index ${G} must be an object`);let o=r(m,d,"name",y);if(typeof o!="string"||o==="")throw new Error(`Plugin at index ${G} is missing a name`);try{let R=r(m,d,"setup",Pe);if(typeof R!="function")throw new Error("Plugin is missing a setup function");V(m,d,`on plugin ${_(o)}`);let E={name:o,onStart:!1,onEnd:!1,onResolve:[],onLoad:[]};G++;let T=R({initialOptions:g,resolve:(u,c={})=>{if(!K)throw new Error('Cannot call "resolve" before plugin setup has completed');if(typeof u!="string")throw new Error("The path to resolve must be a string");let b=Object.create(null),S=r(c,b,"pluginName",y),F=r(c,b,"importer",y),$=r(c,b,"namespace",y),O=r(c,b,"resolveDir",y),A=r(c,b,"kind",y),x=r(c,b,"pluginData",ye),k=r(c,b,"with",ee);return V(c,b,"in resolve() call"),new Promise((P,j)=>{let B={command:"resolve",path:u,key:e,pluginName:o};if(S!=null&&(B.pluginName=S),F!=null&&(B.importer=F),$!=null&&(B.namespace=$),O!=null&&(B.resolveDir=O),A!=null)B.kind=A;else throw new Error('Must specify "kind" when calling "resolve"');x!=null&&(B.pluginData=f.store(x)),k!=null&&(B.with=ot(k,"with")),t(i,B,(H,W)=>{H!==null?j(new Error(H)):P({errors:ue(W.errors,f),warnings:ue(W.warnings,f),path:W.path,external:W.external,sideEffects:W.sideEffects,namespace:W.namespace,suffix:W.suffix,pluginData:f.load(W.pluginData)})})})},onStart(u){let c='This error came from the "onStart" callback registered here:',b=ge(new Error(c),a,"onStart");h.push({name:o,callback:u,note:b}),E.onStart=!0},onEnd(u){let c='This error came from the "onEnd" callback registered here:',b=ge(new Error(c),a,"onEnd");w.push({name:o,callback:u,note:b}),E.onEnd=!0},onResolve(u,c){let b='This error came from the "onResolve" callback registered here:',S=ge(new Error(b),a,"onResolve"),F={},$=r(u,F,"filter",he),O=r(u,F,"namespace",y);if(V(u,F,`in onResolve() call for plugin ${_(o)}`),$==null)throw new Error("onResolve() call is missing a filter");let A=q++;M[A]={name:o,callback:c,note:S},E.onResolve.push({id:A,filter:$.source,namespace:O||""})},onLoad(u,c){let b='This error came from the "onLoad" callback registered here:',S=ge(new Error(b),a,"onLoad"),F={},$=r(u,F,"filter",he),O=r(u,F,"namespace",y);if(V(u,F,`in onLoad() call for plugin ${_(o)}`),$==null)throw new Error("onLoad() call is missing a filter");let A=q++;L[A]={name:o,callback:c,note:S},E.onLoad.push({id:A,filter:$.source,namespace:O||""})},onDispose(u){D.push(u)},esbuild:a.esbuild});T&&(yield T),U.push(E)}catch(R){return{ok:!1,error:R,pluginName:o}}}l["on-start"]=(m,d)=>ne(void 0,null,function*(){let o={errors:[],warnings:[]};yield Promise.all(h.map(T=>ne(void 0,[T],function*({name:R,callback:E,note:C}){try{let u=yield E();if(u!=null){if(typeof u!="object")throw new Error(`Expected onStart() callback in plugin ${_(R)} to return an object`);let c={},b=r(u,c,"errors",z),S=r(u,c,"warnings",z);V(u,c,`from onStart() callback in plugin ${_(R)}`),b!=null&&o.errors.push(...re(b,"errors",f,R,void 0)),S!=null&&o.warnings.push(...re(S,"warnings",f,R,void 0))}}catch(u){o.errors.push(le(u,a,f,C&&C(),R))}}))),n(m,o)}),l["on-resolve"]=(m,d)=>ne(void 0,null,function*(){let o={},R="",E,C;for(let T of d.ids)try{({name:R,callback:E,note:C}=M[T]);let u=yield E({path:d.path,importer:d.importer,namespace:d.namespace,resolveDir:d.resolveDir,kind:d.kind,pluginData:f.load(d.pluginData),with:d.with});if(u!=null){if(typeof u!="object")throw new Error(`Expected onResolve() callback in plugin ${_(R)} to return an object`);let c={},b=r(u,c,"pluginName",y),S=r(u,c,"path",y),F=r(u,c,"namespace",y),$=r(u,c,"suffix",y),O=r(u,c,"external",I),A=r(u,c,"sideEffects",I),x=r(u,c,"pluginData",ye),k=r(u,c,"errors",z),P=r(u,c,"warnings",z),j=r(u,c,"watchFiles",z),B=r(u,c,"watchDirs",z);V(u,c,`from onResolve() callback in plugin ${_(R)}`),o.id=T,b!=null&&(o.pluginName=b),S!=null&&(o.path=S),F!=null&&(o.namespace=F),$!=null&&(o.suffix=$),O!=null&&(o.external=O),A!=null&&(o.sideEffects=A),x!=null&&(o.pluginData=f.store(x)),k!=null&&(o.errors=re(k,"errors",f,R,void 0)),P!=null&&(o.warnings=re(P,"warnings",f,R,void 0)),j!=null&&(o.watchFiles=me(j,"watchFiles")),B!=null&&(o.watchDirs=me(B,"watchDirs"));break}}catch(u){o={id:T,errors:[le(u,a,f,C&&C(),R)]};break}n(m,o)}),l["on-load"]=(m,d)=>ne(void 0,null,function*(){let o={},R="",E,C;for(let T of d.ids)try{({name:R,callback:E,note:C}=L[T]);let u=yield E({path:d.path,namespace:d.namespace,suffix:d.suffix,pluginData:f.load(d.pluginData),with:d.with});if(u!=null){if(typeof u!="object")throw new Error(`Expected onLoad() callback in plugin ${_(R)} to return an object`);let c={},b=r(u,c,"pluginName",y),S=r(u,c,"contents",Be),F=r(u,c,"resolveDir",y),$=r(u,c,"pluginData",ye),O=r(u,c,"loader",y),A=r(u,c,"errors",z),x=r(u,c,"warnings",z),k=r(u,c,"watchFiles",z),P=r(u,c,"watchDirs",z);V(u,c,`from onLoad() callback in plugin ${_(R)}`),o.id=T,b!=null&&(o.pluginName=b),S instanceof Uint8Array?o.contents=S:S!=null&&(o.contents=Z(S)),F!=null&&(o.resolveDir=F),$!=null&&(o.pluginData=f.store($)),O!=null&&(o.loader=O),A!=null&&(o.errors=re(A,"errors",f,R,void 0)),x!=null&&(o.warnings=re(x,"warnings",f,R,void 0)),k!=null&&(o.watchFiles=me(k,"watchFiles")),P!=null&&(o.watchDirs=me(P,"watchDirs"));break}}catch(u){o={id:T,errors:[le(u,a,f,C&&C(),R)]};break}n(m,o)});let Y=(m,d)=>d([],[]);w.length>0&&(Y=(m,d)=>{ne(void 0,null,function*(){let o=[],R=[];for(let{name:E,callback:C,note:T}of w){let u,c;try{let b=yield C(m);if(b!=null){if(typeof b!="object")throw new Error(`Expected onEnd() callback in plugin ${_(E)} to return an object`);let S={},F=r(b,S,"errors",z),$=r(b,S,"warnings",z);V(b,S,`from onEnd() callback in plugin ${_(E)}`),F!=null&&(u=re(F,"errors",f,E,void 0)),$!=null&&(c=re($,"warnings",f,E,void 0))}}catch(b){u=[le(b,a,f,T&&T(),E)]}if(u){o.push(...u);try{m.errors.push(...u)}catch(b){}}if(c){R.push(...c);try{m.warnings.push(...c)}catch(b){}}}d(o,R)})});let v=()=>{for(let m of D)setTimeout(()=>m(),0)};return K=!0,{ok:!0,requestPlugins:U,runOnEndCallbacks:Y,scheduleOnDisposeCallbacks:v}});function Ne(){let e=new Map,t=0;return{load(n){return e.get(n)},store(n){if(n===void 0)return-1;let i=t++;return e.set(i,n),i}}}function ge(e,t,n){let i,a=!1;return()=>{if(a)return i;a=!0;try{let l=(e.stack+"").split(`
|
| 8 |
+
`);l.splice(1,1);let g=Ie(t,l,n);if(g)return i={text:e.message,location:g},i}catch(l){}}}function le(e,t,n,i,a){let l="Internal error",g=null;try{l=(e&&e.message||e)+""}catch(s){}try{g=Ie(t,(e.stack+"").split(`
|
| 9 |
+
`),"")}catch(s){}return{id:"",pluginName:a,text:l,location:g,notes:i?[i]:[],detail:n?n.store(e):-1}}function Ie(e,t,n){let i=" at ";if(e.readFileSync&&!t[0].startsWith(i)&&t[1].startsWith(i))for(let a=1;a<t.length;a++){let l=t[a];if(l.startsWith(i))for(l=l.slice(i.length);;){let g=/^(?:new |async )?\S+ \((.*)\)$/.exec(l);if(g){l=g[1];continue}if(g=/^eval at \S+ \((.*)\)(?:, \S+:\d+:\d+)?$/.exec(l),g){l=g[1];continue}if(g=/^(\S+):(\d+):(\d+)$/.exec(l),g){let s;try{s=e.readFileSync(g[1],"utf8")}catch(M){break}let f=s.split(/\r\n|\r|\n|\u2028|\u2029/)[+g[2]-1]||"",h=+g[3]-1,w=f.slice(h,h+n.length)===n?n.length:0;return{file:g[1],namespace:"file",line:+g[2],column:Z(f.slice(0,h)).length,length:Z(f.slice(h,h+w)).length,lineText:f+`
|
| 10 |
+
`+t.slice(1).join(`
|
| 11 |
+
`),suggestion:""}}break}}return null}function fe(e,t,n){let i=5;e+=t.length<1?"":` with ${t.length} error${t.length<2?"":"s"}:`+t.slice(0,i+1).map((l,g)=>{if(g===i)return`
|
| 12 |
+
...`;if(!l.location)return`
|
| 13 |
+
error: ${l.text}`;let{file:s,line:f,column:h}=l.location,w=l.pluginName?`[plugin: ${l.pluginName}] `:"";return`
|
| 14 |
+
${s}:${f}:${h}: ERROR: ${w}${l.text}`}).join("");let a=new Error(e);for(let[l,g]of[["errors",t],["warnings",n]])Object.defineProperty(a,l,{configurable:!0,enumerable:!0,get:()=>g,set:s=>Object.defineProperty(a,l,{configurable:!0,enumerable:!0,value:s})});return a}function ue(e,t){for(let n of e)n.detail=t.load(n.detail);return e}function Te(e,t,n){if(e==null)return null;let i={},a=r(e,i,"file",y),l=r(e,i,"namespace",y),g=r(e,i,"line",se),s=r(e,i,"column",se),f=r(e,i,"length",se),h=r(e,i,"lineText",y),w=r(e,i,"suggestion",y);if(V(e,i,t),h){let M=h.slice(0,(s&&s>0?s:0)+(f&&f>0?f:0)+(n&&n>0?n:80));!/[\x7F-\uFFFF]/.test(M)&&!/\n/.test(h)&&(h=M)}return{file:a||"",namespace:l||"",line:g||0,column:s||0,length:f||0,lineText:h||"",suggestion:w||""}}function re(e,t,n,i,a){let l=[],g=0;for(let s of e){let f={},h=r(s,f,"id",y),w=r(s,f,"pluginName",y),M=r(s,f,"text",y),L=r(s,f,"location",Fe),D=r(s,f,"notes",z),q=r(s,f,"detail",ye),G=`in element ${g} of "${t}"`;V(s,f,G);let U=[];if(D)for(let K of D){let Y={},v=r(K,Y,"text",y),m=r(K,Y,"location",Fe);V(K,Y,G),U.push({text:v||"",location:Te(m,G,a)})}l.push({id:h||"",pluginName:w||i,text:M||"",location:Te(L,G,a),notes:U,detail:n?n.store(q):-1}),g++}return l}function me(e,t){let n=[];for(let i of e){if(typeof i!="string")throw new Error(`${_(t)} must be an array of strings`);n.push(i)}return n}function ot(e,t){let n=Object.create(null);for(let i in e){let a=e[i];if(typeof a!="string")throw new Error(`key ${_(i)} in object ${_(t)} must be a string`);n[i]=a}return n}function at({path:e,contents:t,hash:n}){let i=null;return{path:e,contents:t,hash:n,get text(){let a=this.contents;return(i===null||a!==t)&&(t=a,i=ie(a)),i}}}var ct="0.21.5",ft=e=>de().build(e),dt=e=>de().context(e),pt=(e,t)=>de().transform(e,t),gt=(e,t)=>de().formatMessages(e,t),mt=(e,t)=>de().analyzeMetafile(e,t),yt=()=>{throw new Error('The "buildSync" API only works in node')},ht=()=>{throw new Error('The "transformSync" API only works in node')},bt=()=>{throw new Error('The "formatMessagesSync" API only works in node')},wt=()=>{throw new Error('The "analyzeMetafileSync" API only works in node')},vt=()=>(we&&we(),Promise.resolve()),oe,we,ve,de=()=>{if(ve)return ve;throw oe?new Error('You need to wait for the promise returned from "initialize" to be resolved before calling this'):new Error('You need to call "initialize" before calling this')},Rt=e=>{e=Ue(e||{});let t=e.wasmURL,n=e.wasmModule,i=e.worker!==!1;if(!t&&!n)throw new Error('Must provide either the "wasmURL" option or the "wasmModule" option');if(oe)throw new Error('Cannot call "initialize" more than once');return oe=xt(t||"",n,i),oe.catch(()=>{oe=void 0}),oe},xt=(e,t,n)=>ne(void 0,null,function*(){let i;if(n){let h=new Blob(['onmessage=(postMessage=>{\n// Copyright 2018 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\nvar y=(r,f,m)=>new Promise((c,n)=>{var s=u=>{try{l(m.next(u))}catch(h){n(h)}},i=u=>{try{l(m.throw(u))}catch(h){n(h)}},l=u=>u.done?c(u.value):Promise.resolve(u.value).then(s,i);l((m=m.apply(r,f)).next())});let onmessage,globalThis={};for(let r=self;r;r=Object.getPrototypeOf(r))for(let f of Object.getOwnPropertyNames(r))f in globalThis||Object.defineProperty(globalThis,f,{get:()=>self[f]});(()=>{const r=()=>{const c=new Error("not implemented");return c.code="ENOSYS",c};if(!globalThis.fs){let c="";globalThis.fs={constants:{O_WRONLY:-1,O_RDWR:-1,O_CREAT:-1,O_TRUNC:-1,O_APPEND:-1,O_EXCL:-1},writeSync(n,s){c+=m.decode(s);const i=c.lastIndexOf(`\n`);return i!=-1&&(console.log(c.substring(0,i)),c=c.substring(i+1)),s.length},write(n,s,i,l,u,h){if(i!==0||l!==s.length||u!==null){h(r());return}const g=this.writeSync(n,s);h(null,g)},chmod(n,s,i){i(r())},chown(n,s,i,l){l(r())},close(n,s){s(r())},fchmod(n,s,i){i(r())},fchown(n,s,i,l){l(r())},fstat(n,s){s(r())},fsync(n,s){s(null)},ftruncate(n,s,i){i(r())},lchown(n,s,i,l){l(r())},link(n,s,i){i(r())},lstat(n,s){s(r())},mkdir(n,s,i){i(r())},open(n,s,i,l){l(r())},read(n,s,i,l,u,h){h(r())},readdir(n,s){s(r())},readlink(n,s){s(r())},rename(n,s,i){i(r())},rmdir(n,s){s(r())},stat(n,s){s(r())},symlink(n,s,i){i(r())},truncate(n,s,i){i(r())},unlink(n,s){s(r())},utimes(n,s,i,l){l(r())}}}if(globalThis.process||(globalThis.process={getuid(){return-1},getgid(){return-1},geteuid(){return-1},getegid(){return-1},getgroups(){throw r()},pid:-1,ppid:-1,umask(){throw r()},cwd(){throw r()},chdir(){throw r()}}),!globalThis.crypto)throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");if(!globalThis.performance)throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");if(!globalThis.TextEncoder)throw new Error("globalThis.TextEncoder is not available, polyfill required");if(!globalThis.TextDecoder)throw new Error("globalThis.TextDecoder is not available, polyfill required");const f=new TextEncoder("utf-8"),m=new TextDecoder("utf-8");globalThis.Go=class{constructor(){this.argv=["js"],this.env={},this.exit=t=>{t!==0&&console.warn("exit code:",t)},this._exitPromise=new Promise(t=>{this._resolveExitPromise=t}),this._pendingEvent=null,this._scheduledTimeouts=new Map,this._nextCallbackTimeoutID=1;const c=(t,e)=>{this.mem.setUint32(t+0,e,!0),this.mem.setUint32(t+4,Math.floor(e/4294967296),!0)},n=t=>{const e=this.mem.getUint32(t+0,!0),o=this.mem.getInt32(t+4,!0);return e+o*4294967296},s=t=>{const e=this.mem.getFloat64(t,!0);if(e===0)return;if(!isNaN(e))return e;const o=this.mem.getUint32(t,!0);return this._values[o]},i=(t,e)=>{if(typeof e=="number"&&e!==0){if(isNaN(e)){this.mem.setUint32(t+4,2146959360,!0),this.mem.setUint32(t,0,!0);return}this.mem.setFloat64(t,e,!0);return}if(e===void 0){this.mem.setFloat64(t,0,!0);return}let a=this._ids.get(e);a===void 0&&(a=this._idPool.pop(),a===void 0&&(a=this._values.length),this._values[a]=e,this._goRefCounts[a]=0,this._ids.set(e,a)),this._goRefCounts[a]++;let d=0;switch(typeof e){case"object":e!==null&&(d=1);break;case"string":d=2;break;case"symbol":d=3;break;case"function":d=4;break}this.mem.setUint32(t+4,2146959360|d,!0),this.mem.setUint32(t,a,!0)},l=t=>{const e=n(t+0),o=n(t+8);return new Uint8Array(this._inst.exports.mem.buffer,e,o)},u=t=>{const e=n(t+0),o=n(t+8),a=new Array(o);for(let d=0;d<o;d++)a[d]=s(e+d*8);return a},h=t=>{const e=n(t+0),o=n(t+8);return m.decode(new DataView(this._inst.exports.mem.buffer,e,o))},g=Date.now()-performance.now();this.importObject={go:{"runtime.wasmExit":t=>{t>>>=0;const e=this.mem.getInt32(t+8,!0);this.exited=!0,delete this._inst,delete this._values,delete this._goRefCounts,delete this._ids,delete this._idPool,this.exit(e)},"runtime.wasmWrite":t=>{t>>>=0;const e=n(t+8),o=n(t+16),a=this.mem.getInt32(t+24,!0);globalThis.fs.writeSync(e,new Uint8Array(this._inst.exports.mem.buffer,o,a))},"runtime.resetMemoryDataView":t=>{t>>>=0,this.mem=new DataView(this._inst.exports.mem.buffer)},"runtime.nanotime1":t=>{t>>>=0,c(t+8,(g+performance.now())*1e6)},"runtime.walltime":t=>{t>>>=0;const e=new Date().getTime();c(t+8,e/1e3),this.mem.setInt32(t+16,e%1e3*1e6,!0)},"runtime.scheduleTimeoutEvent":t=>{t>>>=0;const e=this._nextCallbackTimeoutID;this._nextCallbackTimeoutID++,this._scheduledTimeouts.set(e,setTimeout(()=>{for(this._resume();this._scheduledTimeouts.has(e);)console.warn("scheduleTimeoutEvent: missed timeout event"),this._resume()},n(t+8)+1)),this.mem.setInt32(t+16,e,!0)},"runtime.clearTimeoutEvent":t=>{t>>>=0;const e=this.mem.getInt32(t+8,!0);clearTimeout(this._scheduledTimeouts.get(e)),this._scheduledTimeouts.delete(e)},"runtime.getRandomData":t=>{t>>>=0,crypto.getRandomValues(l(t+8))},"syscall/js.finalizeRef":t=>{t>>>=0;const e=this.mem.getUint32(t+8,!0);if(this._goRefCounts[e]--,this._goRefCounts[e]===0){const o=this._values[e];this._values[e]=null,this._ids.delete(o),this._idPool.push(e)}},"syscall/js.stringVal":t=>{t>>>=0,i(t+24,h(t+8))},"syscall/js.valueGet":t=>{t>>>=0;const e=Reflect.get(s(t+8),h(t+16));t=this._inst.exports.getsp()>>>0,i(t+32,e)},"syscall/js.valueSet":t=>{t>>>=0,Reflect.set(s(t+8),h(t+16),s(t+32))},"syscall/js.valueDelete":t=>{t>>>=0,Reflect.deleteProperty(s(t+8),h(t+16))},"syscall/js.valueIndex":t=>{t>>>=0,i(t+24,Reflect.get(s(t+8),n(t+16)))},"syscall/js.valueSetIndex":t=>{t>>>=0,Reflect.set(s(t+8),n(t+16),s(t+24))},"syscall/js.valueCall":t=>{t>>>=0;try{const e=s(t+8),o=Reflect.get(e,h(t+16)),a=u(t+32),d=Reflect.apply(o,e,a);t=this._inst.exports.getsp()>>>0,i(t+56,d),this.mem.setUint8(t+64,1)}catch(e){t=this._inst.exports.getsp()>>>0,i(t+56,e),this.mem.setUint8(t+64,0)}},"syscall/js.valueInvoke":t=>{t>>>=0;try{const e=s(t+8),o=u(t+16),a=Reflect.apply(e,void 0,o);t=this._inst.exports.getsp()>>>0,i(t+40,a),this.mem.setUint8(t+48,1)}catch(e){t=this._inst.exports.getsp()>>>0,i(t+40,e),this.mem.setUint8(t+48,0)}},"syscall/js.valueNew":t=>{t>>>=0;try{const e=s(t+8),o=u(t+16),a=Reflect.construct(e,o);t=this._inst.exports.getsp()>>>0,i(t+40,a),this.mem.setUint8(t+48,1)}catch(e){t=this._inst.exports.getsp()>>>0,i(t+40,e),this.mem.setUint8(t+48,0)}},"syscall/js.valueLength":t=>{t>>>=0,c(t+16,parseInt(s(t+8).length))},"syscall/js.valuePrepareString":t=>{t>>>=0;const e=f.encode(String(s(t+8)));i(t+16,e),c(t+24,e.length)},"syscall/js.valueLoadString":t=>{t>>>=0;const e=s(t+8);l(t+16).set(e)},"syscall/js.valueInstanceOf":t=>{t>>>=0,this.mem.setUint8(t+24,s(t+8)instanceof s(t+16)?1:0)},"syscall/js.copyBytesToGo":t=>{t>>>=0;const e=l(t+8),o=s(t+32);if(!(o instanceof Uint8Array||o instanceof Uint8ClampedArray)){this.mem.setUint8(t+48,0);return}const a=o.subarray(0,e.length);e.set(a),c(t+40,a.length),this.mem.setUint8(t+48,1)},"syscall/js.copyBytesToJS":t=>{t>>>=0;const e=s(t+8),o=l(t+16);if(!(e instanceof Uint8Array||e instanceof Uint8ClampedArray)){this.mem.setUint8(t+48,0);return}const a=o.subarray(0,e.length);e.set(a),c(t+40,a.length),this.mem.setUint8(t+48,1)},debug:t=>{console.log(t)}}}}run(c){return y(this,null,function*(){if(!(c instanceof WebAssembly.Instance))throw new Error("Go.run: WebAssembly.Instance expected");this._inst=c,this.mem=new DataView(this._inst.exports.mem.buffer),this._values=[NaN,0,null,!0,!1,globalThis,this],this._goRefCounts=new Array(this._values.length).fill(1/0),this._ids=new Map([[0,1],[null,2],[!0,3],[!1,4],[globalThis,5],[this,6]]),this._idPool=[],this.exited=!1;let n=4096;const s=t=>{const e=n,o=f.encode(t+"\\0");return new Uint8Array(this.mem.buffer,n,o.length).set(o),n+=o.length,n%8!==0&&(n+=8-n%8),e},i=this.argv.length,l=[];this.argv.forEach(t=>{l.push(s(t))}),l.push(0),Object.keys(this.env).sort().forEach(t=>{l.push(s(`${t}=${this.env[t]}`))}),l.push(0);const h=n;if(l.forEach(t=>{this.mem.setUint32(n,t,!0),this.mem.setUint32(n+4,0,!0),n+=8}),n>=12288)throw new Error("total length of command line and environment variables exceeds limit");this._inst.exports.run(i,h),this.exited&&this._resolveExitPromise(),yield this._exitPromise})}_resume(){if(this.exited)throw new Error("Go program has already exited");this._inst.exports.resume(),this.exited&&this._resolveExitPromise()}_makeFuncWrapper(c){const n=this;return function(){const s={id:c,this:this,args:arguments};return n._pendingEvent=s,n._resume(),s.result}}}})(),onmessage=({data:r})=>{let f=new TextDecoder,m=globalThis.fs,c="";m.writeSync=(u,h)=>{if(u===1)postMessage(h);else if(u===2){c+=f.decode(h);let g=c.split(`\n`);g.length>1&&console.log(g.slice(0,-1).join(`\n`)),c=g[g.length-1]}else throw new Error("Bad write");return h.length};let n=[],s,i=0;onmessage=({data:u})=>(u.length>0&&(n.push(u),s&&s()),l),m.read=(u,h,g,t,e,o)=>{if(u!==0||g!==0||t!==h.length||e!==null)throw new Error("Bad read");if(n.length===0){s=()=>m.read(u,h,g,t,e,o);return}let a=n[0],d=Math.max(0,Math.min(t,a.length-i));h.set(a.subarray(i,i+d),g),i+=d,i===a.length&&(n.shift(),i=0),o(null,d)};let l=new globalThis.Go;return l.argv=["","--service=0.21.5"],tryToInstantiateModule(r,l).then(u=>{postMessage(null),l.run(u)},u=>{postMessage(u)}),l};function tryToInstantiateModule(r,f){return y(this,null,function*(){if(r instanceof WebAssembly.Module)return WebAssembly.instantiate(r,f.importObject);const m=yield fetch(r);if(!m.ok)throw new Error(`Failed to download ${JSON.stringify(r)}`);if("instantiateStreaming"in WebAssembly&&/^application\\/wasm($|;)/i.test(m.headers.get("Content-Type")||""))return(yield WebAssembly.instantiateStreaming(m,f.importObject)).instance;const c=yield m.arrayBuffer();return(yield WebAssembly.instantiate(c,f.importObject)).instance})}return r=>onmessage(r);})(postMessage)'],{type:"text/javascript"});i=new Worker(URL.createObjectURL(h))}else{let h=(postMessage=>{
|
| 15 |
+
// Copyright 2018 The Go Authors. All rights reserved.
|
| 16 |
+
// Use of this source code is governed by a BSD-style
|
| 17 |
+
// license that can be found in the LICENSE file.
|
| 18 |
+
var y=(r,f,m)=>new Promise((c,n)=>{var s=u=>{try{l(m.next(u))}catch(h){n(h)}},i=u=>{try{l(m.throw(u))}catch(h){n(h)}},l=u=>u.done?c(u.value):Promise.resolve(u.value).then(s,i);l((m=m.apply(r,f)).next())});let onmessage,globalThis={};for(let r=self;r;r=Object.getPrototypeOf(r))for(let f of Object.getOwnPropertyNames(r))f in globalThis||Object.defineProperty(globalThis,f,{get:()=>self[f]});(()=>{const r=()=>{const c=new Error("not implemented");return c.code="ENOSYS",c};if(!globalThis.fs){let c="";globalThis.fs={constants:{O_WRONLY:-1,O_RDWR:-1,O_CREAT:-1,O_TRUNC:-1,O_APPEND:-1,O_EXCL:-1},writeSync(n,s){c+=m.decode(s);const i=c.lastIndexOf(`
|
| 19 |
+
`);return i!=-1&&(console.log(c.substring(0,i)),c=c.substring(i+1)),s.length},write(n,s,i,l,u,h){if(i!==0||l!==s.length||u!==null){h(r());return}const g=this.writeSync(n,s);h(null,g)},chmod(n,s,i){i(r())},chown(n,s,i,l){l(r())},close(n,s){s(r())},fchmod(n,s,i){i(r())},fchown(n,s,i,l){l(r())},fstat(n,s){s(r())},fsync(n,s){s(null)},ftruncate(n,s,i){i(r())},lchown(n,s,i,l){l(r())},link(n,s,i){i(r())},lstat(n,s){s(r())},mkdir(n,s,i){i(r())},open(n,s,i,l){l(r())},read(n,s,i,l,u,h){h(r())},readdir(n,s){s(r())},readlink(n,s){s(r())},rename(n,s,i){i(r())},rmdir(n,s){s(r())},stat(n,s){s(r())},symlink(n,s,i){i(r())},truncate(n,s,i){i(r())},unlink(n,s){s(r())},utimes(n,s,i,l){l(r())}}}if(globalThis.process||(globalThis.process={getuid(){return-1},getgid(){return-1},geteuid(){return-1},getegid(){return-1},getgroups(){throw r()},pid:-1,ppid:-1,umask(){throw r()},cwd(){throw r()},chdir(){throw r()}}),!globalThis.crypto)throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");if(!globalThis.performance)throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");if(!globalThis.TextEncoder)throw new Error("globalThis.TextEncoder is not available, polyfill required");if(!globalThis.TextDecoder)throw new Error("globalThis.TextDecoder is not available, polyfill required");const f=new TextEncoder("utf-8"),m=new TextDecoder("utf-8");globalThis.Go=class{constructor(){this.argv=["js"],this.env={},this.exit=t=>{t!==0&&console.warn("exit code:",t)},this._exitPromise=new Promise(t=>{this._resolveExitPromise=t}),this._pendingEvent=null,this._scheduledTimeouts=new Map,this._nextCallbackTimeoutID=1;const c=(t,e)=>{this.mem.setUint32(t+0,e,!0),this.mem.setUint32(t+4,Math.floor(e/4294967296),!0)},n=t=>{const e=this.mem.getUint32(t+0,!0),o=this.mem.getInt32(t+4,!0);return e+o*4294967296},s=t=>{const e=this.mem.getFloat64(t,!0);if(e===0)return;if(!isNaN(e))return e;const o=this.mem.getUint32(t,!0);return this._values[o]},i=(t,e)=>{if(typeof e=="number"&&e!==0){if(isNaN(e)){this.mem.setUint32(t+4,2146959360,!0),this.mem.setUint32(t,0,!0);return}this.mem.setFloat64(t,e,!0);return}if(e===void 0){this.mem.setFloat64(t,0,!0);return}let a=this._ids.get(e);a===void 0&&(a=this._idPool.pop(),a===void 0&&(a=this._values.length),this._values[a]=e,this._goRefCounts[a]=0,this._ids.set(e,a)),this._goRefCounts[a]++;let d=0;switch(typeof e){case"object":e!==null&&(d=1);break;case"string":d=2;break;case"symbol":d=3;break;case"function":d=4;break}this.mem.setUint32(t+4,2146959360|d,!0),this.mem.setUint32(t,a,!0)},l=t=>{const e=n(t+0),o=n(t+8);return new Uint8Array(this._inst.exports.mem.buffer,e,o)},u=t=>{const e=n(t+0),o=n(t+8),a=new Array(o);for(let d=0;d<o;d++)a[d]=s(e+d*8);return a},h=t=>{const e=n(t+0),o=n(t+8);return m.decode(new DataView(this._inst.exports.mem.buffer,e,o))},g=Date.now()-performance.now();this.importObject={go:{"runtime.wasmExit":t=>{t>>>=0;const e=this.mem.getInt32(t+8,!0);this.exited=!0,delete this._inst,delete this._values,delete this._goRefCounts,delete this._ids,delete this._idPool,this.exit(e)},"runtime.wasmWrite":t=>{t>>>=0;const e=n(t+8),o=n(t+16),a=this.mem.getInt32(t+24,!0);globalThis.fs.writeSync(e,new Uint8Array(this._inst.exports.mem.buffer,o,a))},"runtime.resetMemoryDataView":t=>{t>>>=0,this.mem=new DataView(this._inst.exports.mem.buffer)},"runtime.nanotime1":t=>{t>>>=0,c(t+8,(g+performance.now())*1e6)},"runtime.walltime":t=>{t>>>=0;const e=new Date().getTime();c(t+8,e/1e3),this.mem.setInt32(t+16,e%1e3*1e6,!0)},"runtime.scheduleTimeoutEvent":t=>{t>>>=0;const e=this._nextCallbackTimeoutID;this._nextCallbackTimeoutID++,this._scheduledTimeouts.set(e,setTimeout(()=>{for(this._resume();this._scheduledTimeouts.has(e);)console.warn("scheduleTimeoutEvent: missed timeout event"),this._resume()},n(t+8)+1)),this.mem.setInt32(t+16,e,!0)},"runtime.clearTimeoutEvent":t=>{t>>>=0;const e=this.mem.getInt32(t+8,!0);clearTimeout(this._scheduledTimeouts.get(e)),this._scheduledTimeouts.delete(e)},"runtime.getRandomData":t=>{t>>>=0,crypto.getRandomValues(l(t+8))},"syscall/js.finalizeRef":t=>{t>>>=0;const e=this.mem.getUint32(t+8,!0);if(this._goRefCounts[e]--,this._goRefCounts[e]===0){const o=this._values[e];this._values[e]=null,this._ids.delete(o),this._idPool.push(e)}},"syscall/js.stringVal":t=>{t>>>=0,i(t+24,h(t+8))},"syscall/js.valueGet":t=>{t>>>=0;const e=Reflect.get(s(t+8),h(t+16));t=this._inst.exports.getsp()>>>0,i(t+32,e)},"syscall/js.valueSet":t=>{t>>>=0,Reflect.set(s(t+8),h(t+16),s(t+32))},"syscall/js.valueDelete":t=>{t>>>=0,Reflect.deleteProperty(s(t+8),h(t+16))},"syscall/js.valueIndex":t=>{t>>>=0,i(t+24,Reflect.get(s(t+8),n(t+16)))},"syscall/js.valueSetIndex":t=>{t>>>=0,Reflect.set(s(t+8),n(t+16),s(t+24))},"syscall/js.valueCall":t=>{t>>>=0;try{const e=s(t+8),o=Reflect.get(e,h(t+16)),a=u(t+32),d=Reflect.apply(o,e,a);t=this._inst.exports.getsp()>>>0,i(t+56,d),this.mem.setUint8(t+64,1)}catch(e){t=this._inst.exports.getsp()>>>0,i(t+56,e),this.mem.setUint8(t+64,0)}},"syscall/js.valueInvoke":t=>{t>>>=0;try{const e=s(t+8),o=u(t+16),a=Reflect.apply(e,void 0,o);t=this._inst.exports.getsp()>>>0,i(t+40,a),this.mem.setUint8(t+48,1)}catch(e){t=this._inst.exports.getsp()>>>0,i(t+40,e),this.mem.setUint8(t+48,0)}},"syscall/js.valueNew":t=>{t>>>=0;try{const e=s(t+8),o=u(t+16),a=Reflect.construct(e,o);t=this._inst.exports.getsp()>>>0,i(t+40,a),this.mem.setUint8(t+48,1)}catch(e){t=this._inst.exports.getsp()>>>0,i(t+40,e),this.mem.setUint8(t+48,0)}},"syscall/js.valueLength":t=>{t>>>=0,c(t+16,parseInt(s(t+8).length))},"syscall/js.valuePrepareString":t=>{t>>>=0;const e=f.encode(String(s(t+8)));i(t+16,e),c(t+24,e.length)},"syscall/js.valueLoadString":t=>{t>>>=0;const e=s(t+8);l(t+16).set(e)},"syscall/js.valueInstanceOf":t=>{t>>>=0,this.mem.setUint8(t+24,s(t+8)instanceof s(t+16)?1:0)},"syscall/js.copyBytesToGo":t=>{t>>>=0;const e=l(t+8),o=s(t+32);if(!(o instanceof Uint8Array||o instanceof Uint8ClampedArray)){this.mem.setUint8(t+48,0);return}const a=o.subarray(0,e.length);e.set(a),c(t+40,a.length),this.mem.setUint8(t+48,1)},"syscall/js.copyBytesToJS":t=>{t>>>=0;const e=s(t+8),o=l(t+16);if(!(e instanceof Uint8Array||e instanceof Uint8ClampedArray)){this.mem.setUint8(t+48,0);return}const a=o.subarray(0,e.length);e.set(a),c(t+40,a.length),this.mem.setUint8(t+48,1)},debug:t=>{console.log(t)}}}}run(c){return y(this,null,function*(){if(!(c instanceof WebAssembly.Instance))throw new Error("Go.run: WebAssembly.Instance expected");this._inst=c,this.mem=new DataView(this._inst.exports.mem.buffer),this._values=[NaN,0,null,!0,!1,globalThis,this],this._goRefCounts=new Array(this._values.length).fill(1/0),this._ids=new Map([[0,1],[null,2],[!0,3],[!1,4],[globalThis,5],[this,6]]),this._idPool=[],this.exited=!1;let n=4096;const s=t=>{const e=n,o=f.encode(t+"\0");return new Uint8Array(this.mem.buffer,n,o.length).set(o),n+=o.length,n%8!==0&&(n+=8-n%8),e},i=this.argv.length,l=[];this.argv.forEach(t=>{l.push(s(t))}),l.push(0),Object.keys(this.env).sort().forEach(t=>{l.push(s(`${t}=${this.env[t]}`))}),l.push(0);const h=n;if(l.forEach(t=>{this.mem.setUint32(n,t,!0),this.mem.setUint32(n+4,0,!0),n+=8}),n>=12288)throw new Error("total length of command line and environment variables exceeds limit");this._inst.exports.run(i,h),this.exited&&this._resolveExitPromise(),yield this._exitPromise})}_resume(){if(this.exited)throw new Error("Go program has already exited");this._inst.exports.resume(),this.exited&&this._resolveExitPromise()}_makeFuncWrapper(c){const n=this;return function(){const s={id:c,this:this,args:arguments};return n._pendingEvent=s,n._resume(),s.result}}}})(),onmessage=({data:r})=>{let f=new TextDecoder,m=globalThis.fs,c="";m.writeSync=(u,h)=>{if(u===1)postMessage(h);else if(u===2){c+=f.decode(h);let g=c.split(`
|
| 20 |
+
`);g.length>1&&console.log(g.slice(0,-1).join(`
|
| 21 |
+
`)),c=g[g.length-1]}else throw new Error("Bad write");return h.length};let n=[],s,i=0;onmessage=({data:u})=>(u.length>0&&(n.push(u),s&&s()),l),m.read=(u,h,g,t,e,o)=>{if(u!==0||g!==0||t!==h.length||e!==null)throw new Error("Bad read");if(n.length===0){s=()=>m.read(u,h,g,t,e,o);return}let a=n[0],d=Math.max(0,Math.min(t,a.length-i));h.set(a.subarray(i,i+d),g),i+=d,i===a.length&&(n.shift(),i=0),o(null,d)};let l=new globalThis.Go;return l.argv=["","--service=0.21.5"],tryToInstantiateModule(r,l).then(u=>{postMessage(null),l.run(u)},u=>{postMessage(u)}),l};function tryToInstantiateModule(r,f){return y(this,null,function*(){if(r instanceof WebAssembly.Module)return WebAssembly.instantiate(r,f.importObject);const m=yield fetch(r);if(!m.ok)throw new Error(`Failed to download ${JSON.stringify(r)}`);if("instantiateStreaming"in WebAssembly&&/^application\/wasm($|;)/i.test(m.headers.get("Content-Type")||""))return(yield WebAssembly.instantiateStreaming(m,f.importObject)).instance;const c=yield m.arrayBuffer();return(yield WebAssembly.instantiate(c,f.importObject)).instance})}return r=>onmessage(r);})(M=>i.onmessage({data:M})),w;i={onmessage:null,postMessage:M=>setTimeout(()=>w=h({data:M})),terminate(){if(w)for(let M of w._scheduledTimeouts.values())clearTimeout(M)}}}let a,l,g=new Promise((h,w)=>{a=h,l=w});i.onmessage=({data:h})=>{i.onmessage=({data:w})=>s(w),h?l(h):a()},i.postMessage(t||new URL(e,location.href).toString());let{readFromStdout:s,service:f}=qe({writeToStdin(h){i.postMessage(h)},isSync:!1,hasFS:!1,esbuild:Re});yield g,we=()=>{i.terminate(),oe=void 0,we=void 0,ve=void 0},ve={build:h=>new Promise((w,M)=>f.buildOrContext({callName:"build",refs:null,options:h,isTTY:!1,defaultWD:"/",callback:(L,D)=>L?M(L):w(D)})),context:h=>new Promise((w,M)=>f.buildOrContext({callName:"context",refs:null,options:h,isTTY:!1,defaultWD:"/",callback:(L,D)=>L?M(L):w(D)})),transform:(h,w)=>new Promise((M,L)=>f.transform({callName:"transform",refs:null,input:h,options:w||{},isTTY:!1,fs:{readFile(D,q){q(new Error("Internal error"),null)},writeFile(D,q){q(null)}},callback:(D,q)=>D?L(D):M(q)})),formatMessages:(h,w)=>new Promise((M,L)=>f.formatMessages({callName:"formatMessages",refs:null,messages:h,options:w,callback:(D,q)=>D?L(D):M(q)})),analyzeMetafile:(h,w)=>new Promise((M,L)=>f.analyzeMetafile({callName:"analyzeMetafile",refs:null,metafile:typeof h=="string"?h:JSON.stringify(h),options:w,callback:(D,q)=>D?L(D):M(q)}))}}),Ot=Re;
|
| 22 |
+
})(typeof module==="object"?module:{set exports(x){(typeof self!=="undefined"?self:this).esbuild=x}});
|