everything-dev 2.0.0-rc.7 → 2.0.0-rc.9

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.
Files changed (142) hide show
  1. package/dist/api/auth-middleware.cjs +14 -1
  2. package/dist/api/auth-middleware.cjs.map +1 -1
  3. package/dist/api/auth-middleware.d.cts +9 -0
  4. package/dist/api/auth-middleware.d.mts +9 -0
  5. package/dist/api/auth-middleware.mjs +14 -1
  6. package/dist/api/auth-middleware.mjs.map +1 -1
  7. package/dist/apps/upload/src/client.cjs +7 -1
  8. package/dist/apps/upload/src/client.cjs.map +1 -1
  9. package/dist/apps/upload/src/client.mjs +7 -1
  10. package/dist/apps/upload/src/client.mjs.map +1 -1
  11. package/dist/auth-login.cjs +24 -1
  12. package/dist/auth-login.cjs.map +1 -1
  13. package/dist/auth-login.mjs +24 -1
  14. package/dist/auth-login.mjs.map +1 -1
  15. package/dist/auth-session.cjs +4 -2
  16. package/dist/auth-session.cjs.map +1 -1
  17. package/dist/auth-session.mjs +4 -2
  18. package/dist/auth-session.mjs.map +1 -1
  19. package/dist/cdn-deploy.cjs +11 -7
  20. package/dist/cdn-deploy.cjs.map +1 -1
  21. package/dist/cdn-deploy.mjs +11 -7
  22. package/dist/cdn-deploy.mjs.map +1 -1
  23. package/dist/cli/import.cjs +1140 -0
  24. package/dist/cli/import.cjs.map +1 -0
  25. package/dist/cli/import.mjs +1126 -0
  26. package/dist/cli/import.mjs.map +1 -0
  27. package/dist/cli/init.cjs +33 -14
  28. package/dist/cli/init.cjs.map +1 -1
  29. package/dist/cli/init.d.cts +8 -2
  30. package/dist/cli/init.d.mts +8 -2
  31. package/dist/cli/init.mjs +33 -15
  32. package/dist/cli/init.mjs.map +1 -1
  33. package/dist/cli.cjs +59 -3
  34. package/dist/cli.cjs.map +1 -1
  35. package/dist/cli.mjs +59 -3
  36. package/dist/cli.mjs.map +1 -1
  37. package/dist/commands/import.cjs +49 -0
  38. package/dist/commands/import.cjs.map +1 -0
  39. package/dist/commands/import.mjs +46 -0
  40. package/dist/commands/import.mjs.map +1 -0
  41. package/dist/commands/keys.cjs +25 -4
  42. package/dist/commands/keys.cjs.map +1 -1
  43. package/dist/commands/keys.mjs +25 -4
  44. package/dist/commands/keys.mjs.map +1 -1
  45. package/dist/config.d.cts +1 -0
  46. package/dist/config.d.mts +1 -0
  47. package/dist/contract.cjs +89 -0
  48. package/dist/contract.cjs.map +1 -1
  49. package/dist/contract.d.cts +190 -1
  50. package/dist/contract.d.mts +190 -1
  51. package/dist/contract.meta.cjs +17 -1
  52. package/dist/contract.meta.cjs.map +1 -1
  53. package/dist/contract.meta.d.cts +27 -1
  54. package/dist/contract.meta.d.mts +27 -1
  55. package/dist/contract.meta.mjs +17 -1
  56. package/dist/contract.meta.mjs.map +1 -1
  57. package/dist/contract.mjs +84 -1
  58. package/dist/contract.mjs.map +1 -1
  59. package/dist/custody-signer.cjs +70 -0
  60. package/dist/custody-signer.cjs.map +1 -0
  61. package/dist/custody-signer.mjs +68 -0
  62. package/dist/custody-signer.mjs.map +1 -0
  63. package/dist/db/core.cjs +22 -4
  64. package/dist/db/core.cjs.map +1 -1
  65. package/dist/db/core.mjs +22 -4
  66. package/dist/db/core.mjs.map +1 -1
  67. package/dist/descriptor/schema.cjs +8 -5
  68. package/dist/descriptor/schema.cjs.map +1 -1
  69. package/dist/descriptor/schema.d.cts +1 -0
  70. package/dist/descriptor/schema.d.mts +1 -0
  71. package/dist/descriptor/schema.mjs +8 -5
  72. package/dist/descriptor/schema.mjs.map +1 -1
  73. package/dist/descriptor/serialize.cjs +1 -1
  74. package/dist/descriptor/serialize.cjs.map +1 -1
  75. package/dist/descriptor/serialize.mjs +1 -1
  76. package/dist/descriptor/serialize.mjs.map +1 -1
  77. package/dist/http-client.cjs +14 -4
  78. package/dist/http-client.cjs.map +1 -1
  79. package/dist/http-client.mjs +14 -4
  80. package/dist/http-client.mjs.map +1 -1
  81. package/dist/index.cjs +6 -0
  82. package/dist/index.d.cts +2 -2
  83. package/dist/index.d.mts +2 -2
  84. package/dist/index.mjs +2 -2
  85. package/dist/infra/planner.cjs +6 -2
  86. package/dist/infra/planner.cjs.map +1 -1
  87. package/dist/infra/planner.mjs +6 -2
  88. package/dist/infra/planner.mjs.map +1 -1
  89. package/dist/outlayer-login.cjs +54 -0
  90. package/dist/outlayer-login.cjs.map +1 -0
  91. package/dist/outlayer-login.mjs +54 -0
  92. package/dist/outlayer-login.mjs.map +1 -0
  93. package/dist/plugin.cjs +2 -0
  94. package/dist/plugin.cjs.map +1 -1
  95. package/dist/plugin.d.cts +74 -0
  96. package/dist/plugin.d.mts +74 -0
  97. package/dist/plugin.mjs +2 -0
  98. package/dist/plugin.mjs.map +1 -1
  99. package/dist/publish.cjs +145 -22
  100. package/dist/publish.cjs.map +1 -1
  101. package/dist/publish.mjs +147 -24
  102. package/dist/publish.mjs.map +1 -1
  103. package/dist/source-archive.cjs +51 -0
  104. package/dist/source-archive.cjs.map +1 -0
  105. package/dist/source-archive.mjs +50 -0
  106. package/dist/source-archive.mjs.map +1 -0
  107. package/dist/storage-upload.cjs +6 -1
  108. package/dist/storage-upload.cjs.map +1 -1
  109. package/dist/storage-upload.mjs +6 -1
  110. package/dist/storage-upload.mjs.map +1 -1
  111. package/dist/types.cjs +5 -1
  112. package/dist/types.cjs.map +1 -1
  113. package/dist/types.d.cts +2 -0
  114. package/dist/types.d.mts +2 -0
  115. package/dist/types.mjs +5 -1
  116. package/dist/types.mjs.map +1 -1
  117. package/dist/ui/auth.d.cts +217 -123
  118. package/dist/ui/auth.d.mts +217 -123
  119. package/dist/ui/router-client.cjs +2 -1
  120. package/dist/ui/router-client.cjs.map +1 -1
  121. package/dist/ui/router-client.mjs +2 -1
  122. package/dist/ui/router-client.mjs.map +1 -1
  123. package/dist/utils/workspace-subtree.cjs +37 -0
  124. package/dist/utils/workspace-subtree.cjs.map +1 -0
  125. package/dist/utils/workspace-subtree.mjs +36 -0
  126. package/dist/utils/workspace-subtree.mjs.map +1 -0
  127. package/dist/version-manifest-deploy.cjs +4 -2
  128. package/dist/version-manifest-deploy.cjs.map +1 -1
  129. package/dist/version-manifest-deploy.mjs +4 -2
  130. package/dist/version-manifest-deploy.mjs.map +1 -1
  131. package/dist/version-manifest-resolve.cjs +2 -1
  132. package/dist/version-manifest-resolve.cjs.map +1 -1
  133. package/dist/version-manifest-resolve.d.cts +3 -0
  134. package/dist/version-manifest-resolve.d.mts +3 -0
  135. package/dist/version-manifest-resolve.mjs +2 -1
  136. package/dist/version-manifest-resolve.mjs.map +1 -1
  137. package/package.json +11 -10
  138. package/skills/api-and-auth/SKILL.md +12 -9
  139. package/skills/api-and-auth/references/middleware.md +38 -0
  140. package/skills/cli-reference/SKILL.md +38 -6
  141. package/skills/init-upgrade/SKILL.md +76 -3
  142. package/skills/talk-to-the-app/SKILL.md +32 -5
@@ -33,7 +33,8 @@ function createRouter(opts) {
33
33
  cspNonce: opts.context.cspNonce
34
34
  }),
35
35
  session: opts.context.session,
36
- locale: opts.context.locale
36
+ locale: opts.context.locale,
37
+ pluginNav: opts.context.pluginNav
37
38
  },
38
39
  ...cspNonce ? { ssr: { nonce: cspNonce } } : {},
39
40
  defaultPreload: "intent",
