everything-dev 1.28.6 → 1.28.8

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/index.mjs CHANGED
@@ -3,8 +3,8 @@ import { BOS_CONFIG_ORDER, rebuildOrderedConfig } from "./merge.mjs";
3
3
  import { ApiPluginConfigSchema, BosConfigInputSchema, BosConfigSchema, BosPluginRefSchema, BosStagingSchema, ClientRuntimeConfigSchema, ClientRuntimeInfoSchema, ComposableAppEntrySchema, ExtendsSchema, FederationEntrySchema, HostConfigSchema, PluginUiConfigSchema, RuntimeConfigSchema, RuntimePluginConfigSchema, SharedConfigSchema, SharedDepConfigSchema, SidebarItemSchema, SidebarRoleSchema, SourceModeSchema, UiConfigSchema } from "./types.mjs";
4
4
  import { buildRuntimeConfig, buildRuntimePluginsForConfig, clearConfigCache, drainConfigWarnings, findConfigPath, getConfig, getHostDevelopmentPort, getProjectRoot, getResolvedConfigPath, isLocalDevelopmentTarget, isRuntimeOverrideAllowed, loadBosConfig, loadConfig, loadRemoteConfig, loadResolvedConfig, parsePort, parseRuntimeOverrideTargets, readBosConfigForBuild, resolveBosConfigPath, resolveComposableReference, resolveDevelopmentHostUrl, resolveLocalDevelopmentPath, resolvePluginRuntimeName, resumeWarnings, suppressWarnings, writeResolvedConfig } from "./config.mjs";
5
5
  import { Context, Effect, Layer, PluginRuntime, Scope, createPlugin, createPluginRuntime, oc, z } from "./sdk.mjs";
6
- import { BuildOptionsSchema, BuildResultSchema, ConfigResultSchema, DevOptionsSchema, DevResultSchema, InitOptionsSchema, InitResultSchema, KeyPublishOptionsSchema, KeyPublishResultSchema, OverrideSectionSchema, PhaseTimingSchema, PluginAddOptionsSchema, PluginAddResultSchema, PluginListResultSchema, PluginPublishOptionsSchema, PluginPublishResultSchema, PluginRemoveOptionsSchema, PluginRemoveResultSchema, PublishOptionsSchema, PublishResultSchema, StartOptionsSchema, StartResultSchema, StatusResultSchema, SyncOptionsSchema, SyncResultSchema, TypesGenOptionsSchema, TypesGenResultSchema, UpgradeOptionsSchema, UpgradeResultSchema, bosContract } from "./contract.mjs";
6
+ import { BuildOptionsSchema, BuildResultSchema, ConfigResultSchema, DevOptionsSchema, DevResultSchema, InitOptionsSchema, InitResultSchema, KeyPublishOptionsSchema, KeyPublishResultSchema, OverrideSectionSchema, PhaseTimingSchema, PluginAddOptionsSchema, PluginAddResultSchema, PluginListResultSchema, PluginPublishOptionsSchema, PluginPublishResultSchema, PluginRemoveOptionsSchema, PluginRemoveResultSchema, PublishOptionsSchema, PublishResultSchema, RuntimeOverrideTargetBaseSchema, RuntimeOverrideTargetSchema, StartOptionsSchema, StartResultSchema, StatusResultSchema, SyncOptionsSchema, SyncResultSchema, TypesGenOptionsSchema, TypesGenResultSchema, UpgradeOptionsSchema, UpgradeResultSchema, bosContract } from "./contract.mjs";
7
7
  import { cliCommandMeta } from "./contract.meta.mjs";
8
8
  import { generatePluginSidebarContent, writePluginSidebarGen } from "./sidebar.mjs";
9
9
 
10
- export { ApiPluginConfigSchema, BOS_CONFIG_ORDER, BosConfigInputSchema, BosConfigSchema, BosPluginRefSchema, BosStagingSchema, BuildOptionsSchema, BuildResultSchema, ClientRuntimeConfigSchema, ClientRuntimeInfoSchema, ComposableAppEntrySchema, ConfigResultSchema, Context, DevOptionsSchema, DevResultSchema, Effect, ExtendsSchema, FederationEntrySchema, HostConfigSchema, InitOptionsSchema, InitResultSchema, KeyPublishOptionsSchema, KeyPublishResultSchema, Layer, OverrideSectionSchema, PhaseTimingSchema, PluginAddOptionsSchema, PluginAddResultSchema, PluginListResultSchema, PluginPublishOptionsSchema, PluginPublishResultSchema, PluginRemoveOptionsSchema, PluginRemoveResultSchema, PluginRuntime, PluginUiConfigSchema, PublishOptionsSchema, PublishResultSchema, RuntimeConfigSchema, RuntimePluginConfigSchema, Scope, SharedConfigSchema, SharedDepConfigSchema, SidebarItemSchema, SidebarRoleSchema, SourceModeSchema, StartOptionsSchema, StartResultSchema, StatusResultSchema, SyncOptionsSchema, SyncResultSchema, TypesGenOptionsSchema, TypesGenResultSchema, UiConfigSchema, UpgradeOptionsSchema, UpgradeResultSchema, bosContract, buildRegistryConfigUrl, buildRegistryConfigUrlForNetwork, buildRuntimeConfig, buildRuntimePluginsForConfig, clearConfigCache, cliCommandMeta, createPlugin, createPluginRuntime, drainConfigWarnings, fetchBosConfigFromFastKv, fetchRemotePluginManifest, findConfigPath, generatePluginSidebarContent, getConfig, getFastKvBaseUrlForNetwork, getHostDevelopmentPort, getProjectRoot, getRegistryNamespaceForAccount, getRegistryNamespaceForNetwork, getResolvedConfigPath, isLocalDevelopmentTarget, isRuntimeOverrideAllowed, loadBosConfig, loadConfig, loadRemoteConfig, loadResolvedConfig, oc, parsePort, parseRuntimeOverrideTargets, readBosConfigForBuild, rebuildOrderedConfig, resolveBosConfigPath, resolveComposableReference, resolveDevelopmentHostUrl, resolveLocalDevelopmentPath, resolvePluginRuntimeName, resumeWarnings, suppressWarnings, writePluginSidebarGen, writeResolvedConfig, z };
10
+ export { ApiPluginConfigSchema, BOS_CONFIG_ORDER, BosConfigInputSchema, BosConfigSchema, BosPluginRefSchema, BosStagingSchema, BuildOptionsSchema, BuildResultSchema, ClientRuntimeConfigSchema, ClientRuntimeInfoSchema, ComposableAppEntrySchema, ConfigResultSchema, Context, DevOptionsSchema, DevResultSchema, Effect, ExtendsSchema, FederationEntrySchema, HostConfigSchema, InitOptionsSchema, InitResultSchema, KeyPublishOptionsSchema, KeyPublishResultSchema, Layer, OverrideSectionSchema, PhaseTimingSchema, PluginAddOptionsSchema, PluginAddResultSchema, PluginListResultSchema, PluginPublishOptionsSchema, PluginPublishResultSchema, PluginRemoveOptionsSchema, PluginRemoveResultSchema, PluginRuntime, PluginUiConfigSchema, PublishOptionsSchema, PublishResultSchema, RuntimeConfigSchema, RuntimeOverrideTargetBaseSchema, RuntimeOverrideTargetSchema, RuntimePluginConfigSchema, Scope, SharedConfigSchema, SharedDepConfigSchema, SidebarItemSchema, SidebarRoleSchema, SourceModeSchema, StartOptionsSchema, StartResultSchema, StatusResultSchema, SyncOptionsSchema, SyncResultSchema, TypesGenOptionsSchema, TypesGenResultSchema, UiConfigSchema, UpgradeOptionsSchema, UpgradeResultSchema, bosContract, buildRegistryConfigUrl, buildRegistryConfigUrlForNetwork, buildRuntimeConfig, buildRuntimePluginsForConfig, clearConfigCache, cliCommandMeta, createPlugin, createPluginRuntime, drainConfigWarnings, fetchBosConfigFromFastKv, fetchRemotePluginManifest, findConfigPath, generatePluginSidebarContent, getConfig, getFastKvBaseUrlForNetwork, getHostDevelopmentPort, getProjectRoot, getRegistryNamespaceForAccount, getRegistryNamespaceForNetwork, getResolvedConfigPath, isLocalDevelopmentTarget, isRuntimeOverrideAllowed, loadBosConfig, loadConfig, loadRemoteConfig, loadResolvedConfig, oc, parsePort, parseRuntimeOverrideTargets, readBosConfigForBuild, rebuildOrderedConfig, resolveBosConfigPath, resolveComposableReference, resolveDevelopmentHostUrl, resolveLocalDevelopmentPath, resolvePluginRuntimeName, resumeWarnings, suppressWarnings, writePluginSidebarGen, writeResolvedConfig, z };
@@ -4,18 +4,48 @@ const require_fastkv = require('./fastkv.cjs');
4
4
  let node_crypto = require("node:crypto");
