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.
- package/dist/api/auth-middleware.cjs +14 -1
- package/dist/api/auth-middleware.cjs.map +1 -1
- package/dist/api/auth-middleware.d.cts +9 -0
- package/dist/api/auth-middleware.d.mts +9 -0
- package/dist/api/auth-middleware.mjs +14 -1
- package/dist/api/auth-middleware.mjs.map +1 -1
- package/dist/apps/upload/src/client.cjs +7 -1
- package/dist/apps/upload/src/client.cjs.map +1 -1
- package/dist/apps/upload/src/client.mjs +7 -1
- package/dist/apps/upload/src/client.mjs.map +1 -1
- package/dist/auth-login.cjs +24 -1
- package/dist/auth-login.cjs.map +1 -1
- package/dist/auth-login.mjs +24 -1
- package/dist/auth-login.mjs.map +1 -1
- package/dist/auth-session.cjs +4 -2
- package/dist/auth-session.cjs.map +1 -1
- package/dist/auth-session.mjs +4 -2
- package/dist/auth-session.mjs.map +1 -1
- package/dist/cdn-deploy.cjs +11 -7
- package/dist/cdn-deploy.cjs.map +1 -1
- package/dist/cdn-deploy.mjs +11 -7
- package/dist/cdn-deploy.mjs.map +1 -1
- package/dist/cli/import.cjs +1140 -0
- package/dist/cli/import.cjs.map +1 -0
- package/dist/cli/import.mjs +1126 -0
- package/dist/cli/import.mjs.map +1 -0
- package/dist/cli/init.cjs +33 -14
- package/dist/cli/init.cjs.map +1 -1
- package/dist/cli/init.d.cts +8 -2
- package/dist/cli/init.d.mts +8 -2
- package/dist/cli/init.mjs +33 -15
- package/dist/cli/init.mjs.map +1 -1
- package/dist/cli.cjs +59 -3
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +59 -3
- package/dist/cli.mjs.map +1 -1
- package/dist/commands/import.cjs +49 -0
- package/dist/commands/import.cjs.map +1 -0
- package/dist/commands/import.mjs +46 -0
- package/dist/commands/import.mjs.map +1 -0
- package/dist/commands/keys.cjs +25 -4
- package/dist/commands/keys.cjs.map +1 -1
- package/dist/commands/keys.mjs +25 -4
- package/dist/commands/keys.mjs.map +1 -1
- package/dist/config.d.cts +1 -0
- package/dist/config.d.mts +1 -0
- package/dist/contract.cjs +89 -0
- package/dist/contract.cjs.map +1 -1
- package/dist/contract.d.cts +190 -1
- package/dist/contract.d.mts +190 -1
- package/dist/contract.meta.cjs +17 -1
- package/dist/contract.meta.cjs.map +1 -1
- package/dist/contract.meta.d.cts +27 -1
- package/dist/contract.meta.d.mts +27 -1
- package/dist/contract.meta.mjs +17 -1
- package/dist/contract.meta.mjs.map +1 -1
- package/dist/contract.mjs +84 -1
- package/dist/contract.mjs.map +1 -1
- package/dist/custody-signer.cjs +70 -0
- package/dist/custody-signer.cjs.map +1 -0
- package/dist/custody-signer.mjs +68 -0
- package/dist/custody-signer.mjs.map +1 -0
- package/dist/db/core.cjs +22 -4
- package/dist/db/core.cjs.map +1 -1
- package/dist/db/core.mjs +22 -4
- package/dist/db/core.mjs.map +1 -1
- package/dist/descriptor/schema.cjs +8 -5
- package/dist/descriptor/schema.cjs.map +1 -1
- package/dist/descriptor/schema.d.cts +1 -0
- package/dist/descriptor/schema.d.mts +1 -0
- package/dist/descriptor/schema.mjs +8 -5
- package/dist/descriptor/schema.mjs.map +1 -1
- package/dist/descriptor/serialize.cjs +1 -1
- package/dist/descriptor/serialize.cjs.map +1 -1
- package/dist/descriptor/serialize.mjs +1 -1
- package/dist/descriptor/serialize.mjs.map +1 -1
- package/dist/http-client.cjs +14 -4
- package/dist/http-client.cjs.map +1 -1
- package/dist/http-client.mjs +14 -4
- package/dist/http-client.mjs.map +1 -1
- package/dist/index.cjs +6 -0
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/infra/planner.cjs +6 -2
- package/dist/infra/planner.cjs.map +1 -1
- package/dist/infra/planner.mjs +6 -2
- package/dist/infra/planner.mjs.map +1 -1
- package/dist/outlayer-login.cjs +54 -0
- package/dist/outlayer-login.cjs.map +1 -0
- package/dist/outlayer-login.mjs +54 -0
- package/dist/outlayer-login.mjs.map +1 -0
- package/dist/plugin.cjs +2 -0
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts +74 -0
- package/dist/plugin.d.mts +74 -0
- package/dist/plugin.mjs +2 -0
- package/dist/plugin.mjs.map +1 -1
- package/dist/publish.cjs +145 -22
- package/dist/publish.cjs.map +1 -1
- package/dist/publish.mjs +147 -24
- package/dist/publish.mjs.map +1 -1
- package/dist/source-archive.cjs +51 -0
- package/dist/source-archive.cjs.map +1 -0
- package/dist/source-archive.mjs +50 -0
- package/dist/source-archive.mjs.map +1 -0
- package/dist/storage-upload.cjs +6 -1
- package/dist/storage-upload.cjs.map +1 -1
- package/dist/storage-upload.mjs +6 -1
- package/dist/storage-upload.mjs.map +1 -1
- package/dist/types.cjs +5 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +2 -0
- package/dist/types.d.mts +2 -0
- package/dist/types.mjs +5 -1
- package/dist/types.mjs.map +1 -1
- package/dist/ui/auth.d.cts +217 -123
- package/dist/ui/auth.d.mts +217 -123
- package/dist/ui/router-client.cjs +2 -1
- package/dist/ui/router-client.cjs.map +1 -1
- package/dist/ui/router-client.mjs +2 -1
- package/dist/ui/router-client.mjs.map +1 -1
- package/dist/utils/workspace-subtree.cjs +37 -0
- package/dist/utils/workspace-subtree.cjs.map +1 -0
- package/dist/utils/workspace-subtree.mjs +36 -0
- package/dist/utils/workspace-subtree.mjs.map +1 -0
- package/dist/version-manifest-deploy.cjs +4 -2
- package/dist/version-manifest-deploy.cjs.map +1 -1
- package/dist/version-manifest-deploy.mjs +4 -2
- package/dist/version-manifest-deploy.mjs.map +1 -1
- package/dist/version-manifest-resolve.cjs +2 -1
- package/dist/version-manifest-resolve.cjs.map +1 -1
- package/dist/version-manifest-resolve.d.cts +3 -0
- package/dist/version-manifest-resolve.d.mts +3 -0
- package/dist/version-manifest-resolve.mjs +2 -1
- package/dist/version-manifest-resolve.mjs.map +1 -1
- package/package.json +11 -10
- package/skills/api-and-auth/SKILL.md +12 -9
- package/skills/api-and-auth/references/middleware.md +38 -0
- package/skills/cli-reference/SKILL.md +38 -6
- package/skills/init-upgrade/SKILL.md +76 -3
- 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;
|
|
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;
|
|
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":";;;;;;;
|
|
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":";;;;;;;
|
|
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":";;;;;;;;;;
|
|
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":";;;;;;;
|
|
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":";;;;;;;AA2CA,MAAM,wBAAQ,IAAI,IAAiC;AAEnD,SAAgB,wBAA8B;CAC5C,MAAM,MAAM;AACd;AAEA,SAAS,UAAU,KAAa,WAA0C;CACxE,OAAO,UAAU,GAAG,CAAC,CAAC,MAAM,aAAa;EACvC,IAAI,CAAC,SAAS,IAAI,MAAM,IAAI,MAAM,kCAAkC,SAAS,QAAQ;EACrF,OAAO,SAAS,KAAK;CACvB,CAAC;AACH;AAEA,SAAS,wBACP,MACA,UACqB;CACrB,MAAM,cAAc,KAAK,QAAQ,OAAO,EAAE;CAC1C,OAAO;EACL,UAAU,GAAG,YAAY,GAAG,SAAS;EACrC,gBAAgB,SAAS;EACzB,GAAI,SAAS,kBACT,EAAE,oBAAoB,GAAG,YAAY,GAAG,SAAS,gBAAgB,OAAO,IACxE,CAAC;EACL,GAAI,SAAS,MACT;GACE,aAAa,GAAG,YAAY,GAAG,SAAS,IAAI;GAC5C,cAAc,SAAS,IAAI;EAC7B,IACA,CAAC;EACL,GAAI,SAAS,QAAQ,EAAE,OAAO,SAAS,MAAM,IAAI,CAAC;EAClD,GAAI,SAAS,SAAS,EAAE,QAAQ,SAAS,OAAO,IAAI,CAAC;CACvD;AACF;;;;;;;;;;;;;AAcA,SAAS,sBAAsB,MAAc,KAA0C;CACrF,MAAM,YAAY,QAAQ,IAAI;CAC9B,IAAI,CAAC,WAAW,OAAO;CAEvB,MAAM,UAAU,sBAAsB,MAAM,SAAS;CACrD,IAAI,CAAC,WAAW,CAAC,WAAW,OAAO,GAAG,OAAO;CAE7C,MAAM,qBAAqB,iBAAiB,SAAS,IAAI,QAAQ;CACjE,IAAI,CAAC,sBAAsB,WAAW,kBAAkB,GAAG,OAAO;CAElE,MAAM,cAAc,KAAK,KAAK,SAAS,UAAU;CACjD,IAAI,CAAC,WAAW,WAAW,GAAG,OAAO;CACrC,MAAM,YAAY,YAAY,WAAW,CAAC,CACvC,QAAQ,SAAS,KAAK,SAAS,OAAO,CAAC,CAAC,CACxC,KAAK,SAAS,KAAK,KAAK,aAAa,IAAI,CAAC,CAAC,CAC3C,QAAQ,SAAS,SAAS,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CACzC,MAAM,GAAG,MAAM,SAAS,CAAC,CAAC,CAAC,UAAU,SAAS,CAAC,CAAC,CAAC,YAAY,IAAI,IAAI,KAAK,EAAE;CAC/E,IAAI,UAAU,WAAW,GAAG,OAAO;CACnC,MAAM,eAAe,UAAU;CAC/B,IAAI,CAAC,cAAc,OAAO;CAE1B,IAAI;EAIF,OAAO,wBAAwB,MAHd,+BAA+B,MAC9C,KAAK,MAAM,aAAa,cAAc,MAAM,CAAC,CAEH,CAAC;CAC/C,QAAQ;EACN,OAAO;CACT;AACF;AAEA,eAAsB,mBAAmB,OAIR;CAC/B,MAAM,MAAM,GAAG,MAAM,KAAK,IAAI,MAAM,IAAI,SAAS,IAAI,MAAM,IAAI;CAG/D,MAAM,SAAS,MAAM,IAAI,GAAG;CAC5B,IAAI,QAAQ,OAAO;CAEnB,MAAM,SAAS,sBAAsB,MAAM,MAAM,MAAM,GAAG;CAC1D,IAAI,QAAQ;EACV,MAAM,IAAI,KAAK,MAAM;EACrB,OAAO;CACT;CAEA,MAAM,cAAc,GAAG,MAAM,KAAK,QAAQ,OAAO,EAAE,EAAE,GAAG,MAAM,IAAI,SAAS,QAAQ,OAAO,EAAE;CAC5F,MAAM,OAAO,MAAM,UAAU,aAAa,MAAM,aAAa,KAAK;CAClE,MAAM,WAAW,UAAU,WAAW,QAAQ,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,QAAQ;CAC5E,IAAI,aAAa,MAAM,IAAI,WACzB,MAAM,IAAI,MACR,iDAAiD,YAAY,gBAAgB,MAAM,IAAI,UAAU,gBAAgB,UACnH;CAEF,MAAM,WAAqC,+BAA+B,MAAM,KAAK,MAAM,IAAI,CAAC;CAEhG,MAAM,WAAW,wBAAwB,MAAM,MAAM,QAAQ;CAC7D,MAAM,IAAI,KAAK,QAAQ;CACvB,OAAO;AACT"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "everything-dev",
|
|
3
|
-
"version": "2.0.0-rc.
|
|
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.
|
|
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.
|
|
257
|
-
"@better-auth/passkey": "1.
|
|
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.
|
|
266
|
-
"better-near-auth": "^2.0.0-rc.
|
|
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.
|
|
313
|
-
"@better-auth/passkey": "1.
|
|
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.
|
|
331
|
-
"better-near-auth": "^2.0.0-rc.
|
|
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
|
|
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 `
|
|
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
|
-
|
|
210
|
-
import { getAuthClient, createAuthMiddleware } from "./lib/auth";
|
|
209
|
+
## Auth on the Client
|
|
211
210
|
|
|
212
|
-
|
|
213
|
-
const session = await authClient.getSession();
|
|
214
|
-
```
|
|
211
|
+
Create the client in `ui/src/lib/auth.ts`:
|
|
215
212
|
|
|
216
|
-
|
|
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."**
|
|
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
|
-
|
|
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
|
|
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
|
|
186
|
-
bos key generate --allowance 2NEAR
|
|
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.
|