@@ -1 +1 @@
1
- {"version":3,"file":"router-client.cjs","names":["createBrowserHistory","createTanStackRouter","createAuthClient","RouterError","dehydrate"],"sources":["../../src/ui/router-client.tsx"],"sourcesContent":["/**\n * Client router factory — creates a TanStack Router with browser history.\n * The app-specific route tree is injected as `defaultRouteTree` (the\n * bundled core-only fallback); the composed tree wins when manifest\n * construction produced one.\n */\n\nimport { dehydrate, hydrate } from \"@tanstack/react-query\";\nimport {\n type AnyRoute,\n createBrowserHistory,\n createRouter as createTanStackRouter,\n} from \"@tanstack/react-router\";\nimport { createAuthClient } from \"./auth\";\nimport { defaultNotFoundComponent, defaultPendingComponent } from \"./router-defaults\";\nimport { RouterError } from \"./router-error\";\nimport type { CreateRouterOptions, RouterContextWithApi } from \"./types\";\n\nexport interface CoreRouterOptions<TApiClient = unknown, TSession = unknown>\n extends CreateRouterOptions<TApiClient, TSession> {\n /** The app's generated route tree — the core-only fallback when no composed tree is passed. */\n defaultRouteTree?: unknown;\n}\n\nexport function createRouter<\n TApiClient = unknown,\n TSession = unknown,\n TRouteTree extends AnyRoute = AnyRoute,\n>(\n opts: CoreRouterOptions<TApiClient, TSession> & {\n context: RouterContextWithApi<TApiClient, TSession> & { authClient?: unknown };\n },\n) {\n const queryClient = opts.context.queryClient;\n const history = opts.history ?? createBrowserHistory();\n const cspNonce = opts.context.cspNonce;\n\n const routeTree = opts.routeTree ?? opts.defaultRouteTree;\n if (!routeTree) {\n throw new Error(\n \"createRouter: no route tree — compose failed and no core-only fallback was constructed\",\n );\n }\n\n const router = createTanStackRouter({\n routeTree: routeTree as TRouteTree,\n history,\n basepath: opts.basepath ?? opts.context.runtimeConfig?.runtime?.runtimeBasePath ?? \"/\",\n context: {\n queryClient,\n runtimeConfig: opts.context.runtimeConfig,\n cspNonce: opts.context.cspNonce,\n apiClient: opts.context.apiClient,\n authClient:\n opts.context.authClient ??\n createAuthClient({\n runtimeConfig: opts.context.runtimeConfig,\n cspNonce: opts.context.cspNonce,\n }),\n session: opts.context.session,\n locale: opts.context.locale,\n },\n ...(cspNonce ? { ssr: { nonce: cspNonce } } : {}),\n defaultPreload: \"intent\",\n scrollRestoration: true,\n defaultStructuralSharing: true,\n defaultPreloadStaleTime: 0,\n defaultPendingMinMs: 0,\n defaultErrorComponent: RouterError,\n defaultNotFoundComponent,\n defaultPendingComponent,\n dehydrate: () => {\n if (typeof window === \"undefined\") {\n return { queryClientState: dehydrate(queryClient) };\n }\n\n return { queryClientState: {} };\n },\n hydrate: (dehydrated: { queryClientState?: unknown }) => {\n if (typeof window !== \"undefined\" && dehydrated?.queryClientState) {\n hydrate(queryClient, dehydrated.queryClientState);\n }\n },\n });\n\n return { router, queryClient };\n}\n"],"mappings":";;;;;;;;;;;;;;AAwBA,SAAgB,aAKd,MAGA;CACA,MAAM,cAAc,KAAK,QAAQ;CACjC,MAAM,UAAU,KAAK,eAAWA,6CAAqB;CACrD,MAAM,WAAW,KAAK,QAAQ;CAE9B,MAAM,YAAY,KAAK,aAAa,KAAK;CACzC,IAAI,CAAC,WACH,MAAM,IAAI,MACR,wFACF;CA4CF,OAAO;EAAE,YAzCMC,qCAAqB;GACvB;GACX;GACA,UAAU,KAAK,YAAY,KAAK,QAAQ,eAAe,SAAS,mBAAmB;GACnF,SAAS;IACP;IACA,eAAe,KAAK,QAAQ;IAC5B,UAAU,KAAK,QAAQ;IACvB,WAAW,KAAK,QAAQ;IACxB,YACE,KAAK,QAAQ,cACbC,iCAAiB;KACf,eAAe,KAAK,QAAQ;KAC5B,UAAU,KAAK,QAAQ;IACzB,CAAC;IACH,SAAS,KAAK,QAAQ;IACtB,QAAQ,KAAK,QAAQ;GACvB;GACA,GAAI,WAAW,EAAE,KAAK,EAAE,OAAO,SAAS,EAAE,IAAI,CAAC;GAC/C,gBAAgB;GAChB,mBAAmB;GACnB,0BAA0B;GAC1B,yBAAyB;GACzB,qBAAqB;GACrB,uBAAuBC;GACvB;GACA;GACA,iBAAiB;IACf,IAAI,OAAO,WAAW,aACpB,OAAO,EAAE,sBAAkBC,iCAAU,WAAW,EAAE;IAGpD,OAAO,EAAE,kBAAkB,CAAC,EAAE;GAChC;GACA,UAAU,eAA+C;IACvD,IAAI,OAAO,WAAW,eAAe,YAAY,kBAC/C,mCAAQ,aAAa,WAAW,gBAAgB;GAEpD;EACF,CAEc;EAAG;CAAY;AAC/B"}
1
+ {"version":3,"file":"router-client.cjs","names":["createBrowserHistory","createTanStackRouter","createAuthClient","RouterError","dehydrate"],"sources":["../../src/ui/router-client.tsx"],"sourcesContent":["/**\n * Client router factory — creates a TanStack Router with browser history.\n * The app-specific route tree is injected as `defaultRouteTree` (the\n * bundled core-only fallback); the composed tree wins when manifest\n * construction produced one.\n */\n\nimport { dehydrate, hydrate } from \"@tanstack/react-query\";\nimport {\n type AnyRoute,\n createBrowserHistory,\n createRouter as createTanStackRouter,\n} from \"@tanstack/react-router\";\nimport { createAuthClient } from \"./auth\";\nimport { defaultNotFoundComponent, defaultPendingComponent } from \"./router-defaults\";\nimport { RouterError } from \"./router-error\";\nimport type { CreateRouterOptions, RouterContextWithApi } from \"./types\";\n\nexport interface CoreRouterOptions<TApiClient = unknown, TSession = unknown>\n extends CreateRouterOptions<TApiClient, TSession> {\n /** The app's generated route tree — the core-only fallback when no composed tree is passed. */\n defaultRouteTree?: unknown;\n}\n\nexport function createRouter<\n TApiClient = unknown,\n TSession = unknown,\n TRouteTree extends AnyRoute = AnyRoute,\n>(\n opts: CoreRouterOptions<TApiClient, TSession> & {\n context: RouterContextWithApi<TApiClient, TSession> & { authClient?: unknown };\n },\n) {\n const queryClient = opts.context.queryClient;\n const history = opts.history ?? createBrowserHistory();\n const cspNonce = opts.context.cspNonce;\n\n const routeTree = opts.routeTree ?? opts.defaultRouteTree;\n if (!routeTree) {\n throw new Error(\n \"createRouter: no route tree — compose failed and no core-only fallback was constructed\",\n );\n }\n\n const router = createTanStackRouter({\n routeTree: routeTree as TRouteTree,\n history,\n basepath: opts.basepath ?? opts.context.runtimeConfig?.runtime?.runtimeBasePath ?? \"/\",\n context: {\n queryClient,\n runtimeConfig: opts.context.runtimeConfig,\n cspNonce: opts.context.cspNonce,\n apiClient: opts.context.apiClient,\n authClient:\n opts.context.authClient ??\n createAuthClient({\n runtimeConfig: opts.context.runtimeConfig,\n cspNonce: opts.context.cspNonce,\n }),\n session: opts.context.session,\n locale: opts.context.locale,\n pluginNav: opts.context.pluginNav,\n },\n ...(cspNonce ? { ssr: { nonce: cspNonce } } : {}),\n defaultPreload: \"intent\",\n scrollRestoration: true,\n defaultStructuralSharing: true,\n defaultPreloadStaleTime: 0,\n defaultPendingMinMs: 0,\n defaultErrorComponent: RouterError,\n defaultNotFoundComponent,\n defaultPendingComponent,\n dehydrate: () => {\n if (typeof window === \"undefined\") {\n return { queryClientState: dehydrate(queryClient) };\n }\n\n return { queryClientState: {} };\n },\n hydrate: (dehydrated: { queryClientState?: unknown }) => {\n if (typeof window !== \"undefined\" && dehydrated?.queryClientState) {\n hydrate(queryClient, dehydrated.queryClientState);\n }\n },\n });\n\n return { router, queryClient };\n}\n"],"mappings":";;;;;;;;;;;;;;AAwBA,SAAgB,aAKd,MAGA;CACA,MAAM,cAAc,KAAK,QAAQ;CACjC,MAAM,UAAU,KAAK,eAAWA,6CAAqB;CACrD,MAAM,WAAW,KAAK,QAAQ;CAE9B,MAAM,YAAY,KAAK,aAAa,KAAK;CACzC,IAAI,CAAC,WACH,MAAM,IAAI,MACR,wFACF;CA6CF,OAAO;EAAE,YA1CMC,qCAAqB;GACvB;GACX;GACA,UAAU,KAAK,YAAY,KAAK,QAAQ,eAAe,SAAS,mBAAmB;GACnF,SAAS;IACP;IACA,eAAe,KAAK,QAAQ;IAC5B,UAAU,KAAK,QAAQ;IACvB,WAAW,KAAK,QAAQ;IACxB,YACE,KAAK,QAAQ,cACbC,iCAAiB;KACf,eAAe,KAAK,QAAQ;KAC5B,UAAU,KAAK,QAAQ;IACzB,CAAC;IACH,SAAS,KAAK,QAAQ;IACtB,QAAQ,KAAK,QAAQ;IACrB,WAAW,KAAK,QAAQ;GAC1B;GACA,GAAI,WAAW,EAAE,KAAK,EAAE,OAAO,SAAS,EAAE,IAAI,CAAC;GAC/C,gBAAgB;GAChB,mBAAmB;GACnB,0BAA0B;GAC1B,yBAAyB;GACzB,qBAAqB;GACrB,uBAAuBC;GACvB;GACA;GACA,iBAAiB;IACf,IAAI,OAAO,WAAW,aACpB,OAAO,EAAE,sBAAkBC,iCAAU,WAAW,EAAE;IAGpD,OAAO,EAAE,kBAAkB,CAAC,EAAE;GAChC;GACA,UAAU,eAA+C;IACvD,IAAI,OAAO,WAAW,eAAe,YAAY,kBAC/C,mCAAQ,aAAa,WAAW,gBAAgB;GAEpD;EACF,CAEc;EAAG;CAAY;AAC/B"}
@@ -32,7 +32,8 @@ function createRouter(opts) {
32
32
  cspNonce: opts.context.cspNonce
33
33
  }),
34
34
  session: opts.context.session,
35
- locale: opts.context.locale
35
+ locale: opts.context.locale,
36
+ pluginNav: opts.context.pluginNav
36
37
  },
37
38
  ...cspNonce ? { ssr: { nonce: cspNonce } } : {},
38
39
  defaultPreload: "intent",