5
5
 
6
6
  //#region src/integrity.ts
7
+ const DEFAULT_MAX_SRI_RESPONSE_BYTES = 20 * 1024 * 1024;
7
8
  function computeSriHash(content) {
8
9
  return `sha384-${(0, node_crypto.createHash)("sha384").update(content).digest("base64")}`;
9
10
  }
10
- async function computeSriHashForUrl(url) {
11
+ function resolveSriTargetUrl(url, options) {
12
+ return options?.resolveEntryUrl === false ? url : resolveEntryUrl(url);
13
+ }
14
+ function getMaxSriResponseBytes(options) {
15
+ return options?.maxBytes ?? DEFAULT_MAX_SRI_RESPONSE_BYTES;
16
+ }
17
+ async function computeSriHashFromResponse(response, url, options) {
18
+ const maxBytes = getMaxSriResponseBytes(options);
19
+ const contentLengthHeader = response.headers.get("content-length");
20
+ if (contentLengthHeader) {
21
+ const contentLength = Number(contentLengthHeader);
22
+ if (Number.isFinite(contentLength) && contentLength > maxBytes) throw new Error(`[SRI] Response for ${url} exceeds max size of ${maxBytes} bytes (${contentLength})`);
23
+ }
24
+ if (!response.body) throw new Error(`[SRI] Missing response body for ${url}`);
25
+ const hash = (0, node_crypto.createHash)("sha384");
26
+ const reader = response.body.getReader();
27
+ let totalBytes = 0;
28
+ while (true) {
29
+ const { done, value } = await reader.read();
30
+ if (done) break;
31
+ totalBytes += value.byteLength;
32
+ if (totalBytes > maxBytes) {
33
+ await reader.cancel();
34
+ throw new Error(`[SRI] Response for ${url} exceeds max size of ${maxBytes} bytes (${totalBytes})`);
35
+ }
36
+ hash.update(value);
37
+ }
38
+ return `sha384-${hash.digest("base64")}`;
39
+ }
40
+ async function computeSriHashForUrl(url, options) {
11
41
  try {
12
- const entryUrl = resolveEntryUrl(url);
42
+ const entryUrl = resolveSriTargetUrl(url, options);
13
43
  const response = await fetch(entryUrl);
14
44
  if (!response.ok) {
15
45
  console.warn(`[SRI] Failed to fetch ${entryUrl}: ${response.status} ${response.statusText}`);
16
46
  return null;
17
47
  }
18
- return computeSriHash(Buffer.from(await response.arrayBuffer()));
48
+ return await computeSriHashFromResponse(response, entryUrl, options);
19
49
  } catch (error) {
20
50
  console.warn(`[SRI] Error computing integrity for ${url}:`, error instanceof Error ? error.message : error);
21
51
  return null;
@@ -26,14 +56,14 @@ function resolveEntryUrl(url) {
26
56
  if (url.endsWith("/mf-manifest.json")) return `${url.replace(/\/mf-manifest\.json$/, "")}/remoteEntry.js`;
27
57
  return `${url.replace(/\/$/, "")}/remoteEntry.js`;
28
58
  }
29
- async function verifySriForUrl(url, expectedIntegrity) {
30
- const entryUrl = resolveEntryUrl(url);
59
+ async function verifySriForUrl(url, expectedIntegrity, options) {
60
+ const entryUrl = resolveSriTargetUrl(url, options);
31
61
  const response = await fetch(entryUrl);
32
62
  if (!response.ok) {
33
63
  console.warn(`[SRI] Failed to fetch ${entryUrl} for verification: ${response.status}`);
34
64
  return;
35
65
  }
36
- const computed = computeSriHash(Buffer.from(await response.arrayBuffer()));
66
+ const computed = await computeSriHashFromResponse(response, entryUrl, options);
37
67
  if (computed !== expectedIntegrity) throw new Error(`[SRI] Integrity check failed for ${entryUrl}\n Expected: ${expectedIntegrity}\n Computed: ${computed}`);
38
68
  }
39
69
  var IntegrityRegistry = class {
@@ -1 +1 @@
1
- {"version":3,"file":"integrity.cjs","names":["fetchBosConfigFromFastKv"],"sources":["../src/integrity.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { fetchBosConfigFromFastKv } from \"./fastkv\";\n\nexport function computeSriHash(content: string | Buffer): string {\n return `sha384-${createHash(\"sha384\").update(content).digest(\"base64\")}`;\n}\n\nexport async function computeSriHashForUrl(url: string): Promise<string | null> {\n try {\n const entryUrl = resolveEntryUrl(url);\n\n const response = await fetch(entryUrl);\n if (!response.ok) {\n console.warn(`[SRI] Failed to fetch ${entryUrl}: ${response.status} ${response.statusText}`);\n return null;\n }\n const buffer = Buffer.from(await response.arrayBuffer());\n return computeSriHash(buffer);\n } catch (error) {\n console.warn(\n `[SRI] Error computing integrity for ${url}:`,\n error instanceof Error ? error.message : error,\n );\n return null;\n }\n}\n\nexport function resolveEntryUrl(url: string): string {\n if (url.endsWith(\"/remoteEntry.js\")) return url;\n if (url.endsWith(\"/mf-manifest.json\"))\n return `${url.replace(/\\/mf-manifest\\.json$/, \"\")}/remoteEntry.js`;\n return `${url.replace(/\\/$/, \"\")}/remoteEntry.js`;\n}\n\nexport async function verifySriForUrl(url: string, expectedIntegrity: string): Promise<void> {\n const entryUrl = resolveEntryUrl(url);\n\n const response = await fetch(entryUrl);\n if (!response.ok) {\n console.warn(`[SRI] Failed to fetch ${entryUrl} for verification: ${response.status}`);\n return;\n }\n\n const buffer = Buffer.from(await response.arrayBuffer());\n const computed = computeSriHash(buffer);\n\n if (computed !== expectedIntegrity) {\n throw new Error(\n `[SRI] Integrity check failed for ${entryUrl}\\n Expected: ${expectedIntegrity}\\n Computed: ${computed}`,\n );\n }\n}\n\nexport class IntegrityRegistry {\n private hashes = new Map<string, string>();\n\n register(url: string, integrity: string): void {\n this.hashes.set(url, integrity);\n }\n\n registerEntry(baseUrl: string, integrity: string): void {\n this.hashes.set(resolveEntryUrl(baseUrl), integrity);\n }\n\n get(url: string): string | undefined {\n return this.hashes.get(url);\n }\n\n has(url: string): boolean {\n return this.hashes.has(url);\n }\n\n entries(): IterableIterator<[string, string]> {\n return this.hashes.entries();\n }\n}\n\nfunction extractIntegrityHashes(config: Record<string, unknown>): Map<string, string> {\n const hashes = new Map<string, string>();\n const app = config.app as Record<string, Record<string, unknown>> | undefined;\n const plugins = config.plugins as Record<string, Record<string, unknown>> | undefined;\n\n if (app) {\n for (const [, entry] of Object.entries(app)) {\n if (entry?.integrity && entry?.production) {\n hashes.set(resolveEntryUrl(entry.production as string), entry.integrity as string);\n }\n }\n }\n\n if (plugins) {\n for (const [, entry] of Object.entries(plugins)) {\n if (entry?.integrity && entry?.production) {\n hashes.set(resolveEntryUrl(entry.production as string), entry.integrity as string);\n }\n }\n }\n\n return hashes;\n}\n\nexport async function verifyConfigAgainstChain(\n localConfig: Record<string, unknown>,\n bosUrl: string,\n): Promise<{ verified: boolean; mismatches: string[] }> {\n const mismatches: string[] = [];\n\n let chainConfig: Record<string, unknown>;\n try {\n chainConfig = await fetchBosConfigFromFastKv<Record<string, unknown>>(bosUrl);\n } catch (error) {\n console.warn(\n `[Attestation] Failed to fetch on-chain config: ${error instanceof Error ? error.message : String(error)}`,\n );\n return { verified: false, mismatches: [\"chain-fetch-failed\"] };\n }\n\n const localHashes = extractIntegrityHashes(localConfig);\n const chainHashes = extractIntegrityHashes(chainConfig);\n\n for (const [url, chainHash] of chainHashes) {\n const localHash = localHashes.get(url);\n if (localHash && localHash !== chainHash) {\n mismatches.push(url);\n console.error(\n `[Attestation] Integrity mismatch for ${url}\\n Local: ${localHash}\\n Chain: ${chainHash}`,\n );\n }\n }\n\n if (mismatches.length === 0 && localHashes.size > 0) {\n console.log(\n `[Attestation] Local config verified against on-chain anchor (${localHashes.size} entries checked)`,\n );\n }\n\n return { verified: mismatches.length === 0, mismatches };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"integrity.cjs","names":["fetchBosConfigFromFastKv"],"sources":["../src/integrity.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { fetchBosConfigFromFastKv } from \"./fastkv\";\n\nconst DEFAULT_MAX_SRI_RESPONSE_BYTES = 20 * 1024 * 1024;\n\ninterface SriUrlOptions {\n resolveEntryUrl?: boolean;\n maxBytes?: number;\n}\n\nexport function computeSriHash(content: string | Buffer): string {\n return `sha384-${createHash(\"sha384\").update(content).digest(\"base64\")}`;\n}\n\nfunction resolveSriTargetUrl(url: string, options?: SriUrlOptions): string {\n return options?.resolveEntryUrl === false ? url : resolveEntryUrl(url);\n}\n\nfunction getMaxSriResponseBytes(options?: SriUrlOptions): number {\n return options?.maxBytes ?? DEFAULT_MAX_SRI_RESPONSE_BYTES;\n}\n\nasync function computeSriHashFromResponse(\n response: Response,\n url: string,\n options?: SriUrlOptions,\n): Promise<string> {\n const maxBytes = getMaxSriResponseBytes(options);\n const contentLengthHeader = response.headers.get(\"content-length\");\n\n if (contentLengthHeader) {\n const contentLength = Number(contentLengthHeader);\n if (Number.isFinite(contentLength) && contentLength > maxBytes) {\n throw new Error(\n `[SRI] Response for ${url} exceeds max size of ${maxBytes} bytes (${contentLength})`,\n );\n }\n }\n\n if (!response.body) {\n throw new Error(`[SRI] Missing response body for ${url}`);\n }\n\n const hash = createHash(\"sha384\");\n const reader = response.body.getReader();\n let totalBytes = 0;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n\n totalBytes += value.byteLength;\n if (totalBytes > maxBytes) {\n await reader.cancel();\n throw new Error(\n `[SRI] Response for ${url} exceeds max size of ${maxBytes} bytes (${totalBytes})`,\n );\n }\n\n hash.update(value);\n }\n\n return `sha384-${hash.digest(\"base64\")}`;\n}\n\nexport async function computeSriHashForUrl(\n url: string,\n options?: SriUrlOptions,\n): Promise<string | null> {\n try {\n const entryUrl = resolveSriTargetUrl(url, options);\n\n const response = await fetch(entryUrl);\n if (!response.ok) {\n console.warn(`[SRI] Failed to fetch ${entryUrl}: ${response.status} ${response.statusText}`);\n return null;\n }\n return await computeSriHashFromResponse(response, entryUrl, options);\n } catch (error) {\n console.warn(\n `[SRI] Error computing integrity for ${url}:`,\n error instanceof Error ? error.message : error,\n );\n return null;\n }\n}\n\nexport function resolveEntryUrl(url: string): string {\n if (url.endsWith(\"/remoteEntry.js\")) return url;\n if (url.endsWith(\"/mf-manifest.json\"))\n return `${url.replace(/\\/mf-manifest\\.json$/, \"\")}/remoteEntry.js`;\n return `${url.replace(/\\/$/, \"\")}/remoteEntry.js`;\n}\n\nexport async function verifySriForUrl(\n url: string,\n expectedIntegrity: string,\n options?: SriUrlOptions,\n): Promise<void> {\n const entryUrl = resolveSriTargetUrl(url, options);\n\n const response = await fetch(entryUrl);\n if (!response.ok) {\n console.warn(`[SRI] Failed to fetch ${entryUrl} for verification: ${response.status}`);\n return;\n }\n\n const computed = await computeSriHashFromResponse(response, entryUrl, options);\n\n if (computed !== expectedIntegrity) {\n throw new Error(\n `[SRI] Integrity check failed for ${entryUrl}\\n Expected: ${expectedIntegrity}\\n Computed: ${computed}`,\n );\n }\n}\n\nexport class IntegrityRegistry {\n private hashes = new Map<string, string>();\n\n register(url: string, integrity: string): void {\n this.hashes.set(url, integrity);\n }\n\n registerEntry(baseUrl: string, integrity: string): void {\n this.hashes.set(resolveEntryUrl(baseUrl), integrity);\n }\n\n get(url: string): string | undefined {\n return this.hashes.get(url);\n }\n\n has(url: string): boolean {\n return this.hashes.has(url);\n }\n\n entries(): IterableIterator<[string, string]> {\n return this.hashes.entries();\n }\n}\n\nfunction extractIntegrityHashes(config: Record<string, unknown>): Map<string, string> {\n const hashes = new Map<string, string>();\n const app = config.app as Record<string, Record<string, unknown>> | undefined;\n const plugins = config.plugins as Record<string, Record<string, unknown>> | undefined;\n\n if (app) {\n for (const [, entry] of Object.entries(app)) {\n if (entry?.integrity && entry?.production) {\n hashes.set(resolveEntryUrl(entry.production as string), entry.integrity as string);\n }\n }\n }\n\n if (plugins) {\n for (const [, entry] of Object.entries(plugins)) {\n if (entry?.integrity && entry?.production) {\n hashes.set(resolveEntryUrl(entry.production as string), entry.integrity as string);\n }\n }\n }\n\n return hashes;\n}\n\nexport async function verifyConfigAgainstChain(\n localConfig: Record<string, unknown>,\n bosUrl: string,\n): Promise<{ verified: boolean; mismatches: string[] }> {\n const mismatches: string[] = [];\n\n let chainConfig: Record<string, unknown>;\n try {\n chainConfig = await fetchBosConfigFromFastKv<Record<string, unknown>>(bosUrl);\n } catch (error) {\n console.warn(\n `[Attestation] Failed to fetch on-chain config: ${error instanceof Error ? error.message : String(error)}`,\n );\n return { verified: false, mismatches: [\"chain-fetch-failed\"] };\n }\n\n const localHashes = extractIntegrityHashes(localConfig);\n const chainHashes = extractIntegrityHashes(chainConfig);\n\n for (const [url, chainHash] of chainHashes) {\n const localHash = localHashes.get(url);\n if (localHash && localHash !== chainHash) {\n mismatches.push(url);\n console.error(\n `[Attestation] Integrity mismatch for ${url}\\n Local: ${localHash}\\n Chain: ${chainHash}`,\n );\n }\n }\n\n if (mismatches.length === 0 && localHashes.size > 0) {\n console.log(\n `[Attestation] Local config verified against on-chain anchor (${localHashes.size} entries checked)`,\n );\n }\n\n return { verified: mismatches.length === 0, mismatches };\n}\n"],"mappings":"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"}
@@ -1,8 +1,12 @@
1
1
  //#region src/integrity.d.ts
2
+ interface SriUrlOptions {
3
+ resolveEntryUrl?: boolean;
4
+ maxBytes?: number;
5
+ }
2
6
  declare function computeSriHash(content: string | Buffer): string;
3
- declare function computeSriHashForUrl(url: string): Promise<string | null>;
7
+ declare function computeSriHashForUrl(url: string, options?: SriUrlOptions): Promise<string | null>;
4
8
  declare function resolveEntryUrl(url: string): string;
5
- declare function verifySriForUrl(url: string, expectedIntegrity: string): Promise<void>;
9
+ declare function verifySriForUrl(url: string, expectedIntegrity: string, options?: SriUrlOptions): Promise<void>;
6
10
  declare class IntegrityRegistry {
7
11
  private hashes;
8
12
  register(url: string, integrity: string): void;
@@ -1 +1 @@
1
- {"version":3,"file":"integrity.d.cts","names":[],"sources":["../src/integrity.ts"],"mappings":";iBAGgB,cAAA,CAAe,OAAA,WAAkB,MAAA;AAAA,iBAI3B,oBAAA,CAAqB,GAAA,WAAc,OAAA;AAAA,iBAoBzC,eAAA,CAAgB,GAAA;AAAA,iBAOV,eAAA,CAAgB,GAAA,UAAa,iBAAA,WAA4B,OAAA;AAAA,cAmBlE,iBAAA;EAAA,QACH,MAAA;EAER,QAAA,CAAS,GAAA,UAAa,SAAA;EAItB,aAAA,CAAc,OAAA,UAAiB,SAAA;EAI/B,GAAA,CAAI,GAAA;EAIJ,GAAA,CAAI,GAAA;EAIJ,OAAA,CAAA,GAAW,gBAAA;AAAA;AAAA,iBA6BS,wBAAA,CACpB,WAAA,EAAa,MAAA,mBACb,MAAA,WACC,OAAA;EAAU,QAAA;EAAmB,UAAA;AAAA"}
1
+ {"version":3,"file":"integrity.d.cts","names":[],"sources":["../src/integrity.ts"],"mappings":";UAKU,aAAA;EACR,eAAA;EACA,QAAA;AAAA;AAAA,iBAGc,cAAA,CAAe,OAAA,WAAkB,MAAA;AAAA,iBAyD3B,oBAAA,CACpB,GAAA,UACA,OAAA,GAAU,aAAA,GACT,OAAA;AAAA,iBAmBa,eAAA,CAAgB,GAAA;AAAA,iBAOV,eAAA,CACpB,GAAA,UACA,iBAAA,UACA,OAAA,GAAU,aAAA,GACT,OAAA;AAAA,cAkBU,iBAAA;EAAA,QACH,MAAA;EAER,QAAA,CAAS,GAAA,UAAa,SAAA;EAItB,aAAA,CAAc,OAAA,UAAiB,SAAA;EAI/B,GAAA,CAAI,GAAA;EAIJ,GAAA,CAAI,GAAA;EAIJ,OAAA,CAAA,GAAW,gBAAA;AAAA;AAAA,iBA6BS,wBAAA,CACpB,WAAA,EAAa,MAAA,mBACb,MAAA,WACC,OAAA;EAAU,QAAA;EAAmB,UAAA;AAAA"}
@@ -1,8 +1,12 @@
1
1
  //#region src/integrity.d.ts
2
+ interface SriUrlOptions {
3
+ resolveEntryUrl?: boolean;
4
+ maxBytes?: number;
5
+ }
2
6
  declare function computeSriHash(content: string | Buffer): string;
3
- declare function computeSriHashForUrl(url: string): Promise<string | null>;
7
+ declare function computeSriHashForUrl(url: string, options?: SriUrlOptions): Promise<string | null>;
4
8
  declare function resolveEntryUrl(url: string): string;
5
- declare function verifySriForUrl(url: string, expectedIntegrity: string): Promise<void>;
9
+ declare function verifySriForUrl(url: string, expectedIntegrity: string, options?: SriUrlOptions): Promise<void>;
6
10
  declare class IntegrityRegistry {
7
11
  private hashes;
8
12
  register(url: string, integrity: string): void;
@@ -1 +1 @@
1
- {"version":3,"file":"integrity.d.mts","names":[],"sources":["../src/integrity.ts"],"mappings":";iBAGgB,cAAA,CAAe,OAAA,WAAkB,MAAA;AAAA,iBAI3B,oBAAA,CAAqB,GAAA,WAAc,OAAA;AAAA,iBAoBzC,eAAA,CAAgB,GAAA;AAAA,iBAOV,eAAA,CAAgB,GAAA,UAAa,iBAAA,WAA4B,OAAA;AAAA,cAmBlE,iBAAA;EAAA,QACH,MAAA;EAER,QAAA,CAAS,GAAA,UAAa,SAAA;EAItB,aAAA,CAAc,OAAA,UAAiB,SAAA;EAI/B,GAAA,CAAI,GAAA;EAIJ,GAAA,CAAI,GAAA;EAIJ,OAAA,CAAA,GAAW,gBAAA;AAAA;AAAA,iBA6BS,wBAAA,CACpB,WAAA,EAAa,MAAA,mBACb,MAAA,WACC,OAAA;EAAU,QAAA;EAAmB,UAAA;AAAA"}
1
+ {"version":3,"file":"integrity.d.mts","names":[],"sources":["../src/integrity.ts"],"mappings":";UAKU,aAAA;EACR,eAAA;EACA,QAAA;AAAA;AAAA,iBAGc,cAAA,CAAe,OAAA,WAAkB,MAAA;AAAA,iBAyD3B,oBAAA,CACpB,GAAA,UACA,OAAA,GAAU,aAAA,GACT,OAAA;AAAA,iBAmBa,eAAA,CAAgB,GAAA;AAAA,iBAOV,eAAA,CACpB,GAAA,UACA,iBAAA,UACA,OAAA,GAAU,aAAA,GACT,OAAA;AAAA,cAkBU,iBAAA;EAAA,QACH,MAAA;EAER,QAAA,CAAS,GAAA,UAAa,SAAA;EAItB,aAAA,CAAc,OAAA,UAAiB,SAAA;EAI/B,GAAA,CAAI,GAAA;EAIJ,GAAA,CAAI,GAAA;EAIJ,OAAA,CAAA,GAAW,gBAAA;AAAA;AAAA,iBA6BS,wBAAA,CACpB,WAAA,EAAa,MAAA,mBACb,MAAA,WACC,OAAA;EAAU,QAAA;EAAmB,UAAA;AAAA"}
@@ -2,18 +2,48 @@ import { fetchBosConfigFromFastKv } from "./fastkv.mjs";
2
2
  import { createHash } from "node:crypto";
3
3
 
4
4
  //#region src/integrity.ts
5
+ const DEFAULT_MAX_SRI_RESPONSE_BYTES = 20 * 1024 * 1024;
5
6
  function computeSriHash(content) {
6
7
  return `sha384-${createHash("sha384").update(content).digest("base64")}`;
7
8
  }
8
- async function computeSriHashForUrl(url) {
9
+ function resolveSriTargetUrl(url, options) {
10
+ return options?.resolveEntryUrl === false ? url : resolveEntryUrl(url);
11
+ }
12
+ function getMaxSriResponseBytes(options) {
13
+ return options?.maxBytes ?? DEFAULT_MAX_SRI_RESPONSE_BYTES;
14
+ }
15
+ async function computeSriHashFromResponse(response, url, options) {
16
+ const maxBytes = getMaxSriResponseBytes(options);
17
+ const contentLengthHeader = response.headers.get("content-length");
18
+ if (contentLengthHeader) {
19
+ const contentLength = Number(contentLengthHeader);
20
+ if (Number.isFinite(contentLength) && contentLength > maxBytes) throw new Error(`[SRI] Response for ${url} exceeds max size of ${maxBytes} bytes (${contentLength})`);
21
+ }
22
+ if (!response.body) throw new Error(`[SRI] Missing response body for ${url}`);
23
+ const hash = createHash("sha384");
24
+ const reader = response.body.getReader();
25
+ let totalBytes = 0;
26
+ while (true) {
27
+ const { done, value } = await reader.read();
28
+ if (done) break;
29
+ totalBytes += value.byteLength;
30
+ if (totalBytes > maxBytes) {
31
+ await reader.cancel();
32
+ throw new Error(`[SRI] Response for ${url} exceeds max size of ${maxBytes} bytes (${totalBytes})`);
33
+ }
34
+ hash.update(value);
35
+ }
36
+ return `sha384-${hash.digest("base64")}`;
37
+ }
38
+ async function computeSriHashForUrl(url, options) {
9
39
  try {
10
- const entryUrl = resolveEntryUrl(url);
40
+ const entryUrl = resolveSriTargetUrl(url, options);
11
41
  const response = await fetch(entryUrl);
12
42
  if (!response.ok) {
13
43
  console.warn(`[SRI] Failed to fetch ${entryUrl}: ${response.status} ${response.statusText}`);
14
44
  return null;
15
45
  }
16
- return computeSriHash(Buffer.from(await response.arrayBuffer()));
46
+ return await computeSriHashFromResponse(response, entryUrl, options);
17
47
  } catch (error) {
18
48
  console.warn(`[SRI] Error computing integrity for ${url}:`, error instanceof Error ? error.message : error);
19
49
  return null;
@@ -24,14 +54,14 @@ function resolveEntryUrl(url) {
24
54
  if (url.endsWith("/mf-manifest.json")) return `${url.replace(/\/mf-manifest\.json$/, "")}/remoteEntry.js`;
25
55
  return `${url.replace(/\/$/, "")}/remoteEntry.js`;
26
56
  }
27
- async function verifySriForUrl(url, expectedIntegrity) {
28
- const entryUrl = resolveEntryUrl(url);
57
+ async function verifySriForUrl(url, expectedIntegrity, options) {
58
+ const entryUrl = resolveSriTargetUrl(url, options);
29
59
  const response = await fetch(entryUrl);
30
60
  if (!response.ok) {
31
61
  console.warn(`[SRI] Failed to fetch ${entryUrl} for verification: ${response.status}`);
32
62
  return;
33
63
  }
34
- const computed = computeSriHash(Buffer.from(await response.arrayBuffer()));
64
+ const computed = await computeSriHashFromResponse(response, entryUrl, options);
35
65
  if (computed !== expectedIntegrity) throw new Error(`[SRI] Integrity check failed for ${entryUrl}\n Expected: ${expectedIntegrity}\n Computed: ${computed}`);
36
66
  }
37
67
  var IntegrityRegistry = class {
@@ -1 +1 @@
1
- {"version":3,"file":"integrity.mjs","names":[],"sources":["../src/integrity.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { fetchBosConfigFromFastKv } from \"./fastkv\";\n\nexport function computeSriHash(content: string | Buffer): string {\n return `sha384-${createHash(\"sha384\").update(content).digest(\"base64\")}`;\n}\n\nexport async function computeSriHashForUrl(url: string): Promise<string | null> {\n try {\n const entryUrl = resolveEntryUrl(url);\n\n const response = await fetch(entryUrl);\n if (!response.ok) {\n console.warn(`[SRI] Failed to fetch ${entryUrl}: ${response.status} ${response.statusText}`);\n return null;\n }\n const buffer = Buffer.from(await response.arrayBuffer());\n return computeSriHash(buffer);\n } catch (error) {\n console.warn(\n `[SRI] Error computing integrity for ${url}:`,\n error instanceof Error ? error.message : error,\n );\n return null;\n }\n}\n\nexport function resolveEntryUrl(url: string): string {\n if (url.endsWith(\"/remoteEntry.js\")) return url;\n if (url.endsWith(\"/mf-manifest.json\"))\n return `${url.replace(/\\/mf-manifest\\.json$/, \"\")}/remoteEntry.js`;\n return `${url.replace(/\\/$/, \"\")}/remoteEntry.js`;\n}\n\nexport async function verifySriForUrl(url: string, expectedIntegrity: string): Promise<void> {\n const entryUrl = resolveEntryUrl(url);\n\n const response = await fetch(entryUrl);\n if (!response.ok) {\n console.warn(`[SRI] Failed to fetch ${entryUrl} for verification: ${response.status}`);\n return;\n }\n\n const buffer = Buffer.from(await response.arrayBuffer());\n const computed = computeSriHash(buffer);\n\n if (computed !== expectedIntegrity) {\n throw new Error(\n `[SRI] Integrity check failed for ${entryUrl}\\n Expected: ${expectedIntegrity}\\n Computed: ${computed}`,\n );\n }\n}\n\nexport class IntegrityRegistry {\n private hashes = new Map<string, string>();\n\n register(url: string, integrity: string): void {\n this.hashes.set(url, integrity);\n }\n\n registerEntry(baseUrl: string, integrity: string): void {\n this.hashes.set(resolveEntryUrl(baseUrl), integrity);\n }\n\n get(url: string): string | undefined {\n return this.hashes.get(url);\n }\n\n has(url: string): boolean {\n return this.hashes.has(url);\n }\n\n entries(): IterableIterator<[string, string]> {\n return this.hashes.entries();\n }\n}\n\nfunction extractIntegrityHashes(config: Record<string, unknown>): Map<string, string> {\n const hashes = new Map<string, string>();\n const app = config.app as Record<string, Record<string, unknown>> | undefined;\n const plugins = config.plugins as Record<string, Record<string, unknown>> | undefined;\n\n if (app) {\n for (const [, entry] of Object.entries(app)) {\n if (entry?.integrity && entry?.production) {\n hashes.set(resolveEntryUrl(entry.production as string), entry.integrity as string);\n }\n }\n }\n\n if (plugins) {\n for (const [, entry] of Object.entries(plugins)) {\n if (entry?.integrity && entry?.production) {\n hashes.set(resolveEntryUrl(entry.production as string), entry.integrity as string);\n }\n }\n }\n\n return hashes;\n}\n\nexport async function verifyConfigAgainstChain(\n localConfig: Record<string, unknown>,\n bosUrl: string,\n): Promise<{ verified: boolean; mismatches: string[] }> {\n const mismatches: string[] = [];\n\n let chainConfig: Record<string, unknown>;\n try {\n chainConfig = await fetchBosConfigFromFastKv<Record<string, unknown>>(bosUrl);\n } catch (error) {\n console.warn(\n `[Attestation] Failed to fetch on-chain config: ${error instanceof Error ? error.message : String(error)}`,\n );\n return { verified: false, mismatches: [\"chain-fetch-failed\"] };\n }\n\n const localHashes = extractIntegrityHashes(localConfig);\n const chainHashes = extractIntegrityHashes(chainConfig);\n\n for (const [url, chainHash] of chainHashes) {\n const localHash = localHashes.get(url);\n if (localHash && localHash !== chainHash) {\n mismatches.push(url);\n console.error(\n `[Attestation] Integrity mismatch for ${url}\\n Local: ${localHash}\\n Chain: ${chainHash}`,\n );\n }\n }\n\n if (mismatches.length === 0 && localHashes.size > 0) {\n console.log(\n `[Attestation] Local config verified against on-chain anchor (${localHashes.size} entries checked)`,\n );\n }\n\n return { verified: mismatches.length === 0, mismatches };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"integrity.mjs","names":[],"sources":["../src/integrity.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { fetchBosConfigFromFastKv } from \"./fastkv\";\n\nconst DEFAULT_MAX_SRI_RESPONSE_BYTES = 20 * 1024 * 1024;\n\ninterface SriUrlOptions {\n resolveEntryUrl?: boolean;\n maxBytes?: number;\n}\n\nexport function computeSriHash(content: string | Buffer): string {\n return `sha384-${createHash(\"sha384\").update(content).digest(\"base64\")}`;\n}\n\nfunction resolveSriTargetUrl(url: string, options?: SriUrlOptions): string {\n return options?.resolveEntryUrl === false ? url : resolveEntryUrl(url);\n}\n\nfunction getMaxSriResponseBytes(options?: SriUrlOptions): number {\n return options?.maxBytes ?? DEFAULT_MAX_SRI_RESPONSE_BYTES;\n}\n\nasync function computeSriHashFromResponse(\n response: Response,\n url: string,\n options?: SriUrlOptions,\n): Promise<string> {\n const maxBytes = getMaxSriResponseBytes(options);\n const contentLengthHeader = response.headers.get(\"content-length\");\n\n if (contentLengthHeader) {\n const contentLength = Number(contentLengthHeader);\n if (Number.isFinite(contentLength) && contentLength > maxBytes) {\n throw new Error(\n `[SRI] Response for ${url} exceeds max size of ${maxBytes} bytes (${contentLength})`,\n );\n }\n }\n\n if (!response.body) {\n throw new Error(`[SRI] Missing response body for ${url}`);\n }\n\n const hash = createHash(\"sha384\");\n const reader = response.body.getReader();\n let totalBytes = 0;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n\n totalBytes += value.byteLength;\n if (totalBytes > maxBytes) {\n await reader.cancel();\n throw new Error(\n `[SRI] Response for ${url} exceeds max size of ${maxBytes} bytes (${totalBytes})`,\n );\n }\n\n hash.update(value);\n }\n\n return `sha384-${hash.digest(\"base64\")}`;\n}\n\nexport async function computeSriHashForUrl(\n url: string,\n options?: SriUrlOptions,\n): Promise<string | null> {\n try {\n const entryUrl = resolveSriTargetUrl(url, options);\n\n const response = await fetch(entryUrl);\n if (!response.ok) {\n console.warn(`[SRI] Failed to fetch ${entryUrl}: ${response.status} ${response.statusText}`);\n return null;\n }\n return await computeSriHashFromResponse(response, entryUrl, options);\n } catch (error) {\n console.warn(\n `[SRI] Error computing integrity for ${url}:`,\n error instanceof Error ? error.message : error,\n );\n return null;\n }\n}\n\nexport function resolveEntryUrl(url: string): string {\n if (url.endsWith(\"/remoteEntry.js\")) return url;\n if (url.endsWith(\"/mf-manifest.json\"))\n return `${url.replace(/\\/mf-manifest\\.json$/, \"\")}/remoteEntry.js`;\n return `${url.replace(/\\/$/, \"\")}/remoteEntry.js`;\n}\n\nexport async function verifySriForUrl(\n url: string,\n expectedIntegrity: string,\n options?: SriUrlOptions,\n): Promise<void> {\n const entryUrl = resolveSriTargetUrl(url, options);\n\n const response = await fetch(entryUrl);\n if (!response.ok) {\n console.warn(`[SRI] Failed to fetch ${entryUrl} for verification: ${response.status}`);\n return;\n }\n\n const computed = await computeSriHashFromResponse(response, entryUrl, options);\n\n if (computed !== expectedIntegrity) {\n throw new Error(\n `[SRI] Integrity check failed for ${entryUrl}\\n Expected: ${expectedIntegrity}\\n Computed: ${computed}`,\n );\n }\n}\n\nexport class IntegrityRegistry {\n private hashes = new Map<string, string>();\n\n register(url: string, integrity: string): void {\n this.hashes.set(url, integrity);\n }\n\n registerEntry(baseUrl: string, integrity: string): void {\n this.hashes.set(resolveEntryUrl(baseUrl), integrity);\n }\n\n get(url: string): string | undefined {\n return this.hashes.get(url);\n }\n\n has(url: string): boolean {\n return this.hashes.has(url);\n }\n\n entries(): IterableIterator<[string, string]> {\n return this.hashes.entries();\n }\n}\n\nfunction extractIntegrityHashes(config: Record<string, unknown>): Map<string, string> {\n const hashes = new Map<string, string>();\n const app = config.app as Record<string, Record<string, unknown>> | undefined;\n const plugins = config.plugins as Record<string, Record<string, unknown>> | undefined;\n\n if (app) {\n for (const [, entry] of Object.entries(app)) {\n if (entry?.integrity && entry?.production) {\n hashes.set(resolveEntryUrl(entry.production as string), entry.integrity as string);\n }\n }\n }\n\n if (plugins) {\n for (const [, entry] of Object.entries(plugins)) {\n if (entry?.integrity && entry?.production) {\n hashes.set(resolveEntryUrl(entry.production as string), entry.integrity as string);\n }\n }\n }\n\n return hashes;\n}\n\nexport async function verifyConfigAgainstChain(\n localConfig: Record<string, unknown>,\n bosUrl: string,\n): Promise<{ verified: boolean; mismatches: string[] }> {\n const mismatches: string[] = [];\n\n let chainConfig: Record<string, unknown>;\n try {\n chainConfig = await fetchBosConfigFromFastKv<Record<string, unknown>>(bosUrl);\n } catch (error) {\n console.warn(\n `[Attestation] Failed to fetch on-chain config: ${error instanceof Error ? error.message : String(error)}`,\n );\n return { verified: false, mismatches: [\"chain-fetch-failed\"] };\n }\n\n const localHashes = extractIntegrityHashes(localConfig);\n const chainHashes = extractIntegrityHashes(chainConfig);\n\n for (const [url, chainHash] of chainHashes) {\n const localHash = localHashes.get(url);\n if (localHash && localHash !== chainHash) {\n mismatches.push(url);\n console.error(\n `[Attestation] Integrity mismatch for ${url}\\n Local: ${localHash}\\n Chain: ${chainHash}`,\n );\n }\n }\n\n if (mismatches.length === 0 && localHashes.size > 0) {\n console.log(\n `[Attestation] Local config verified against on-chain anchor (${localHashes.size} entries checked)`,\n );\n }\n\n return { verified: mismatches.length === 0, mismatches };\n}\n"],"mappings":"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"}
package/dist/plugin.d.cts CHANGED
@@ -87,8 +87,8 @@ declare const _default: _$every_plugin0.LoadedPluginWithBinding<{
87
87
  deploy: z.ZodDefault<z.ZodBoolean>;
88
88
  }, z.core.$strip>, z.ZodObject<{
89
89
  status: z.ZodEnum<{
90
- success: "success";
91
90
  error: "error";
91
+ success: "success";
92
92
  }>;
93
93
  built: z.ZodArray<z.ZodString>;
94
94
  skipped: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -333,10 +333,10 @@ declare const _default: _$every_plugin0.LoadedPluginWithBinding<{
333
333
  source: z.ZodOptional<z.ZodString>;
334
334
  plugins: z.ZodOptional<z.ZodArray<z.ZodString>>;
335
335
  overrides: z.ZodOptional<z.ZodArray<z.ZodEnum<{
336
- plugins: "plugins";
337
- ui: "ui";
338
336
  host: "host";
337
+ ui: "ui";
339
338
  api: "api";
339
+ plugins: "plugins";
340
340
  }>>>;
341
341
  noInteractive: z.ZodDefault<z.ZodBoolean>;
342
342
  noInstall: z.ZodDefault<z.ZodBoolean>;
@@ -352,10 +352,10 @@ declare const _default: _$every_plugin0.LoadedPluginWithBinding<{
352
352
  extends: z.ZodString;
353
353
  plugins: z.ZodOptional<z.ZodArray<z.ZodString>>;
354
354
  overrides: z.ZodOptional<z.ZodArray<z.ZodEnum<{
355
- plugins: "plugins";
356
- ui: "ui";
357
355
  host: "host";
356
+ ui: "ui";
358
357
  api: "api";
358
+ plugins: "plugins";
359
359
  }>>>;
360
360
  filesCopied: z.ZodNumber;
361
361
  timings: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -441,14 +441,14 @@ declare const _default: _$every_plugin0.LoadedPluginWithBinding<{
441
441
  }, z.core.$strip>, _$_orpc_contract0.MergedErrorMap<Record<never, never>, Record<never, never>>, Record<never, never>>;
442
442
  typesGen: _$_orpc_contract0.ContractProcedure<z.ZodObject<{
443
443
  env: z.ZodOptional<z.ZodEnum<{
444
- development: "development";
445
444
  production: "production";
445
+ development: "development";
446
446
  }>>;
447
447
  dryRun: z.ZodDefault<z.ZodBoolean>;
448
448
  }, z.core.$strip>, z.ZodObject<{
449
449
  status: z.ZodEnum<{
450
- success: "success";
451
450
  error: "error";
451
+ success: "success";
452
452
  }>;
453
453
  generated: z.ZodArray<z.ZodString>;
454
454
  fetched: z.ZodArray<z.ZodString>;
@@ -578,7 +578,7 @@ declare const _default: _$every_plugin0.LoadedPluginWithBinding<{
578
578
  }> | undefined;
579
579
  } | null;
580
580
  runtimeConfig: {
581
- env: "development" | "production" | "staging";
581
+ env: "production" | "staging" | "development";
582
582
  account: string;
583
583
  networkId: "testnet" | "mainnet";
584
584
  host: {
package/dist/plugin.d.mts CHANGED
@@ -87,8 +87,8 @@ declare const _default: _$every_plugin0.LoadedPluginWithBinding<{
87
87
  deploy: z.ZodDefault<z.ZodBoolean>;
88
88
  }, z.core.$strip>, z.ZodObject<{
89
89
  status: z.ZodEnum<{
90
- success: "success";
91
90
  error: "error";
91
+ success: "success";
92
92
  }>;
93
93
  built: z.ZodArray<z.ZodString>;
94
94
  skipped: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -333,10 +333,10 @@ declare const _default: _$every_plugin0.LoadedPluginWithBinding<{
333
333
  source: z.ZodOptional<z.ZodString>;
334
334
  plugins: z.ZodOptional<z.ZodArray<z.ZodString>>;
335
335
  overrides: z.ZodOptional<z.ZodArray<z.ZodEnum<{
336
- plugins: "plugins";
337
- ui: "ui";
338
336
  host: "host";
337
+ ui: "ui";
339
338
  api: "api";
339
+ plugins: "plugins";
340
340
  }>>>;
341
341
  noInteractive: z.ZodDefault<z.ZodBoolean>;
342
342
  noInstall: z.ZodDefault<z.ZodBoolean>;
@@ -352,10 +352,10 @@ declare const _default: _$every_plugin0.LoadedPluginWithBinding<{
352
352
  extends: z.ZodString;
353
353
  plugins: z.ZodOptional<z.ZodArray<z.ZodString>>;
354
354
  overrides: z.ZodOptional<z.ZodArray<z.ZodEnum<{
355
- plugins: "plugins";
356
- ui: "ui";
357
355
  host: "host";
356
+ ui: "ui";
358
357
  api: "api";
358
+ plugins: "plugins";
359
359
  }>>>;
360
360
  filesCopied: z.ZodNumber;
361
361
  timings: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -441,14 +441,14 @@ declare const _default: _$every_plugin0.LoadedPluginWithBinding<{
441
441
  }, z.core.$strip>, _$_orpc_contract0.MergedErrorMap<Record<never, never>, Record<never, never>>, Record<never, never>>;
442
442
  typesGen: _$_orpc_contract0.ContractProcedure<z.ZodObject<{
443
443
  env: z.ZodOptional<z.ZodEnum<{
444
- development: "development";
445
444
  production: "production";
445
+ development: "development";
446
446
  }>>;
447
447
  dryRun: z.ZodDefault<z.ZodBoolean>;
448
448
  }, z.core.$strip>, z.ZodObject<{
449
449
  status: z.ZodEnum<{
450
- success: "success";
451
450
  error: "error";
451
+ success: "success";
452
452
  }>;
453
453
  generated: z.ZodArray<z.ZodString>;
454
454
  fetched: z.ZodArray<z.ZodString>;
@@ -578,7 +578,7 @@ declare const _default: _$every_plugin0.LoadedPluginWithBinding<{
578
578
  }> | undefined;
579
579
  } | null;
580
580
  runtimeConfig: {
581
- env: "development" | "production" | "staging";
581
+ env: "production" | "staging" | "development";
582
582
  account: string;
583
583
  networkId: "testnet" | "mainnet";
584
584
  host: {
package/dist/types.d.cts CHANGED
@@ -397,9 +397,9 @@ declare const BosConfigSchema: z.ZodObject<{
397
397
  type BosConfig = z.infer<typeof BosConfigSchema>;
398
398
  declare const RuntimeConfigSchema: z.ZodObject<{
399
399
  env: z.ZodEnum<{
400
- development: "development";
401
400
  production: "production";
402
401
  staging: "staging";
402
+ development: "development";
403
403
  }>;
404
404
  account: z.ZodString;
405
405
  domain: z.ZodOptional<z.ZodString>;
@@ -539,9 +539,9 @@ type RuntimeConfig = z.infer<typeof RuntimeConfigSchema>;
539
539
  declare const ClientRuntimeConfigSchema: z.ZodObject<{
540
540
  cspNonce: z.ZodOptional<z.ZodString>;
541
541
  env: z.ZodEnum<{
542
- development: "development";
543
542
  production: "production";
544
543
  staging: "staging";
544
+ development: "development";
545
545
  }>;
546
546
  account: z.ZodString;
547
547
  networkId: z.ZodEnum<{
package/dist/types.d.mts CHANGED
@@ -397,9 +397,9 @@ declare const BosConfigSchema: z.ZodObject<{
397
397
  type BosConfig = z.infer<typeof BosConfigSchema>;
398
398
  declare const RuntimeConfigSchema: z.ZodObject<{
399
399
  env: z.ZodEnum<{
400
- development: "development";
401
400
  production: "production";
402
401
  staging: "staging";
402
+ development: "development";
403
403
  }>;
404
404
  account: z.ZodString;
405
405
  domain: z.ZodOptional<z.ZodString>;
@@ -539,9 +539,9 @@ type RuntimeConfig = z.infer<typeof RuntimeConfigSchema>;
539
539
  declare const ClientRuntimeConfigSchema: z.ZodObject<{
540
540
  cspNonce: z.ZodOptional<z.ZodString>;
541
541
  env: z.ZodEnum<{
542
- development: "development";
543
542
  production: "production";
544
543
  staging: "staging";
544
+ development: "development";
545
545
  }>;
546
546
  account: z.ZodString;
547
547
  networkId: z.ZodEnum<{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "everything-dev",
3
- "version": "1.28.6",
3
+ "version": "1.28.8",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -53,6 +53,49 @@ Set a plugin to `null` to explicitly remove an inherited plugin:
53
53
  }
54
54
  ```
55
55
 
56
+ ## Fixed-Core Tenant Mode
57
+
58
+ For shared-host tenant setups, the tenant app extends a base runtime and the host resolves that tenant config per request.
59
+
60
+ Example:
61
+
62
+ ```json
63
+ {
64
+ "extends": "bos://linktree.near/linktree.com",
65
+ "account": "alice.near",
66
+ "domain": "linktree.com"
67
+ }
68
+ ```
69
+
70
+ With host env like:
71
+
72
+ ```bash
73
+ NETWORK_ID=mainnet
74
+ ALLOW_OVERRIDE=ui,plugins.*
75
+ TENANT_WHITELIST=alice.near
76
+ ALLOW_UNTRUSTED_SSR=false
77
+ ```
78
+
79
+ Request mapping is by subdomain convention:
80
+ - `linktree.com` -> base runtime
81
+ - `alice.linktree.com` -> `bos://alice.near/linktree.com`
82
+
83
+ ### What the tenant may override today
84
+
85
+ In fixed-core mode, the host keeps the server core from the base runtime and only applies request-scoped UI-facing overrides from the tenant config:
86
+ - `app.ui`
87
+ - existing `plugins.<id>.ui`
88
+ - existing `plugins.<id>.sidebar`
89
+
90
+ The tenant config must extend the base BOS runtime. Tenant API/auth overrides and dynamic new plugin IDs are not part of this mode.
91
+
92
+ ### SSR behavior
93
+
94
+ Tenant SSR is gated separately from inheritance:
95
+ - if `ALLOW_UNTRUSTED_SSR=true`, any valid tenant with SSR config may SSR
96
+ - otherwise the tenant account must be listed in `TENANT_WHITELIST`
97
+ - non-whitelisted tenants fall back to client rendering
98
+
56
99
  ## Resolved Config: `.bos/bos.resolved-config.json`
57
100
 
58
101
  **Generated by**: `bos dev`, `bos build`, `syncAndGenerateSharedUi()`
@@ -27,6 +27,71 @@ bos init --overrides ui,api,host # Include host locally
27
27
  7. Write initial snapshot (`.bos/sync-snapshot.json`)
28
28
  8. `bun install` + `bos types gen`
29
29
 
30
+ ## Shared Host + Custom Tenant App
31
+
32
+ Use this pattern when you want one deployed host runtime and separate tenant apps that inherit from it.
33
+
34
+ ### 1. Create the base host runtime
35
+
36
+ Create the app that owns the shared host, auth, API, and base plugin list:
37
+
38
+ ```bash
39
+ bos init --overrides ui,api,host
40
+ ```
41
+
42
+ This base runtime is the app the host boots from.
43
+
44
+ ### 2. Create the tenant app from the base runtime
45
+
46
+ Create a second app whose `bos.config.json` extends the base runtime:
47
+
48
+ ```json
49
+ {
50
+ "extends": "bos://linktree.near/linktree.com",
51
+ "account": "alice.near",
52
+ "domain": "linktree.com"
53
+ }
54
+ ```
55
+
56
+ Then customize the tenant-owned sections, usually:
57
+ - `account`
58
+ - `repository`
59
+ - `app.ui`
60
+ - existing `plugins.<id>.ui`
61
+ - existing `plugins.<id>.sidebar`
62
+
63
+ ### 3. Understand fixed-core tenant mode
64
+
65
+ Today the shared host stays fixed to the base runtime for:
66
+ - `app.host`
67
+ - `app.api`
68
+ - `app.auth`
69
+ - server-side plugin loading
70
+
71
+ Tenant apps are request-scoped overlays on top of that base host. In fixed-core mode, the supported tenant overrides are:
72
+ - `app.ui`
73
+ - existing `plugins.<id>.ui`
74
+ - existing `plugins.<id>.sidebar`
75
+
76
+ Tenant apps must extend the base runtime and do not introduce new server-side plugin IDs dynamically.
77
+
78
+ ### 4. Host deployment env for tenant mode
79
+
80
+ The shared host uses these env vars to resolve tenant requests:
81
+
82
+ ```bash
83
+ NETWORK_ID=mainnet
84
+ ALLOW_OVERRIDE=ui,plugins.*
85
+ TENANT_WHITELIST=alice.near,bob.near
86
+ ALLOW_UNTRUSTED_SSR=false
87
+ ```
88
+
89
+ Subdomains resolve by convention, for example:
90
+ - `linktree.com` -> base runtime
91
+ - `alice.linktree.com` -> `bos://alice.near/linktree.com`
92
+
93
+ Use the `extends-config` skill when reasoning about how the tenant config merges with the base runtime.
94
+
30
95
  ### Init File Selection
31
96
 
32
97
  `buildInitPatterns(overrides, plugins)` chooses what init copies from the template source: