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/config.cjs +1 -0
- package/dist/config.cjs.map +1 -1
- package/dist/config.d.cts.map +1 -1
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +1 -0
- package/dist/config.mjs.map +1 -1
- package/dist/contract.cjs +8 -0
- package/dist/contract.cjs.map +1 -1
- package/dist/contract.d.cts +27 -16
- package/dist/contract.d.cts.map +1 -1
- package/dist/contract.d.mts +27 -16
- package/dist/contract.d.mts.map +1 -1
- package/dist/contract.mjs +7 -1
- package/dist/contract.mjs.map +1 -1
- package/dist/index.cjs +2 -0
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/integrity.cjs +36 -6
- package/dist/integrity.cjs.map +1 -1
- package/dist/integrity.d.cts +6 -2
- package/dist/integrity.d.cts.map +1 -1
- package/dist/integrity.d.mts +6 -2
- package/dist/integrity.d.mts.map +1 -1
- package/dist/integrity.mjs +36 -6
- package/dist/integrity.mjs.map +1 -1
- package/dist/plugin.d.cts +8 -8
- package/dist/plugin.d.mts +8 -8
- package/dist/types.d.cts +2 -2
- package/dist/types.d.mts +2 -2
- package/package.json +1 -1
- package/skills/extends-config/SKILL.md +43 -0
- package/skills/init-upgrade/SKILL.md +65 -0
- package/skills/super-app/SKILL.md +147 -0
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 };
|
package/dist/integrity.cjs
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 {
|
package/dist/integrity.cjs.map
CHANGED
|
@@ -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 =
|
|
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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"}
|
package/dist/integrity.d.cts
CHANGED
|
@@ -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;
|
package/dist/integrity.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integrity.d.cts","names":[],"sources":["../src/integrity.ts"],"mappings":";
|
|
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"}
|
package/dist/integrity.d.mts
CHANGED
|
@@ -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;
|
package/dist/integrity.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integrity.d.mts","names":[],"sources":["../src/integrity.ts"],"mappings":";
|
|
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"}
|
package/dist/integrity.mjs
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 {
|
package/dist/integrity.mjs.map
CHANGED
|
@@ -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 =
|
|
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: "
|
|
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: "
|
|
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
|
@@ -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:
|