@@ -1 +1 @@
1
- {"version":3,"file":"router-client.mjs","names":["createTanStackRouter"],"sources":["../../src/ui/router-client.tsx"],"sourcesContent":["/**\n * Client router factory — creates a TanStack Router with browser history.\n * The app-specific route tree is injected as `defaultRouteTree` (the\n * bundled core-only fallback); the composed tree wins when manifest\n * construction produced one.\n */\n\nimport { dehydrate, hydrate } from \"@tanstack/react-query\";\nimport {\n type AnyRoute,\n createBrowserHistory,\n createRouter as createTanStackRouter,\n} from \"@tanstack/react-router\";\nimport { createAuthClient } from \"./auth\";\nimport { defaultNotFoundComponent, defaultPendingComponent } from \"./router-defaults\";\nimport { RouterError } from \"./router-error\";\nimport type { CreateRouterOptions, RouterContextWithApi } from \"./types\";\n\nexport interface CoreRouterOptions<TApiClient = unknown, TSession = unknown>\n extends CreateRouterOptions<TApiClient, TSession> {\n /** The app's generated route tree — the core-only fallback when no composed tree is passed. */\n defaultRouteTree?: unknown;\n}\n\nexport function createRouter<\n TApiClient = unknown,\n TSession = unknown,\n TRouteTree extends AnyRoute = AnyRoute,\n>(\n opts: CoreRouterOptions<TApiClient, TSession> & {\n context: RouterContextWithApi<TApiClient, TSession> & { authClient?: unknown };\n },\n) {\n const queryClient = opts.context.queryClient;\n const history = opts.history ?? createBrowserHistory();\n const cspNonce = opts.context.cspNonce;\n\n const routeTree = opts.routeTree ?? opts.defaultRouteTree;\n if (!routeTree) {\n throw new Error(\n \"createRouter: no route tree — compose failed and no core-only fallback was constructed\",\n );\n }\n\n const router = createTanStackRouter({\n routeTree: routeTree as TRouteTree,\n history,\n basepath: opts.basepath ?? opts.context.runtimeConfig?.runtime?.runtimeBasePath ?? \"/\",\n context: {\n queryClient,\n runtimeConfig: opts.context.runtimeConfig,\n cspNonce: opts.context.cspNonce,\n apiClient: opts.context.apiClient,\n authClient:\n opts.context.authClient ??\n createAuthClient({\n runtimeConfig: opts.context.runtimeConfig,\n cspNonce: opts.context.cspNonce,\n }),\n session: opts.context.session,\n locale: opts.context.locale,\n },\n ...(cspNonce ? { ssr: { nonce: cspNonce } } : {}),\n defaultPreload: \"intent\",\n scrollRestoration: true,\n defaultStructuralSharing: true,\n defaultPreloadStaleTime: 0,\n defaultPendingMinMs: 0,\n defaultErrorComponent: RouterError,\n defaultNotFoundComponent,\n defaultPendingComponent,\n dehydrate: () => {\n if (typeof window === \"undefined\") {\n return { queryClientState: dehydrate(queryClient) };\n }\n\n return { queryClientState: {} };\n },\n hydrate: (dehydrated: { queryClientState?: unknown }) => {\n if (typeof window !== \"undefined\" && dehydrated?.queryClientState) {\n hydrate(queryClient, dehydrated.queryClientState);\n }\n },\n });\n\n return { router, queryClient };\n}\n"],"mappings":";;;;;;;;;;;;;AAwBA,SAAgB,aAKd,MAGA;CACA,MAAM,cAAc,KAAK,QAAQ;CACjC,MAAM,UAAU,KAAK,WAAW,qBAAqB;CACrD,MAAM,WAAW,KAAK,QAAQ;CAE9B,MAAM,YAAY,KAAK,aAAa,KAAK;CACzC,IAAI,CAAC,WACH,MAAM,IAAI,MACR,wFACF;CA4CF,OAAO;EAAE,QAzCMA,eAAqB;GACvB;GACX;GACA,UAAU,KAAK,YAAY,KAAK,QAAQ,eAAe,SAAS,mBAAmB;GACnF,SAAS;IACP;IACA,eAAe,KAAK,QAAQ;IAC5B,UAAU,KAAK,QAAQ;IACvB,WAAW,KAAK,QAAQ;IACxB,YACE,KAAK,QAAQ,cACb,iBAAiB;KACf,eAAe,KAAK,QAAQ;KAC5B,UAAU,KAAK,QAAQ;IACzB,CAAC;IACH,SAAS,KAAK,QAAQ;IACtB,QAAQ,KAAK,QAAQ;GACvB;GACA,GAAI,WAAW,EAAE,KAAK,EAAE,OAAO,SAAS,EAAE,IAAI,CAAC;GAC/C,gBAAgB;GAChB,mBAAmB;GACnB,0BAA0B;GAC1B,yBAAyB;GACzB,qBAAqB;GACrB,uBAAuB;GACvB;GACA;GACA,iBAAiB;IACf,IAAI,OAAO,WAAW,aACpB,OAAO,EAAE,kBAAkB,UAAU,WAAW,EAAE;IAGpD,OAAO,EAAE,kBAAkB,CAAC,EAAE;GAChC;GACA,UAAU,eAA+C;IACvD,IAAI,OAAO,WAAW,eAAe,YAAY,kBAC/C,QAAQ,aAAa,WAAW,gBAAgB;GAEpD;EACF,CAEc;EAAG;CAAY;AAC/B"}
1
+ {"version":3,"file":"router-client.mjs","names":["createTanStackRouter"],"sources":["../../src/ui/router-client.tsx"],"sourcesContent":["/**\n * Client router factory — creates a TanStack Router with browser history.\n * The app-specific route tree is injected as `defaultRouteTree` (the\n * bundled core-only fallback); the composed tree wins when manifest\n * construction produced one.\n */\n\nimport { dehydrate, hydrate } from \"@tanstack/react-query\";\nimport {\n type AnyRoute,\n createBrowserHistory,\n createRouter as createTanStackRouter,\n} from \"@tanstack/react-router\";\nimport { createAuthClient } from \"./auth\";\nimport { defaultNotFoundComponent, defaultPendingComponent } from \"./router-defaults\";\nimport { RouterError } from \"./router-error\";\nimport type { CreateRouterOptions, RouterContextWithApi } from \"./types\";\n\nexport interface CoreRouterOptions<TApiClient = unknown, TSession = unknown>\n extends CreateRouterOptions<TApiClient, TSession> {\n /** The app's generated route tree — the core-only fallback when no composed tree is passed. */\n defaultRouteTree?: unknown;\n}\n\nexport function createRouter<\n TApiClient = unknown,\n TSession = unknown,\n TRouteTree extends AnyRoute = AnyRoute,\n>(\n opts: CoreRouterOptions<TApiClient, TSession> & {\n context: RouterContextWithApi<TApiClient, TSession> & { authClient?: unknown };\n },\n) {\n const queryClient = opts.context.queryClient;\n const history = opts.history ?? createBrowserHistory();\n const cspNonce = opts.context.cspNonce;\n\n const routeTree = opts.routeTree ?? opts.defaultRouteTree;\n if (!routeTree) {\n throw new Error(\n \"createRouter: no route tree — compose failed and no core-only fallback was constructed\",\n );\n }\n\n const router = createTanStackRouter({\n routeTree: routeTree as TRouteTree,\n history,\n basepath: opts.basepath ?? opts.context.runtimeConfig?.runtime?.runtimeBasePath ?? \"/\",\n context: {\n queryClient,\n runtimeConfig: opts.context.runtimeConfig,\n cspNonce: opts.context.cspNonce,\n apiClient: opts.context.apiClient,\n authClient:\n opts.context.authClient ??\n createAuthClient({\n runtimeConfig: opts.context.runtimeConfig,\n cspNonce: opts.context.cspNonce,\n }),\n session: opts.context.session,\n locale: opts.context.locale,\n pluginNav: opts.context.pluginNav,\n },\n ...(cspNonce ? { ssr: { nonce: cspNonce } } : {}),\n defaultPreload: \"intent\",\n scrollRestoration: true,\n defaultStructuralSharing: true,\n defaultPreloadStaleTime: 0,\n defaultPendingMinMs: 0,\n defaultErrorComponent: RouterError,\n defaultNotFoundComponent,\n defaultPendingComponent,\n dehydrate: () => {\n if (typeof window === \"undefined\") {\n return { queryClientState: dehydrate(queryClient) };\n }\n\n return { queryClientState: {} };\n },\n hydrate: (dehydrated: { queryClientState?: unknown }) => {\n if (typeof window !== \"undefined\" && dehydrated?.queryClientState) {\n hydrate(queryClient, dehydrated.queryClientState);\n }\n },\n });\n\n return { router, queryClient };\n}\n"],"mappings":";;;;;;;;;;;;;AAwBA,SAAgB,aAKd,MAGA;CACA,MAAM,cAAc,KAAK,QAAQ;CACjC,MAAM,UAAU,KAAK,WAAW,qBAAqB;CACrD,MAAM,WAAW,KAAK,QAAQ;CAE9B,MAAM,YAAY,KAAK,aAAa,KAAK;CACzC,IAAI,CAAC,WACH,MAAM,IAAI,MACR,wFACF;CA6CF,OAAO;EAAE,QA1CMA,eAAqB;GACvB;GACX;GACA,UAAU,KAAK,YAAY,KAAK,QAAQ,eAAe,SAAS,mBAAmB;GACnF,SAAS;IACP;IACA,eAAe,KAAK,QAAQ;IAC5B,UAAU,KAAK,QAAQ;IACvB,WAAW,KAAK,QAAQ;IACxB,YACE,KAAK,QAAQ,cACb,iBAAiB;KACf,eAAe,KAAK,QAAQ;KAC5B,UAAU,KAAK,QAAQ;IACzB,CAAC;IACH,SAAS,KAAK,QAAQ;IACtB,QAAQ,KAAK,QAAQ;IACrB,WAAW,KAAK,QAAQ;GAC1B;GACA,GAAI,WAAW,EAAE,KAAK,EAAE,OAAO,SAAS,EAAE,IAAI,CAAC;GAC/C,gBAAgB;GAChB,mBAAmB;GACnB,0BAA0B;GAC1B,yBAAyB;GACzB,qBAAqB;GACrB,uBAAuB;GACvB;GACA;GACA,iBAAiB;IACf,IAAI,OAAO,WAAW,aACpB,OAAO,EAAE,kBAAkB,UAAU,WAAW,EAAE;IAGpD,OAAO,EAAE,kBAAkB,CAAC,EAAE;GAChC;GACA,UAAU,eAA+C;IACvD,IAAI,OAAO,WAAW,eAAe,YAAY,kBAC/C,QAAQ,aAAa,WAAW,gBAAgB;GAEpD;EACF,CAEc;EAAG;CAAY;AAC/B"}
@@ -0,0 +1,37 @@
1
+ let node_fs = require("node:fs");
2
+ let node_path = require("node:path");
3
+ let glob = require("glob");
4
+
5
+ //#region src/utils/workspace-subtree.ts
6
+ /**
7
+ * The ignore set both sides of a source handoff agree on: `bos import`
8
+ * classifies files under it, and source archives published by the deploy
9
+ * train (#406) exclude it — so an imported archive never smuggles in
10
+ * build output, VCS state, or local .bos state. Keep this list in sync
11
+ * with nothing else: it is the single source of truth.
12
+ */
13
+ const WORKSPACE_SUBTREE_IGNORE = [
14
+ "**/node_modules/**",
15
+ "**/.git/**",
16
+ "**/dist/**",
17
+ "**/.bos/**",
18
+ "**/.every-plugin/**"
19
+ ];
20
+ /** Sorted, file-only list of the workspace subtree at `root`, under the shared ignore set. */
21
+ async function listWorkspaceSubtreeFiles(root, workspace) {
22
+ const matches = await (0, glob.glob)(`${workspace}/**`, {
23
+ cwd: root,
24
+ nodir: true,
25
+ dot: true,
26
+ absolute: false,
27
+ ignore: WORKSPACE_SUBTREE_IGNORE
28
+ });
29
+ const files = [];
30
+ for (const match of matches) if ((0, node_fs.lstatSync)((0, node_path.join)(root, match)).isFile()) files.push(match);
31
+ return files.sort();
32
+ }
33
+
34
+ //#endregion
35
+ exports.WORKSPACE_SUBTREE_IGNORE = WORKSPACE_SUBTREE_IGNORE;
36
+ exports.listWorkspaceSubtreeFiles = listWorkspaceSubtreeFiles;
37
+ //# sourceMappingURL=workspace-subtree.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-subtree.cjs","names":["lstatSync","join"],"sources":["../../src/utils/workspace-subtree.ts"],"sourcesContent":["import { lstatSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { glob } from \"glob\";\n\n/**\n * The ignore set both sides of a source handoff agree on: `bos import`\n * classifies files under it, and source archives published by the deploy\n * train (#406) exclude it — so an imported archive never smuggles in\n * build output, VCS state, or local .bos state. Keep this list in sync\n * with nothing else: it is the single source of truth.\n */\nexport const WORKSPACE_SUBTREE_IGNORE = [\n \"**/node_modules/**\",\n \"**/.git/**\",\n \"**/dist/**\",\n \"**/.bos/**\",\n \"**/.every-plugin/**\",\n];\n\n/** Sorted, file-only list of the workspace subtree at `root`, under the shared ignore set. */\nexport async function listWorkspaceSubtreeFiles(\n root: string,\n workspace: string,\n): Promise<string[]> {\n const matches = await glob(`${workspace}/**`, {\n cwd: root,\n nodir: true,\n dot: true,\n absolute: false,\n ignore: WORKSPACE_SUBTREE_IGNORE,\n });\n const files: string[] = [];\n for (const match of matches) {\n if (lstatSync(join(root, match)).isFile()) {\n files.push(match);\n }\n }\n return files.sort();\n}\n"],"mappings":";;;;;;;;;;;;AAWA,MAAa,2BAA2B;CACtC;CACA;CACA;CACA;CACA;AACF;;AAGA,eAAsB,0BACpB,MACA,WACmB;CACnB,MAAM,UAAU,UAAM,WAAK,GAAG,UAAU,MAAM;EAC5C,KAAK;EACL,OAAO;EACP,KAAK;EACL,UAAU;EACV,QAAQ;CACV,CAAC;CACD,MAAM,QAAkB,CAAC;CACzB,KAAK,MAAM,SAAS,SAClB,QAAIA,uBAAUC,gBAAK,MAAM,KAAK,CAAC,CAAC,CAAC,OAAO,GACtC,MAAM,KAAK,KAAK;CAGpB,OAAO,MAAM,KAAK;AACpB"}
@@ -0,0 +1,36 @@
1
+ import { lstatSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { glob } from "glob";
4
+
5
+ //#region src/utils/workspace-subtree.ts
6
+ /**
7
+ * The ignore set both sides of a source handoff agree on: `bos import`
8
+ * classifies files under it, and source archives published by the deploy
9
+ * train (#406) exclude it — so an imported archive never smuggles in
10
+ * build output, VCS state, or local .bos state. Keep this list in sync
11
+ * with nothing else: it is the single source of truth.
12
+ */
13
+ const WORKSPACE_SUBTREE_IGNORE = [
14
+ "**/node_modules/**",
15
+ "**/.git/**",
16
+ "**/dist/**",
17
+ "**/.bos/**",
18
+ "**/.every-plugin/**"
19
+ ];
20
+ /** Sorted, file-only list of the workspace subtree at `root`, under the shared ignore set. */
21
+ async function listWorkspaceSubtreeFiles(root, workspace) {
22
+ const matches = await glob(`${workspace}/**`, {
23
+ cwd: root,
24
+ nodir: true,
25
+ dot: true,
26
+ absolute: false,
27
+ ignore: WORKSPACE_SUBTREE_IGNORE
28
+ });
29
+ const files = [];
30
+ for (const match of matches) if (lstatSync(join(root, match)).isFile()) files.push(match);
31
+ return files.sort();
32
+ }
33
+
34
+ //#endregion
35
+ export { WORKSPACE_SUBTREE_IGNORE, listWorkspaceSubtreeFiles };
36
+ //# sourceMappingURL=workspace-subtree.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-subtree.mjs","names":[],"sources":["../../src/utils/workspace-subtree.ts"],"sourcesContent":["import { lstatSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { glob } from \"glob\";\n\n/**\n * The ignore set both sides of a source handoff agree on: `bos import`\n * classifies files under it, and source archives published by the deploy\n * train (#406) exclude it — so an imported archive never smuggles in\n * build output, VCS state, or local .bos state. Keep this list in sync\n * with nothing else: it is the single source of truth.\n */\nexport const WORKSPACE_SUBTREE_IGNORE = [\n \"**/node_modules/**\",\n \"**/.git/**\",\n \"**/dist/**\",\n \"**/.bos/**\",\n \"**/.every-plugin/**\",\n];\n\n/** Sorted, file-only list of the workspace subtree at `root`, under the shared ignore set. */\nexport async function listWorkspaceSubtreeFiles(\n root: string,\n workspace: string,\n): Promise<string[]> {\n const matches = await glob(`${workspace}/**`, {\n cwd: root,\n nodir: true,\n dot: true,\n absolute: false,\n ignore: WORKSPACE_SUBTREE_IGNORE,\n });\n const files: string[] = [];\n for (const match of matches) {\n if (lstatSync(join(root, match)).isFile()) {\n files.push(match);\n }\n }\n return files.sort();\n}\n"],"mappings":";;;;;;;;;;;;AAWA,MAAa,2BAA2B;CACtC;CACA;CACA;CACA;CACA;AACF;;AAGA,eAAsB,0BACpB,MACA,WACmB;CACnB,MAAM,UAAU,MAAM,KAAK,GAAG,UAAU,MAAM;EAC5C,KAAK;EACL,OAAO;EACP,KAAK;EACL,UAAU;EACV,QAAQ;CACV,CAAC;CACD,MAAM,QAAkB,CAAC;CACzB,KAAK,MAAM,SAAS,SAClB,IAAI,UAAU,KAAK,MAAM,KAAK,CAAC,CAAC,CAAC,OAAO,GACtC,MAAM,KAAK,KAAK;CAGpB,OAAO,MAAM,KAAK;AACpB"}
@@ -23,7 +23,7 @@ function readBuildReport(distRoot) {
23
23
  * uploaded workspaces must pin.
24
24
  */
25
25
  function composeWorkspaceVersionManifest(input) {
26
- const { report, ssrReport, integrityMap, files } = input;
26
+ const { report, ssrReport, integrityMap, files, source, gitSha } = input;
27
27
  const entryIntegrity = integrityMap[report.entry];
28
28
  if (!entryIntegrity) return null;
29
29
  const ssrEntry = ssrReport?.entry;
@@ -40,7 +40,9 @@ function composeWorkspaceVersionManifest(input) {
40
40
  integrity: integrityMap[report.browserManifest]
41
41
  } } : {},
42
42
  ...report.css && integrityMap[report.css] ? { assets: { css: integrityMap[report.css] } } : {},
43
- ...files ? { files } : {}
43
+ ...files ? { files } : {},
44
+ ...source ? { source } : {},
45
+ ...gitSha ? { gitSha } : {}
44
46
  });
