@oondemand/oon-core-front 0.3.9 → 0.3.11

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.d.ts CHANGED
@@ -40,6 +40,7 @@ interface MenuMeta {
40
40
  section?: string;
41
41
  order?: number;
42
42
  permissions?: string[];
43
+ capabilities?: string[];
43
44
  children?: OonMenuItem[];
44
45
  badge?: string | number;
45
46
  external?: boolean;
@@ -99,8 +100,22 @@ interface OonUser {
99
100
  tipo: string;
100
101
  nome: string;
101
102
  email: string;
103
+ capabilities?: string[];
104
+ permissoes?: {
105
+ capabilities?: string[];
106
+ [extra: string]: unknown;
107
+ };
108
+ aplicativo?: {
109
+ capabilities?: string[];
110
+ permissoes?: {
111
+ capabilities?: string[];
112
+ [extra: string]: unknown;
113
+ };
114
+ [extra: string]: unknown;
115
+ };
102
116
  [extra: string]: unknown;
103
117
  }
118
+ type OonAppKind = "root-central" | "member-central" | "portal-cockpit";
104
119
  interface OonAppConfig {
105
120
  id: string;
106
121
  name: string;
@@ -108,6 +123,13 @@ interface OonAppConfig {
108
123
  logo?: string;
109
124
  favicon?: string;
110
125
  version?: string;
126
+ /**
127
+ * Perfil arquitetural da aplicação no Ecossistema Oon.
128
+ * - root-central: Central de Ativações.
129
+ * - member-central: Central publicada para tenant/licença/ambiente.
130
+ * - portal-cockpit: Portal/Cockpit como Meus Apps.
131
+ */
132
+ kind?: OonAppKind;
111
133
  }
112
134
  interface OonApiConfig {
113
135
  baseUrl: string;
@@ -117,8 +139,9 @@ interface OonApiConfig {
117
139
  versionPrefix?: string;
118
140
  timeout?: number;
119
141
  }
142
+ type OonAuthMode = "bearer" | "cookie" | "external-sso" | "central-instance" | "central-client";
120
143
  interface OonAuthConfig {
121
- mode?: "bearer" | "cookie" | "external-sso";
144
+ mode?: OonAuthMode;
122
145
  /** URL de login externo (Meus Apps) para redirect quando sem token. */
123
146
  loginUrl?: string;
124
147
  /** Nome do query param que carrega o token na volta do SSO. Default: "code". */
@@ -159,6 +182,7 @@ interface OonRoute {
159
182
  /** rota pública não passa pelo RouteGuard. */
160
183
  public?: boolean;
161
184
  permissions?: string[];
185
+ capabilities?: string[];
162
186
  }
163
187
  interface OonMenuItem {
164
188
  label: string;
@@ -167,6 +191,7 @@ interface OonMenuItem {
167
191
  section?: string;
168
192
  order?: number;
169
193
  permissions?: string[];
194
+ capabilities?: string[];
170
195
  children?: OonMenuItem[];
171
196
  badge?: string | number;
172
197
  external?: boolean;
@@ -219,6 +244,7 @@ interface OonColumnDef {
219
244
  width?: number;
220
245
  renderer?: string;
221
246
  permissions?: string[];
247
+ capabilities?: string[];
222
248
  }
223
249
  interface OonFormFieldDef {
224
250
  field: string;
@@ -230,6 +256,7 @@ interface OonFormFieldDef {
230
256
  ref?: string;
231
257
  renderer?: string;
232
258
  permissions?: string[];
259
+ capabilities?: string[];
233
260
  }
234
261
  /** Definição de uma coleção nas três formas da Seção 4.4. */
235
262
  interface OonActionDef {
@@ -237,6 +264,7 @@ interface OonActionDef {
237
264
  label?: string;
238
265
  renderer?: string;
239
266
  permissions?: string[];
267
+ capabilities?: string[];
240
268
  action?: string;
241
269
  }
242
270
  interface CollectionViewDef {
@@ -259,6 +287,7 @@ interface CollectionViewDef {
259
287
  cardRenderer?: string;
260
288
  actions?: OonActionDef[];
261
289
  permissions?: string[];
290
+ capabilities?: string[];
262
291
  }
263
292
  interface DocumentViewDef {
264
293
  type: "document";
@@ -271,6 +300,7 @@ interface DocumentViewDef {
271
300
  approval?: boolean;
272
301
  attachments?: boolean;
273
302
  permissions?: string[];
303
+ capabilities?: string[];
274
304
  }
275
305
  interface PipelineViewDef {
276
306
  type: "pipeline";
@@ -289,6 +319,7 @@ interface PipelineViewDef {
289
319
  label: string;
290
320
  }>;
291
321
  permissions?: string[];
322
+ capabilities?: string[];
292
323
  }
293
324
  interface DashboardViewDef {
294
325
  type: "dashboard";
@@ -298,6 +329,8 @@ interface DashboardViewDef {
298
329
  icon?: ReactNode;
299
330
  section?: string;
300
331
  widgets?: DashboardWidgetDef[];
332
+ permissions?: string[];
333
+ capabilities?: string[];
301
334
  }
302
335
  type OonPageLayout = "default" | "content-only" | "portal";
303
336
  interface OonPageDef {
@@ -311,6 +344,7 @@ interface OonPageDef {
311
344
  blocks?: OonBlockDef[];
312
345
  component?: string;
313
346
  permissions?: string[];
347
+ capabilities?: string[];
314
348
  hidden?: boolean;
315
349
  order?: number;
316
350
  }
@@ -319,6 +353,7 @@ interface OonBaseBlockDef {
319
353
  id?: string;
320
354
  title?: string;
321
355
  permissions?: string[];
356
+ capabilities?: string[];
322
357
  hidden?: boolean;
323
358
  }
324
359
  interface OonCustomBlockDef extends OonBaseBlockDef {
@@ -399,6 +434,10 @@ interface CentralUiManifest {
399
434
  metadataUrl?: string;
400
435
  };
401
436
  schemaVersion?: 1 | 2;
437
+ /** Perfil arquitetural: root-central, member-central ou portal-cockpit. */
438
+ appKind?: OonAppKind;
439
+ /** Configuração declarativa de autenticação. Segredos nunca entram aqui. */
440
+ auth?: Pick<OonAuthConfig, "mode" | "loginUrl" | "tokenParam">;
402
441
  layout?: OonLayoutConfig;
403
442
  registry?: OonComponentRegistry;
404
443
  navigation?: OonNavigationConfig;
@@ -429,7 +468,7 @@ interface CentralUiManifest {
429
468
  attachments?: boolean;
430
469
  }>;
431
470
  }
432
- /** Dados de runtime que o `central.ui.json` não carrega (vêm do .env). */
471
+ /** Dados de runtime que o `central.ui.json` não carrega (vêm do .env/código). */
433
472
  interface ManifestRuntime {
434
473
  apiBaseUrl: string;
435
474
  meusAppsUrl?: string;
@@ -437,6 +476,14 @@ interface ManifestRuntime {
437
476
  versionPrefix?: string;
438
477
  /** Token local enviado ao backend somente quando o consumidor o configurar. */
439
478
  devToken?: string;
479
+ /** Registry de componentes React, registrado em código TypeScript. */
480
+ registry?: OonComponentRegistry;
481
+ /** Atalho para registrar páginas/componentes custom sem reescrever o frontend. */
482
+ customComponents?: OonComponentRegistry["pageComponents"];
483
+ /** Overrides de autenticação vindos do runtime/env. */
484
+ auth?: Partial<OonAuthConfig>;
485
+ /** Override do perfil arquitetural da aplicação. */
486
+ appKind?: OonAppKind;
440
487
  }
441
488
  /** Converte o manifesto declarativo + runtime em config do Core. */
442
489
  declare function manifestToConfig(manifest: CentralUiManifest, runtime: ManifestRuntime): OonCoreFrontConfig;
@@ -607,6 +654,7 @@ type AuthStatus = "loading" | "authenticated" | "unauthenticated" | "forbidden"
607
654
  interface AuthContextValue {
608
655
  user: OonUser | null;
609
656
  roles: string[];
657
+ capabilities: string[];
610
658
  status: AuthStatus;
611
659
  isAuthenticated: boolean;
612
660
  hasPermission: (permission?: string | string[]) => boolean;
@@ -686,4 +734,4 @@ declare function useCoreMetadata(): _tanstack_react_query.UseQueryResult<NoInfer
686
734
  /** Descrição de uma model específica — base do modo "dynamic". */
687
735
  declare function useModelSchema(name: string): _tanstack_react_query.UseQueryResult<NoInfer<ModelSummary>, Error>;
688
736
 
689
- export { type CentralUiManifest, type CollectionMode, type CollectionViewDef, CoreActions, CoreAssistant, CoreCards, CoreCollection, CoreCurrency, CoreDashboard, CoreDataGrid, CoreDocument, CoreEmptyState, CoreField, CoreForm, CoreIntegration, CoreLoadingState, type CoreMetadata, CorePage, CorePageHeader, CorePipeline, CoreRelationField, CoreToolbar, type DashboardViewDef, type DashboardWidgetDef, type DocumentViewDef, type FieldKind, type FieldMeta, type ListParams, type ManifestRuntime, type ModelSummary, type OonActionDef, type OonApiConfig, type OonAppConfig, type OonAuthConfig, type OonBlockDef, type OonColumnDef, type OonComponentRegistry, type OonCoreFrontConfig, type OonError, type OonFormFieldDef, type OonLayoutConfig, type OonMenuItem, type OonModule, type OonNavigationConfig, type OonNavigationMode, type OonPageDef, type OonRoute, type OonSchemaVersion, type OonSecurityConfig, type OonUiManifest, type OonUser, type OonViewDefinition, type PaginatedResult, type Pagination, PermissionGate, type PipelineViewDef, defineCollectionView, defineDashboard, defineDocumentView, defineOonModule, definePipelineView, manifestToConfig, oonCoreFront, start, startFromManifest, useCoreMetadata, useModelSchema, useOonApi, useOonAuth, useOonResource };
737
+ export { type CentralUiManifest, type CollectionMode, type CollectionViewDef, CoreActions, CoreAssistant, CoreCards, CoreCollection, CoreCurrency, CoreDashboard, CoreDataGrid, CoreDocument, CoreEmptyState, CoreField, CoreForm, CoreIntegration, CoreLoadingState, type CoreMetadata, CorePage, CorePageHeader, CorePipeline, CoreRelationField, CoreToolbar, type DashboardViewDef, type DashboardWidgetDef, type DocumentViewDef, type FieldKind, type FieldMeta, type ListParams, type ManifestRuntime, type ModelSummary, type OonActionDef, type OonApiConfig, type OonAppConfig, type OonAppKind, type OonAuthConfig, type OonAuthMode, type OonBlockDef, type OonColumnDef, type OonComponentRegistry, type OonCoreFrontConfig, type OonError, type OonFormFieldDef, type OonLayoutConfig, type OonMenuItem, type OonModule, type OonNavigationConfig, type OonNavigationMode, type OonPageDef, type OonRoute, type OonSchemaVersion, type OonSecurityConfig, type OonUiManifest, type OonUser, type OonViewDefinition, type PaginatedResult, type Pagination, PermissionGate, type PipelineViewDef, defineCollectionView, defineDashboard, defineDocumentView, defineOonModule, definePipelineView, manifestToConfig, oonCoreFront, start, startFromManifest, useCoreMetadata, useModelSchema, useOonApi, useOonAuth, useOonResource };
package/dist/index.js CHANGED
@@ -15,6 +15,18 @@ function normalizeError(err) {
15
15
  if (axios.isAxiosError(err)) {
16
16
  const status = err.response?.status;
17
17
  const data = err.response?.data;
18
+ const contentType = String(err.response?.headers?.["content-type"] || "");
19
+ const isTextPayload = typeof data === "string";
20
+ const isProbablyHtml = isTextPayload && /<html|<!doctype/i.test(data);
21
+ if (isTextPayload || contentType && !contentType.includes("json")) {
22
+ return {
23
+ code: codeFromStatus(status),
24
+ message: nonJsonMessage(status, err.config?.url, isProbablyHtml),
25
+ status,
26
+ details: isProbablyHtml ? void 0 : data,
27
+ requestId: err.response?.headers?.["x-request-id"]
28
+ };
29
+ }
18
30
  const envelope = data?.error;
19
31
  return {
20
32
  code: envelope?.code || codeFromStatus(status),
@@ -29,6 +41,16 @@ function normalizeError(err) {
29
41
  }
30
42
  return { code: "UNKNOWN", message: "Erro inesperado." };
31
43
  }
44
+ function nonJsonMessage(status, url, html) {
45
+ const path = url ? ` (${url})` : "";
46
+ if (status === 404) {
47
+ return `Rota da API n\xE3o encontrada${path}. Verifique VITE_API_URL, porta do backend e modo da Central.`;
48
+ }
49
+ if (html) {
50
+ return `Backend retornou HTML em vez de JSON${path}. Verifique se o frontend est\xE1 apontando para a API correta.`;
51
+ }
52
+ return `Backend retornou resposta n\xE3o JSON${path}. Verifique a configura\xE7\xE3o da API.`;
53
+ }
32
54
  function codeFromStatus(status) {
33
55
  switch (status) {
34
56
  case 400:
@@ -433,6 +455,20 @@ function encodeBasicCredentials(email, password) {
433
455
  for (const byte of bytes) binary += String.fromCharCode(byte);
434
456
  return window.btoa(binary);
435
457
  }
458
+ function normalizeList(value) {
459
+ if (Array.isArray(value)) return value.map(String).filter(Boolean);
460
+ if (typeof value === "string") return value.split(/[\s,;]+/).map((item) => item.trim()).filter(Boolean);
461
+ return [];
462
+ }
463
+ function extractCapabilities(user) {
464
+ if (!user) return [];
465
+ return [
466
+ ...normalizeList(user.capabilities),
467
+ ...normalizeList(user.permissoes?.capabilities),
468
+ ...normalizeList(user.aplicativo?.capabilities),
469
+ ...normalizeList(user.aplicativo?.permissoes?.capabilities)
470
+ ].filter((item, index, all) => all.indexOf(item) === index);
471
+ }
436
472
  function AuthProvider({
437
473
  http,
438
474
  storage,
@@ -558,6 +594,7 @@ function AuthProvider({
558
594
  activation?.status
559
595
  ]);
560
596
  const roles = user?.tipo ? [user.tipo] : [];
597
+ const capabilities = extractCapabilities(user);
561
598
  const hasPermission = useCallback2(
562
599
  (permission) => {
563
600
  if (!permission) return true;
@@ -565,14 +602,15 @@ function AuthProvider({
565
602
  if (required.length === 0) return true;
566
603
  if (roles.includes("admin") || roles.includes("master")) return true;
567
604
  return required.some(
568
- (value2) => roles.includes(value2) || roles.some((role) => value2.startsWith(`${role}.`))
605
+ (value2) => roles.includes(value2) || capabilities.includes(value2) || roles.some((role) => value2.startsWith(`${role}.`))
569
606
  );
570
607
  },
571
- [roles]
608
+ [roles, capabilities]
572
609
  );
573
610
  const value = {
574
611
  user,
575
612
  roles,
613
+ capabilities,
576
614
  status,
577
615
  isAuthenticated: status === "authenticated",
578
616
  hasPermission,
@@ -1905,6 +1943,10 @@ import { jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
1905
1943
  function renderMarkdown(content) {
1906
1944
  return content.split(/\n{2,}/).map((paragraph, index) => /* @__PURE__ */ jsx17(Text11, { whiteSpace: "pre-wrap", children: paragraph }, index));
1907
1945
  }
1946
+ function requiredAccess(source) {
1947
+ const values = [...source.permissions ?? [], ...source.capabilities ?? []].filter(Boolean);
1948
+ return values.length ? values : void 0;
1949
+ }
1908
1950
  function CoreBlock({ block, registry }) {
1909
1951
  if (block.hidden) return null;
1910
1952
  const content = (() => {
@@ -1931,11 +1973,11 @@ function CoreBlock({ block, registry }) {
1931
1973
  return /* @__PURE__ */ jsx17(MissingRenderer, { renderer: block.type });
1932
1974
  }
1933
1975
  })();
1934
- return /* @__PURE__ */ jsx17(PermissionGate, { permission: block.permissions, children: content });
1976
+ return /* @__PURE__ */ jsx17(PermissionGate, { permission: requiredAccess(block), children: content });
1935
1977
  }
1936
1978
  function CorePage2({ page, registry }) {
1937
1979
  const Component = resolveComponent(registry, page.component, "pageComponents");
1938
- return /* @__PURE__ */ jsx17(PermissionGate, { permission: page.permissions, children: Component ? /* @__PURE__ */ jsx17(Component, { page }) : /* @__PURE__ */ jsxs11(CorePage, { children: [
1980
+ return /* @__PURE__ */ jsx17(PermissionGate, { permission: requiredAccess(page), children: Component ? /* @__PURE__ */ jsx17(Component, { page }) : /* @__PURE__ */ jsxs11(CorePage, { children: [
1939
1981
  /* @__PURE__ */ jsx17(CorePageHeader, { title: page.title ?? page.label }),
1940
1982
  /* @__PURE__ */ jsx17(Stack6, { gap: 6, children: page.blocks?.map((block, index) => /* @__PURE__ */ jsx17(CoreBlock, { block, registry }, block.id ?? index)) })
1941
1983
  ] }) });
@@ -1946,6 +1988,10 @@ import { jsx as jsx18 } from "react/jsx-runtime";
1946
1988
  function slug(value) {
1947
1989
  return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
1948
1990
  }
1991
+ function requiredAccess2(source) {
1992
+ const values = [...source.permissions ?? [], ...source.capabilities ?? []].filter(Boolean);
1993
+ return values.length ? values : void 0;
1994
+ }
1949
1995
  function viewId(view) {
1950
1996
  if (view.id) return view.id;
1951
1997
  if (view.type === "dashboard") return "dashboard";
@@ -2005,15 +2051,15 @@ function viewToRoute(view) {
2005
2051
  return {
2006
2052
  path: viewPath(view),
2007
2053
  element: renderView(view),
2008
- permissions: "permissions" in view ? view.permissions : void 0
2054
+ permissions: requiredAccess2(view)
2009
2055
  };
2010
2056
  }
2011
2057
  function pageToRoute(page, registry) {
2012
- return { path: page.path, element: /* @__PURE__ */ jsx18(CorePage2, { page, registry }), permissions: page.permissions };
2058
+ return { path: page.path, element: /* @__PURE__ */ jsx18(CorePage2, { page, registry }), permissions: requiredAccess2(page) };
2013
2059
  }
2014
2060
  function pageToMenu(page) {
2015
2061
  if (page.hidden || !page.label) return null;
2016
- return { label: page.label, href: page.path, icon: page.icon, section: page.section, permissions: page.permissions, order: page.order };
2062
+ return { label: page.label, href: page.path, icon: page.icon, section: page.section, permissions: requiredAccess2(page), capabilities: page.capabilities, order: page.order };
2017
2063
  }
2018
2064
  function viewToMenu(view) {
2019
2065
  return {
@@ -2021,7 +2067,8 @@ function viewToMenu(view) {
2021
2067
  href: viewPath(view),
2022
2068
  icon: view.icon,
2023
2069
  section: view.section,
2024
- permissions: "permissions" in view ? view.permissions : void 0
2070
+ permissions: requiredAccess2(view),
2071
+ capabilities: "capabilities" in view ? view.capabilities : void 0
2025
2072
  };
2026
2073
  }
2027
2074
  function resolveRegistry(config) {
@@ -2034,8 +2081,8 @@ function resolveRegistry(config) {
2034
2081
  routes.push(route);
2035
2082
  };
2036
2083
  for (const mod of config.modules ?? []) {
2037
- for (const route of mod.routes ?? []) pushRoute(route);
2038
- for (const item of mod.menu ?? []) menu.push(item);
2084
+ for (const route of mod.routes ?? []) pushRoute({ ...route, permissions: requiredAccess2(route) });
2085
+ for (const item of mod.menu ?? []) menu.push({ ...item, permissions: requiredAccess2(item) });
2039
2086
  for (const view of mod.views ?? []) {
2040
2087
  pushRoute(viewToRoute(view));
2041
2088
  menu.push(viewToMenu(view));
@@ -2056,7 +2103,7 @@ function resolveRegistry(config) {
2056
2103
  }
2057
2104
  }
2058
2105
  if (includeManual) {
2059
- for (const item of config.ui?.navigation?.items ?? []) menu.push(item);
2106
+ for (const item of config.ui?.navigation?.items ?? []) menu.push({ ...item, permissions: requiredAccess2(item) });
2060
2107
  }
2061
2108
  menu.sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
2062
2109
  return { routes, menu };
@@ -3076,6 +3123,18 @@ function start(config) {
3076
3123
  var oonCoreFront = { start };
3077
3124
 
3078
3125
  // src/manifest.ts
3126
+ function mergeRegistry(manifestRegistry, runtimeRegistry, customComponents) {
3127
+ if (!manifestRegistry && !runtimeRegistry && !customComponents) return void 0;
3128
+ return {
3129
+ layoutSlots: { ...manifestRegistry?.layoutSlots, ...runtimeRegistry?.layoutSlots },
3130
+ fieldRenderers: { ...manifestRegistry?.fieldRenderers, ...runtimeRegistry?.fieldRenderers },
3131
+ cellRenderers: { ...manifestRegistry?.cellRenderers, ...runtimeRegistry?.cellRenderers },
3132
+ cardRenderers: { ...manifestRegistry?.cardRenderers, ...runtimeRegistry?.cardRenderers },
3133
+ actionRenderers: { ...manifestRegistry?.actionRenderers, ...runtimeRegistry?.actionRenderers },
3134
+ pageComponents: { ...manifestRegistry?.pageComponents, ...runtimeRegistry?.pageComponents, ...customComponents },
3135
+ menuRenderers: { ...manifestRegistry?.menuRenderers, ...runtimeRegistry?.menuRenderers }
3136
+ };
3137
+ }
3079
3138
  function manifestToConfig(manifest, runtime) {
3080
3139
  const views = [];
3081
3140
  for (const c of manifest.collections ?? []) {
@@ -3114,11 +3173,19 @@ function manifestToConfig(manifest, runtime) {
3114
3173
  };
3115
3174
  views.push(view);
3116
3175
  }
3176
+ const registry = mergeRegistry(manifest.registry, runtime.registry, runtime.customComponents);
3177
+ const auth = {
3178
+ mode: runtime.auth?.mode ?? manifest.auth?.mode ?? "bearer",
3179
+ loginUrl: runtime.auth?.loginUrl ?? manifest.auth?.loginUrl,
3180
+ tokenParam: runtime.auth?.tokenParam ?? manifest.auth?.tokenParam ?? "code",
3181
+ devToken: runtime.auth?.devToken ?? runtime.devToken
3182
+ };
3117
3183
  return {
3118
3184
  app: {
3119
3185
  id: manifest.slug,
3120
3186
  name: manifest.name,
3121
- title: manifest.name
3187
+ title: manifest.name,
3188
+ kind: runtime.appKind ?? manifest.appKind
3122
3189
  },
3123
3190
  api: {
3124
3191
  baseUrl: runtime.apiBaseUrl,
@@ -3126,7 +3193,7 @@ function manifestToConfig(manifest, runtime) {
3126
3193
  assistantBaseUrl: runtime.assistantBaseUrl,
3127
3194
  versionPrefix: runtime.versionPrefix ?? ""
3128
3195
  },
3129
- auth: { mode: "bearer", tokenParam: "code", devToken: runtime.devToken },
3196
+ auth,
3130
3197
  security: {
3131
3198
  enableRouteGuard: true,
3132
3199
  enablePermissionGate: true,
@@ -3134,8 +3201,15 @@ function manifestToConfig(manifest, runtime) {
3134
3201
  disableConsoleInProduction: true
3135
3202
  },
3136
3203
  layout: manifest.layout,
3137
- registry: manifest.registry,
3138
- ui: { schemaVersion: manifest.schemaVersion, layout: manifest.layout, registry: manifest.registry, navigation: manifest.navigation, views, pages: manifest.pages }
3204
+ registry,
3205
+ ui: {
3206
+ schemaVersion: manifest.schemaVersion,
3207
+ layout: manifest.layout,
3208
+ registry,
3209
+ navigation: manifest.navigation,
3210
+ views,
3211
+ pages: manifest.pages
3212
+ }
3139
3213
  };
3140
3214
  }
3141
3215
  function startFromManifest(manifest, runtime) {