@jami-studio/core 0.92.35 → 0.92.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +12 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/deploy/route-discovery.ts +26 -0
- package/corpus/core/src/deploy/workspace-build-cache.ts +305 -0
- package/corpus/core/src/deploy/workspace-deploy.ts +81 -3
- package/corpus/templates/calendar/app/root.tsx +4 -1
- package/corpus/templates/clips/app/root.tsx +4 -1
- package/corpus/templates/content/app/root.tsx +4 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/route-discovery.d.ts +7 -0
- package/dist/deploy/route-discovery.d.ts.map +1 -1
- package/dist/deploy/route-discovery.js +29 -0
- package/dist/deploy/route-discovery.js.map +1 -1
- package/dist/deploy/workspace-build-cache.d.ts +20 -0
- package/dist/deploy/workspace-build-cache.d.ts.map +1 -0
- package/dist/deploy/workspace-build-cache.js +258 -0
- package/dist/deploy/workspace-build-cache.js.map +1 -0
- package/dist/deploy/workspace-deploy.d.ts.map +1 -1
- package/dist/deploy/workspace-deploy.js +56 -3
- package/dist/deploy/workspace-deploy.js.map +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-discovery.js","sourceRoot":"","sources":["../../src/deploy/route-discovery.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,sDAAsD;AACtD,yFAAyF;AACzF,IAAI,GAAoC,CAAC;AACzC,KAAK,UAAU,KAAK;IAClB,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,GAAG,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe;IAI5C,0BAA0B;IAC1B,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAEnD,+EAA+E;IAC/E,MAAM,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAC3C,IAAI,MAAM,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAE/B,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAC1D,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;IAC1E,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IAEhD,IAAI,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAE5C,8BAA8B;IAC9B,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC;IAEtD,yEAAyE;IACzE,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,iBAAiB,EAAE,IAAI,CAAC,CAAC;IAEvD,yBAAyB;IACzB,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAE9C,uBAAuB;IACvB,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,SAAS,GAAG,GAAG,GAAG,SAAS,CAAC;IAE5D,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;AACtC,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,GAAW,EACX,MAAM,GAAG,EAAE;IAEX,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,MAAM,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7D,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;YAC5D,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACxB,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;YACxE,CAAC;iBAAM,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBACpE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAClB,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC,CAAC,+BAA+B;IAC5C,CAAC;AACH,CAAC;AAWD;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,GAAW;IAEX,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,mBAAmB,CAAC,CAAC;IACnD,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,6BAA6B,CAAC,CAAC;IACrE,MAAM,UAAU,GAAG;QACjB,GAAG,CAAC,MAAM,aAAa,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACvC,GAAG,CAAC,MAAM,aAAa,CAAC,cAAc,EAAE,eAAe,CAAC,CAAC;KAC1D,CAAC;IACF,MAAM,MAAM,GAAsB,EAAE,CAAC;IAErC,KAAK,MAAM,OAAO,IAAI,UAAU,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;QACvC,IAAI,CAAC,MAAM;YAAE,SAAS;QACtB,MAAM,CAAC,IAAI,CAAC;YACV,GAAG,MAAM;YACT,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,CAAC;SAClD,CAAC,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAW;IAC/C,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,MAAM,KAAK,EAAE,CAAC;QACzB,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;QACpD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,OAAO,EAAE,CAAC;QAC1C,OAAO,EAAE;aACN,WAAW,CAAC,UAAU,CAAC;aACvB,MAAM,CAAC,mBAAmB,CAAC;aAC3B,IAAI,EAAE;aACN,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC,CAAC,+BAA+B;IAC5C,CAAC;AACH,CAAC;AAED,SAAS,mBAAmB,CAAC,QAAgB;IAC3C,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IAC/C,IAAI,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,yBAAyB,CAAC,IAAI,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3D,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAA2B;IAC7D,YAAY,EAAE,wBAAwB;IACtC,IAAI,EAAE,mBAAmB;IACzB,cAAc,EAAE,0BAA0B;IAC1C,aAAa,EAAE,yBAAyB;IACxC,YAAY,EAAE,2BAA2B;IACzC,sBAAsB,EAAE,kCAAkC;IAC1D,UAAU,EAAE,yBAAyB;IACrC,GAAG,EAAE,kBAAkB;IACvB,SAAS,EAAE,wBAAwB;IACnC,MAAM,EAAE,qBAAqB;IAC7B,QAAQ,EAAE,uBAAuB;CAClC,CAAC;AAEF,2EAA2E;AAC3E,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC;IAChC,SAAS;IACT,KAAK;IACL,YAAY;IACZ,WAAW;IACX,UAAU;CACX,CAAC,CAAC;AAiBH,2DAA2D;AAC3D,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC;IACnC,KAAK;IACL,MAAM;IACN,KAAK;IACL,OAAO;IACP,QAAQ;IACR,SAAS;IACT,MAAM;CACP,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,qBAAqB,CACnC,OAAe;IAEf,IAAI,MAAM,GAAG,MAAM,CAAC;IACpB,IAAI,IAAwB,CAAC;IAE7B,MAAM,UAAU,GAAG,uBAAuB,CAAC,OAAO,CAAC,CAAC;IACpD,IAAI,UAAU,KAAK,KAAK;QAAE,OAAO,KAAK,CAAC;IAEvC,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;QACnC,MAAM,IAAI,GAAG,UAAU,CAAC;QACxB,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACrE,IAAI,WAAW,EAAE,CAAC;YAChB,MAAM,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;YACvC,IAAI,oBAAoB,CAAC,GAAG,CAAC,CAAC,CAAC;gBAAE,MAAM,GAAG,CAAC,CAAC;QAC9C,CAAC;QACD,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;QAC9D,IAAI,SAAS;YAAE,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;IACrC,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AAC1B,CAAC;AAED,SAAS,uBAAuB,CAAC,OAAe;IAC9C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,GAAI,CAAC;QACrC,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACxC,IAAI,OAAO,KAAK,CAAC,EAAE,CAAC;YAClB,CAAC,GAAG,OAAO,CAAC;YACZ,SAAS;QACX,CAAC;QAED,IACE,OAAO,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;YAC7B,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACjC,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EACjC,CAAC;YACD,IAAI,UAAU,GAAG,yBAAyB,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;YAC3D,IAAI,OAAO,CAAC,UAAU,CAAC,KAAK,GAAG,EAAE,CAAC;gBAChC,CAAC,IAAI,CAAC,CAAC;gBACP,SAAS;YACX,CAAC;YAED,UAAU,GAAG,yBAAyB,CAAC,OAAO,EAAE,UAAU,GAAG,CAAC,CAAC,CAAC;YAChE,IACE,OAAO,CAAC,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC;gBACvC,CAAC,gBAAgB,CAAC,OAAO,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,EAC1C,CAAC;gBACD,OAAO,KAAK,CAAC;YACf,CAAC;YAED,IAAI,OAAO,CAAC,UAAU,CAAC,KAAK,GAAG,EAAE,CAAC;gBAChC,OAAO,yBAAyB,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;YACxD,CAAC;QACH,CAAC;QAED,CAAC,IAAI,CAAC,CAAC;IACT,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,yBAAyB,CAChC,OAAe,EACf,cAAsB;IAEtB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,IAAI,CAAC,GAAG,cAAc,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,GAAI,CAAC;QAClD,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACxC,IAAI,OAAO,KAAK,CAAC,EAAE,CAAC;YAClB,CAAC,GAAG,OAAO,CAAC;YACZ,SAAS;QACX,CAAC;QAED,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACf,KAAK,IAAI,CAAC,CAAC;QACb,CAAC;aAAM,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACtB,KAAK,IAAI,CAAC,CAAC;YACX,IAAI,KAAK,KAAK,CAAC;gBAAE,OAAO,OAAO,CAAC,KAAK,CAAC,cAAc,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAC/D,CAAC;QAED,CAAC,IAAI,CAAC,CAAC;IACT,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,yBAAyB,CAAC,OAAe,EAAE,KAAa;IAC/D,IAAI,CAAC,GAAG,KAAK,CAAC;IACd,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;QAC1B,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1B,CAAC,IAAI,CAAC,CAAC;YACP,SAAS;QACX,CAAC;QAED,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACxC,IAAI,OAAO,KAAK,CAAC,EAAE,CAAC;YAClB,CAAC,GAAG,OAAO,CAAC;YACZ,SAAS;QACX,CAAC;QAED,MAAM;IACR,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,SAAS,WAAW,CAAC,OAAe,EAAE,KAAa;IACjD,OAAO,WAAW,CAAC,OAAO,EAAE,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED,SAAS,WAAW,CAAC,OAAe,EAAE,KAAa;IACjD,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;QACzD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;QACjD,OAAO,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC;IACvD,CAAC;IAED,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;QACzD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;QAC/C,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;IACnD,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,UAAU,CAAC,OAAe,EAAE,KAAa;IAChD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7B,IAAI,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,GAAG;QAAE,OAAO,KAAK,CAAC;IAElE,KAAK,IAAI,CAAC,GAAG,KAAK,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACnD,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YACxB,CAAC,IAAI,CAAC,CAAC;YACP,SAAS;QACX,CAAC;QACD,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,KAAK;YAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IACzC,CAAC;IAED,OAAO,OAAO,CAAC,MAAM,CAAC;AACxB,CAAC;AAED,SAAS,gBAAgB,CAAC,EAAsB;IAC9C,OAAO,EAAE,KAAK,SAAS,IAAI,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACtD,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,cAAc,CAAC,UAAkB;IAC9C,MAAM,EAAE,GAAG,MAAM,KAAK,EAAE,CAAC;IACzB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,EAAE,CAAC;IAE1C,MAAM,KAAK,GAAG,EAAE,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;QACpD,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC;YAAE,OAAO,KAAK,CAAC;QAC1C,MAAM,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;QACzC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QACvC,IAAI,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC;QAC9C,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;IAEH,MAAM,GAAG,GAAuB,EAAE,CAAC;IACnC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;QAC5C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QAE5C,8DAA8D;QAC9D,6DAA6D;QAC7D,6DAA6D;QAC7D,iEAAiE;QACjE,IAAI,OAAe,CAAC;QACpB,IAAI,CAAC;YACH,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC9C,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC;YAAE,SAAS;QAEhD,MAAM,IAAI,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;QAC5C,IAAI,IAAI,KAAK,KAAK;YAAE,SAAS,CAAC,aAAa;QAE3C,GAAG,CAAC,IAAI,CAAC;YACP,IAAI;YACJ,OAAO;YACP,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC1C,CAAC,CAAC;IACL,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,GAAW;IAEX,MAAM,eAAe,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC;IACxE,MAAM,MAAM,GAAG,IAAI,GAAG,EAA4B,CAAC;IACnD,KAAK,MAAM,CAAC,IAAI,eAAe;QAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAEvD,6DAA6D;IAC7D,IAAI,CAAC;QACH,MAAM,EAAE,uBAAuB,EAAE,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAC;QACxE,MAAM,EAAE,GAAG,MAAM,uBAAuB,CAAC,GAAG,CAAC,CAAC;QAC9C,IAAI,EAAE,IAAI,EAAE,CAAC,UAAU,EAAE,CAAC;YACxB,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC;YACtD,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;gBAC1B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;oBAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YACjD,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,oDAAoD;IACtD,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;AACrC,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAAC,GAAW;IACxD,IAAI,aAA0B,CAAC;IAC/B,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,MAAM,KAAK,EAAE,CAAC;QACzB,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;QACpD,aAAa,GAAG,IAAI,GAAG,CACrB,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC;YACvB,CAAC,CAAC,EAAE;iBACC,WAAW,CAAC,UAAU,CAAC;iBACvB,MAAM,CAAC,mBAAmB,CAAC;iBAC3B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;YAClD,CAAC,CAAC,EAAE,CACP,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,aAAa,GAAG,IAAI,GAAG,EAAE,CAAC,CAAC,mDAAmD;IAChF,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC,MAAM,CAChD,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CACnC,CAAC;AACJ,CAAC","sourcesContent":["import path from \"path\";\r\n\r\n// Lazy fs — loaded via dynamic import() on first use.\r\n// Avoids require() which bundlers convert to createRequire() that crashes on CF Workers.\r\nlet _fs: typeof import(\"fs\") | undefined;\r\nasync function getFs(): Promise<typeof import(\"fs\")> {\r\n if (!_fs) {\r\n _fs = await import(\"node:fs\");\r\n }\r\n return _fs;\r\n}\r\n\r\n/**\r\n * Map a Nitro-style route file path to { method, route }.\r\n *\r\n * Examples:\r\n * api/emails/index.get.ts → GET /api/emails\r\n * api/emails/[id].get.ts → GET /api/emails/:id\r\n * api/emails/[id]/star.patch.ts→ PATCH /api/emails/:id/star\r\n * api/events.get.ts → GET /api/events\r\n */\r\nexport function parseRouteFile(relPath: string): {\r\n method: string;\r\n route: string;\r\n} | null {\r\n // Strip .ts/.js extension\r\n const withoutExt = relPath.replace(/\\.[tj]s$/, \"\");\r\n\r\n // Extract HTTP method from the last segment (e.g. \"status.get\" → method=\"get\")\r\n const dotIdx = withoutExt.lastIndexOf(\".\");\r\n if (dotIdx === -1) return null;\r\n\r\n const method = withoutExt.slice(dotIdx + 1).toLowerCase();\r\n const validMethods = [\"get\", \"post\", \"put\", \"patch\", \"delete\", \"options\"];\r\n if (!validMethods.includes(method)) return null;\r\n\r\n let routePath = withoutExt.slice(0, dotIdx);\r\n\r\n // Replace [param] with :param\r\n routePath = routePath.replace(/\\[([^\\]]+)\\]/g, \":$1\");\r\n\r\n // Replace [...catchall] with ** (H3 catch-all syntax, value in params._)\r\n routePath = routePath.replace(/:\\.\\.\\.([^/]+)/g, \"**\");\r\n\r\n // Remove trailing /index\r\n routePath = routePath.replace(/\\/index$/, \"\");\r\n\r\n // Ensure leading slash\r\n if (!routePath.startsWith(\"/\")) routePath = \"/\" + routePath;\r\n\r\n return { method, route: routePath };\r\n}\r\n\r\n/**\r\n * Recursively discover all .ts files under a directory.\r\n */\r\nexport async function discoverFiles(\r\n dir: string,\r\n prefix = \"\",\r\n): Promise<string[]> {\r\n try {\r\n const fs = await getFs();\r\n if (!fs.existsSync(dir)) return [];\r\n const entries = fs.readdirSync(dir, { withFileTypes: true });\r\n const files: string[] = [];\r\n for (const entry of entries) {\r\n const rel = prefix ? `${prefix}/${entry.name}` : entry.name;\r\n if (entry.isDirectory()) {\r\n files.push(...(await discoverFiles(path.join(dir, entry.name), rel)));\r\n } else if (entry.name.endsWith(\".ts\") || entry.name.endsWith(\".js\")) {\r\n files.push(rel);\r\n }\r\n }\r\n return files;\r\n } catch {\r\n return []; // Edge runtime — no filesystem\r\n }\r\n}\r\n\r\nexport interface DiscoveredRoute {\r\n method: string;\r\n route: string;\r\n /** Relative path from server/routes/ */\r\n filePath: string;\r\n /** Absolute path on disk */\r\n absPath: string;\r\n}\r\n\r\n/**\r\n * Discover all API routes in a project's server/routes/ directory.\r\n */\r\nexport async function discoverApiRoutes(\r\n cwd: string,\r\n): Promise<DiscoveredRoute[]> {\r\n const apiDir = path.join(cwd, \"server/routes/api\");\r\n const agentNativeDir = path.join(cwd, \"server/routes/_agent-native\");\r\n const routeFiles = [\r\n ...(await discoverFiles(apiDir, \"api\")),\r\n ...(await discoverFiles(agentNativeDir, \"_agent-native\")),\r\n ];\r\n const routes: DiscoveredRoute[] = [];\r\n\r\n for (const relFile of routeFiles) {\r\n const parsed = parseRouteFile(relFile);\r\n if (!parsed) continue;\r\n routes.push({\r\n ...parsed,\r\n filePath: relFile,\r\n absPath: path.join(cwd, \"server/routes\", relFile),\r\n });\r\n }\r\n\r\n return routes;\r\n}\r\n\r\n/**\r\n * Discover all server plugins in a project's server/plugins/ directory.\r\n */\r\nexport async function discoverPlugins(cwd: string): Promise<string[]> {\r\n try {\r\n const fs = await getFs();\r\n const pluginsDir = path.join(cwd, \"server/plugins\");\r\n if (!fs.existsSync(pluginsDir)) return [];\r\n return fs\r\n .readdirSync(pluginsDir)\r\n .filter(isRuntimeSourceFile)\r\n .sort()\r\n .map((f) => path.join(pluginsDir, f));\r\n } catch {\r\n return []; // Edge runtime — no filesystem\r\n }\r\n}\r\n\r\nfunction isRuntimeSourceFile(filename: string): boolean {\r\n if (!/\\.(ts|js)$/.test(filename)) return false;\r\n if (/\\.d\\.ts$/.test(filename)) return false;\r\n if (/\\.(test|spec)\\.(ts|js)$/.test(filename)) return false;\r\n return true;\r\n}\r\n\r\n/**\r\n * Default plugins that auto-mount when not provided by the template.\r\n * Key = filename stem, value = export name from @agent-native/core/server/edge.\r\n */\r\nexport const DEFAULT_PLUGIN_REGISTRY: Record<string, string> = {\r\n \"agent-chat\": \"defaultAgentChatPlugin\",\r\n auth: \"defaultAuthPlugin\",\r\n \"context-xray\": \"defaultContextXrayPlugin\",\r\n \"core-routes\": \"defaultCoreRoutesPlugin\",\r\n integrations: \"defaultIntegrationsPlugin\",\r\n \"observational-memory\": \"defaultObservationalMemoryPlugin\",\r\n onboarding: \"defaultOnboardingPlugin\",\r\n org: \"defaultOrgPlugin\",\r\n resources: \"defaultResourcesPlugin\",\r\n sentry: \"defaultSentryPlugin\",\r\n terminal: \"defaultTerminalPlugin\",\r\n};\r\n\r\n/** Files to skip during action discovery (mirrors action-discovery.ts). */\r\nconst SKIP_ACTION_FILES = new Set([\r\n \"helpers\",\r\n \"run\",\r\n \"db-connect\",\r\n \"db-status\",\r\n \"registry\",\r\n]);\r\n\r\nexport interface DiscoveredAction {\r\n /** Action name (filename without extension) */\r\n name: string;\r\n /** Absolute path to the action file */\r\n absPath: string;\r\n /** HTTP method (from defineAction's http config, default POST) */\r\n method: string;\r\n /**\r\n * Custom route segment from defineAction's `http.path`. When unset the\r\n * route falls back to `name`, mirroring the runtime mount\r\n * (`action-routes.ts`: `path = http?.path ?? name`).\r\n */\r\n path?: string;\r\n}\r\n\r\n/** HTTP methods an action may expose via `http.method`. */\r\nconst VALID_ACTION_METHODS = new Set([\r\n \"get\",\r\n \"post\",\r\n \"put\",\r\n \"patch\",\r\n \"delete\",\r\n \"options\",\r\n \"head\",\r\n]);\r\n\r\n/**\r\n * Statically extract the `http` config from a defineAction source file.\r\n *\r\n * Deploy discovery cannot import the action module — edge bundlers rewrite\r\n * require()/import in ways that crash (see getFs note above), and action\r\n * files often pull in Node-only deps — so we parse the source text instead.\r\n * The parse is scoped to the `http: { ... }` object literal so unrelated\r\n * `method:`/`path:` keys elsewhere in the file (e.g. a\r\n * `fetch(url, { method: \"GET\" })` in the action body) cannot flip the\r\n * route's method. A naive `content.includes('method: \"GET\"')` did exactly\r\n * that, and it also missed PUT/PATCH/DELETE and dropped `http.path`.\r\n *\r\n * The http config may contain nested object literals before `method` or\r\n * `path`, so extract the object body with a small balanced-brace scan rather\r\n * than a non-greedy regex that stops at the first closing brace.\r\n *\r\n * Returns `false` when the action opts out of HTTP (`http: false`); otherwise\r\n * `{ method, path? }` with method lowercased and defaulting to \"post\".\r\n */\r\nexport function parseActionHttpConfig(\r\n content: string,\r\n): false | { method: string; path?: string } {\r\n let method = \"post\";\r\n let path: string | undefined;\r\n\r\n const httpConfig = extractActionHttpConfig(content);\r\n if (httpConfig === false) return false;\r\n\r\n if (typeof httpConfig === \"string\") {\r\n const body = httpConfig;\r\n const methodMatch = body.match(/\\bmethod\\s*:\\s*['\"]([A-Za-z]+)['\"]/);\r\n if (methodMatch) {\r\n const m = methodMatch[1].toLowerCase();\r\n if (VALID_ACTION_METHODS.has(m)) method = m;\r\n }\r\n const pathMatch = body.match(/\\bpath\\s*:\\s*['\"]([^'\"]+)['\"]/);\r\n if (pathMatch) path = pathMatch[1];\r\n }\r\n\r\n return { method, path };\r\n}\r\n\r\nfunction extractActionHttpConfig(content: string): false | string | undefined {\r\n for (let i = 0; i < content.length; ) {\r\n const skipped = skipNonCode(content, i);\r\n if (skipped !== i) {\r\n i = skipped;\r\n continue;\r\n }\r\n\r\n if (\r\n content.startsWith(\"http\", i) &&\r\n !isIdentifierChar(content[i - 1]) &&\r\n !isIdentifierChar(content[i + 4])\r\n ) {\r\n let valueStart = skipWhitespaceAndComments(content, i + 4);\r\n if (content[valueStart] !== \":\") {\r\n i += 4;\r\n continue;\r\n }\r\n\r\n valueStart = skipWhitespaceAndComments(content, valueStart + 1);\r\n if (\r\n content.startsWith(\"false\", valueStart) &&\r\n !isIdentifierChar(content[valueStart + 5])\r\n ) {\r\n return false;\r\n }\r\n\r\n if (content[valueStart] === \"{\") {\r\n return extractBalancedObjectBody(content, valueStart);\r\n }\r\n }\r\n\r\n i += 1;\r\n }\r\n\r\n return undefined;\r\n}\r\n\r\nfunction extractBalancedObjectBody(\r\n content: string,\r\n openBraceIndex: number,\r\n): string | undefined {\r\n let depth = 0;\r\n for (let i = openBraceIndex; i < content.length; ) {\r\n const skipped = skipNonCode(content, i);\r\n if (skipped !== i) {\r\n i = skipped;\r\n continue;\r\n }\r\n\r\n const ch = content[i];\r\n if (ch === \"{\") {\r\n depth += 1;\r\n } else if (ch === \"}\") {\r\n depth -= 1;\r\n if (depth === 0) return content.slice(openBraceIndex + 1, i);\r\n }\r\n\r\n i += 1;\r\n }\r\n\r\n return undefined;\r\n}\r\n\r\nfunction skipWhitespaceAndComments(content: string, start: number): number {\r\n let i = start;\r\n while (i < content.length) {\r\n if (/\\s/.test(content[i])) {\r\n i += 1;\r\n continue;\r\n }\r\n\r\n const skipped = skipComment(content, i);\r\n if (skipped !== i) {\r\n i = skipped;\r\n continue;\r\n }\r\n\r\n break;\r\n }\r\n return i;\r\n}\r\n\r\nfunction skipNonCode(content: string, start: number): number {\r\n return skipComment(content, skipString(content, start));\r\n}\r\n\r\nfunction skipComment(content: string, start: number): number {\r\n if (content[start] === \"/\" && content[start + 1] === \"/\") {\r\n const newline = content.indexOf(\"\\n\", start + 2);\r\n return newline === -1 ? content.length : newline + 1;\r\n }\r\n\r\n if (content[start] === \"/\" && content[start + 1] === \"*\") {\r\n const close = content.indexOf(\"*/\", start + 2);\r\n return close === -1 ? content.length : close + 2;\r\n }\r\n\r\n return start;\r\n}\r\n\r\nfunction skipString(content: string, start: number): number {\r\n const quote = content[start];\r\n if (quote !== \"'\" && quote !== '\"' && quote !== \"`\") return start;\r\n\r\n for (let i = start + 1; i < content.length; i += 1) {\r\n if (content[i] === \"\\\\\") {\r\n i += 1;\r\n continue;\r\n }\r\n if (content[i] === quote) return i + 1;\r\n }\r\n\r\n return content.length;\r\n}\r\n\r\nfunction isIdentifierChar(ch: string | undefined): boolean {\r\n return ch !== undefined && /[A-Za-z0-9_$]/.test(ch);\r\n}\r\n\r\n/**\r\n * Scan a single actions directory for defineAction-backed files. Shared\r\n * between the template-actions path and the workspace-core actions layer.\r\n */\r\nasync function scanActionsDir(actionsDir: string): Promise<DiscoveredAction[]> {\r\n const fs = await getFs();\r\n if (!fs.existsSync(actionsDir)) return [];\r\n\r\n const files = fs.readdirSync(actionsDir).filter((f) => {\r\n if (!isRuntimeSourceFile(f)) return false;\r\n const name = f.replace(/\\.(ts|js)$/, \"\");\r\n if (name.startsWith(\"_\")) return false;\r\n if (SKIP_ACTION_FILES.has(name)) return false;\r\n return true;\r\n });\r\n\r\n const out: DiscoveredAction[] = [];\r\n for (const file of files) {\r\n const name = file.replace(/\\.(ts|js)$/, \"\");\r\n const absPath = path.join(actionsDir, file);\r\n\r\n // Only mount actions that use defineAction. CLI-style scripts\r\n // (export default async function()) often use Node-only APIs\r\n // (fs, path) that can't run on edge runtimes — they're meant\r\n // to be invoked via `pnpm action <name>`, not as HTTP endpoints.\r\n let content: string;\r\n try {\r\n content = fs.readFileSync(absPath, \"utf-8\");\r\n } catch {\r\n continue;\r\n }\r\n if (!content.includes(\"defineAction\")) continue;\r\n\r\n const http = parseActionHttpConfig(content);\r\n if (http === false) continue; // agent-only\r\n\r\n out.push({\r\n name,\r\n absPath,\r\n method: http.method,\r\n ...(http.path ? { path: http.path } : {}),\r\n });\r\n }\r\n\r\n return out;\r\n}\r\n\r\n/**\r\n * Discover action files in the actions/ directory.\r\n *\r\n * When a workspace core is present in the ancestor chain, its actions/\r\n * directory is also scanned and its actions are merged in after the\r\n * template's — with template actions winning on name collision.\r\n *\r\n * These become `/_agent-native/actions/:name` HTTP endpoints.\r\n */\r\nexport async function discoverActionFiles(\r\n cwd: string,\r\n): Promise<DiscoveredAction[]> {\r\n const templateActions = await scanActionsDir(path.join(cwd, \"actions\"));\r\n const byName = new Map<string, DiscoveredAction>();\r\n for (const a of templateActions) byName.set(a.name, a);\r\n\r\n // Merge workspace-core actions (template wins on collision).\r\n try {\r\n const { getWorkspaceCoreExports } = await import(\"./workspace-core.js\");\r\n const ws = await getWorkspaceCoreExports(cwd);\r\n if (ws && ws.actionsDir) {\r\n const wsActions = await scanActionsDir(ws.actionsDir);\r\n for (const a of wsActions) {\r\n if (!byName.has(a.name)) byName.set(a.name, a);\r\n }\r\n }\r\n } catch {\r\n // Edge runtime / no fs — skip workspace-core merge.\r\n }\r\n\r\n return Array.from(byName.values());\r\n}\r\n\r\n/**\r\n * Returns the stems of default plugins that are missing from the project.\r\n */\r\nexport async function getMissingDefaultPlugins(cwd: string): Promise<string[]> {\r\n let existingStems: Set<string>;\r\n try {\r\n const fs = await getFs();\r\n const pluginsDir = path.join(cwd, \"server/plugins\");\r\n existingStems = new Set(\r\n fs.existsSync(pluginsDir)\r\n ? fs\r\n .readdirSync(pluginsDir)\r\n .filter(isRuntimeSourceFile)\r\n .map((f) => path.basename(f, path.extname(f)))\r\n : [],\r\n );\r\n } catch {\r\n existingStems = new Set(); // Edge runtime — all defaults will be auto-mounted\r\n }\r\n return Object.keys(DEFAULT_PLUGIN_REGISTRY).filter(\r\n (stem) => !existingStems.has(stem),\r\n );\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"file":"route-discovery.js","sourceRoot":"","sources":["../../src/deploy/route-discovery.ts"],"names":[],"mappings":"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","sourcesContent":["import path from \"path\";\r\n\r\n// Lazy fs — loaded via dynamic import() on first use.\r\n// Avoids require() which bundlers convert to createRequire() that crashes on CF Workers.\r\nlet _fs: typeof import(\"fs\") | undefined;\r\nasync function getFs(): Promise<typeof import(\"fs\")> {\r\n if (!_fs) {\r\n _fs = await import(\"node:fs\");\r\n }\r\n return _fs;\r\n}\r\n\r\n/**\r\n * Map a Nitro-style route file path to { method, route }.\r\n *\r\n * Examples:\r\n * api/emails/index.get.ts → GET /api/emails\r\n * api/emails/[id].get.ts → GET /api/emails/:id\r\n * api/emails/[id]/star.patch.ts→ PATCH /api/emails/:id/star\r\n * api/events.get.ts → GET /api/events\r\n */\r\nexport function parseRouteFile(relPath: string): {\r\n method: string;\r\n route: string;\r\n} | null {\r\n // Strip .ts/.js extension\r\n const withoutExt = relPath.replace(/\\.[tj]s$/, \"\");\r\n\r\n // Extract HTTP method from the last segment (e.g. \"status.get\" → method=\"get\")\r\n const dotIdx = withoutExt.lastIndexOf(\".\");\r\n if (dotIdx === -1) return null;\r\n\r\n const method = withoutExt.slice(dotIdx + 1).toLowerCase();\r\n const validMethods = [\"get\", \"post\", \"put\", \"patch\", \"delete\", \"options\"];\r\n if (!validMethods.includes(method)) return null;\r\n\r\n let routePath = withoutExt.slice(0, dotIdx);\r\n\r\n // Replace [param] with :param\r\n routePath = routePath.replace(/\\[([^\\]]+)\\]/g, \":$1\");\r\n\r\n // Replace [...catchall] with ** (H3 catch-all syntax, value in params._)\r\n routePath = routePath.replace(/:\\.\\.\\.([^/]+)/g, \"**\");\r\n\r\n // Remove trailing /index\r\n routePath = routePath.replace(/\\/index$/, \"\");\r\n\r\n // Ensure leading slash\r\n if (!routePath.startsWith(\"/\")) routePath = \"/\" + routePath;\r\n\r\n return { method, route: routePath };\r\n}\r\n\r\n/**\r\n * Recursively discover all .ts files under a directory.\r\n */\r\nexport async function discoverFiles(\r\n dir: string,\r\n prefix = \"\",\r\n): Promise<string[]> {\r\n try {\r\n const fs = await getFs();\r\n if (!fs.existsSync(dir)) return [];\r\n const entries = fs.readdirSync(dir, { withFileTypes: true });\r\n const files: string[] = [];\r\n for (const entry of entries) {\r\n const rel = prefix ? `${prefix}/${entry.name}` : entry.name;\r\n if (entry.isDirectory()) {\r\n files.push(...(await discoverFiles(path.join(dir, entry.name), rel)));\r\n } else if (entry.name.endsWith(\".ts\") || entry.name.endsWith(\".js\")) {\r\n files.push(rel);\r\n }\r\n }\r\n return files;\r\n } catch {\r\n return []; // Edge runtime — no filesystem\r\n }\r\n}\r\n\r\nexport interface DiscoveredRoute {\r\n method: string;\r\n route: string;\r\n /** Relative path from server/routes/ */\r\n filePath: string;\r\n /** Absolute path on disk */\r\n absPath: string;\r\n}\r\n\r\n/**\r\n * Discover all API routes in a project's server/routes/ directory.\r\n *\r\n * Scans the `/api` and `/_agent-native` subtrees plus TOP-LEVEL route files\r\n * (e.g. analytics `track.post.ts` → `POST /track`, an ingest endpoint that\r\n * deliberately lives outside `/api`). Top-level catch-alls (`[...page].get.ts`\r\n * — the SSR page fallback every template ships) are excluded: on serverless\r\n * workers pages are served by the static app shell, and mounting the\r\n * catch-all would shadow it.\r\n */\r\nexport async function discoverApiRoutes(\r\n cwd: string,\r\n): Promise<DiscoveredRoute[]> {\r\n const fs = await getFs();\r\n const routesDir = path.join(cwd, \"server/routes\");\r\n const apiDir = path.join(cwd, \"server/routes/api\");\r\n const agentNativeDir = path.join(cwd, \"server/routes/_agent-native\");\r\n const topLevelFiles: string[] = [];\r\n try {\r\n if (fs.existsSync(routesDir)) {\r\n for (const entry of fs.readdirSync(routesDir, { withFileTypes: true })) {\r\n if (entry.isDirectory()) continue;\r\n if (!entry.name.endsWith(\".ts\") && !entry.name.endsWith(\".js\")) {\r\n continue;\r\n }\r\n // Skip page catch-alls — the static shell owns page serving.\r\n if (entry.name.startsWith(\"[...\")) continue;\r\n topLevelFiles.push(entry.name);\r\n }\r\n }\r\n } catch {\r\n // Edge runtime — no filesystem.\r\n }\r\n const routeFiles = [\r\n ...(await discoverFiles(apiDir, \"api\")),\r\n ...(await discoverFiles(agentNativeDir, \"_agent-native\")),\r\n ...topLevelFiles,\r\n ];\r\n const routes: DiscoveredRoute[] = [];\r\n\r\n for (const relFile of routeFiles) {\r\n const parsed = parseRouteFile(relFile);\r\n if (!parsed) continue;\r\n routes.push({\r\n ...parsed,\r\n filePath: relFile,\r\n absPath: path.join(cwd, \"server/routes\", relFile),\r\n });\r\n }\r\n\r\n return routes;\r\n}\r\n\r\n/**\r\n * Discover all server plugins in a project's server/plugins/ directory.\r\n */\r\nexport async function discoverPlugins(cwd: string): Promise<string[]> {\r\n try {\r\n const fs = await getFs();\r\n const pluginsDir = path.join(cwd, \"server/plugins\");\r\n if (!fs.existsSync(pluginsDir)) return [];\r\n return fs\r\n .readdirSync(pluginsDir)\r\n .filter(isRuntimeSourceFile)\r\n .sort()\r\n .map((f) => path.join(pluginsDir, f));\r\n } catch {\r\n return []; // Edge runtime — no filesystem\r\n }\r\n}\r\n\r\nfunction isRuntimeSourceFile(filename: string): boolean {\r\n if (!/\\.(ts|js)$/.test(filename)) return false;\r\n if (/\\.d\\.ts$/.test(filename)) return false;\r\n if (/\\.(test|spec)\\.(ts|js)$/.test(filename)) return false;\r\n return true;\r\n}\r\n\r\n/**\r\n * Default plugins that auto-mount when not provided by the template.\r\n * Key = filename stem, value = export name from @agent-native/core/server/edge.\r\n */\r\nexport const DEFAULT_PLUGIN_REGISTRY: Record<string, string> = {\r\n \"agent-chat\": \"defaultAgentChatPlugin\",\r\n auth: \"defaultAuthPlugin\",\r\n \"context-xray\": \"defaultContextXrayPlugin\",\r\n \"core-routes\": \"defaultCoreRoutesPlugin\",\r\n integrations: \"defaultIntegrationsPlugin\",\r\n \"observational-memory\": \"defaultObservationalMemoryPlugin\",\r\n onboarding: \"defaultOnboardingPlugin\",\r\n org: \"defaultOrgPlugin\",\r\n resources: \"defaultResourcesPlugin\",\r\n sentry: \"defaultSentryPlugin\",\r\n terminal: \"defaultTerminalPlugin\",\r\n};\r\n\r\n/** Files to skip during action discovery (mirrors action-discovery.ts). */\r\nconst SKIP_ACTION_FILES = new Set([\r\n \"helpers\",\r\n \"run\",\r\n \"db-connect\",\r\n \"db-status\",\r\n \"registry\",\r\n]);\r\n\r\nexport interface DiscoveredAction {\r\n /** Action name (filename without extension) */\r\n name: string;\r\n /** Absolute path to the action file */\r\n absPath: string;\r\n /** HTTP method (from defineAction's http config, default POST) */\r\n method: string;\r\n /**\r\n * Custom route segment from defineAction's `http.path`. When unset the\r\n * route falls back to `name`, mirroring the runtime mount\r\n * (`action-routes.ts`: `path = http?.path ?? name`).\r\n */\r\n path?: string;\r\n}\r\n\r\n/** HTTP methods an action may expose via `http.method`. */\r\nconst VALID_ACTION_METHODS = new Set([\r\n \"get\",\r\n \"post\",\r\n \"put\",\r\n \"patch\",\r\n \"delete\",\r\n \"options\",\r\n \"head\",\r\n]);\r\n\r\n/**\r\n * Statically extract the `http` config from a defineAction source file.\r\n *\r\n * Deploy discovery cannot import the action module — edge bundlers rewrite\r\n * require()/import in ways that crash (see getFs note above), and action\r\n * files often pull in Node-only deps — so we parse the source text instead.\r\n * The parse is scoped to the `http: { ... }` object literal so unrelated\r\n * `method:`/`path:` keys elsewhere in the file (e.g. a\r\n * `fetch(url, { method: \"GET\" })` in the action body) cannot flip the\r\n * route's method. A naive `content.includes('method: \"GET\"')` did exactly\r\n * that, and it also missed PUT/PATCH/DELETE and dropped `http.path`.\r\n *\r\n * The http config may contain nested object literals before `method` or\r\n * `path`, so extract the object body with a small balanced-brace scan rather\r\n * than a non-greedy regex that stops at the first closing brace.\r\n *\r\n * Returns `false` when the action opts out of HTTP (`http: false`); otherwise\r\n * `{ method, path? }` with method lowercased and defaulting to \"post\".\r\n */\r\nexport function parseActionHttpConfig(\r\n content: string,\r\n): false | { method: string; path?: string } {\r\n let method = \"post\";\r\n let path: string | undefined;\r\n\r\n const httpConfig = extractActionHttpConfig(content);\r\n if (httpConfig === false) return false;\r\n\r\n if (typeof httpConfig === \"string\") {\r\n const body = httpConfig;\r\n const methodMatch = body.match(/\\bmethod\\s*:\\s*['\"]([A-Za-z]+)['\"]/);\r\n if (methodMatch) {\r\n const m = methodMatch[1].toLowerCase();\r\n if (VALID_ACTION_METHODS.has(m)) method = m;\r\n }\r\n const pathMatch = body.match(/\\bpath\\s*:\\s*['\"]([^'\"]+)['\"]/);\r\n if (pathMatch) path = pathMatch[1];\r\n }\r\n\r\n return { method, path };\r\n}\r\n\r\nfunction extractActionHttpConfig(content: string): false | string | undefined {\r\n for (let i = 0; i < content.length; ) {\r\n const skipped = skipNonCode(content, i);\r\n if (skipped !== i) {\r\n i = skipped;\r\n continue;\r\n }\r\n\r\n if (\r\n content.startsWith(\"http\", i) &&\r\n !isIdentifierChar(content[i - 1]) &&\r\n !isIdentifierChar(content[i + 4])\r\n ) {\r\n let valueStart = skipWhitespaceAndComments(content, i + 4);\r\n if (content[valueStart] !== \":\") {\r\n i += 4;\r\n continue;\r\n }\r\n\r\n valueStart = skipWhitespaceAndComments(content, valueStart + 1);\r\n if (\r\n content.startsWith(\"false\", valueStart) &&\r\n !isIdentifierChar(content[valueStart + 5])\r\n ) {\r\n return false;\r\n }\r\n\r\n if (content[valueStart] === \"{\") {\r\n return extractBalancedObjectBody(content, valueStart);\r\n }\r\n }\r\n\r\n i += 1;\r\n }\r\n\r\n return undefined;\r\n}\r\n\r\nfunction extractBalancedObjectBody(\r\n content: string,\r\n openBraceIndex: number,\r\n): string | undefined {\r\n let depth = 0;\r\n for (let i = openBraceIndex; i < content.length; ) {\r\n const skipped = skipNonCode(content, i);\r\n if (skipped !== i) {\r\n i = skipped;\r\n continue;\r\n }\r\n\r\n const ch = content[i];\r\n if (ch === \"{\") {\r\n depth += 1;\r\n } else if (ch === \"}\") {\r\n depth -= 1;\r\n if (depth === 0) return content.slice(openBraceIndex + 1, i);\r\n }\r\n\r\n i += 1;\r\n }\r\n\r\n return undefined;\r\n}\r\n\r\nfunction skipWhitespaceAndComments(content: string, start: number): number {\r\n let i = start;\r\n while (i < content.length) {\r\n if (/\\s/.test(content[i])) {\r\n i += 1;\r\n continue;\r\n }\r\n\r\n const skipped = skipComment(content, i);\r\n if (skipped !== i) {\r\n i = skipped;\r\n continue;\r\n }\r\n\r\n break;\r\n }\r\n return i;\r\n}\r\n\r\nfunction skipNonCode(content: string, start: number): number {\r\n return skipComment(content, skipString(content, start));\r\n}\r\n\r\nfunction skipComment(content: string, start: number): number {\r\n if (content[start] === \"/\" && content[start + 1] === \"/\") {\r\n const newline = content.indexOf(\"\\n\", start + 2);\r\n return newline === -1 ? content.length : newline + 1;\r\n }\r\n\r\n if (content[start] === \"/\" && content[start + 1] === \"*\") {\r\n const close = content.indexOf(\"*/\", start + 2);\r\n return close === -1 ? content.length : close + 2;\r\n }\r\n\r\n return start;\r\n}\r\n\r\nfunction skipString(content: string, start: number): number {\r\n const quote = content[start];\r\n if (quote !== \"'\" && quote !== '\"' && quote !== \"`\") return start;\r\n\r\n for (let i = start + 1; i < content.length; i += 1) {\r\n if (content[i] === \"\\\\\") {\r\n i += 1;\r\n continue;\r\n }\r\n if (content[i] === quote) return i + 1;\r\n }\r\n\r\n return content.length;\r\n}\r\n\r\nfunction isIdentifierChar(ch: string | undefined): boolean {\r\n return ch !== undefined && /[A-Za-z0-9_$]/.test(ch);\r\n}\r\n\r\n/**\r\n * Scan a single actions directory for defineAction-backed files. Shared\r\n * between the template-actions path and the workspace-core actions layer.\r\n */\r\nasync function scanActionsDir(actionsDir: string): Promise<DiscoveredAction[]> {\r\n const fs = await getFs();\r\n if (!fs.existsSync(actionsDir)) return [];\r\n\r\n const files = fs.readdirSync(actionsDir).filter((f) => {\r\n if (!isRuntimeSourceFile(f)) return false;\r\n const name = f.replace(/\\.(ts|js)$/, \"\");\r\n if (name.startsWith(\"_\")) return false;\r\n if (SKIP_ACTION_FILES.has(name)) return false;\r\n return true;\r\n });\r\n\r\n const out: DiscoveredAction[] = [];\r\n for (const file of files) {\r\n const name = file.replace(/\\.(ts|js)$/, \"\");\r\n const absPath = path.join(actionsDir, file);\r\n\r\n // Only mount actions that use defineAction. CLI-style scripts\r\n // (export default async function()) often use Node-only APIs\r\n // (fs, path) that can't run on edge runtimes — they're meant\r\n // to be invoked via `pnpm action <name>`, not as HTTP endpoints.\r\n let content: string;\r\n try {\r\n content = fs.readFileSync(absPath, \"utf-8\");\r\n } catch {\r\n continue;\r\n }\r\n if (!content.includes(\"defineAction\")) continue;\r\n\r\n const http = parseActionHttpConfig(content);\r\n if (http === false) continue; // agent-only\r\n\r\n out.push({\r\n name,\r\n absPath,\r\n method: http.method,\r\n ...(http.path ? { path: http.path } : {}),\r\n });\r\n }\r\n\r\n return out;\r\n}\r\n\r\n/**\r\n * Discover action files in the actions/ directory.\r\n *\r\n * When a workspace core is present in the ancestor chain, its actions/\r\n * directory is also scanned and its actions are merged in after the\r\n * template's — with template actions winning on name collision.\r\n *\r\n * These become `/_agent-native/actions/:name` HTTP endpoints.\r\n */\r\nexport async function discoverActionFiles(\r\n cwd: string,\r\n): Promise<DiscoveredAction[]> {\r\n const templateActions = await scanActionsDir(path.join(cwd, \"actions\"));\r\n const byName = new Map<string, DiscoveredAction>();\r\n for (const a of templateActions) byName.set(a.name, a);\r\n\r\n // Merge workspace-core actions (template wins on collision).\r\n try {\r\n const { getWorkspaceCoreExports } = await import(\"./workspace-core.js\");\r\n const ws = await getWorkspaceCoreExports(cwd);\r\n if (ws && ws.actionsDir) {\r\n const wsActions = await scanActionsDir(ws.actionsDir);\r\n for (const a of wsActions) {\r\n if (!byName.has(a.name)) byName.set(a.name, a);\r\n }\r\n }\r\n } catch {\r\n // Edge runtime / no fs — skip workspace-core merge.\r\n }\r\n\r\n return Array.from(byName.values());\r\n}\r\n\r\n/**\r\n * Returns the stems of default plugins that are missing from the project.\r\n */\r\nexport async function getMissingDefaultPlugins(cwd: string): Promise<string[]> {\r\n let existingStems: Set<string>;\r\n try {\r\n const fs = await getFs();\r\n const pluginsDir = path.join(cwd, \"server/plugins\");\r\n existingStems = new Set(\r\n fs.existsSync(pluginsDir)\r\n ? fs\r\n .readdirSync(pluginsDir)\r\n .filter(isRuntimeSourceFile)\r\n .map((f) => path.basename(f, path.extname(f)))\r\n : [],\r\n );\r\n } catch {\r\n existingStems = new Set(); // Edge runtime — all defaults will be auto-mounted\r\n }\r\n return Object.keys(DEFAULT_PLUGIN_REGISTRY).filter(\r\n (stem) => !existingStems.has(stem),\r\n );\r\n}\r\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface WorkspaceBuildCacheOptions {
|
|
2
|
+
workspaceRoot: string;
|
|
3
|
+
appDir: string;
|
|
4
|
+
app: string;
|
|
5
|
+
preset: string;
|
|
6
|
+
/** The exact env block the app build is invoked with (delta over
|
|
7
|
+
* process.env computed by the deploy orchestrator). */
|
|
8
|
+
buildEnv: Record<string, string | undefined>;
|
|
9
|
+
/** Version of the framework package performing the build. */
|
|
10
|
+
builderVersion: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function isWorkspaceBuildCacheEnabled(rawArgs: string[]): boolean;
|
|
13
|
+
/** Compute the cache key for one app build. Returns null when hashing is not
|
|
14
|
+
* possible (treat as cache miss — never as a hit). */
|
|
15
|
+
export declare function computeWorkspaceAppBuildHash(opts: WorkspaceBuildCacheOptions): string | null;
|
|
16
|
+
/** True when the app's previous build can be reused as-is. */
|
|
17
|
+
export declare function workspaceAppBuildCacheHit(opts: WorkspaceBuildCacheOptions, hashValue: string | null): boolean;
|
|
18
|
+
/** Record a successful build so the next identical run can skip it. */
|
|
19
|
+
export declare function writeWorkspaceAppBuildStamp(opts: WorkspaceBuildCacheOptions, hashValue: string | null): void;
|
|
20
|
+
//# sourceMappingURL=workspace-build-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-build-cache.d.ts","sourceRoot":"","sources":["../../src/deploy/workspace-build-cache.ts"],"names":[],"mappings":"AA8DA,MAAM,WAAW,0BAA0B;IACzC,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf;2DACuD;IACvD,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAC7C,6DAA6D;IAC7D,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAKvE;AAED;sDACsD;AACtD,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,0BAA0B,GAC/B,MAAM,GAAG,IAAI,CAmDf;AAED,8DAA8D;AAC9D,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,0BAA0B,EAChC,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,OAAO,CAOT;AAED,uEAAuE;AACvE,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,0BAA0B,EAChC,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,IAAI,CAqBN"}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content-hash build cache for `agent-native deploy` workspace builds.
|
|
3
|
+
*
|
|
4
|
+
* Rebuilding every app on every workspace deploy is pure waste when only a
|
|
5
|
+
* few apps changed. Each app's build is a deterministic function of:
|
|
6
|
+
*
|
|
7
|
+
* 1. The app directory's source files (excluding build outputs + deps).
|
|
8
|
+
* 2. The workspace-local packages it depends on via `workspace:*`.
|
|
9
|
+
* 3. The root lockfile (captures every registry dependency version,
|
|
10
|
+
* including the framework runtime itself).
|
|
11
|
+
* 4. The environment the build is invoked with: the deploy preset, the
|
|
12
|
+
* computed per-app env block (base path, audience, workspace manifest,
|
|
13
|
+
* gateway URL, ...) and any ambient env Vite/Nitro can bake into the
|
|
14
|
+
* bundle (`VITE_*`, `AGENT_NATIVE_*`, `WORKSPACE_*`, ...).
|
|
15
|
+
* 5. The version of this package doing the building.
|
|
16
|
+
*
|
|
17
|
+
* When all of that is byte-identical to the previous run AND the app's
|
|
18
|
+
* previous build output still exists on disk, the build is skipped and the
|
|
19
|
+
* existing output is reused. The workspace output assembly (copy into
|
|
20
|
+
* dist/, manifests, dedupe, routing) always runs fresh — it is cheap and
|
|
21
|
+
* keeps the artifact deterministic.
|
|
22
|
+
*
|
|
23
|
+
* Safety posture: a false MISS costs one redundant rebuild; a false HIT
|
|
24
|
+
* ships a stale artifact. Every choice below prefers false misses — full
|
|
25
|
+
* content hashing (no mtimes), lockfile-wide invalidation, and the entire
|
|
26
|
+
* invocation env block folded into the key.
|
|
27
|
+
*
|
|
28
|
+
* Opt out with `--no-build-cache` or AGENT_NATIVE_WORKSPACE_BUILD_CACHE=0.
|
|
29
|
+
*/
|
|
30
|
+
import crypto from "crypto";
|
|
31
|
+
import fs from "fs";
|
|
32
|
+
import path from "path";
|
|
33
|
+
/** Directories inside an app that are build outputs or dependencies — never
|
|
34
|
+
* build inputs. Everything else in the app dir participates in the hash. */
|
|
35
|
+
const EXCLUDED_APP_DIRS = new Set([
|
|
36
|
+
"node_modules",
|
|
37
|
+
"dist",
|
|
38
|
+
"build",
|
|
39
|
+
".output",
|
|
40
|
+
".deploy-tmp",
|
|
41
|
+
".netlify",
|
|
42
|
+
".vercel",
|
|
43
|
+
".react-router",
|
|
44
|
+
".cache",
|
|
45
|
+
".turbo",
|
|
46
|
+
".git",
|
|
47
|
+
]);
|
|
48
|
+
/** Ambient process.env prefixes that can be baked into app bundles. */
|
|
49
|
+
const ENV_PREFIXES = ["VITE_", "AGENT_NATIVE_", "WORKSPACE_", "NETLIFY_"];
|
|
50
|
+
const ENV_EXACT_KEYS = [
|
|
51
|
+
"APP_URL",
|
|
52
|
+
"BETTER_AUTH_URL",
|
|
53
|
+
"DATABASE_URL",
|
|
54
|
+
"NITRO_PRESET",
|
|
55
|
+
"NODE_ENV",
|
|
56
|
+
];
|
|
57
|
+
const STAMP_DIR = path.join("node_modules", ".cache", "agent-native");
|
|
58
|
+
const STAMP_FILE = "workspace-build.json";
|
|
59
|
+
export function isWorkspaceBuildCacheEnabled(rawArgs) {
|
|
60
|
+
if (rawArgs.includes("--no-build-cache"))
|
|
61
|
+
return false;
|
|
62
|
+
const env = process.env.AGENT_NATIVE_WORKSPACE_BUILD_CACHE;
|
|
63
|
+
if (env === "0" || env === "false")
|
|
64
|
+
return false;
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
/** Compute the cache key for one app build. Returns null when hashing is not
|
|
68
|
+
* possible (treat as cache miss — never as a hit). */
|
|
69
|
+
export function computeWorkspaceAppBuildHash(opts) {
|
|
70
|
+
try {
|
|
71
|
+
const hash = crypto.createHash("sha256");
|
|
72
|
+
hash.update(`builder:${opts.builderVersion}\0`);
|
|
73
|
+
hash.update(`preset:${opts.preset}\0`);
|
|
74
|
+
// 1. App sources.
|
|
75
|
+
hashDirInto(hash, opts.appDir, opts.appDir);
|
|
76
|
+
// 2. workspace:* dependency packages (source-level inputs the lockfile
|
|
77
|
+
// does not pin).
|
|
78
|
+
for (const depDir of resolveWorkspaceDepDirs(opts.workspaceRoot, opts.appDir)) {
|
|
79
|
+
hash.update(`workspace-dep:${path.basename(depDir)}\0`);
|
|
80
|
+
hashDirInto(hash, depDir, depDir);
|
|
81
|
+
}
|
|
82
|
+
// 3. Root lockfile — pins every registry dependency (framework runtime
|
|
83
|
+
// included), so a core bump or any dep change invalidates all apps.
|
|
84
|
+
for (const lock of ["pnpm-lock.yaml", "package-lock.json", "yarn.lock"]) {
|
|
85
|
+
const lockPath = path.join(opts.workspaceRoot, lock);
|
|
86
|
+
if (fs.existsSync(lockPath)) {
|
|
87
|
+
hash.update(`lock:${lock}\0`);
|
|
88
|
+
hash.update(fs.readFileSync(lockPath));
|
|
89
|
+
hash.update("\0");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// 4. Invocation env: the computed per-app block plus ambient bakeable
|
|
93
|
+
// env. Values are hashed, never stored.
|
|
94
|
+
const envEntries = [];
|
|
95
|
+
for (const [key, value] of Object.entries(opts.buildEnv)) {
|
|
96
|
+
if (value !== undefined)
|
|
97
|
+
envEntries.push(`${key}=${value}`);
|
|
98
|
+
}
|
|
99
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
100
|
+
if (value === undefined)
|
|
101
|
+
continue;
|
|
102
|
+
if (key in opts.buildEnv)
|
|
103
|
+
continue; // computed block wins
|
|
104
|
+
const relevant = ENV_EXACT_KEYS.includes(key) ||
|
|
105
|
+
ENV_PREFIXES.some((p) => key.startsWith(p));
|
|
106
|
+
if (relevant)
|
|
107
|
+
envEntries.push(`${key}=${value}`);
|
|
108
|
+
}
|
|
109
|
+
envEntries.sort();
|
|
110
|
+
hash.update(`env:${envEntries.join("\n")}\0`);
|
|
111
|
+
return hash.digest("hex");
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/** True when the app's previous build can be reused as-is. */
|
|
118
|
+
export function workspaceAppBuildCacheHit(opts, hashValue) {
|
|
119
|
+
if (!hashValue)
|
|
120
|
+
return false;
|
|
121
|
+
const stamp = readStamp(opts.appDir);
|
|
122
|
+
if (!stamp || stamp.hash !== hashValue || stamp.preset !== opts.preset) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
return requiredOutputsExist(opts.appDir, opts.preset);
|
|
126
|
+
}
|
|
127
|
+
/** Record a successful build so the next identical run can skip it. */
|
|
128
|
+
export function writeWorkspaceAppBuildStamp(opts, hashValue) {
|
|
129
|
+
if (!hashValue)
|
|
130
|
+
return;
|
|
131
|
+
try {
|
|
132
|
+
const dir = path.join(opts.appDir, STAMP_DIR);
|
|
133
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
134
|
+
fs.writeFileSync(path.join(dir, STAMP_FILE), JSON.stringify({
|
|
135
|
+
hash: hashValue,
|
|
136
|
+
preset: opts.preset,
|
|
137
|
+
builderVersion: opts.builderVersion,
|
|
138
|
+
createdAt: new Date().toISOString(),
|
|
139
|
+
}, null, 2));
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// Best-effort: a missing stamp only costs a rebuild next time.
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function readStamp(appDir) {
|
|
146
|
+
try {
|
|
147
|
+
const raw = fs.readFileSync(path.join(appDir, STAMP_DIR, STAMP_FILE), "utf-8");
|
|
148
|
+
const parsed = JSON.parse(raw);
|
|
149
|
+
if (typeof parsed.hash !== "string" || typeof parsed.preset !== "string") {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
return { hash: parsed.hash, preset: parsed.preset };
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function requiredOutputsExist(appDir, preset) {
|
|
159
|
+
if (preset === "vercel") {
|
|
160
|
+
return fs.existsSync(path.join(appDir, ".vercel", "output"));
|
|
161
|
+
}
|
|
162
|
+
const buildOut = fs.existsSync(path.join(appDir, "dist")) ||
|
|
163
|
+
fs.existsSync(path.join(appDir, ".output"));
|
|
164
|
+
if (!buildOut)
|
|
165
|
+
return false;
|
|
166
|
+
if (preset === "netlify") {
|
|
167
|
+
return fs.existsSync(path.join(appDir, ".netlify", "functions-internal", "server"));
|
|
168
|
+
}
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
/** Deterministically hash every build-input file under `dir`. */
|
|
172
|
+
function hashDirInto(hash, dir, rootDir) {
|
|
173
|
+
let entries;
|
|
174
|
+
try {
|
|
175
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
181
|
+
for (const entry of entries) {
|
|
182
|
+
if (entry.name.startsWith(".DS_"))
|
|
183
|
+
continue;
|
|
184
|
+
const full = path.join(dir, entry.name);
|
|
185
|
+
if (entry.isDirectory()) {
|
|
186
|
+
if (dir === rootDir && EXCLUDED_APP_DIRS.has(entry.name))
|
|
187
|
+
continue;
|
|
188
|
+
// Nested node_modules (e.g. inside checked-in fixtures) are still deps.
|
|
189
|
+
if (entry.name === "node_modules")
|
|
190
|
+
continue;
|
|
191
|
+
hashDirInto(hash, full, rootDir);
|
|
192
|
+
}
|
|
193
|
+
else if (entry.isFile()) {
|
|
194
|
+
const rel = path.relative(rootDir, full).split(path.sep).join("/");
|
|
195
|
+
hash.update(`f:${rel}\0`);
|
|
196
|
+
try {
|
|
197
|
+
hash.update(fs.readFileSync(full));
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
hash.update("<unreadable>");
|
|
201
|
+
}
|
|
202
|
+
hash.update("\0");
|
|
203
|
+
}
|
|
204
|
+
// Symlinks and other entry types are skipped: their targets are either
|
|
205
|
+
// covered elsewhere (workspace deps) or not build inputs.
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/** Resolve the app's `workspace:*` dependencies to their package dirs. */
|
|
209
|
+
function resolveWorkspaceDepDirs(workspaceRoot, appDir) {
|
|
210
|
+
const dirs = [];
|
|
211
|
+
let pkg;
|
|
212
|
+
try {
|
|
213
|
+
pkg = JSON.parse(fs.readFileSync(path.join(appDir, "package.json"), "utf-8"));
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
return dirs;
|
|
217
|
+
}
|
|
218
|
+
const workspaceDepNames = Object.entries({
|
|
219
|
+
...pkg.dependencies,
|
|
220
|
+
...pkg.devDependencies,
|
|
221
|
+
})
|
|
222
|
+
.filter(([, spec]) => typeof spec === "string" && spec.startsWith("workspace:"))
|
|
223
|
+
.map(([name]) => name);
|
|
224
|
+
if (workspaceDepNames.length === 0)
|
|
225
|
+
return dirs;
|
|
226
|
+
const packagesDir = path.join(workspaceRoot, "packages");
|
|
227
|
+
let candidates = [];
|
|
228
|
+
try {
|
|
229
|
+
candidates = fs
|
|
230
|
+
.readdirSync(packagesDir, { withFileTypes: true })
|
|
231
|
+
.filter((e) => e.isDirectory())
|
|
232
|
+
.map((e) => path.join(packagesDir, e.name));
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
return dirs;
|
|
236
|
+
}
|
|
237
|
+
const byName = new Map();
|
|
238
|
+
for (const candidate of candidates) {
|
|
239
|
+
try {
|
|
240
|
+
const name = JSON.parse(fs.readFileSync(path.join(candidate, "package.json"), "utf-8")).name;
|
|
241
|
+
if (typeof name === "string")
|
|
242
|
+
byName.set(name, candidate);
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
// skip unreadable packages
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
for (const depName of workspaceDepNames.sort()) {
|
|
249
|
+
const dir = byName.get(depName);
|
|
250
|
+
if (dir)
|
|
251
|
+
dirs.push(dir);
|
|
252
|
+
// Unresolvable workspace deps fall back to lockfile coverage; the sorted
|
|
253
|
+
// dep-name list itself is not hashed separately because package.json is
|
|
254
|
+
// already part of the app-source hash.
|
|
255
|
+
}
|
|
256
|
+
return dirs;
|
|
257
|
+
}
|
|
258
|
+
//# sourceMappingURL=workspace-build-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-build-cache.js","sourceRoot":"","sources":["../../src/deploy/workspace-build-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB;4EAC4E;AAC5E,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC;IAChC,cAAc;IACd,MAAM;IACN,OAAO;IACP,SAAS;IACT,aAAa;IACb,UAAU;IACV,SAAS;IACT,eAAe;IACf,QAAQ;IACR,QAAQ;IACR,MAAM;CACP,CAAC,CAAC;AAEH,uEAAuE;AACvE,MAAM,YAAY,GAAG,CAAC,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,UAAU,CAAC,CAAC;AAC1E,MAAM,cAAc,GAAG;IACrB,SAAS;IACT,iBAAiB;IACjB,cAAc;IACd,cAAc;IACd,UAAU;CACX,CAAC;AAEF,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC;AACtE,MAAM,UAAU,GAAG,sBAAsB,CAAC;AAc1C,MAAM,UAAU,4BAA4B,CAAC,OAAiB;IAC5D,IAAI,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC;QAAE,OAAO,KAAK,CAAC;IACvD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC;IAC3D,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC;IACjD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;sDACsD;AACtD,MAAM,UAAU,4BAA4B,CAC1C,IAAgC;IAEhC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QACzC,IAAI,CAAC,MAAM,CAAC,WAAW,IAAI,CAAC,cAAc,IAAI,CAAC,CAAC;QAChD,IAAI,CAAC,MAAM,CAAC,UAAU,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC;QAEvC,kBAAkB;QAClB,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAE5C,uEAAuE;QACvE,oBAAoB;QACpB,KAAK,MAAM,MAAM,IAAI,uBAAuB,CAC1C,IAAI,CAAC,aAAa,EAClB,IAAI,CAAC,MAAM,CACZ,EAAE,CAAC;YACF,IAAI,CAAC,MAAM,CAAC,iBAAiB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACxD,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QACpC,CAAC;QAED,uEAAuE;QACvE,uEAAuE;QACvE,KAAK,MAAM,IAAI,IAAI,CAAC,gBAAgB,EAAE,mBAAmB,EAAE,WAAW,CAAC,EAAE,CAAC;YACxE,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;YACrD,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC5B,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,IAAI,CAAC,CAAC;gBAC9B,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC;gBACvC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACpB,CAAC;QACH,CAAC;QAED,sEAAsE;QACtE,2CAA2C;QAC3C,MAAM,UAAU,GAAa,EAAE,CAAC;QAChC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YACzD,IAAI,KAAK,KAAK,SAAS;gBAAE,UAAU,CAAC,IAAI,CAAC,GAAG,GAAG,IAAI,KAAK,EAAE,CAAC,CAAC;QAC9D,CAAC;QACD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YACvD,IAAI,KAAK,KAAK,SAAS;gBAAE,SAAS;YAClC,IAAI,GAAG,IAAI,IAAI,CAAC,QAAQ;gBAAE,SAAS,CAAC,sBAAsB;YAC1D,MAAM,QAAQ,GACZ,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC;gBAC5B,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;YAC9C,IAAI,QAAQ;gBAAE,UAAU,CAAC,IAAI,CAAC,GAAG,GAAG,IAAI,KAAK,EAAE,CAAC,CAAC;QACnD,CAAC;QACD,UAAU,CAAC,IAAI,EAAE,CAAC;QAClB,IAAI,CAAC,MAAM,CAAC,OAAO,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAE9C,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,yBAAyB,CACvC,IAAgC,EAChC,SAAwB;IAExB,IAAI,CAAC,SAAS;QAAE,OAAO,KAAK,CAAC;IAC7B,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACrC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;QACvE,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,oBAAoB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;AACxD,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,2BAA2B,CACzC,IAAgC,EAChC,SAAwB;IAExB,IAAI,CAAC,SAAS;QAAE,OAAO;IACvB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QAC9C,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACvC,EAAE,CAAC,aAAa,CACd,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,EAC1B,IAAI,CAAC,SAAS,CACZ;YACE,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,cAAc,EAAE,IAAI,CAAC,cAAc;YACnC,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,EACD,IAAI,EACJ,CAAC,CACF,CACF,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,+DAA+D;IACjE,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAChB,MAAc;IAEd,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CACzB,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,UAAU,CAAC,EACxC,OAAO,CACR,CAAC;QACF,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAyC,CAAC;QACvE,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YACzE,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,oBAAoB,CAAC,MAAc,EAAE,MAAc;IAC1D,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;QACxB,OAAO,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC/D,CAAC;IACD,MAAM,QAAQ,GACZ,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACxC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;IAC9C,IAAI,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,EAAE,CAAC,UAAU,CAClB,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,oBAAoB,EAAE,QAAQ,CAAC,CAC9D,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,iEAAiE;AACjE,SAAS,WAAW,CAClB,IAAiB,EACjB,GAAW,EACX,OAAe;IAEf,IAAI,OAAoB,CAAC;IACzB,IAAI,CAAC;QACH,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IACzD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;IACT,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACrD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;YAAE,SAAS;QAC5C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,IAAI,GAAG,KAAK,OAAO,IAAI,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;gBAAE,SAAS;YACnE,wEAAwE;YACxE,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc;gBAAE,SAAS;YAC5C,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;QACnC,CAAC;aAAM,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACnE,IAAI,CAAC,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;YAC1B,IAAI,CAAC;gBACH,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;YACrC,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;YAC9B,CAAC;YACD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACpB,CAAC;QACD,uEAAuE;QACvE,0DAA0D;IAC5D,CAAC;AACH,CAAC;AAED,0EAA0E;AAC1E,SAAS,uBAAuB,CAC9B,aAAqB,EACrB,MAAc;IAEd,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,IAAI,GAGH,CAAC;IACF,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CACd,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,CAC5D,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,iBAAiB,GAAG,MAAM,CAAC,OAAO,CAAC;QACvC,GAAG,GAAG,CAAC,YAAY;QACnB,GAAG,GAAG,CAAC,eAAe;KACvB,CAAC;SACC,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;SAC/E,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;IACzB,IAAI,iBAAiB,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEhD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC;IACzD,IAAI,UAAU,GAAa,EAAE,CAAC;IAC9B,IAAI,CAAC;QACH,UAAU,GAAG,EAAE;aACZ,WAAW,CAAC,WAAW,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;aACjD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;aAC9B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,CAAC;YACH,MAAM,IAAI,GACR,IAAI,CAAC,KAAK,CACR,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,CAEjE,CAAC,IAAI,CAAC;YACP,IAAI,OAAO,IAAI,KAAK,QAAQ;gBAAE,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAC5D,CAAC;QAAC,MAAM,CAAC;YACP,2BAA2B;QAC7B,CAAC;IACH,CAAC;IACD,KAAK,MAAM,OAAO,IAAI,iBAAiB,CAAC,IAAI,EAAE,EAAE,CAAC;QAC/C,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAChC,IAAI,GAAG;YAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxB,yEAAyE;QACzE,wEAAwE;QACxE,uCAAuC;IACzC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["/**\r\n * Content-hash build cache for `agent-native deploy` workspace builds.\r\n *\r\n * Rebuilding every app on every workspace deploy is pure waste when only a\r\n * few apps changed. Each app's build is a deterministic function of:\r\n *\r\n * 1. The app directory's source files (excluding build outputs + deps).\r\n * 2. The workspace-local packages it depends on via `workspace:*`.\r\n * 3. The root lockfile (captures every registry dependency version,\r\n * including the framework runtime itself).\r\n * 4. The environment the build is invoked with: the deploy preset, the\r\n * computed per-app env block (base path, audience, workspace manifest,\r\n * gateway URL, ...) and any ambient env Vite/Nitro can bake into the\r\n * bundle (`VITE_*`, `AGENT_NATIVE_*`, `WORKSPACE_*`, ...).\r\n * 5. The version of this package doing the building.\r\n *\r\n * When all of that is byte-identical to the previous run AND the app's\r\n * previous build output still exists on disk, the build is skipped and the\r\n * existing output is reused. The workspace output assembly (copy into\r\n * dist/, manifests, dedupe, routing) always runs fresh — it is cheap and\r\n * keeps the artifact deterministic.\r\n *\r\n * Safety posture: a false MISS costs one redundant rebuild; a false HIT\r\n * ships a stale artifact. Every choice below prefers false misses — full\r\n * content hashing (no mtimes), lockfile-wide invalidation, and the entire\r\n * invocation env block folded into the key.\r\n *\r\n * Opt out with `--no-build-cache` or AGENT_NATIVE_WORKSPACE_BUILD_CACHE=0.\r\n */\r\nimport crypto from \"crypto\";\r\nimport fs from \"fs\";\r\nimport path from \"path\";\r\n\r\n/** Directories inside an app that are build outputs or dependencies — never\r\n * build inputs. Everything else in the app dir participates in the hash. */\r\nconst EXCLUDED_APP_DIRS = new Set([\r\n \"node_modules\",\r\n \"dist\",\r\n \"build\",\r\n \".output\",\r\n \".deploy-tmp\",\r\n \".netlify\",\r\n \".vercel\",\r\n \".react-router\",\r\n \".cache\",\r\n \".turbo\",\r\n \".git\",\r\n]);\r\n\r\n/** Ambient process.env prefixes that can be baked into app bundles. */\r\nconst ENV_PREFIXES = [\"VITE_\", \"AGENT_NATIVE_\", \"WORKSPACE_\", \"NETLIFY_\"];\r\nconst ENV_EXACT_KEYS = [\r\n \"APP_URL\",\r\n \"BETTER_AUTH_URL\",\r\n \"DATABASE_URL\",\r\n \"NITRO_PRESET\",\r\n \"NODE_ENV\",\r\n];\r\n\r\nconst STAMP_DIR = path.join(\"node_modules\", \".cache\", \"agent-native\");\r\nconst STAMP_FILE = \"workspace-build.json\";\r\n\r\nexport interface WorkspaceBuildCacheOptions {\r\n workspaceRoot: string;\r\n appDir: string;\r\n app: string;\r\n preset: string;\r\n /** The exact env block the app build is invoked with (delta over\r\n * process.env computed by the deploy orchestrator). */\r\n buildEnv: Record<string, string | undefined>;\r\n /** Version of the framework package performing the build. */\r\n builderVersion: string;\r\n}\r\n\r\nexport function isWorkspaceBuildCacheEnabled(rawArgs: string[]): boolean {\r\n if (rawArgs.includes(\"--no-build-cache\")) return false;\r\n const env = process.env.AGENT_NATIVE_WORKSPACE_BUILD_CACHE;\r\n if (env === \"0\" || env === \"false\") return false;\r\n return true;\r\n}\r\n\r\n/** Compute the cache key for one app build. Returns null when hashing is not\r\n * possible (treat as cache miss — never as a hit). */\r\nexport function computeWorkspaceAppBuildHash(\r\n opts: WorkspaceBuildCacheOptions,\r\n): string | null {\r\n try {\r\n const hash = crypto.createHash(\"sha256\");\r\n hash.update(`builder:${opts.builderVersion}\\0`);\r\n hash.update(`preset:${opts.preset}\\0`);\r\n\r\n // 1. App sources.\r\n hashDirInto(hash, opts.appDir, opts.appDir);\r\n\r\n // 2. workspace:* dependency packages (source-level inputs the lockfile\r\n // does not pin).\r\n for (const depDir of resolveWorkspaceDepDirs(\r\n opts.workspaceRoot,\r\n opts.appDir,\r\n )) {\r\n hash.update(`workspace-dep:${path.basename(depDir)}\\0`);\r\n hashDirInto(hash, depDir, depDir);\r\n }\r\n\r\n // 3. Root lockfile — pins every registry dependency (framework runtime\r\n // included), so a core bump or any dep change invalidates all apps.\r\n for (const lock of [\"pnpm-lock.yaml\", \"package-lock.json\", \"yarn.lock\"]) {\r\n const lockPath = path.join(opts.workspaceRoot, lock);\r\n if (fs.existsSync(lockPath)) {\r\n hash.update(`lock:${lock}\\0`);\r\n hash.update(fs.readFileSync(lockPath));\r\n hash.update(\"\\0\");\r\n }\r\n }\r\n\r\n // 4. Invocation env: the computed per-app block plus ambient bakeable\r\n // env. Values are hashed, never stored.\r\n const envEntries: string[] = [];\r\n for (const [key, value] of Object.entries(opts.buildEnv)) {\r\n if (value !== undefined) envEntries.push(`${key}=${value}`);\r\n }\r\n for (const [key, value] of Object.entries(process.env)) {\r\n if (value === undefined) continue;\r\n if (key in opts.buildEnv) continue; // computed block wins\r\n const relevant =\r\n ENV_EXACT_KEYS.includes(key) ||\r\n ENV_PREFIXES.some((p) => key.startsWith(p));\r\n if (relevant) envEntries.push(`${key}=${value}`);\r\n }\r\n envEntries.sort();\r\n hash.update(`env:${envEntries.join(\"\\n\")}\\0`);\r\n\r\n return hash.digest(\"hex\");\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n/** True when the app's previous build can be reused as-is. */\r\nexport function workspaceAppBuildCacheHit(\r\n opts: WorkspaceBuildCacheOptions,\r\n hashValue: string | null,\r\n): boolean {\r\n if (!hashValue) return false;\r\n const stamp = readStamp(opts.appDir);\r\n if (!stamp || stamp.hash !== hashValue || stamp.preset !== opts.preset) {\r\n return false;\r\n }\r\n return requiredOutputsExist(opts.appDir, opts.preset);\r\n}\r\n\r\n/** Record a successful build so the next identical run can skip it. */\r\nexport function writeWorkspaceAppBuildStamp(\r\n opts: WorkspaceBuildCacheOptions,\r\n hashValue: string | null,\r\n): void {\r\n if (!hashValue) return;\r\n try {\r\n const dir = path.join(opts.appDir, STAMP_DIR);\r\n fs.mkdirSync(dir, { recursive: true });\r\n fs.writeFileSync(\r\n path.join(dir, STAMP_FILE),\r\n JSON.stringify(\r\n {\r\n hash: hashValue,\r\n preset: opts.preset,\r\n builderVersion: opts.builderVersion,\r\n createdAt: new Date().toISOString(),\r\n },\r\n null,\r\n 2,\r\n ),\r\n );\r\n } catch {\r\n // Best-effort: a missing stamp only costs a rebuild next time.\r\n }\r\n}\r\n\r\nfunction readStamp(\r\n appDir: string,\r\n): { hash: string; preset: string } | null {\r\n try {\r\n const raw = fs.readFileSync(\r\n path.join(appDir, STAMP_DIR, STAMP_FILE),\r\n \"utf-8\",\r\n );\r\n const parsed = JSON.parse(raw) as { hash?: unknown; preset?: unknown };\r\n if (typeof parsed.hash !== \"string\" || typeof parsed.preset !== \"string\") {\r\n return null;\r\n }\r\n return { hash: parsed.hash, preset: parsed.preset };\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\nfunction requiredOutputsExist(appDir: string, preset: string): boolean {\r\n if (preset === \"vercel\") {\r\n return fs.existsSync(path.join(appDir, \".vercel\", \"output\"));\r\n }\r\n const buildOut =\r\n fs.existsSync(path.join(appDir, \"dist\")) ||\r\n fs.existsSync(path.join(appDir, \".output\"));\r\n if (!buildOut) return false;\r\n if (preset === \"netlify\") {\r\n return fs.existsSync(\r\n path.join(appDir, \".netlify\", \"functions-internal\", \"server\"),\r\n );\r\n }\r\n return true;\r\n}\r\n\r\n/** Deterministically hash every build-input file under `dir`. */\r\nfunction hashDirInto(\r\n hash: crypto.Hash,\r\n dir: string,\r\n rootDir: string,\r\n): void {\r\n let entries: fs.Dirent[];\r\n try {\r\n entries = fs.readdirSync(dir, { withFileTypes: true });\r\n } catch {\r\n return;\r\n }\r\n entries.sort((a, b) => a.name.localeCompare(b.name));\r\n for (const entry of entries) {\r\n if (entry.name.startsWith(\".DS_\")) continue;\r\n const full = path.join(dir, entry.name);\r\n if (entry.isDirectory()) {\r\n if (dir === rootDir && EXCLUDED_APP_DIRS.has(entry.name)) continue;\r\n // Nested node_modules (e.g. inside checked-in fixtures) are still deps.\r\n if (entry.name === \"node_modules\") continue;\r\n hashDirInto(hash, full, rootDir);\r\n } else if (entry.isFile()) {\r\n const rel = path.relative(rootDir, full).split(path.sep).join(\"/\");\r\n hash.update(`f:${rel}\\0`);\r\n try {\r\n hash.update(fs.readFileSync(full));\r\n } catch {\r\n hash.update(\"<unreadable>\");\r\n }\r\n hash.update(\"\\0\");\r\n }\r\n // Symlinks and other entry types are skipped: their targets are either\r\n // covered elsewhere (workspace deps) or not build inputs.\r\n }\r\n}\r\n\r\n/** Resolve the app's `workspace:*` dependencies to their package dirs. */\r\nfunction resolveWorkspaceDepDirs(\r\n workspaceRoot: string,\r\n appDir: string,\r\n): string[] {\r\n const dirs: string[] = [];\r\n let pkg: {\r\n dependencies?: Record<string, string>;\r\n devDependencies?: Record<string, string>;\r\n };\r\n try {\r\n pkg = JSON.parse(\r\n fs.readFileSync(path.join(appDir, \"package.json\"), \"utf-8\"),\r\n );\r\n } catch {\r\n return dirs;\r\n }\r\n const workspaceDepNames = Object.entries({\r\n ...pkg.dependencies,\r\n ...pkg.devDependencies,\r\n })\r\n .filter(([, spec]) => typeof spec === \"string\" && spec.startsWith(\"workspace:\"))\r\n .map(([name]) => name);\r\n if (workspaceDepNames.length === 0) return dirs;\r\n\r\n const packagesDir = path.join(workspaceRoot, \"packages\");\r\n let candidates: string[] = [];\r\n try {\r\n candidates = fs\r\n .readdirSync(packagesDir, { withFileTypes: true })\r\n .filter((e) => e.isDirectory())\r\n .map((e) => path.join(packagesDir, e.name));\r\n } catch {\r\n return dirs;\r\n }\r\n const byName = new Map<string, string>();\r\n for (const candidate of candidates) {\r\n try {\r\n const name = (\r\n JSON.parse(\r\n fs.readFileSync(path.join(candidate, \"package.json\"), \"utf-8\"),\r\n ) as { name?: string }\r\n ).name;\r\n if (typeof name === \"string\") byName.set(name, candidate);\r\n } catch {\r\n // skip unreadable packages\r\n }\r\n }\r\n for (const depName of workspaceDepNames.sort()) {\r\n const dir = byName.get(depName);\r\n if (dir) dirs.push(dir);\r\n // Unresolvable workspace deps fall back to lockfile coverage; the sorted\r\n // dep-name list itself is not hashed separately because package.json is\r\n // already part of the app-source hash.\r\n }\r\n return dirs;\r\n}\r\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace-deploy.d.ts","sourceRoot":"","sources":["../../src/deploy/workspace-deploy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"workspace-deploy.d.ts","sourceRoot":"","sources":["../../src/deploy/workspace-deploy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AA4B7C,MAAM,MAAM,qBAAqB,GAAG,kBAAkB,GAAG,SAAS,GAAG,QAAQ,CAAC;AA8D9E,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,yDAAyD;IACzD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,qDAAqD;IACrD,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAC/B,qDAAqD;IACrD,QAAQ,CAAC,EAAE,OAAO,YAAY,CAAC;CAChC;AAED,wBAAsB,kBAAkB,CACtC,IAAI,GAAE,sBAA2B,GAChC,OAAO,CAAC,IAAI,CAAC,CA8Hf;AAmOD;;;;;;;;;;;GAWG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,GACb,MAAM,EAAE,CAmCV"}
|
|
@@ -17,8 +17,10 @@
|
|
|
17
17
|
import { execFileSync } from "child_process";
|
|
18
18
|
import fs from "fs";
|
|
19
19
|
import path from "path";
|
|
20
|
+
import { fileURLToPath } from "url";
|
|
20
21
|
import { AGENT_CHAT_PROCESS_RUN_PATH } from "../agent/durable-background.js";
|
|
21
22
|
import { findWorkspaceRoot } from "../scripts/utils.js";
|
|
23
|
+
import { computeWorkspaceAppBuildHash, isWorkspaceBuildCacheEnabled, workspaceAppBuildCacheHit, writeWorkspaceAppBuildStamp, } from "./workspace-build-cache.js";
|
|
22
24
|
import { DEFAULT_WORKSPACE_APP_AUDIENCE, normalizeWorkspaceAppAudience, normalizeWorkspaceAppPathList, workspaceAppAudienceFromPackageJson, workspaceAppRouteAccessFromPackageJson, } from "../shared/workspace-app-audience.js";
|
|
23
25
|
import { DISPATCH_WORKSPACE_ROOT_REDIRECTS } from "../shared/workspace-app-id.js";
|
|
24
26
|
import { collectImmutableAssetPaths, IMMUTABLE_ASSET_CACHE_HEADERS, } from "./immutable-assets.js";
|
|
@@ -48,6 +50,15 @@ const WORKSPACE_APPS_ENV_KEY = "AGENT_NATIVE_WORKSPACE_APPS_JSON";
|
|
|
48
50
|
const WORKSPACE_APPS_MANIFEST_DIR = ".agent-native";
|
|
49
51
|
const WORKSPACE_APPS_MANIFEST_FILE = "workspace-apps.json";
|
|
50
52
|
const VERCEL_OUTPUT_DIR = ".vercel/output";
|
|
53
|
+
// Version of this package — folds into the build-cache key so upgrading the
|
|
54
|
+
// framework invalidates every cached app build.
|
|
55
|
+
let BUILDER_VERSION = "unknown";
|
|
56
|
+
try {
|
|
57
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
58
|
+
// dist/deploy/workspace-deploy.js → ../../package.json
|
|
59
|
+
BUILDER_VERSION = JSON.parse(fs.readFileSync(path.resolve(__dirname, "../../package.json"), "utf-8")).version ?? "unknown";
|
|
60
|
+
}
|
|
61
|
+
catch { }
|
|
51
62
|
export async function runWorkspaceDeploy(opts = {}) {
|
|
52
63
|
const workspaceRoot = opts.workspaceRoot ?? findWorkspaceRoot(process.cwd()) ?? process.cwd();
|
|
53
64
|
const appsDir = path.join(workspaceRoot, "apps");
|
|
@@ -89,11 +100,21 @@ export async function runWorkspaceDeploy(opts = {}) {
|
|
|
89
100
|
fs.mkdirSync(functionsDir, { recursive: true });
|
|
90
101
|
}
|
|
91
102
|
console.log(`[workspace-deploy] Building ${apps.length} app(s) for preset=${preset}`);
|
|
103
|
+
const buildCacheEnabled = isWorkspaceBuildCacheEnabled(rawArgs);
|
|
104
|
+
if (!buildCacheEnabled) {
|
|
105
|
+
console.log(`[workspace-deploy] Build cache disabled — rebuilding every app.`);
|
|
106
|
+
}
|
|
107
|
+
let cachedCount = 0;
|
|
92
108
|
const execFile = opts.execFile ?? execFileSync;
|
|
93
109
|
for (const app of apps) {
|
|
94
|
-
buildOneApp(workspaceRoot, app, preset, execFile, workspaceApps);
|
|
110
|
+
const skipped = buildOneApp(workspaceRoot, app, preset, execFile, workspaceApps, buildCacheEnabled);
|
|
111
|
+
if (skipped)
|
|
112
|
+
cachedCount++;
|
|
95
113
|
moveAppBuildIntoWorkspaceOutput(workspaceRoot, app, preset, distDir, vercelOutputDir, workspaceApps);
|
|
96
114
|
}
|
|
115
|
+
if (buildCacheEnabled && cachedCount > 0) {
|
|
116
|
+
console.log(`[workspace-deploy] Build cache: reused ${cachedCount}/${apps.length} unchanged app build(s), rebuilt ${apps.length - cachedCount}.`);
|
|
117
|
+
}
|
|
97
118
|
writeWorkspaceAppManifests(workspaceRoot, distDir, apps, workspaceApps, preset);
|
|
98
119
|
if (preset === "netlify") {
|
|
99
120
|
writeNetlifyRedirects(distDir, apps);
|
|
@@ -124,7 +145,7 @@ export async function runWorkspaceDeploy(opts = {}) {
|
|
|
124
145
|
}
|
|
125
146
|
console.log(`All apps live at https://<origin>/<app-name>/*. Log in once on any app\nand the session is shared across the workspace.`);
|
|
126
147
|
}
|
|
127
|
-
function buildOneApp(workspaceRoot, app, preset, execFile, workspaceApps) {
|
|
148
|
+
function buildOneApp(workspaceRoot, app, preset, execFile, workspaceApps, buildCacheEnabled) {
|
|
128
149
|
const appDir = path.join(workspaceRoot, "apps", app);
|
|
129
150
|
const workspaceAppAudience = workspaceAppAudienceForApp(workspaceApps, app);
|
|
130
151
|
const workspaceAppRouteAccess = workspaceAppRouteAccessForApp(workspaceApps, app);
|
|
@@ -174,6 +195,24 @@ function buildOneApp(workspaceRoot, app, preset, execFile, workspaceApps) {
|
|
|
174
195
|
process.env.DATABASE_URL ??
|
|
175
196
|
env.DATABASE_URL;
|
|
176
197
|
}
|
|
198
|
+
// Content-hash cache: when the app's sources, workspace deps, lockfile,
|
|
199
|
+
// and invocation env are byte-identical to the previous successful build
|
|
200
|
+
// (and its output still exists), reuse it instead of rebuilding.
|
|
201
|
+
const cacheOpts = {
|
|
202
|
+
workspaceRoot,
|
|
203
|
+
appDir,
|
|
204
|
+
app,
|
|
205
|
+
preset,
|
|
206
|
+
buildEnv: buildCacheEnvDelta(env),
|
|
207
|
+
builderVersion: BUILDER_VERSION,
|
|
208
|
+
};
|
|
209
|
+
const buildHash = buildCacheEnabled
|
|
210
|
+
? computeWorkspaceAppBuildHash(cacheOpts)
|
|
211
|
+
: null;
|
|
212
|
+
if (buildCacheEnabled && workspaceAppBuildCacheHit(cacheOpts, buildHash)) {
|
|
213
|
+
console.log(`[workspace-deploy] ${app} unchanged — reusing cached build (base=/${app}, preset=${preset})`);
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
177
216
|
console.log(`[workspace-deploy] Building ${app} (base=/${app}, preset=${preset})`);
|
|
178
217
|
cleanAppBuildOutputs(appDir);
|
|
179
218
|
// Windows: app builds occasionally die with a native access violation
|
|
@@ -191,7 +230,10 @@ function buildOneApp(workspaceRoot, app, preset, execFile, workspaceApps) {
|
|
|
191
230
|
env,
|
|
192
231
|
stdio: "inherit",
|
|
193
232
|
});
|
|
194
|
-
|
|
233
|
+
if (buildCacheEnabled) {
|
|
234
|
+
writeWorkspaceAppBuildStamp(cacheOpts, buildHash);
|
|
235
|
+
}
|
|
236
|
+
return false;
|
|
195
237
|
}
|
|
196
238
|
catch (error) {
|
|
197
239
|
const status = error?.status ?? null;
|
|
@@ -854,6 +896,17 @@ function netlifyPublicRootAssetPaths(app, staticDir) {
|
|
|
854
896
|
function netlifyFunctionsDir(workspaceRoot) {
|
|
855
897
|
return path.join(workspaceRoot, ".netlify", "functions-internal");
|
|
856
898
|
}
|
|
899
|
+
/** The per-app build env is `{...process.env, ...computed}` — only the
|
|
900
|
+
* computed delta belongs in the cache key (ambient env is covered by the
|
|
901
|
+
* cache module's own prefix filter, machine noise like PATH stays out). */
|
|
902
|
+
function buildCacheEnvDelta(env) {
|
|
903
|
+
const delta = {};
|
|
904
|
+
for (const [key, value] of Object.entries(env)) {
|
|
905
|
+
if (process.env[key] !== value)
|
|
906
|
+
delta[key] = value;
|
|
907
|
+
}
|
|
908
|
+
return delta;
|
|
909
|
+
}
|
|
857
910
|
function cleanAppBuildOutputs(appDir) {
|
|
858
911
|
// .deploy-tmp: a crashed prior per-app build leaves partial artifacts that
|
|
859
912
|
// silently poison the next build for that app (observed on Windows: stale
|