45
47
  }
46
48
 
@@ -1 +1 @@
1
- {"version":3,"file":"version-manifest-deploy.cjs","names":["join","existsSync","BuildEntryReportSchema","readFileSync","composeVersionManifest"],"sources":["../src/version-manifest-deploy.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { type BuildEntryReport, BuildEntryReportSchema } from \"every-plugin/build/artifact-names\";\nimport {\n composeVersionManifest,\n type WorkspaceVersionManifest,\n} from \"every-plugin/version-manifest\";\n\n/** Read a workspace dist's build-report.json (ticket 01's entry-name report). */\nexport function readBuildReport(distRoot: string): BuildEntryReport | null {\n const reportPath = join(distRoot, \"build-report.json\");\n if (!existsSync(reportPath)) return null;\n try {\n return BuildEntryReportSchema.parse(JSON.parse(readFileSync(reportPath, \"utf8\")));\n } catch {\n return null;\n }\n}\n\n/**\n * Compose the workspace's version manifest from the dist build reports and\n * the **server-computed** SRI map of the upload response (MAP decision 6 —\n * SRI is authoritative from the storage boundary, never computed\n * client-side). Returns null when the dist predates hashed entry names\n * (no report, or the SRI map lacks the entry) — the deploy leg then aborts:\n * uploaded workspaces must pin.\n */\nexport function composeWorkspaceVersionManifest(input: {\n report: BuildEntryReport;\n ssrReport?: BuildEntryReport | null;\n integrityMap: Record<string, string>;\n /** full per-file SRI map of the dist — recorded for the next deploy's diff */\n files?: Record<string, string>;\n}): WorkspaceVersionManifest | null {\n const { report, ssrReport, integrityMap, files } = input;\n\n const entryIntegrity = integrityMap[report.entry];\n if (!entryIntegrity) return null;\n\n const ssrEntry = ssrReport?.entry;\n const ssrIntegrity = ssrEntry ? integrityMap[`ssr/${ssrEntry}`] : undefined;\n\n return composeVersionManifest({\n entry: report.entry,\n entryIntegrity,\n ...(ssrEntry && ssrIntegrity\n ? { ssr: { entry: `ssr/${ssrEntry}`, integrity: ssrIntegrity } }\n : {}),\n ...(report.browserManifest && integrityMap[report.browserManifest]\n ? {\n browserManifest: {\n file: report.browserManifest,\n integrity: integrityMap[report.browserManifest]!,\n },\n }\n : {}),\n ...(report.css && integrityMap[report.css]\n ? { assets: { css: integrityMap[report.css]! } }\n : {}),\n ...(files ? { files } : {}),\n });\n}\n"],"mappings":";;;;;;;AASA,SAAgB,gBAAgB,UAA2C;CACzE,MAAM,iBAAaA,gBAAK,UAAU,mBAAmB;CACrD,IAAI,KAACC,oBAAW,UAAU,GAAG,OAAO;CACpC,IAAI;EACF,OAAOC,yDAAuB,MAAM,KAAK,UAAMC,sBAAa,YAAY,MAAM,CAAC,CAAC;CAClF,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;AAUA,SAAgB,gCAAgC,OAMZ;CAClC,MAAM,EAAE,QAAQ,WAAW,cAAc,UAAU;CAEnD,MAAM,iBAAiB,aAAa,OAAO;CAC3C,IAAI,CAAC,gBAAgB,OAAO;CAE5B,MAAM,WAAW,WAAW;CAC5B,MAAM,eAAe,WAAW,aAAa,OAAO,cAAc;CAElE,WAAOC,sDAAuB;EAC5B,OAAO,OAAO;EACd;EACA,GAAI,YAAY,eACZ,EAAE,KAAK;GAAE,OAAO,OAAO;GAAY,WAAW;EAAa,EAAE,IAC7D,CAAC;EACL,GAAI,OAAO,mBAAmB,aAAa,OAAO,mBAC9C,EACE,iBAAiB;GACf,MAAM,OAAO;GACb,WAAW,aAAa,OAAO;EACjC,EACF,IACA,CAAC;EACL,GAAI,OAAO,OAAO,aAAa,OAAO,OAClC,EAAE,QAAQ,EAAE,KAAK,aAAa,OAAO,KAAM,EAAE,IAC7C,CAAC;EACL,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;CAC3B,CAAC;AACH"}
1
+ {"version":3,"file":"version-manifest-deploy.cjs","names":["join","existsSync","BuildEntryReportSchema","readFileSync","composeVersionManifest"],"sources":["../src/version-manifest-deploy.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { type BuildEntryReport, BuildEntryReportSchema } from \"every-plugin/build/artifact-names\";\nimport {\n composeVersionManifest,\n type SourceRef,\n type WorkspaceVersionManifest,\n} from \"every-plugin/version-manifest\";\n\n/** Read a workspace dist's build-report.json (ticket 01's entry-name report). */\nexport function readBuildReport(distRoot: string): BuildEntryReport | null {\n const reportPath = join(distRoot, \"build-report.json\");\n if (!existsSync(reportPath)) return null;\n try {\n return BuildEntryReportSchema.parse(JSON.parse(readFileSync(reportPath, \"utf8\")));\n } catch {\n return null;\n }\n}\n\n/**\n * Compose the workspace's version manifest from the dist build reports and\n * the **server-computed** SRI map of the upload response (MAP decision 6 —\n * SRI is authoritative from the storage boundary, never computed\n * client-side). Returns null when the dist predates hashed entry names\n * (no report, or the SRI map lacks the entry) — the deploy leg then aborts:\n * uploaded workspaces must pin.\n */\nexport function composeWorkspaceVersionManifest(input: {\n report: BuildEntryReport;\n ssrReport?: BuildEntryReport | null;\n integrityMap: Record<string, string>;\n /** full per-file SRI map of the dist — recorded for the next deploy's diff */\n files?: Record<string, string>;\n /** source archive reference (publish.sourcePublish) — server-computed SRI */\n source?: SourceRef;\n /** the repo HEAD the build was produced from — best-effort, may be absent */\n gitSha?: string;\n}): WorkspaceVersionManifest | null {\n const { report, ssrReport, integrityMap, files, source, gitSha } = input;\n\n const entryIntegrity = integrityMap[report.entry];\n if (!entryIntegrity) return null;\n\n const ssrEntry = ssrReport?.entry;\n const ssrIntegrity = ssrEntry ? integrityMap[`ssr/${ssrEntry}`] : undefined;\n\n return composeVersionManifest({\n entry: report.entry,\n entryIntegrity,\n ...(ssrEntry && ssrIntegrity\n ? { ssr: { entry: `ssr/${ssrEntry}`, integrity: ssrIntegrity } }\n : {}),\n ...(report.browserManifest && integrityMap[report.browserManifest]\n ? {\n browserManifest: {\n file: report.browserManifest,\n integrity: integrityMap[report.browserManifest]!,\n },\n }\n : {}),\n ...(report.css && integrityMap[report.css]\n ? { assets: { css: integrityMap[report.css]! } }\n : {}),\n ...(files ? { files } : {}),\n ...(source ? { source } : {}),\n ...(gitSha ? { gitSha } : {}),\n });\n}\n"],"mappings":";;;;;;;AAUA,SAAgB,gBAAgB,UAA2C;CACzE,MAAM,iBAAaA,gBAAK,UAAU,mBAAmB;CACrD,IAAI,KAACC,oBAAW,UAAU,GAAG,OAAO;CACpC,IAAI;EACF,OAAOC,yDAAuB,MAAM,KAAK,UAAMC,sBAAa,YAAY,MAAM,CAAC,CAAC;CAClF,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;AAUA,SAAgB,gCAAgC,OAUZ;CAClC,MAAM,EAAE,QAAQ,WAAW,cAAc,OAAO,QAAQ,WAAW;CAEnE,MAAM,iBAAiB,aAAa,OAAO;CAC3C,IAAI,CAAC,gBAAgB,OAAO;CAE5B,MAAM,WAAW,WAAW;CAC5B,MAAM,eAAe,WAAW,aAAa,OAAO,cAAc;CAElE,WAAOC,sDAAuB;EAC5B,OAAO,OAAO;EACd;EACA,GAAI,YAAY,eACZ,EAAE,KAAK;GAAE,OAAO,OAAO;GAAY,WAAW;EAAa,EAAE,IAC7D,CAAC;EACL,GAAI,OAAO,mBAAmB,aAAa,OAAO,mBAC9C,EACE,iBAAiB;GACf,MAAM,OAAO;GACb,WAAW,aAAa,OAAO;EACjC,EACF,IACA,CAAC;EACL,GAAI,OAAO,OAAO,aAAa,OAAO,OAClC,EAAE,QAAQ,EAAE,KAAK,aAAa,OAAO,KAAM,EAAE,IAC7C,CAAC;EACL,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;EACzB,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;EAC3B,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;CAC7B,CAAC;AACH"}
@@ -23,7 +23,7 @@ function readBuildReport(distRoot) {
23
23
  * uploaded workspaces must pin.
24
24
  */
25
25
  function composeWorkspaceVersionManifest(input) {
26
- const { report, ssrReport, integrityMap, files } = input;
26
+ const { report, ssrReport, integrityMap, files, source, gitSha } = input;
27
27
  const entryIntegrity = integrityMap[report.entry];
28
28
  if (!entryIntegrity) return null;
29
29
  const ssrEntry = ssrReport?.entry;
@@ -40,7 +40,9 @@ function composeWorkspaceVersionManifest(input) {
40
40
  integrity: integrityMap[report.browserManifest]
41
41
  } } : {},
42
42
  ...report.css && integrityMap[report.css] ? { assets: { css: integrityMap[report.css] } } : {},
43
- ...files ? { files } : {}
43
+ ...files ? { files } : {},
44
+ ...source ? { source } : {},
45
+ ...gitSha ? { gitSha } : {}
44
46
  });
45
47
  }
46
48
 
@@ -1 +1 @@
1
- {"version":3,"file":"version-manifest-deploy.mjs","names":[],"sources":["../src/version-manifest-deploy.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { type BuildEntryReport, BuildEntryReportSchema } from \"every-plugin/build/artifact-names\";\nimport {\n composeVersionManifest,\n type WorkspaceVersionManifest,\n} from \"every-plugin/version-manifest\";\n\n/** Read a workspace dist's build-report.json (ticket 01's entry-name report). */\nexport function readBuildReport(distRoot: string): BuildEntryReport | null {\n const reportPath = join(distRoot, \"build-report.json\");\n if (!existsSync(reportPath)) return null;\n try {\n return BuildEntryReportSchema.parse(JSON.parse(readFileSync(reportPath, \"utf8\")));\n } catch {\n return null;\n }\n}\n\n/**\n * Compose the workspace's version manifest from the dist build reports and\n * the **server-computed** SRI map of the upload response (MAP decision 6 —\n * SRI is authoritative from the storage boundary, never computed\n * client-side). Returns null when the dist predates hashed entry names\n * (no report, or the SRI map lacks the entry) — the deploy leg then aborts:\n * uploaded workspaces must pin.\n */\nexport function composeWorkspaceVersionManifest(input: {\n report: BuildEntryReport;\n ssrReport?: BuildEntryReport | null;\n integrityMap: Record<string, string>;\n /** full per-file SRI map of the dist — recorded for the next deploy's diff */\n files?: Record<string, string>;\n}): WorkspaceVersionManifest | null {\n const { report, ssrReport, integrityMap, files } = input;\n\n const entryIntegrity = integrityMap[report.entry];\n if (!entryIntegrity) return null;\n\n const ssrEntry = ssrReport?.entry;\n const ssrIntegrity = ssrEntry ? integrityMap[`ssr/${ssrEntry}`] : undefined;\n\n return composeVersionManifest({\n entry: report.entry,\n entryIntegrity,\n ...(ssrEntry && ssrIntegrity\n ? { ssr: { entry: `ssr/${ssrEntry}`, integrity: ssrIntegrity } }\n : {}),\n ...(report.browserManifest && integrityMap[report.browserManifest]\n ? {\n browserManifest: {\n file: report.browserManifest,\n integrity: integrityMap[report.browserManifest]!,\n },\n }\n : {}),\n ...(report.css && integrityMap[report.css]\n ? { assets: { css: integrityMap[report.css]! } }\n : {}),\n ...(files ? { files } : {}),\n });\n}\n"],"mappings":";;;;;;;AASA,SAAgB,gBAAgB,UAA2C;CACzE,MAAM,aAAa,KAAK,UAAU,mBAAmB;CACrD,IAAI,CAAC,WAAW,UAAU,GAAG,OAAO;CACpC,IAAI;EACF,OAAO,uBAAuB,MAAM,KAAK,MAAM,aAAa,YAAY,MAAM,CAAC,CAAC;CAClF,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;AAUA,SAAgB,gCAAgC,OAMZ;CAClC,MAAM,EAAE,QAAQ,WAAW,cAAc,UAAU;CAEnD,MAAM,iBAAiB,aAAa,OAAO;CAC3C,IAAI,CAAC,gBAAgB,OAAO;CAE5B,MAAM,WAAW,WAAW;CAC5B,MAAM,eAAe,WAAW,aAAa,OAAO,cAAc;CAElE,OAAO,uBAAuB;EAC5B,OAAO,OAAO;EACd;EACA,GAAI,YAAY,eACZ,EAAE,KAAK;GAAE,OAAO,OAAO;GAAY,WAAW;EAAa,EAAE,IAC7D,CAAC;EACL,GAAI,OAAO,mBAAmB,aAAa,OAAO,mBAC9C,EACE,iBAAiB;GACf,MAAM,OAAO;GACb,WAAW,aAAa,OAAO;EACjC,EACF,IACA,CAAC;EACL,GAAI,OAAO,OAAO,aAAa,OAAO,OAClC,EAAE,QAAQ,EAAE,KAAK,aAAa,OAAO,KAAM,EAAE,IAC7C,CAAC;EACL,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;CAC3B,CAAC;AACH"}
1
+ {"version":3,"file":"version-manifest-deploy.mjs","names":[],"sources":["../src/version-manifest-deploy.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { type BuildEntryReport, BuildEntryReportSchema } from \"every-plugin/build/artifact-names\";\nimport {\n composeVersionManifest,\n type SourceRef,\n type WorkspaceVersionManifest,\n} from \"every-plugin/version-manifest\";\n\n/** Read a workspace dist's build-report.json (ticket 01's entry-name report). */\nexport function readBuildReport(distRoot: string): BuildEntryReport | null {\n const reportPath = join(distRoot, \"build-report.json\");\n if (!existsSync(reportPath)) return null;\n try {\n return BuildEntryReportSchema.parse(JSON.parse(readFileSync(reportPath, \"utf8\")));\n } catch {\n return null;\n }\n}\n\n/**\n * Compose the workspace's version manifest from the dist build reports and\n * the **server-computed** SRI map of the upload response (MAP decision 6 —\n * SRI is authoritative from the storage boundary, never computed\n * client-side). Returns null when the dist predates hashed entry names\n * (no report, or the SRI map lacks the entry) — the deploy leg then aborts:\n * uploaded workspaces must pin.\n */\nexport function composeWorkspaceVersionManifest(input: {\n report: BuildEntryReport;\n ssrReport?: BuildEntryReport | null;\n integrityMap: Record<string, string>;\n /** full per-file SRI map of the dist — recorded for the next deploy's diff */\n files?: Record<string, string>;\n /** source archive reference (publish.sourcePublish) — server-computed SRI */\n source?: SourceRef;\n /** the repo HEAD the build was produced from — best-effort, may be absent */\n gitSha?: string;\n}): WorkspaceVersionManifest | null {\n const { report, ssrReport, integrityMap, files, source, gitSha } = input;\n\n const entryIntegrity = integrityMap[report.entry];\n if (!entryIntegrity) return null;\n\n const ssrEntry = ssrReport?.entry;\n const ssrIntegrity = ssrEntry ? integrityMap[`ssr/${ssrEntry}`] : undefined;\n\n return composeVersionManifest({\n entry: report.entry,\n entryIntegrity,\n ...(ssrEntry && ssrIntegrity\n ? { ssr: { entry: `ssr/${ssrEntry}`, integrity: ssrIntegrity } }\n : {}),\n ...(report.browserManifest && integrityMap[report.browserManifest]\n ? {\n browserManifest: {\n file: report.browserManifest,\n integrity: integrityMap[report.browserManifest]!,\n },\n }\n : {}),\n ...(report.css && integrityMap[report.css]\n ? { assets: { css: integrityMap[report.css]! } }\n : {}),\n ...(files ? { files } : {}),\n ...(source ? { source } : {}),\n ...(gitSha ? { gitSha } : {}),\n });\n}\n"],"mappings":";;;;;;;AAUA,SAAgB,gBAAgB,UAA2C;CACzE,MAAM,aAAa,KAAK,UAAU,mBAAmB;CACrD,IAAI,CAAC,WAAW,UAAU,GAAG,OAAO;CACpC,IAAI;EACF,OAAO,uBAAuB,MAAM,KAAK,MAAM,aAAa,YAAY,MAAM,CAAC,CAAC;CAClF,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;AAUA,SAAgB,gCAAgC,OAUZ;CAClC,MAAM,EAAE,QAAQ,WAAW,cAAc,OAAO,QAAQ,WAAW;CAEnE,MAAM,iBAAiB,aAAa,OAAO;CAC3C,IAAI,CAAC,gBAAgB,OAAO;CAE5B,MAAM,WAAW,WAAW;CAC5B,MAAM,eAAe,WAAW,aAAa,OAAO,cAAc;CAElE,OAAO,uBAAuB;EAC5B,OAAO,OAAO;EACd;EACA,GAAI,YAAY,eACZ,EAAE,KAAK;GAAE,OAAO,OAAO;GAAY,WAAW;EAAa,EAAE,IAC7D,CAAC;EACL,GAAI,OAAO,mBAAmB,aAAa,OAAO,mBAC9C,EACE,iBAAiB;GACf,MAAM,OAAO;GACb,WAAW,aAAa,OAAO;EACjC,EACF,IACA,CAAC;EACL,GAAI,OAAO,OAAO,aAAa,OAAO,OAClC,EAAE,QAAQ,EAAE,KAAK,aAAa,OAAO,KAAM,EAAE,IAC7C,CAAC;EACL,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;EACzB,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;EAC3B,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;CAC7B,CAAC;AACH"}
@@ -28,7 +28,8 @@ function slotVersionFromManifest(base, manifest) {
28
28
  ssrEntryUrl: `${trimmedBase}/${manifest.ssr.entry}`,
29
29
  ssrIntegrity: manifest.ssr.integrity
30
30
  } : {},
31
- ...manifest.files ? { files: manifest.files } : {}
31
+ ...manifest.files ? { files: manifest.files } : {},
32
+ ...manifest.source ? { source: manifest.source } : {}
32
33
  };
33
34
  }
34
35
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"version-manifest-resolve.cjs","names":["bundleUrlToStagedPath","existsSync","resolveContained","path","readdirSync","statSync","WorkspaceVersionManifestSchema","readFileSync","createHash"],"sources":["../src/version-manifest-resolve.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { existsSync, readdirSync, readFileSync, statSync } from \"node:fs\";\nimport path from \"node:path\";\nimport {\n type WorkspaceVersionManifest,\n WorkspaceVersionManifestSchema,\n} from \"every-plugin/version-manifest\";\nimport { bundleUrlToStagedPath, resolveContained } from \"./resolver\";\n\n/**\n * Version-manifest slot resolution (atomic-deploys 04): a config slot's\n * `pin` ({ manifest, integrity }) resolves to the derived entry-level fields\n * every consumer needs. The cache is keyed by the full pin — the pinned\n * bytes are immutable, so a verified entry is cached for the process\n * lifetime and serves as last-known-good if the origin later fails; a fetch\n * or SRI failure with nothing cached propagates (a boot-time resolution\n * failure fails the deploy, which is the platform's rollback).\n */\n\nexport interface ResolvedSlotVersion {\n /** Absolute URL of the content-hashed container entry. */\n entryUrl: string;\n entryIntegrity: string;\n /** Absolute URL of the hashed browser manifest (mf-manifest), when present. */\n browserManifestUrl?: string;\n /** Absolute URL of the content-hashed SSR entry, when present. */\n ssrEntryUrl?: string;\n ssrIntegrity?: string;\n /** Full per-file digest map of the dist (object path → SRI), when the\n * pinned manifest carries one — the diff base for skip-unchanged uploads. */\n files?: Record<string, string>;\n}\n\nexport interface SlotPin {\n /** versioned manifest filename, relative to the slot's production base */\n manifest: string;\n /** the manifest document's own SRI */\n integrity: string;\n}\n\nconst cache = new Map<string, ResolvedSlotVersion>();\n\nexport function clearSlotVersionCache(): void {\n cache.clear();\n}\n\nfunction fetchText(url: string, fetchImpl: typeof fetch): Promise<string> {\n return fetchImpl(url).then((response) => {\n if (!response.ok) throw new Error(`version manifest fetch failed: ${response.status}`);\n return response.text();\n });\n}\n\nfunction slotVersionFromManifest(\n base: string,\n manifest: WorkspaceVersionManifest,\n): ResolvedSlotVersion {\n const trimmedBase = base.replace(/\\/$/, \"\");\n return {\n entryUrl: `${trimmedBase}/${manifest.entry}`,\n entryIntegrity: manifest.entryIntegrity,\n ...(manifest.browserManifest\n ? { browserManifestUrl: `${trimmedBase}/${manifest.browserManifest.file}` }\n : {}),\n ...(manifest.ssr\n ? {\n ssrEntryUrl: `${trimmedBase}/${manifest.ssr.entry}`,\n ssrIntegrity: manifest.ssr.integrity,\n }\n : {}),\n ...(manifest.files ? { files: manifest.files } : {}),\n };\n}\n\n/**\n * Staged-slot preference (ADR 0020/0021 self-contained tier): when a runtime\n * stages its own namespace under `BOS_BUNDLE_DIR`, a slot resolves from the\n * staged dist's own version manifest instead of the config's pin — pinned\n * manifest bytes that predate the image (deploy lag after a release bump)\n * cannot exist in the staged namespace, so their hashed files would 404 the\n * disk and fall through to the network, where the stale immutable bytes\n * still answer 200 and boot a foreign build. The staged manifest is trusted\n * (staged by the image build itself) and skipped entirely when the slot is\n * absent from the staged namespace or the pinned manifest is present (the\n * normal healthy case, which keeps the pin's SRI check).\n */\nfunction readStagedSlotVersion(base: string, pin: SlotPin): ResolvedSlotVersion | null {\n const bundleDir = process.env.BOS_BUNDLE_DIR;\n if (!bundleDir) return null;\n\n const slotDir = bundleUrlToStagedPath(base, bundleDir);\n if (!slotDir || !existsSync(slotDir)) return null;\n\n const pinnedManifestPath = resolveContained(slotDir, pin.manifest);\n if (!pinnedManifestPath || existsSync(pinnedManifestPath)) return null;\n\n const versionsDir = path.join(slotDir, \"versions\");\n if (!existsSync(versionsDir)) return null;\n const manifests = readdirSync(versionsDir)\n .filter((name) => name.endsWith(\".json\"))\n .map((name) => path.join(versionsDir, name))\n .filter((file) => statSync(file).isFile())\n .sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs || (a < b ? -1 : 1));\n if (manifests.length === 0) return null;\n const manifestPath = manifests[0];\n if (!manifestPath) return null;\n\n try {\n const manifest = WorkspaceVersionManifestSchema.parse(\n JSON.parse(readFileSync(manifestPath, \"utf8\")),\n );\n return slotVersionFromManifest(base, manifest);\n } catch {\n return null;\n }\n}\n\nexport async function resolveSlotVersion(input: {\n base: string;\n pin: SlotPin;\n fetchImpl?: typeof fetch;\n}): Promise<ResolvedSlotVersion> {\n const key = `${input.base}::${input.pin.manifest}::${input.pin.integrity}`;\n // content-addressed: a verified pin is cached for the process lifetime and\n // never refetched — the cache IS the last-known-good\n const cached = cache.get(key);\n if (cached) return cached;\n\n const staged = readStagedSlotVersion(input.base, input.pin);\n if (staged) {\n cache.set(key, staged);\n return staged;\n }\n\n const manifestUrl = `${input.base.replace(/\\/$/, \"\")}/${input.pin.manifest.replace(/^\\//, \"\")}`;\n const body = await fetchText(manifestUrl, input.fetchImpl ?? fetch);\n const computed = `sha384-${createHash(\"sha384\").update(body).digest(\"base64\")}`;\n if (computed !== input.pin.integrity) {\n throw new Error(\n `[SRI] version manifest integrity mismatch for ${manifestUrl}\\n Expected: ${input.pin.integrity}\\n Computed: ${computed}`,\n );\n }\n const manifest: WorkspaceVersionManifest = WorkspaceVersionManifestSchema.parse(JSON.parse(body));\n\n const resolved = slotVersionFromManifest(input.base, manifest);\n cache.set(key, resolved);\n return resolved;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"version-manifest-resolve.cjs","names":["bundleUrlToStagedPath","existsSync","resolveContained","path","readdirSync","statSync","WorkspaceVersionManifestSchema","readFileSync","createHash"],"sources":["../src/version-manifest-resolve.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { existsSync, readdirSync, readFileSync, statSync } from \"node:fs\";\nimport path from \"node:path\";\nimport {\n type SourceRef,\n type WorkspaceVersionManifest,\n WorkspaceVersionManifestSchema,\n} from \"every-plugin/version-manifest\";\nimport { bundleUrlToStagedPath, resolveContained } from \"./resolver\";\n\n/**\n * Version-manifest slot resolution (atomic-deploys 04): a config slot's\n * `pin` ({ manifest, integrity }) resolves to the derived entry-level fields\n * every consumer needs. The cache is keyed by the full pin — the pinned\n * bytes are immutable, so a verified entry is cached for the process\n * lifetime and serves as last-known-good if the origin later fails; a fetch\n * or SRI failure with nothing cached propagates (a boot-time resolution\n * failure fails the deploy, which is the platform's rollback).\n */\n\nexport interface ResolvedSlotVersion {\n /** Absolute URL of the content-hashed container entry. */\n entryUrl: string;\n entryIntegrity: string;\n /** Absolute URL of the hashed browser manifest (mf-manifest), when present. */\n browserManifestUrl?: string;\n /** Absolute URL of the content-hashed SSR entry, when present. */\n ssrEntryUrl?: string;\n ssrIntegrity?: string;\n /** Full per-file digest map of the dist (object path → SRI), when the\n * pinned manifest carries one — the diff base for skip-unchanged uploads. */\n files?: Record<string, string>;\n /** Source archive reference from the pinned manifest (publish.sourcePublish). */\n source?: SourceRef;\n}\n\nexport interface SlotPin {\n /** versioned manifest filename, relative to the slot's production base */\n manifest: string;\n /** the manifest document's own SRI */\n integrity: string;\n}\n\nconst cache = new Map<string, ResolvedSlotVersion>();\n\nexport function clearSlotVersionCache(): void {\n cache.clear();\n}\n\nfunction fetchText(url: string, fetchImpl: typeof fetch): Promise<string> {\n return fetchImpl(url).then((response) => {\n if (!response.ok) throw new Error(`version manifest fetch failed: ${response.status}`);\n return response.text();\n });\n}\n\nfunction slotVersionFromManifest(\n base: string,\n manifest: WorkspaceVersionManifest,\n): ResolvedSlotVersion {\n const trimmedBase = base.replace(/\\/$/, \"\");\n return {\n entryUrl: `${trimmedBase}/${manifest.entry}`,\n entryIntegrity: manifest.entryIntegrity,\n ...(manifest.browserManifest\n ? { browserManifestUrl: `${trimmedBase}/${manifest.browserManifest.file}` }\n : {}),\n ...(manifest.ssr\n ? {\n ssrEntryUrl: `${trimmedBase}/${manifest.ssr.entry}`,\n ssrIntegrity: manifest.ssr.integrity,\n }\n : {}),\n ...(manifest.files ? { files: manifest.files } : {}),\n ...(manifest.source ? { source: manifest.source } : {}),\n };\n}\n\n/**\n * Staged-slot preference (ADR 0020/0021 self-contained tier): when a runtime\n * stages its own namespace under `BOS_BUNDLE_DIR`, a slot resolves from the\n * staged dist's own version manifest instead of the config's pin — pinned\n * manifest bytes that predate the image (deploy lag after a release bump)\n * cannot exist in the staged namespace, so their hashed files would 404 the\n * disk and fall through to the network, where the stale immutable bytes\n * still answer 200 and boot a foreign build. The staged manifest is trusted\n * (staged by the image build itself) and skipped entirely when the slot is\n * absent from the staged namespace or the pinned manifest is present (the\n * normal healthy case, which keeps the pin's SRI check).\n */\nfunction readStagedSlotVersion(base: string, pin: SlotPin): ResolvedSlotVersion | null {\n const bundleDir = process.env.BOS_BUNDLE_DIR;\n if (!bundleDir) return null;\n\n const slotDir = bundleUrlToStagedPath(base, bundleDir);\n if (!slotDir || !existsSync(slotDir)) return null;\n\n const pinnedManifestPath = resolveContained(slotDir, pin.manifest);\n if (!pinnedManifestPath || existsSync(pinnedManifestPath)) return null;\n\n const versionsDir = path.join(slotDir, \"versions\");\n if (!existsSync(versionsDir)) return null;\n const manifests = readdirSync(versionsDir)\n .filter((name) => name.endsWith(\".json\"))\n .map((name) => path.join(versionsDir, name))\n .filter((file) => statSync(file).isFile())\n .sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs || (a < b ? -1 : 1));\n if (manifests.length === 0) return null;\n const manifestPath = manifests[0];\n if (!manifestPath) return null;\n\n try {\n const manifest = WorkspaceVersionManifestSchema.parse(\n JSON.parse(readFileSync(manifestPath, \"utf8\")),\n );\n return slotVersionFromManifest(base, manifest);\n } catch {\n return null;\n }\n}\n\nexport async function resolveSlotVersion(input: {\n base: string;\n pin: SlotPin;\n fetchImpl?: typeof fetch;\n}): Promise<ResolvedSlotVersion> {\n const key = `${input.base}::${input.pin.manifest}::${input.pin.integrity}`;\n // content-addressed: a verified pin is cached for the process lifetime and\n // never refetched — the cache IS the last-known-good\n const cached = cache.get(key);\n if (cached) return cached;\n\n const staged = readStagedSlotVersion(input.base, input.pin);\n if (staged) {\n cache.set(key, staged);\n return staged;\n }\n\n const manifestUrl = `${input.base.replace(/\\/$/, \"\")}/${input.pin.manifest.replace(/^\\//, \"\")}`;\n const body = await fetchText(manifestUrl, input.fetchImpl ?? fetch);\n const computed = `sha384-${createHash(\"sha384\").update(body).digest(\"base64\")}`;\n if (computed !== input.pin.integrity) {\n throw new Error(\n `[SRI] version manifest integrity mismatch for ${manifestUrl}\\n Expected: ${input.pin.integrity}\\n Computed: ${computed}`,\n );\n }\n const manifest: WorkspaceVersionManifest = WorkspaceVersionManifestSchema.parse(JSON.parse(body));\n\n const resolved = slotVersionFromManifest(input.base, manifest);\n cache.set(key, resolved);\n return resolved;\n}\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { SourceRef } from "every-plugin/version-manifest";
1
2
  //#region src/version-manifest-resolve.d.ts
2
3
  /**
3
4
  * Version-manifest slot resolution (atomic-deploys 04): a config slot's
@@ -20,6 +21,8 @@ interface ResolvedSlotVersion {
20
21
  /** Full per-file digest map of the dist (object path → SRI), when the
21
22
  * pinned manifest carries one — the diff base for skip-unchanged uploads. */
22
23
  files?: Record<string, string>;
24
+ /** Source archive reference from the pinned manifest (publish.sourcePublish). */
25
+ source?: SourceRef;
23
26
  }
24
27
  interface SlotPin {
25
28
  /** versioned manifest filename, relative to the slot's production base */
@@ -1,3 +1,4 @@
1
+ import { SourceRef } from "every-plugin/version-manifest";
1
2
  //#region src/version-manifest-resolve.d.ts
2
3
  /**
3
4
  * Version-manifest slot resolution (atomic-deploys 04): a config slot's
@@ -20,6 +21,8 @@ interface ResolvedSlotVersion {
20
21
  /** Full per-file digest map of the dist (object path → SRI), when the
21
22
  * pinned manifest carries one — the diff base for skip-unchanged uploads. */
22
23
  files?: Record<string, string>;
24
+ /** Source archive reference from the pinned manifest (publish.sourcePublish). */
25
+ source?: SourceRef;
23
26
  }
24
27
  interface SlotPin {
25
28
  /** versioned manifest filename, relative to the slot's production base */
@@ -25,7 +25,8 @@ function slotVersionFromManifest(base, manifest) {
25
25
  ssrEntryUrl: `${trimmedBase}/${manifest.ssr.entry}`,
26
26
  ssrIntegrity: manifest.ssr.integrity
27
27
  } : {},
28
- ...manifest.files ? { files: manifest.files } : {}
28
+ ...manifest.files ? { files: manifest.files } : {},
29
+ ...manifest.source ? { source: manifest.source } : {}
29
30
  };
30
31
  }
31
32
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"version-manifest-resolve.mjs","names":[],"sources":["../src/version-manifest-resolve.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { existsSync, readdirSync, readFileSync, statSync } from \"node:fs\";\nimport path from \"node:path\";\nimport {\n type WorkspaceVersionManifest,\n WorkspaceVersionManifestSchema,\n} from \"every-plugin/version-manifest\";\nimport { bundleUrlToStagedPath, resolveContained } from \"./resolver\";\n\n/**\n * Version-manifest slot resolution (atomic-deploys 04): a config slot's\n * `pin` ({ manifest, integrity }) resolves to the derived entry-level fields\n * every consumer needs. The cache is keyed by the full pin — the pinned\n * bytes are immutable, so a verified entry is cached for the process\n * lifetime and serves as last-known-good if the origin later fails; a fetch\n * or SRI failure with nothing cached propagates (a boot-time resolution\n * failure fails the deploy, which is the platform's rollback).\n */\n\nexport interface ResolvedSlotVersion {\n /** Absolute URL of the content-hashed container entry. */\n entryUrl: string;\n entryIntegrity: string;\n /** Absolute URL of the hashed browser manifest (mf-manifest), when present. */\n browserManifestUrl?: string;\n /** Absolute URL of the content-hashed SSR entry, when present. */\n ssrEntryUrl?: string;\n ssrIntegrity?: string;\n /** Full per-file digest map of the dist (object path → SRI), when the\n * pinned manifest carries one — the diff base for skip-unchanged uploads. */\n files?: Record<string, string>;\n}\n\nexport interface SlotPin {\n /** versioned manifest filename, relative to the slot's production base */\n manifest: string;\n /** the manifest document's own SRI */\n integrity: string;\n}\n\nconst cache = new Map<string, ResolvedSlotVersion>();\n\nexport function clearSlotVersionCache(): void {\n cache.clear();\n}\n\nfunction fetchText(url: string, fetchImpl: typeof fetch): Promise<string> {\n return fetchImpl(url).then((response) => {\n if (!response.ok) throw new Error(`version manifest fetch failed: ${response.status}`);\n return response.text();\n });\n}\n\nfunction slotVersionFromManifest(\n base: string,\n manifest: WorkspaceVersionManifest,\n): ResolvedSlotVersion {\n const trimmedBase = base.replace(/\\/$/, \"\");\n return {\n entryUrl: `${trimmedBase}/${manifest.entry}`,\n entryIntegrity: manifest.entryIntegrity,\n ...(manifest.browserManifest\n ? { browserManifestUrl: `${trimmedBase}/${manifest.browserManifest.file}` }\n : {}),\n ...(manifest.ssr\n ? {\n ssrEntryUrl: `${trimmedBase}/${manifest.ssr.entry}`,\n ssrIntegrity: manifest.ssr.integrity,\n }\n : {}),\n ...(manifest.files ? { files: manifest.files } : {}),\n };\n}\n\n/**\n * Staged-slot preference (ADR 0020/0021 self-contained tier): when a runtime\n * stages its own namespace under `BOS_BUNDLE_DIR`, a slot resolves from the\n * staged dist's own version manifest instead of the config's pin — pinned\n * manifest bytes that predate the image (deploy lag after a release bump)\n * cannot exist in the staged namespace, so their hashed files would 404 the\n * disk and fall through to the network, where the stale immutable bytes\n * still answer 200 and boot a foreign build. The staged manifest is trusted\n * (staged by the image build itself) and skipped entirely when the slot is\n * absent from the staged namespace or the pinned manifest is present (the\n * normal healthy case, which keeps the pin's SRI check).\n */\nfunction readStagedSlotVersion(base: string, pin: SlotPin): ResolvedSlotVersion | null {\n const bundleDir = process.env.BOS_BUNDLE_DIR;\n if (!bundleDir) return null;\n\n const slotDir = bundleUrlToStagedPath(base, bundleDir);\n if (!slotDir || !existsSync(slotDir)) return null;\n\n const pinnedManifestPath = resolveContained(slotDir, pin.manifest);\n if (!pinnedManifestPath || existsSync(pinnedManifestPath)) return null;\n\n const versionsDir = path.join(slotDir, \"versions\");\n if (!existsSync(versionsDir)) return null;\n const manifests = readdirSync(versionsDir)\n .filter((name) => name.endsWith(\".json\"))\n .map((name) => path.join(versionsDir, name))\n .filter((file) => statSync(file).isFile())\n .sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs || (a < b ? -1 : 1));\n if (manifests.length === 0) return null;\n const manifestPath = manifests[0];\n if (!manifestPath) return null;\n\n try {\n const manifest = WorkspaceVersionManifestSchema.parse(\n JSON.parse(readFileSync(manifestPath, \"utf8\")),\n );\n return slotVersionFromManifest(base, manifest);\n } catch {\n return null;\n }\n}\n\nexport async function resolveSlotVersion(input: {\n base: string;\n pin: SlotPin;\n fetchImpl?: typeof fetch;\n}): Promise<ResolvedSlotVersion> {\n const key = `${input.base}::${input.pin.manifest}::${input.pin.integrity}`;\n // content-addressed: a verified pin is cached for the process lifetime and\n // never refetched — the cache IS the last-known-good\n const cached = cache.get(key);\n if (cached) return cached;\n\n const staged = readStagedSlotVersion(input.base, input.pin);\n if (staged) {\n cache.set(key, staged);\n return staged;\n }\n\n const manifestUrl = `${input.base.replace(/\\/$/, \"\")}/${input.pin.manifest.replace(/^\\//, \"\")}`;\n const body = await fetchText(manifestUrl, input.fetchImpl ?? fetch);\n const computed = `sha384-${createHash(\"sha384\").update(body).digest(\"base64\")}`;\n if (computed !== input.pin.integrity) {\n throw new Error(\n `[SRI] version manifest integrity mismatch for ${manifestUrl}\\n Expected: ${input.pin.integrity}\\n Computed: ${computed}`,\n );\n }\n const manifest: WorkspaceVersionManifest = WorkspaceVersionManifestSchema.parse(JSON.parse(body));\n\n const resolved = slotVersionFromManifest(input.base, manifest);\n cache.set(key, resolved);\n return resolved;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"version-manifest-resolve.mjs","names":[],"sources":["../src/version-manifest-resolve.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { existsSync, readdirSync, readFileSync, statSync } from \"node:fs\";\nimport path from \"node:path\";\nimport {\n type SourceRef,\n type WorkspaceVersionManifest,\n WorkspaceVersionManifestSchema,\n} from \"every-plugin/version-manifest\";\nimport { bundleUrlToStagedPath, resolveContained } from \"./resolver\";\n\n/**\n * Version-manifest slot resolution (atomic-deploys 04): a config slot's\n * `pin` ({ manifest, integrity }) resolves to the derived entry-level fields\n * every consumer needs. The cache is keyed by the full pin — the pinned\n * bytes are immutable, so a verified entry is cached for the process\n * lifetime and serves as last-known-good if the origin later fails; a fetch\n * or SRI failure with nothing cached propagates (a boot-time resolution\n * failure fails the deploy, which is the platform's rollback).\n */\n\nexport interface ResolvedSlotVersion {\n /** Absolute URL of the content-hashed container entry. */\n entryUrl: string;\n entryIntegrity: string;\n /** Absolute URL of the hashed browser manifest (mf-manifest), when present. */\n browserManifestUrl?: string;\n /** Absolute URL of the content-hashed SSR entry, when present. */\n ssrEntryUrl?: string;\n ssrIntegrity?: string;\n /** Full per-file digest map of the dist (object path → SRI), when the\n * pinned manifest carries one — the diff base for skip-unchanged uploads. */\n files?: Record<string, string>;\n /** Source archive reference from the pinned manifest (publish.sourcePublish). */\n source?: SourceRef;\n}\n\nexport interface SlotPin {\n /** versioned manifest filename, relative to the slot's production base */\n manifest: string;\n /** the manifest document's own SRI */\n integrity: string;\n}\n\nconst cache = new Map<string, ResolvedSlotVersion>();\n\nexport function clearSlotVersionCache(): void {\n cache.clear();\n}\n\nfunction fetchText(url: string, fetchImpl: typeof fetch): Promise<string> {\n return fetchImpl(url).then((response) => {\n if (!response.ok) throw new Error(`version manifest fetch failed: ${response.status}`);\n return response.text();\n });\n}\n\nfunction slotVersionFromManifest(\n base: string,\n manifest: WorkspaceVersionManifest,\n): ResolvedSlotVersion {\n const trimmedBase = base.replace(/\\/$/, \"\");\n return {\n entryUrl: `${trimmedBase}/${manifest.entry}`,\n entryIntegrity: manifest.entryIntegrity,\n ...(manifest.browserManifest\n ? { browserManifestUrl: `${trimmedBase}/${manifest.browserManifest.file}` }\n : {}),\n ...(manifest.ssr\n ? {\n ssrEntryUrl: `${trimmedBase}/${manifest.ssr.entry}`,\n ssrIntegrity: manifest.ssr.integrity,\n }\n : {}),\n ...(manifest.files ? { files: manifest.files } : {}),\n ...(manifest.source ? { source: manifest.source } : {}),\n };\n}\n\n/**\n * Staged-slot preference (ADR 0020/0021 self-contained tier): when a runtime\n * stages its own namespace under `BOS_BUNDLE_DIR`, a slot resolves from the\n * staged dist's own version manifest instead of the config's pin — pinned\n * manifest bytes that predate the image (deploy lag after a release bump)\n * cannot exist in the staged namespace, so their hashed files would 404 the\n * disk and fall through to the network, where the stale immutable bytes\n * still answer 200 and boot a foreign build. The staged manifest is trusted\n * (staged by the image build itself) and skipped entirely when the slot is\n * absent from the staged namespace or the pinned manifest is present (the\n * normal healthy case, which keeps the pin's SRI check).\n */\nfunction readStagedSlotVersion(base: string, pin: SlotPin): ResolvedSlotVersion | null {\n const bundleDir = process.env.BOS_BUNDLE_DIR;\n if (!bundleDir) return null;\n\n const slotDir = bundleUrlToStagedPath(base, bundleDir);\n if (!slotDir || !existsSync(slotDir)) return null;\n\n const pinnedManifestPath = resolveContained(slotDir, pin.manifest);\n if (!pinnedManifestPath || existsSync(pinnedManifestPath)) return null;\n\n const versionsDir = path.join(slotDir, \"versions\");\n if (!existsSync(versionsDir)) return null;\n const manifests = readdirSync(versionsDir)\n .filter((name) => name.endsWith(\".json\"))\n .map((name) => path.join(versionsDir, name))\n .filter((file) => statSync(file).isFile())\n .sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs || (a < b ? -1 : 1));\n if (manifests.length === 0) return null;\n const manifestPath = manifests[0];\n if (!manifestPath) return null;\n\n try {\n const manifest = WorkspaceVersionManifestSchema.parse(\n JSON.parse(readFileSync(manifestPath, \"utf8\")),\n );\n return slotVersionFromManifest(base, manifest);\n } catch {\n return null;\n }\n}\n\nexport async function resolveSlotVersion(input: {\n base: string;\n pin: SlotPin;\n fetchImpl?: typeof fetch;\n}): Promise<ResolvedSlotVersion> {\n const key = `${input.base}::${input.pin.manifest}::${input.pin.integrity}`;\n // content-addressed: a verified pin is cached for the process lifetime and\n // never refetched — the cache IS the last-known-good\n const cached = cache.get(key);\n if (cached) return cached;\n\n const staged = readStagedSlotVersion(input.base, input.pin);\n if (staged) {\n cache.set(key, staged);\n return staged;\n }\n\n const manifestUrl = `${input.base.replace(/\\/$/, \"\")}/${input.pin.manifest.replace(/^\\//, \"\")}`;\n const body = await fetchText(manifestUrl, input.fetchImpl ?? fetch);\n const computed = `sha384-${createHash(\"sha384\").update(body).digest(\"base64\")}`;\n if (computed !== input.pin.integrity) {\n throw new Error(\n `[SRI] version manifest integrity mismatch for ${manifestUrl}\\n Expected: ${input.pin.integrity}\\n Computed: ${computed}`,\n );\n }\n const manifest: WorkspaceVersionManifest = WorkspaceVersionManifestSchema.parse(JSON.parse(body));\n\n const resolved = slotVersionFromManifest(input.base, manifest);\n cache.set(key, resolved);\n return resolved;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "everything-dev",
3
- "version": "2.0.0-rc.7",
3
+ "version": "2.0.0-rc.9",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=20.11"
@@ -243,18 +243,19 @@
243
243
  "dotenv": "^17.3.1",
244
244
  "effect": "4.0.0-rc.117",
245
245
  "@everything-dev/upload-app": "workspace:^",
246
- "every-plugin": "^3.0.0-rc.4",
246
+ "every-plugin": "^3.0.0-rc.6",
247
247
  "execa": "^9.5.2",
248
248
  "glob": "^13.0.6",
249
249
  "near-kit": "^0.20.2",
250
+ "@outlayer/sdk": "0.1.0-alpha.4",
250
251
  "pg": "^8.12.0",
251
252
  "tar": "^7.4.3",
252
253
  "yaml": "2.9.0",
253
254
  "zod": "4.6.5"
254
255
  },
255
256
  "peerDependencies": {
256
- "@better-auth/api-key": "1.6.25",
257
- "@better-auth/passkey": "1.6.25",
257
+ "@better-auth/api-key": "1.7.7",
258
+ "@better-auth/passkey": "1.7.7",
258
259
  "@lingui/core": "5.9.0",
259
260
  "@lingui/react": "5.9.0",
260
261
  "@orpc/client": "2.0.0-beta.35",
@@ -262,8 +263,8 @@
262
263
  "@orpc/tanstack-query": "2.0.0-beta.35",
263
264
  "@tanstack/react-query": ">=5.0.0",
264
265
  "@tanstack/react-router": ">=1.0.0",
265
- "better-auth": "1.6.25",
266
- "better-near-auth": "^2.0.0-rc.0",
266
+ "better-auth": "1.7.7",
267
+ "better-near-auth": "^2.0.0-rc.1",
267
268
  "effect": "4.0.0-rc.117",
268
269
  "react": "19.2.4",
269
270
  "react-dom": "19.2.4",
@@ -309,8 +310,8 @@
309
310
  }
310
311
  },
311
312
  "devDependencies": {
312
- "@better-auth/api-key": "1.6.25",
313
- "@better-auth/passkey": "1.6.25",
313
+ "@better-auth/api-key": "1.7.7",
314
+ "@better-auth/passkey": "1.7.7",
314
315
  "@lingui/core": "5.9.0",
315
316
  "@lingui/react": "5.9.0",
316
317
  "@module-federation/rsbuild-plugin": "^2.9.0",
@@ -327,8 +328,8 @@
327
328
  "@tanstack/router-plugin": "1.168.35",
328
329
  "@types/node": "^25.0.3",
329
330
  "@types/tar": "^7.0.0",
330
- "better-auth": "1.6.25",
331
- "better-near-auth": "^2.0.0-rc.0",
331
+ "better-auth": "1.7.7",
332
+ "better-near-auth": "^2.0.0-rc.1",
332
333
  "jsdom": "^30.0.1",
333
334
  "react": "19.2.4",
334
335
  "react-dom": "19.2.4",
@@ -104,7 +104,7 @@ builder.myRoute.use(requireAuth).handler(async ({ input, context }) => {
104
104
  });
105
105
  ```
106
106
 
107
- Available: `requireAuth`, `requireAuthOrApiKey`, `requireRole("admin")`, `requireOrganization`, `requireOrgRole("owner")`, `requireApiKey`. Apply via `.use()`:
107
+ Available: `requireAuth`, `requireAuthOrApiKey`, `requireRole("admin")`, `requireOrganization`, `requireOrgRole("owner")`, `requireApiKey`, `requirePermission("resource", "action")` (gates on the caller's effective permissions from the auth plugin's `getContext` — see `references/middleware.md`). Apply via `.use()`:
108
108
 
109
109
  ```ts
110
110
  builder.authHealth.use(requireAuth).handler(...)
@@ -204,16 +204,19 @@ initialize: (config, plugins, _tools) =>
204
204
  })
205
205
  ```
206
206
 
207
- Use `getAuthClient()` for in-process calls:
207
+ Use the host's session middleware for in-process auth checks. The canonical middleware import is `everything-dev/api` (`packages/everything-dev/src/api/auth-middleware.ts:34`). For in-process calls, access the auth client via the injected `context.authClient` in route handlers, or use `useAuthClient()` from `@/app` in the UI.
208
208
 
209
- ```ts
210
- import { getAuthClient, createAuthMiddleware } from "./lib/auth";
209
+ ## Auth on the Client
211
210
 
212
- const authClient = getAuthClient(services, { reqHeaders: context.reqHeaders });
213
- const session = await authClient.getSession();
214
- ```
211
+ Create the client in `ui/src/lib/auth.ts`:
215
212
 
216
- `AuthCapableServices` requires an `auth` factory. If unavailable, `getAuthClient()` throws.
213
+ ```ts
214
+ export function createAuthClient(runtimeConfig) {
215
+ return betterAuth.createClient({
216
+ baseURL: runtimeConfig.authBaseUrl,
217
+ plugins: [siwn({ recipients, networkId }), passkey(), organization(), admin(), apiKey(), anonymous(), phone()],
218
+ });
219
+ }
217
220
 
218
221
  ## Auth on the Client
219
222
 
@@ -428,7 +431,7 @@ The host (`host/src/program.ts`) creates RPC and OpenAPI handlers from each plug
428
431
 
429
432
  **"Add an admin-only route."** Contract declares `.errors({ UNAUTHORIZED, FORBIDDEN })`; handler is `.effect(function* ...)` and guards with `return yield* Effect.fail(errors.UNAUTHORIZED({ apiKeyProvided: !!context.apiKey }))` — note all-optional error shapes still need an explicit `data: {}`. For shared middleware shapes, see `packages/everything-dev/src/api/auth-middleware.ts`.
430
433
 
431
- **"Call the auth plugin from my route."** `getAuthClient(services, { reqHeaders: context.reqHeaders })` then `await authClient.getSession()` — in-process, no HTTP roundtrip.
434
+ **"Call the auth plugin from my route."** The host injects `context.authClient` into route handlers via the plugin client. Access it as `context.authClient.getSession()` for in-process calls, or use `useAuthClient()` from `@/app` in the UI. See `packages/everything-dev/src/api/auth-middleware.ts` for the middleware pattern and `context.userId`/`context.user` narrowing with `requireAuth`.
432
435
 
433
436
  **"Wire an SSE route."** Contract: `.output(eventIterator(Schema))`. Handler: async generator that subscribes to the publisher and yields per event, filtering server-side on `pluginId`/query params.
434
437
 
@@ -36,3 +36,41 @@ ctx.organization.organization?.metadata?.plan;
36
36
  ```
37
37
 
38
38
  Also available: `OrgMemberAuthenticatedContext<TMeta>` (guarantees `organization.member` is non-null).
39
+
40
+ ## `requirePermission(resource, action)`
41
+
42
+ Gates a route on the caller's **effective permissions** — the map the auth plugin's
43
+ `getContext` returns (session principals: the active organization's role grants;
44
+ API-key principals: the key's declared permissions; anonymous: null, denied).
45
+
46
+ ```ts
47
+ const { requirePermission } = createAuthMiddleware(builder);
48
+
49
+ builder.createProposal
50
+ .use(requireAuth)
51
+ .use(requirePermission("proposal", "create"))
52
+ .handler(...);
53
+ ```
54
+
55
+ Fails `FORBIDDEN` with `data: { requiredPermissions: ["proposal:create"] }` when the
56
+ map lacks the grant.
57
+
58
+ **Team scope.** `getContext`'s `organization.teams` entries carry a per-team
59
+ `permissions` map (the caller's `team_member.role` grants unioned per team). Pass a
60
+ team resolver to gate on the team resolved from the route input:
61
+
62
+ ```ts
63
+ builder.updateTeamTask
64
+ .use(requireAuth)
65
+ .use(requirePermission("team", "update", { scope: "team", teamId: (input) => input.teamId }))
66
+ .handler(...);
67
+ ```
68
+
69
+ The check reads the caller's grants for that specific team; a team they are not a
70
+ member of (or one lacking the grant) is denied. Scope boundary (ADR 0028): the middleware can only see the
71
+ request context, so it gates *active-organization* permissions; checks against an
72
+ *explicit* organization id from route input (or data-masking checks) stay
73
+ service-side via Better Auth `hasPermission`, which evaluates the same
74
+ config-declared roles. The permission surface itself is config-declared in the auth
75
+ plugin's `siwn.permissions` — the plugin ships no defaults (see
76
+ `docs/adr/0028-config-declared-permissions.md`).
@@ -9,7 +9,7 @@ metadata:
9
9
 
10
10
  # CLI Command Reference
11
11
 
12
- 17 `bos` commands organised by workflow category.
12
+ 20 `bos` commands organised by workflow category.
13
13
 
14
14
  ## Development
15
15
 
@@ -119,6 +119,22 @@ bos upgrade --no-sync # only upgrade packages, skip template sync
119
119
 
120
120
  → [`init-upgrade`](.) skill for catalog strategy, legacy import rewrites, and upgrade+publish flow.
121
121
 
122
+ ### `bos import`
123
+
124
+ Import a workspace subtree (e.g. `plugins/auth`) from an upstream — a GitHub repo, a local directory, a `bos://` published runtime, or (default) the authored config's extends chain. Chain-resolved sources walk outward to the nearest ancestor declaring the workspace; when the ancestor publishes source archives (`publish.sourcePublish`, ADR 0030) the import byte-sources from the pinned, SRI-verified archive (no GitHub needed, `repository` optional) and falls back to the chain's `repository` (pinned to the manifest's git sha, HEAD otherwise) only when no archive exists. Per-file classification is snapshot-baselined; diverging migration chains are reconciled — applied local entries are kept verbatim, colliding upstream migrations are renumbered and appended, journal rebuilt. Unresolvable conflicts (content drift on an applied migration, unreadable journal) hold the chain and gate the run (exit 1) unless `--yes` or an interactive confirmation.
125
+
126
+ **Flags:** `--from <bos://<account>/<gateway> | owner/repo[@ref] | url | dir>` (optional — extends chain when omitted) `--yes` `--dry-run` `--no-install` `--json`
127
+
128
+ ```bash
129
+ bos import plugins/auth # resolved through the extends chain
130
+ bos import plugins/auth --from bos://base.near/base # chain of a published runtime
131
+ bos import plugins/auth --from NEARBuilders/everything-dev@v2.1.0
132
+ bos import plugins/auth --from ./local-checkout --dry-run
133
+ bos import plugins/auth --from owner/repo --yes
134
+ ```
135
+
136
+ → [`init-upgrade`](.) skill for source resolution, classification buckets, migration-chain reconciliation, route deltas, and the `.bos/import-report.md` report.
137
+
122
138
  ### `bos status`
123
139
 
124
140
  Show project health: package versions, parent reachability, last sync, env file status, and update availability.
@@ -135,7 +151,7 @@ Output includes installed vs latest versions for each workspace package, whether
135
151
 
136
152
  Publish the resolved config to the FastKV on-chain registry. Optionally build and deploy workspaces first.
137
153
 
138
- **Flags:** `--deploy` `--dry-run` `--network <mainnet|testnet>` `--private-key <key>` `--env <production|staging>` `--packages <list|all|local>`
154
+ **Flags:** `--deploy` `--dry-run` `--network <mainnet|testnet>` `--private-key <key>` `--wallet` `--env <production|staging>` `--packages <list|all|local>`
139
155
 
140
156
  ```bash
141
157
  bos publish # publish current config snapshot
@@ -143,6 +159,7 @@ bos publish --deploy # build all + deploy + publish
143
159
  bos publish --deploy --packages local # build/deploy only the workspaces owned by this repo (CI default)
144
160
  bos publish --dry-run # preview what would be published
145
161
  bos publish --network testnet # publish to testnet registry
162
+ bos publish --wallet # gasless: sign in a browser, relayed delegate write — no local key needed
146
163
  ```
147
164
 
148
165
  → [`publish-sync`](.) skill for config sections, SRI integrity, rollback, and FastKV troubleshooting.
@@ -174,16 +191,31 @@ bos deploy --service my-railway # override Railway service name
174
191
 
175
192
  → [`publish-sync`](.) skill and [`super-app`](.) for tenant-aware deploy considerations.
176
193
 
194
+ ### `bos login` / `bos logout`
195
+
196
+ Establish publish/CLI identity without a seed phrase or private key. `bos login` opens a browser (one approval), persists a device-named API key in `.bos/session.json` (gitignored), and works for both the publish path and API access (`x-api-key` on `/api/*`, `/api/rpc/*`, `/api/mcp`).
197
+
198
+ **Flags (login):** `--key` (additionally mint and export a publish-scoped function-call key to `~/.near-credentials`)
199
+
200
+ ```bash
201
+ bos login # browser approval → API key in .bos/session.json
202
+ bos login --key # also export a scoped publish key (near-CLI format)
203
+ bos logout # revoke the API key (best-effort) + delete the handle and exported key
204
+ ```
205
+
206
+ Sessions/keys expire (90 days by default); re-login is non-destructive. → [`talk-to-the-app`](.) skill for the authentication model and MCP access.
207
+
177
208
  ### `bos key generate`
178
209
 
179
210
  Generate a publish access key (NEAR function-call key) scoped to the FastKV registry contract.
180
- If existing publish keys are found on the account, you will be prompted to remove them.
211
+ If existing publish keys are found on the account, you will be prompted — the prompt defaults to **keeping** them.
181
212
 
182
- **Flags:** `--allowance <amount>` (default: `1NEAR`)
213
+ **Flags:** `--allowance <amount>` (default: `1NEAR`) `--[no-]remove-old-keys` (default: ask on a TTY, keep when non-interactive)
183
214
 
184
215
  ```bash
185
- bos key generate # generate key with default allowance
186
- bos key generate --allowance 2NEAR # custom allowance
216
+ bos key generate # generate key with default allowance
217
+ bos key generate --allowance 2NEAR # custom allowance
218
+ bos key generate --remove-old-keys # also remove existing publish key(s) without prompting
187
219
  ```
188
220
 
189
221
  Outputs the public key, private key, and registry contract address. The generated key can only call the set and get methods on the BOS registry contract — it cannot transfer funds or call other contracts.