@veltrixsecops/app-sdk 1.0.1 → 1.2.0

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/README.md CHANGED
@@ -73,6 +73,50 @@ export default async function onInstall({ db, appId }: AppHookContext): Promise<
73
73
 
74
74
  ```tsx
75
75
  import { useAppContext, usePipelineStatus } from '@veltrixsecops/app-sdk/hooks'
76
+ import { authFetch } from '@veltrixsecops/app-sdk/client'
77
+ ```
78
+
79
+ Your `client/index.tsx` default-exports an `AppClientModule` (`{ id, pages, sidebarItems }`;
80
+ `pages` keys must match `manifest.client.pages[].component`). At packaging time the CLI
81
+ compiles it into a hermetic browser bundle (`client/dist/index.mjs`): `react`, `react-dom`,
82
+ `react/jsx-runtime`, and all `@veltrixsecops/app-sdk` imports are replaced with shims that
83
+ read the platform-provided runtime from `globalThis.__VELTRIX_APP_RUNTIME__`, so your
84
+ components render inside the host React tree with working hooks and shared context — never
85
+ bundle your own copy of React.
86
+
87
+ Two rules for app pages:
88
+
89
+ - Use **`authFetch`** (not plain `fetch`) for calls to your app's server routes
90
+ (`/api/apps/<app-id>/...`) — they are bearer-token protected and a plain `fetch`
91
+ receives 401s.
92
+ - Only import third-party client libraries if you accept them being compiled into your
93
+ bundle; keep pages lean.
94
+
95
+ ## Standard app layout
96
+
97
+ Every Veltrix app follows one canonical folder structure — the CLI scaffolds it (`veltrix init`), `veltrix validate` warns on deviations, and the SDK exports it as constants (`APP_LAYOUT`, `HANDLER_NAMES`, `conventionalPaths(configTypeId)`):
98
+
99
+ ```
100
+ apps/<app-id>/
101
+ ├── manifest.yaml # App contract
102
+ ├── package.json / tsconfig.json / README.md
103
+ ├── config-types/<configTypeId>/ # Everything for one configuration type:
104
+ │ ├── canvas.yaml # form schema
105
+ │ ├── defaults.yaml # default values
106
+ │ ├── validate.ts, deploy.ts, rollback.ts,
107
+ │ ├── healthCheck.ts, driftDetect.ts, getStatus.ts
108
+ │ └── __tests__/
109
+ ├── lib/ # Shared app code (API clients)
110
+ ├── hooks/ # onInstall.ts, onUninstall.ts, ...
111
+ ├── migrations/ # SQL (with database.tablePrefix)
112
+ ├── server/index.ts # Route module (AppRouteContext)
113
+ ├── client/index.tsx + client/pages/ # Optional UI
114
+ └── assets/ # Optional icons
115
+ ```
116
+
117
+ ```ts
118
+ import { conventionalPaths } from '@veltrixsecops/app-sdk'
119
+ conventionalPaths('indexes').handlers.deploy // 'config-types/indexes/deploy'
76
120
  ```
77
121
 
78
122
  ## Package layout
@@ -0,0 +1,28 @@
1
+ // src/client/index.ts
2
+ var HOST_RUNTIME_GLOBAL = "__VELTRIX_APP_RUNTIME__";
3
+ function getHostRuntime() {
4
+ const runtime = globalThis[HOST_RUNTIME_GLOBAL];
5
+ return runtime ?? null;
6
+ }
7
+ function requireHostRuntime() {
8
+ const runtime = getHostRuntime();
9
+ if (!runtime) {
10
+ throw new Error(
11
+ `Veltrix host runtime not found \u2014 app client bundles only run inside the Veltrix platform (missing globalThis.${HOST_RUNTIME_GLOBAL})`
12
+ );
13
+ }
14
+ return runtime;
15
+ }
16
+ function authFetch(input, init) {
17
+ const runtime = getHostRuntime();
18
+ if (runtime) return runtime.authFetch(input, init);
19
+ return fetch(input, init);
20
+ }
21
+
22
+ export {
23
+ HOST_RUNTIME_GLOBAL,
24
+ getHostRuntime,
25
+ requireHostRuntime,
26
+ authFetch
27
+ };
28
+ //# sourceMappingURL=chunk-I4JJX4LL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client/index.ts"],"sourcesContent":["// ========================================================================\n// Client runtime contract — how app client bundles talk to the host.\n//\n// App client code is packaged as a hermetic ESM bundle in which `react`,\n// `react-dom`, `react/jsx-runtime`, and every `@veltrixsecops/app-sdk`\n// subpath are compile-time shims that read the host-provided runtime from\n// `globalThis.__VELTRIX_APP_RUNTIME__`. The platform installs that global\n// (with ITS React instance, the shared AppContext, and an authenticated\n// fetch) before dynamically importing any app bundle, so app components\n// render inside the host React tree with working hooks and context.\n//\n// App authors: import from '@veltrixsecops/app-sdk/client' (and /hooks) —\n// never bundle your own copy of react. Use `authFetch` for calls to your\n// app's server routes (/api/apps/<app-id>/...): plain fetch() lacks the\n// platform's Authorization header and will receive 401s.\n// ========================================================================\n\nimport type { ComponentType, Context, LazyExoticComponent } from 'react'\nimport type { AppContextValue } from '../hooks/use-app-context'\n\n/** Name of the global the platform installs before loading app bundles. */\nexport const HOST_RUNTIME_GLOBAL = '__VELTRIX_APP_RUNTIME__'\n\n/**\n * The runtime surface the platform exposes to app client bundles.\n * The react/reactDom/jsxRuntime members are the host's own module objects —\n * app bundles are compiled with shims that re-export them, guaranteeing a\n * single React instance per page.\n */\nexport interface VeltrixHostRuntime {\n /** The host's `react` module object. */\n react: unknown\n /** The host's `react-dom` module object. */\n reactDom: unknown\n /** The host's `react-dom/client` module object. */\n reactDomClient?: unknown\n /** The host's `react/jsx-runtime` module object. */\n jsxRuntime: unknown\n /** Shared app context — the host wraps app pages in its Provider. */\n AppContext: Context<AppContextValue | null>\n /** fetch() with the platform's Authorization header attached. */\n authFetch: (input: string, init?: RequestInit) => Promise<Response>\n /**\n * The SDK surface app bundles receive for `@veltrixsecops/app-sdk`,\n * `.../hooks`, and `.../client` imports (useAppContext, AppContext,\n * usePipelineStatus, authFetch, getHostRuntime, ...).\n */\n sdk: Record<string, unknown>\n}\n\n/** Read the host runtime, or null outside the platform (tests, storybook). */\nexport function getHostRuntime(): VeltrixHostRuntime | null {\n const runtime = (globalThis as Record<string, unknown>)[HOST_RUNTIME_GLOBAL]\n return (runtime as VeltrixHostRuntime) ?? null\n}\n\n/** Read the host runtime, throwing a diagnosable error when absent. */\nexport function requireHostRuntime(): VeltrixHostRuntime {\n const runtime = getHostRuntime()\n if (!runtime) {\n throw new Error(\n 'Veltrix host runtime not found — app client bundles only run inside the ' +\n `Veltrix platform (missing globalThis.${HOST_RUNTIME_GLOBAL})`,\n )\n }\n return runtime\n}\n\n/**\n * fetch() that carries the platform's Authorization header. Required for an\n * app page to call its own server routes (/api/apps/<app-id>/...), which are\n * bearer-token protected. Falls back to plain fetch outside the platform.\n */\nexport function authFetch(input: string, init?: RequestInit): Promise<Response> {\n const runtime = getHostRuntime()\n if (runtime) return runtime.authFetch(input, init)\n return fetch(input, init)\n}\n\n/** A sidebar entry contributed by the app's client entry module. */\nexport interface AppSidebarItem {\n path: string\n label: string\n icon?: string\n}\n\n/**\n * Shape of the default export of an app's `client/index.tsx`.\n * `pages` keys must match `manifest.client.pages[].component`.\n */\nexport interface AppClientModule {\n id: string\n pages: Record<string, ComponentType | LazyExoticComponent<ComponentType>>\n sidebarItems?: AppSidebarItem[]\n}\n"],"mappings":";AAqBO,IAAM,sBAAsB;AA8B5B,SAAS,iBAA4C;AAC1D,QAAM,UAAW,WAAuC,mBAAmB;AAC3E,SAAQ,WAAkC;AAC5C;AAGO,SAAS,qBAAyC;AACvD,QAAM,UAAU,eAAe;AAC/B,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR,qHAC0C,mBAAmB;AAAA,IAC/D;AAAA,EACF;AACA,SAAO;AACT;AAOO,SAAS,UAAU,OAAe,MAAuC;AAC9E,QAAM,UAAU,eAAe;AAC/B,MAAI,QAAS,QAAO,QAAQ,UAAU,OAAO,IAAI;AACjD,SAAO,MAAM,OAAO,IAAI;AAC1B;","names":[]}
@@ -1,6 +1,11 @@
1
+ import {
2
+ authFetch
3
+ } from "./chunk-I4JJX4LL.js";
4
+
1
5
  // src/hooks/use-app-context.ts
2
6
  import { createContext, useContext } from "react";
3
- var AppContext = createContext(null);
7
+ var hostContext = globalThis.__VELTRIX_APP_RUNTIME__?.AppContext;
8
+ var AppContext = hostContext ?? createContext(null);
4
9
  function useAppContext() {
5
10
  const ctx = useContext(AppContext);
6
11
  if (!ctx) {
@@ -18,7 +23,7 @@ function usePipelineStatus(appId) {
18
23
  const refresh = useCallback(async () => {
19
24
  try {
20
25
  setIsLoading(true);
21
- const response = await fetch(`/api/pipeline/summary?appId=${appId}`);
26
+ const response = await authFetch(`/api/pipeline/summary?appId=${appId}`);
22
27
  if (!response.ok) throw new Error(`Failed to fetch pipeline status`);
23
28
  const result = await response.json();
24
29
  setData(result);
@@ -40,4 +45,4 @@ export {
40
45
  useAppContext,
41
46
  usePipelineStatus
42
47
  };
43
- //# sourceMappingURL=chunk-TSEIWO6T.js.map
48
+ //# sourceMappingURL=chunk-JDEA2F7I.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/use-app-context.ts","../src/hooks/use-pipeline-status.ts"],"sourcesContent":["// ========================================================================\n// React hooks for app developers\n// These provide access to platform data and pipeline state\n// ========================================================================\n\nimport { createContext, useContext, type Context } from 'react'\nimport type { Component, Credential, Tag, User, Customer } from '../types/platform'\n\nexport interface AppContextValue {\n appId: string\n customerId: string\n user: User | null\n customer: Customer | null\n\n // Platform data accessors\n getComponents: () => Promise<Component[]>\n getCredentials: () => Promise<Credential[]>\n getTags: () => Promise<Tag[]>\n\n // App settings\n settings: Record<string, unknown>\n}\n\n// Anchor the context on the host runtime when running inside the platform,\n// so a bundle that inlined its own SDK copy still shares the host's context\n// (two createContext objects never match, even with identical shapes).\nconst hostContext = (\n (globalThis as Record<string, unknown>).__VELTRIX_APP_RUNTIME__ as\n | { AppContext?: Context<AppContextValue | null> }\n | undefined\n)?.AppContext\n\nexport const AppContext: Context<AppContextValue | null> =\n hostContext ?? createContext<AppContextValue | null>(null)\n\n/**\n * Access the app context in any app component.\n *\n * @example\n * ```tsx\n * import { useAppContext } from '@veltrixsecops/app-sdk/hooks'\n *\n * function MyComponent() {\n * const { appId, settings, getComponents } = useAppContext()\n * // ...\n * }\n * ```\n */\nexport function useAppContext(): AppContextValue {\n const ctx = useContext(AppContext)\n if (!ctx) {\n throw new Error('useAppContext must be used within an AppContextProvider')\n }\n return ctx\n}\n","import { useState, useEffect, useCallback } from 'react'\nimport { authFetch } from '../client'\n\nexport interface PipelineStatusData {\n pendingApprovals: number\n activeDeployments: number\n failedDeployments: number\n unresolvedDrifts: number\n recentDeployments: Array<{\n id: string\n canvasName: string\n environment: string\n status: string\n startedAt: string\n completedAt?: string\n }>\n}\n\n/**\n * Hook to access pipeline status for the current app/customer.\n *\n * @example\n * ```tsx\n * import { usePipelineStatus } from '@veltrixsecops/app-sdk/hooks'\n *\n * function Dashboard() {\n * const { data, isLoading } = usePipelineStatus('my-app')\n * if (isLoading) return <Spinner />\n * return <div>{data.activeDeployments} active deployments</div>\n * }\n * ```\n */\nexport function usePipelineStatus(appId: string) {\n const [data, setData] = useState<PipelineStatusData | null>(null)\n const [isLoading, setIsLoading] = useState(true)\n const [error, setError] = useState<Error | null>(null)\n\n const refresh = useCallback(async () => {\n try {\n setIsLoading(true)\n // Platform APIs are bearer-token protected — plain fetch would 401\n const response = await authFetch(`/api/pipeline/summary?appId=${appId}`)\n if (!response.ok) throw new Error(`Failed to fetch pipeline status`)\n const result = await response.json()\n setData(result)\n setError(null)\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Unknown error'))\n } finally {\n setIsLoading(false)\n }\n }, [appId])\n\n useEffect(() => {\n refresh()\n }, [refresh])\n\n return { data, isLoading, error, refresh }\n}\n"],"mappings":";;;;;AAKA,SAAS,eAAe,kBAAgC;AAqBxD,IAAM,cACH,WAAuC,yBAGvC;AAEI,IAAM,aACX,eAAe,cAAsC,IAAI;AAepD,SAAS,gBAAiC;AAC/C,QAAM,MAAM,WAAW,UAAU;AACjC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,yDAAyD;AAAA,EAC3E;AACA,SAAO;AACT;;;ACtDA,SAAS,UAAU,WAAW,mBAAmB;AAgC1C,SAAS,kBAAkB,OAAe;AAC/C,QAAM,CAAC,MAAM,OAAO,IAAI,SAAoC,IAAI;AAChE,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,IAAI;AAC/C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,IAAI;AAErD,QAAM,UAAU,YAAY,YAAY;AACtC,QAAI;AACF,mBAAa,IAAI;AAEjB,YAAM,WAAW,MAAM,UAAU,+BAA+B,KAAK,EAAE;AACvE,UAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,iCAAiC;AACnE,YAAM,SAAS,MAAM,SAAS,KAAK;AACnC,cAAQ,MAAM;AACd,eAAS,IAAI;AAAA,IACf,SAAS,KAAK;AACZ,eAAS,eAAe,QAAQ,MAAM,IAAI,MAAM,eAAe,CAAC;AAAA,IAClE,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,KAAK,CAAC;AAEV,YAAU,MAAM;AACd,YAAQ;AAAA,EACV,GAAG,CAAC,OAAO,CAAC;AAEZ,SAAO,EAAE,MAAM,WAAW,OAAO,QAAQ;AAC3C;","names":[]}
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/client/index.ts
21
+ var client_exports = {};
22
+ __export(client_exports, {
23
+ HOST_RUNTIME_GLOBAL: () => HOST_RUNTIME_GLOBAL,
24
+ authFetch: () => authFetch,
25
+ getHostRuntime: () => getHostRuntime,
26
+ requireHostRuntime: () => requireHostRuntime
27
+ });
28
+ module.exports = __toCommonJS(client_exports);
29
+ var HOST_RUNTIME_GLOBAL = "__VELTRIX_APP_RUNTIME__";
30
+ function getHostRuntime() {
31
+ const runtime = globalThis[HOST_RUNTIME_GLOBAL];
32
+ return runtime ?? null;
33
+ }
34
+ function requireHostRuntime() {
35
+ const runtime = getHostRuntime();
36
+ if (!runtime) {
37
+ throw new Error(
38
+ `Veltrix host runtime not found \u2014 app client bundles only run inside the Veltrix platform (missing globalThis.${HOST_RUNTIME_GLOBAL})`
39
+ );
40
+ }
41
+ return runtime;
42
+ }
43
+ function authFetch(input, init) {
44
+ const runtime = getHostRuntime();
45
+ if (runtime) return runtime.authFetch(input, init);
46
+ return fetch(input, init);
47
+ }
48
+ // Annotate the CommonJS export names for ESM import in node:
49
+ 0 && (module.exports = {
50
+ HOST_RUNTIME_GLOBAL,
51
+ authFetch,
52
+ getHostRuntime,
53
+ requireHostRuntime
54
+ });
55
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/client/index.ts"],"sourcesContent":["// ========================================================================\n// Client runtime contract — how app client bundles talk to the host.\n//\n// App client code is packaged as a hermetic ESM bundle in which `react`,\n// `react-dom`, `react/jsx-runtime`, and every `@veltrixsecops/app-sdk`\n// subpath are compile-time shims that read the host-provided runtime from\n// `globalThis.__VELTRIX_APP_RUNTIME__`. The platform installs that global\n// (with ITS React instance, the shared AppContext, and an authenticated\n// fetch) before dynamically importing any app bundle, so app components\n// render inside the host React tree with working hooks and context.\n//\n// App authors: import from '@veltrixsecops/app-sdk/client' (and /hooks) —\n// never bundle your own copy of react. Use `authFetch` for calls to your\n// app's server routes (/api/apps/<app-id>/...): plain fetch() lacks the\n// platform's Authorization header and will receive 401s.\n// ========================================================================\n\nimport type { ComponentType, Context, LazyExoticComponent } from 'react'\nimport type { AppContextValue } from '../hooks/use-app-context'\n\n/** Name of the global the platform installs before loading app bundles. */\nexport const HOST_RUNTIME_GLOBAL = '__VELTRIX_APP_RUNTIME__'\n\n/**\n * The runtime surface the platform exposes to app client bundles.\n * The react/reactDom/jsxRuntime members are the host's own module objects —\n * app bundles are compiled with shims that re-export them, guaranteeing a\n * single React instance per page.\n */\nexport interface VeltrixHostRuntime {\n /** The host's `react` module object. */\n react: unknown\n /** The host's `react-dom` module object. */\n reactDom: unknown\n /** The host's `react-dom/client` module object. */\n reactDomClient?: unknown\n /** The host's `react/jsx-runtime` module object. */\n jsxRuntime: unknown\n /** Shared app context — the host wraps app pages in its Provider. */\n AppContext: Context<AppContextValue | null>\n /** fetch() with the platform's Authorization header attached. */\n authFetch: (input: string, init?: RequestInit) => Promise<Response>\n /**\n * The SDK surface app bundles receive for `@veltrixsecops/app-sdk`,\n * `.../hooks`, and `.../client` imports (useAppContext, AppContext,\n * usePipelineStatus, authFetch, getHostRuntime, ...).\n */\n sdk: Record<string, unknown>\n}\n\n/** Read the host runtime, or null outside the platform (tests, storybook). */\nexport function getHostRuntime(): VeltrixHostRuntime | null {\n const runtime = (globalThis as Record<string, unknown>)[HOST_RUNTIME_GLOBAL]\n return (runtime as VeltrixHostRuntime) ?? null\n}\n\n/** Read the host runtime, throwing a diagnosable error when absent. */\nexport function requireHostRuntime(): VeltrixHostRuntime {\n const runtime = getHostRuntime()\n if (!runtime) {\n throw new Error(\n 'Veltrix host runtime not found — app client bundles only run inside the ' +\n `Veltrix platform (missing globalThis.${HOST_RUNTIME_GLOBAL})`,\n )\n }\n return runtime\n}\n\n/**\n * fetch() that carries the platform's Authorization header. Required for an\n * app page to call its own server routes (/api/apps/<app-id>/...), which are\n * bearer-token protected. Falls back to plain fetch outside the platform.\n */\nexport function authFetch(input: string, init?: RequestInit): Promise<Response> {\n const runtime = getHostRuntime()\n if (runtime) return runtime.authFetch(input, init)\n return fetch(input, init)\n}\n\n/** A sidebar entry contributed by the app's client entry module. */\nexport interface AppSidebarItem {\n path: string\n label: string\n icon?: string\n}\n\n/**\n * Shape of the default export of an app's `client/index.tsx`.\n * `pages` keys must match `manifest.client.pages[].component`.\n */\nexport interface AppClientModule {\n id: string\n pages: Record<string, ComponentType | LazyExoticComponent<ComponentType>>\n sidebarItems?: AppSidebarItem[]\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqBO,IAAM,sBAAsB;AA8B5B,SAAS,iBAA4C;AAC1D,QAAM,UAAW,WAAuC,mBAAmB;AAC3E,SAAQ,WAAkC;AAC5C;AAGO,SAAS,qBAAyC;AACvD,QAAM,UAAU,eAAe;AAC/B,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR,qHAC0C,mBAAmB;AAAA,IAC/D;AAAA,EACF;AACA,SAAO;AACT;AAOO,SAAS,UAAU,OAAe,MAAuC;AAC9E,QAAM,UAAU,eAAe;AAC/B,MAAI,QAAS,QAAO,QAAQ,UAAU,OAAO,IAAI;AACjD,SAAO,MAAM,OAAO,IAAI;AAC1B;","names":[]}
@@ -0,0 +1,58 @@
1
+ import { ComponentType, LazyExoticComponent, Context } from 'react';
2
+ import { A as AppContextValue } from '../use-app-context-PBuG144m.cjs';
3
+
4
+ /** Name of the global the platform installs before loading app bundles. */
5
+ declare const HOST_RUNTIME_GLOBAL = "__VELTRIX_APP_RUNTIME__";
6
+ /**
7
+ * The runtime surface the platform exposes to app client bundles.
8
+ * The react/reactDom/jsxRuntime members are the host's own module objects —
9
+ * app bundles are compiled with shims that re-export them, guaranteeing a
10
+ * single React instance per page.
11
+ */
12
+ interface VeltrixHostRuntime {
13
+ /** The host's `react` module object. */
14
+ react: unknown;
15
+ /** The host's `react-dom` module object. */
16
+ reactDom: unknown;
17
+ /** The host's `react-dom/client` module object. */
18
+ reactDomClient?: unknown;
19
+ /** The host's `react/jsx-runtime` module object. */
20
+ jsxRuntime: unknown;
21
+ /** Shared app context — the host wraps app pages in its Provider. */
22
+ AppContext: Context<AppContextValue | null>;
23
+ /** fetch() with the platform's Authorization header attached. */
24
+ authFetch: (input: string, init?: RequestInit) => Promise<Response>;
25
+ /**
26
+ * The SDK surface app bundles receive for `@veltrixsecops/app-sdk`,
27
+ * `.../hooks`, and `.../client` imports (useAppContext, AppContext,
28
+ * usePipelineStatus, authFetch, getHostRuntime, ...).
29
+ */
30
+ sdk: Record<string, unknown>;
31
+ }
32
+ /** Read the host runtime, or null outside the platform (tests, storybook). */
33
+ declare function getHostRuntime(): VeltrixHostRuntime | null;
34
+ /** Read the host runtime, throwing a diagnosable error when absent. */
35
+ declare function requireHostRuntime(): VeltrixHostRuntime;
36
+ /**
37
+ * fetch() that carries the platform's Authorization header. Required for an
38
+ * app page to call its own server routes (/api/apps/<app-id>/...), which are
39
+ * bearer-token protected. Falls back to plain fetch outside the platform.
40
+ */
41
+ declare function authFetch(input: string, init?: RequestInit): Promise<Response>;
42
+ /** A sidebar entry contributed by the app's client entry module. */
43
+ interface AppSidebarItem {
44
+ path: string;
45
+ label: string;
46
+ icon?: string;
47
+ }
48
+ /**
49
+ * Shape of the default export of an app's `client/index.tsx`.
50
+ * `pages` keys must match `manifest.client.pages[].component`.
51
+ */
52
+ interface AppClientModule {
53
+ id: string;
54
+ pages: Record<string, ComponentType | LazyExoticComponent<ComponentType>>;
55
+ sidebarItems?: AppSidebarItem[];
56
+ }
57
+
58
+ export { type AppClientModule, type AppSidebarItem, HOST_RUNTIME_GLOBAL, type VeltrixHostRuntime, authFetch, getHostRuntime, requireHostRuntime };
@@ -0,0 +1,58 @@
1
+ import { ComponentType, LazyExoticComponent, Context } from 'react';
2
+ import { A as AppContextValue } from '../use-app-context-PBuG144m.js';
3
+
4
+ /** Name of the global the platform installs before loading app bundles. */
5
+ declare const HOST_RUNTIME_GLOBAL = "__VELTRIX_APP_RUNTIME__";
6
+ /**
7
+ * The runtime surface the platform exposes to app client bundles.
8
+ * The react/reactDom/jsxRuntime members are the host's own module objects —
9
+ * app bundles are compiled with shims that re-export them, guaranteeing a
10
+ * single React instance per page.
11
+ */
12
+ interface VeltrixHostRuntime {
13
+ /** The host's `react` module object. */
14
+ react: unknown;
15
+ /** The host's `react-dom` module object. */
16
+ reactDom: unknown;
17
+ /** The host's `react-dom/client` module object. */
18
+ reactDomClient?: unknown;
19
+ /** The host's `react/jsx-runtime` module object. */
20
+ jsxRuntime: unknown;
21
+ /** Shared app context — the host wraps app pages in its Provider. */
22
+ AppContext: Context<AppContextValue | null>;
23
+ /** fetch() with the platform's Authorization header attached. */
24
+ authFetch: (input: string, init?: RequestInit) => Promise<Response>;
25
+ /**
26
+ * The SDK surface app bundles receive for `@veltrixsecops/app-sdk`,
27
+ * `.../hooks`, and `.../client` imports (useAppContext, AppContext,
28
+ * usePipelineStatus, authFetch, getHostRuntime, ...).
29
+ */
30
+ sdk: Record<string, unknown>;
31
+ }
32
+ /** Read the host runtime, or null outside the platform (tests, storybook). */
33
+ declare function getHostRuntime(): VeltrixHostRuntime | null;
34
+ /** Read the host runtime, throwing a diagnosable error when absent. */
35
+ declare function requireHostRuntime(): VeltrixHostRuntime;
36
+ /**
37
+ * fetch() that carries the platform's Authorization header. Required for an
38
+ * app page to call its own server routes (/api/apps/<app-id>/...), which are
39
+ * bearer-token protected. Falls back to plain fetch outside the platform.
40
+ */
41
+ declare function authFetch(input: string, init?: RequestInit): Promise<Response>;
42
+ /** A sidebar entry contributed by the app's client entry module. */
43
+ interface AppSidebarItem {
44
+ path: string;
45
+ label: string;
46
+ icon?: string;
47
+ }
48
+ /**
49
+ * Shape of the default export of an app's `client/index.tsx`.
50
+ * `pages` keys must match `manifest.client.pages[].component`.
51
+ */
52
+ interface AppClientModule {
53
+ id: string;
54
+ pages: Record<string, ComponentType | LazyExoticComponent<ComponentType>>;
55
+ sidebarItems?: AppSidebarItem[];
56
+ }
57
+
58
+ export { type AppClientModule, type AppSidebarItem, HOST_RUNTIME_GLOBAL, type VeltrixHostRuntime, authFetch, getHostRuntime, requireHostRuntime };
@@ -0,0 +1,13 @@
1
+ import {
2
+ HOST_RUNTIME_GLOBAL,
3
+ authFetch,
4
+ getHostRuntime,
5
+ requireHostRuntime
6
+ } from "../chunk-I4JJX4LL.js";
7
+ export {
8
+ HOST_RUNTIME_GLOBAL,
9
+ authFetch,
10
+ getHostRuntime,
11
+ requireHostRuntime
12
+ };
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -28,7 +28,8 @@ module.exports = __toCommonJS(hooks_exports);
28
28
 
29
29
  // src/hooks/use-app-context.ts
30
30
  var import_react = require("react");
31
- var AppContext = (0, import_react.createContext)(null);
31
+ var hostContext = globalThis.__VELTRIX_APP_RUNTIME__?.AppContext;
32
+ var AppContext = hostContext ?? (0, import_react.createContext)(null);
32
33
  function useAppContext() {
33
34
  const ctx = (0, import_react.useContext)(AppContext);
34
35
  if (!ctx) {
@@ -39,6 +40,20 @@ function useAppContext() {
39
40
 
40
41
  // src/hooks/use-pipeline-status.ts
41
42
  var import_react2 = require("react");
43
+
44
+ // src/client/index.ts
45
+ var HOST_RUNTIME_GLOBAL = "__VELTRIX_APP_RUNTIME__";
46
+ function getHostRuntime() {
47
+ const runtime = globalThis[HOST_RUNTIME_GLOBAL];
48
+ return runtime ?? null;
49
+ }
50
+ function authFetch(input, init) {
51
+ const runtime = getHostRuntime();
52
+ if (runtime) return runtime.authFetch(input, init);
53
+ return fetch(input, init);
54
+ }
55
+
56
+ // src/hooks/use-pipeline-status.ts
42
57
  function usePipelineStatus(appId) {
43
58
  const [data, setData] = (0, import_react2.useState)(null);
44
59
  const [isLoading, setIsLoading] = (0, import_react2.useState)(true);
@@ -46,7 +61,7 @@ function usePipelineStatus(appId) {
46
61
  const refresh = (0, import_react2.useCallback)(async () => {
47
62
  try {
48
63
  setIsLoading(true);
49
- const response = await fetch(`/api/pipeline/summary?appId=${appId}`);
64
+ const response = await authFetch(`/api/pipeline/summary?appId=${appId}`);
50
65
  if (!response.ok) throw new Error(`Failed to fetch pipeline status`);
51
66
  const result = await response.json();
52
67
  setData(result);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/index.ts","../../src/hooks/use-app-context.ts","../../src/hooks/use-pipeline-status.ts"],"sourcesContent":["export { useAppContext, AppContext, type AppContextValue } from './use-app-context'\r\nexport { usePipelineStatus, type PipelineStatusData } from './use-pipeline-status'\r\n","// ========================================================================\n// React hooks for app developers\n// These provide access to platform data and pipeline state\n// ========================================================================\n\nimport { createContext, useContext } from 'react'\nimport type { Component, Credential, Tag, User, Customer } from '../types/platform'\n\nexport interface AppContextValue {\n appId: string\n customerId: string\n user: User | null\n customer: Customer | null\n\n // Platform data accessors\n getComponents: () => Promise<Component[]>\n getCredentials: () => Promise<Credential[]>\n getTags: () => Promise<Tag[]>\n\n // App settings\n settings: Record<string, unknown>\n}\n\nexport const AppContext = createContext<AppContextValue | null>(null)\n\n/**\n * Access the app context in any app component.\n *\n * @example\n * ```tsx\n * import { useAppContext } from '@veltrixsecops/app-sdk/hooks'\n *\n * function MyComponent() {\n * const { appId, settings, getComponents } = useAppContext()\n * // ...\n * }\n * ```\n */\nexport function useAppContext(): AppContextValue {\n const ctx = useContext(AppContext)\n if (!ctx) {\n throw new Error('useAppContext must be used within an AppContextProvider')\n }\n return ctx\n}\n","import { useState, useEffect, useCallback } from 'react'\n\nexport interface PipelineStatusData {\n pendingApprovals: number\n activeDeployments: number\n failedDeployments: number\n unresolvedDrifts: number\n recentDeployments: Array<{\n id: string\n canvasName: string\n environment: string\n status: string\n startedAt: string\n completedAt?: string\n }>\n}\n\n/**\n * Hook to access pipeline status for the current app/customer.\n *\n * @example\n * ```tsx\n * import { usePipelineStatus } from '@veltrixsecops/app-sdk/hooks'\n *\n * function Dashboard() {\n * const { data, isLoading } = usePipelineStatus('my-app')\n * if (isLoading) return <Spinner />\n * return <div>{data.activeDeployments} active deployments</div>\n * }\n * ```\n */\nexport function usePipelineStatus(appId: string) {\n const [data, setData] = useState<PipelineStatusData | null>(null)\n const [isLoading, setIsLoading] = useState(true)\n const [error, setError] = useState<Error | null>(null)\n\n const refresh = useCallback(async () => {\n try {\n setIsLoading(true)\n const response = await fetch(`/api/pipeline/summary?appId=${appId}`)\n if (!response.ok) throw new Error(`Failed to fetch pipeline status`)\n const result = await response.json()\n setData(result)\n setError(null)\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Unknown error'))\n } finally {\n setIsLoading(false)\n }\n }, [appId])\n\n useEffect(() => {\n refresh()\n }, [refresh])\n\n return { data, isLoading, error, refresh }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACKA,mBAA0C;AAkBnC,IAAM,iBAAa,4BAAsC,IAAI;AAe7D,SAAS,gBAAiC;AAC/C,QAAM,UAAM,yBAAW,UAAU;AACjC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,yDAAyD;AAAA,EAC3E;AACA,SAAO;AACT;;;AC5CA,IAAAA,gBAAiD;AA+B1C,SAAS,kBAAkB,OAAe;AAC/C,QAAM,CAAC,MAAM,OAAO,QAAI,wBAAoC,IAAI;AAChE,QAAM,CAAC,WAAW,YAAY,QAAI,wBAAS,IAAI;AAC/C,QAAM,CAAC,OAAO,QAAQ,QAAI,wBAAuB,IAAI;AAErD,QAAM,cAAU,2BAAY,YAAY;AACtC,QAAI;AACF,mBAAa,IAAI;AACjB,YAAM,WAAW,MAAM,MAAM,+BAA+B,KAAK,EAAE;AACnE,UAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,iCAAiC;AACnE,YAAM,SAAS,MAAM,SAAS,KAAK;AACnC,cAAQ,MAAM;AACd,eAAS,IAAI;AAAA,IACf,SAAS,KAAK;AACZ,eAAS,eAAe,QAAQ,MAAM,IAAI,MAAM,eAAe,CAAC;AAAA,IAClE,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,KAAK,CAAC;AAEV,+BAAU,MAAM;AACd,YAAQ;AAAA,EACV,GAAG,CAAC,OAAO,CAAC;AAEZ,SAAO,EAAE,MAAM,WAAW,OAAO,QAAQ;AAC3C;","names":["import_react"]}
1
+ {"version":3,"sources":["../../src/hooks/index.ts","../../src/hooks/use-app-context.ts","../../src/hooks/use-pipeline-status.ts","../../src/client/index.ts"],"sourcesContent":["export { useAppContext, AppContext, type AppContextValue } from './use-app-context'\r\nexport { usePipelineStatus, type PipelineStatusData } from './use-pipeline-status'\r\n","// ========================================================================\n// React hooks for app developers\n// These provide access to platform data and pipeline state\n// ========================================================================\n\nimport { createContext, useContext, type Context } from 'react'\nimport type { Component, Credential, Tag, User, Customer } from '../types/platform'\n\nexport interface AppContextValue {\n appId: string\n customerId: string\n user: User | null\n customer: Customer | null\n\n // Platform data accessors\n getComponents: () => Promise<Component[]>\n getCredentials: () => Promise<Credential[]>\n getTags: () => Promise<Tag[]>\n\n // App settings\n settings: Record<string, unknown>\n}\n\n// Anchor the context on the host runtime when running inside the platform,\n// so a bundle that inlined its own SDK copy still shares the host's context\n// (two createContext objects never match, even with identical shapes).\nconst hostContext = (\n (globalThis as Record<string, unknown>).__VELTRIX_APP_RUNTIME__ as\n | { AppContext?: Context<AppContextValue | null> }\n | undefined\n)?.AppContext\n\nexport const AppContext: Context<AppContextValue | null> =\n hostContext ?? createContext<AppContextValue | null>(null)\n\n/**\n * Access the app context in any app component.\n *\n * @example\n * ```tsx\n * import { useAppContext } from '@veltrixsecops/app-sdk/hooks'\n *\n * function MyComponent() {\n * const { appId, settings, getComponents } = useAppContext()\n * // ...\n * }\n * ```\n */\nexport function useAppContext(): AppContextValue {\n const ctx = useContext(AppContext)\n if (!ctx) {\n throw new Error('useAppContext must be used within an AppContextProvider')\n }\n return ctx\n}\n","import { useState, useEffect, useCallback } from 'react'\nimport { authFetch } from '../client'\n\nexport interface PipelineStatusData {\n pendingApprovals: number\n activeDeployments: number\n failedDeployments: number\n unresolvedDrifts: number\n recentDeployments: Array<{\n id: string\n canvasName: string\n environment: string\n status: string\n startedAt: string\n completedAt?: string\n }>\n}\n\n/**\n * Hook to access pipeline status for the current app/customer.\n *\n * @example\n * ```tsx\n * import { usePipelineStatus } from '@veltrixsecops/app-sdk/hooks'\n *\n * function Dashboard() {\n * const { data, isLoading } = usePipelineStatus('my-app')\n * if (isLoading) return <Spinner />\n * return <div>{data.activeDeployments} active deployments</div>\n * }\n * ```\n */\nexport function usePipelineStatus(appId: string) {\n const [data, setData] = useState<PipelineStatusData | null>(null)\n const [isLoading, setIsLoading] = useState(true)\n const [error, setError] = useState<Error | null>(null)\n\n const refresh = useCallback(async () => {\n try {\n setIsLoading(true)\n // Platform APIs are bearer-token protected — plain fetch would 401\n const response = await authFetch(`/api/pipeline/summary?appId=${appId}`)\n if (!response.ok) throw new Error(`Failed to fetch pipeline status`)\n const result = await response.json()\n setData(result)\n setError(null)\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Unknown error'))\n } finally {\n setIsLoading(false)\n }\n }, [appId])\n\n useEffect(() => {\n refresh()\n }, [refresh])\n\n return { data, isLoading, error, refresh }\n}\n","// ========================================================================\n// Client runtime contract — how app client bundles talk to the host.\n//\n// App client code is packaged as a hermetic ESM bundle in which `react`,\n// `react-dom`, `react/jsx-runtime`, and every `@veltrixsecops/app-sdk`\n// subpath are compile-time shims that read the host-provided runtime from\n// `globalThis.__VELTRIX_APP_RUNTIME__`. The platform installs that global\n// (with ITS React instance, the shared AppContext, and an authenticated\n// fetch) before dynamically importing any app bundle, so app components\n// render inside the host React tree with working hooks and context.\n//\n// App authors: import from '@veltrixsecops/app-sdk/client' (and /hooks) —\n// never bundle your own copy of react. Use `authFetch` for calls to your\n// app's server routes (/api/apps/<app-id>/...): plain fetch() lacks the\n// platform's Authorization header and will receive 401s.\n// ========================================================================\n\nimport type { ComponentType, Context, LazyExoticComponent } from 'react'\nimport type { AppContextValue } from '../hooks/use-app-context'\n\n/** Name of the global the platform installs before loading app bundles. */\nexport const HOST_RUNTIME_GLOBAL = '__VELTRIX_APP_RUNTIME__'\n\n/**\n * The runtime surface the platform exposes to app client bundles.\n * The react/reactDom/jsxRuntime members are the host's own module objects —\n * app bundles are compiled with shims that re-export them, guaranteeing a\n * single React instance per page.\n */\nexport interface VeltrixHostRuntime {\n /** The host's `react` module object. */\n react: unknown\n /** The host's `react-dom` module object. */\n reactDom: unknown\n /** The host's `react-dom/client` module object. */\n reactDomClient?: unknown\n /** The host's `react/jsx-runtime` module object. */\n jsxRuntime: unknown\n /** Shared app context — the host wraps app pages in its Provider. */\n AppContext: Context<AppContextValue | null>\n /** fetch() with the platform's Authorization header attached. */\n authFetch: (input: string, init?: RequestInit) => Promise<Response>\n /**\n * The SDK surface app bundles receive for `@veltrixsecops/app-sdk`,\n * `.../hooks`, and `.../client` imports (useAppContext, AppContext,\n * usePipelineStatus, authFetch, getHostRuntime, ...).\n */\n sdk: Record<string, unknown>\n}\n\n/** Read the host runtime, or null outside the platform (tests, storybook). */\nexport function getHostRuntime(): VeltrixHostRuntime | null {\n const runtime = (globalThis as Record<string, unknown>)[HOST_RUNTIME_GLOBAL]\n return (runtime as VeltrixHostRuntime) ?? null\n}\n\n/** Read the host runtime, throwing a diagnosable error when absent. */\nexport function requireHostRuntime(): VeltrixHostRuntime {\n const runtime = getHostRuntime()\n if (!runtime) {\n throw new Error(\n 'Veltrix host runtime not found — app client bundles only run inside the ' +\n `Veltrix platform (missing globalThis.${HOST_RUNTIME_GLOBAL})`,\n )\n }\n return runtime\n}\n\n/**\n * fetch() that carries the platform's Authorization header. Required for an\n * app page to call its own server routes (/api/apps/<app-id>/...), which are\n * bearer-token protected. Falls back to plain fetch outside the platform.\n */\nexport function authFetch(input: string, init?: RequestInit): Promise<Response> {\n const runtime = getHostRuntime()\n if (runtime) return runtime.authFetch(input, init)\n return fetch(input, init)\n}\n\n/** A sidebar entry contributed by the app's client entry module. */\nexport interface AppSidebarItem {\n path: string\n label: string\n icon?: string\n}\n\n/**\n * Shape of the default export of an app's `client/index.tsx`.\n * `pages` keys must match `manifest.client.pages[].component`.\n */\nexport interface AppClientModule {\n id: string\n pages: Record<string, ComponentType | LazyExoticComponent<ComponentType>>\n sidebarItems?: AppSidebarItem[]\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACKA,mBAAwD;AAqBxD,IAAM,cACH,WAAuC,yBAGvC;AAEI,IAAM,aACX,mBAAe,4BAAsC,IAAI;AAepD,SAAS,gBAAiC;AAC/C,QAAM,UAAM,yBAAW,UAAU;AACjC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,yDAAyD;AAAA,EAC3E;AACA,SAAO;AACT;;;ACtDA,IAAAA,gBAAiD;;;ACqB1C,IAAM,sBAAsB;AA8B5B,SAAS,iBAA4C;AAC1D,QAAM,UAAW,WAAuC,mBAAmB;AAC3E,SAAQ,WAAkC;AAC5C;AAmBO,SAAS,UAAU,OAAe,MAAuC;AAC9E,QAAM,UAAU,eAAe;AAC/B,MAAI,QAAS,QAAO,QAAQ,UAAU,OAAO,IAAI;AACjD,SAAO,MAAM,OAAO,IAAI;AAC1B;;;AD7CO,SAAS,kBAAkB,OAAe;AAC/C,QAAM,CAAC,MAAM,OAAO,QAAI,wBAAoC,IAAI;AAChE,QAAM,CAAC,WAAW,YAAY,QAAI,wBAAS,IAAI;AAC/C,QAAM,CAAC,OAAO,QAAQ,QAAI,wBAAuB,IAAI;AAErD,QAAM,cAAU,2BAAY,YAAY;AACtC,QAAI;AACF,mBAAa,IAAI;AAEjB,YAAM,WAAW,MAAM,UAAU,+BAA+B,KAAK,EAAE;AACvE,UAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,iCAAiC;AACnE,YAAM,SAAS,MAAM,SAAS,KAAK;AACnC,cAAQ,MAAM;AACd,eAAS,IAAI;AAAA,IACf,SAAS,KAAK;AACZ,eAAS,eAAe,QAAQ,MAAM,IAAI,MAAM,eAAe,CAAC;AAAA,IAClE,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,KAAK,CAAC;AAEV,+BAAU,MAAM;AACd,YAAQ;AAAA,EACV,GAAG,CAAC,OAAO,CAAC;AAEZ,SAAO,EAAE,MAAM,WAAW,OAAO,QAAQ;AAC3C;","names":["import_react"]}
@@ -1,2 +1,39 @@
1
- export { a as AppContext, b as AppContextValue, P as PipelineStatusData, u as useAppContext, s as usePipelineStatus } from '../index-BI8f_mBw.cjs';
1
+ export { d as AppContext, A as AppContextValue, w as useAppContext } from '../use-app-context-PBuG144m.cjs';
2
2
  import 'react';
3
+
4
+ interface PipelineStatusData {
5
+ pendingApprovals: number;
6
+ activeDeployments: number;
7
+ failedDeployments: number;
8
+ unresolvedDrifts: number;
9
+ recentDeployments: Array<{
10
+ id: string;
11
+ canvasName: string;
12
+ environment: string;
13
+ status: string;
14
+ startedAt: string;
15
+ completedAt?: string;
16
+ }>;
17
+ }
18
+ /**
19
+ * Hook to access pipeline status for the current app/customer.
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * import { usePipelineStatus } from '@veltrixsecops/app-sdk/hooks'
24
+ *
25
+ * function Dashboard() {
26
+ * const { data, isLoading } = usePipelineStatus('my-app')
27
+ * if (isLoading) return <Spinner />
28
+ * return <div>{data.activeDeployments} active deployments</div>
29
+ * }
30
+ * ```
31
+ */
32
+ declare function usePipelineStatus(appId: string): {
33
+ data: PipelineStatusData | null;
34
+ isLoading: boolean;
35
+ error: Error | null;
36
+ refresh: () => Promise<void>;
37
+ };
38
+
39
+ export { type PipelineStatusData, usePipelineStatus };
@@ -1,2 +1,39 @@
1
- export { a as AppContext, b as AppContextValue, P as PipelineStatusData, u as useAppContext, s as usePipelineStatus } from '../index-BI8f_mBw.js';
1
+ export { d as AppContext, A as AppContextValue, w as useAppContext } from '../use-app-context-PBuG144m.js';
2
2
  import 'react';
3
+
4
+ interface PipelineStatusData {
5
+ pendingApprovals: number;
6
+ activeDeployments: number;
7
+ failedDeployments: number;
8
+ unresolvedDrifts: number;
9
+ recentDeployments: Array<{
10
+ id: string;
11
+ canvasName: string;
12
+ environment: string;
13
+ status: string;
14
+ startedAt: string;
15
+ completedAt?: string;
16
+ }>;
17
+ }
18
+ /**
19
+ * Hook to access pipeline status for the current app/customer.
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * import { usePipelineStatus } from '@veltrixsecops/app-sdk/hooks'
24
+ *
25
+ * function Dashboard() {
26
+ * const { data, isLoading } = usePipelineStatus('my-app')
27
+ * if (isLoading) return <Spinner />
28
+ * return <div>{data.activeDeployments} active deployments</div>
29
+ * }
30
+ * ```
31
+ */
32
+ declare function usePipelineStatus(appId: string): {
33
+ data: PipelineStatusData | null;
34
+ isLoading: boolean;
35
+ error: Error | null;
36
+ refresh: () => Promise<void>;
37
+ };
38
+
39
+ export { type PipelineStatusData, usePipelineStatus };
@@ -2,7 +2,8 @@ import {
2
2
  AppContext,
3
3
  useAppContext,
4
4
  usePipelineStatus
5
- } from "../chunk-TSEIWO6T.js";
5
+ } from "../chunk-JDEA2F7I.js";
6
+ import "../chunk-I4JJX4LL.js";
6
7
  export {
7
8
  AppContext,
8
9
  useAppContext,
package/dist/index.cjs CHANGED
@@ -20,7 +20,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
+ APP_LAYOUT: () => APP_LAYOUT,
24
+ APP_PAGE_LAYOUTS: () => APP_PAGE_LAYOUTS,
25
+ APP_PAGE_NAV: () => APP_PAGE_NAV,
23
26
  AppContext: () => AppContext,
27
+ HANDLER_NAMES: () => HANDLER_NAMES,
28
+ conventionalPaths: () => conventionalPaths,
24
29
  defineDeployer: () => defineDeployer,
25
30
  defineDriftDetector: () => defineDriftDetector,
26
31
  defineHealthChecker: () => defineHealthChecker,
@@ -58,7 +63,8 @@ function defineDriftDetector(handler) {
58
63
 
59
64
  // src/hooks/use-app-context.ts
60
65
  var import_react = require("react");
61
- var AppContext = (0, import_react.createContext)(null);
66
+ var hostContext = globalThis.__VELTRIX_APP_RUNTIME__?.AppContext;
67
+ var AppContext = hostContext ?? (0, import_react.createContext)(null);
62
68
  function useAppContext() {
63
69
  const ctx = (0, import_react.useContext)(AppContext);
64
70
  if (!ctx) {
@@ -69,6 +75,20 @@ function useAppContext() {
69
75
 
70
76
  // src/hooks/use-pipeline-status.ts
71
77
  var import_react2 = require("react");
78
+
79
+ // src/client/index.ts
80
+ var HOST_RUNTIME_GLOBAL = "__VELTRIX_APP_RUNTIME__";
81
+ function getHostRuntime() {
82
+ const runtime = globalThis[HOST_RUNTIME_GLOBAL];
83
+ return runtime ?? null;
84
+ }
85
+ function authFetch(input, init) {
86
+ const runtime = getHostRuntime();
87
+ if (runtime) return runtime.authFetch(input, init);
88
+ return fetch(input, init);
89
+ }
90
+
91
+ // src/hooks/use-pipeline-status.ts
72
92
  function usePipelineStatus(appId) {
73
93
  const [data, setData] = (0, import_react2.useState)(null);
74
94
  const [isLoading, setIsLoading] = (0, import_react2.useState)(true);
@@ -76,7 +96,7 @@ function usePipelineStatus(appId) {
76
96
  const refresh = (0, import_react2.useCallback)(async () => {
77
97
  try {
78
98
  setIsLoading(true);
79
- const response = await fetch(`/api/pipeline/summary?appId=${appId}`);
99
+ const response = await authFetch(`/api/pipeline/summary?appId=${appId}`);
80
100
  if (!response.ok) throw new Error(`Failed to fetch pipeline status`);
81
101
  const result = await response.json();
82
102
  setData(result);
@@ -92,9 +112,68 @@ function usePipelineStatus(appId) {
92
112
  }, [refresh]);
93
113
  return { data, isLoading, error, refresh };
94
114
  }
115
+
116
+ // src/types/manifest.ts
117
+ var APP_PAGE_LAYOUTS = ["standard", "full-bleed", "canvas"];
118
+ var APP_PAGE_NAV = ["sidebar", "tab", "hidden"];
119
+
120
+ // src/structure.ts
121
+ var APP_LAYOUT = {
122
+ /** The app contract. Always at the app root. */
123
+ manifest: "manifest.yaml",
124
+ /**
125
+ * The unit of extension: config-types/<configTypeId>/ colocates everything
126
+ * for one configuration type — canvas.yaml, defaults.yaml, the six pipeline
127
+ * handlers (extensionless in the manifest), and __tests__/.
128
+ */
129
+ configTypesDir: "config-types",
130
+ /** Canvas form schema filename inside a config-type folder. */
131
+ canvasFile: "canvas.yaml",
132
+ /** Default field values filename inside a config-type folder. */
133
+ defaultsFile: "defaults.yaml",
134
+ /** Lifecycle hooks (camelCase): hooks/onInstall, hooks/onUninstall, ... */
135
+ hooksDir: "hooks",
136
+ /** Shared app code used by multiple handlers (API clients, parsers). */
137
+ libDir: "lib",
138
+ /** SQL migrations (requires manifest `database.tablePrefix`). */
139
+ migrationsDir: "migrations",
140
+ /** Fastify route module receiving (fastify, AppRouteContext). */
141
+ serverEntry: "server/index",
142
+ /** Client entry registering pages/sidebar items (optional). */
143
+ clientEntry: "client/index",
144
+ /** Icons and logos (optional). */
145
+ assetsDir: "assets",
146
+ /** Tests live next to the code they cover: handlers/<id>/__tests__/ */
147
+ testsDirName: "__tests__"
148
+ };
149
+ var HANDLER_NAMES = [
150
+ "validate",
151
+ "deploy",
152
+ "rollback",
153
+ "healthCheck",
154
+ "driftDetect",
155
+ "getStatus"
156
+ ];
157
+ function conventionalPaths(configTypeId) {
158
+ const base = `${APP_LAYOUT.configTypesDir}/${configTypeId}`;
159
+ const handlers = {};
160
+ for (const name of HANDLER_NAMES) {
161
+ handlers[name] = `${base}/${name}`;
162
+ }
163
+ return {
164
+ canvasTemplate: `${base}/${APP_LAYOUT.canvasFile}`,
165
+ defaultConfig: `${base}/${APP_LAYOUT.defaultsFile}`,
166
+ handlers
167
+ };
168
+ }
95
169
  // Annotate the CommonJS export names for ESM import in node:
96
170
  0 && (module.exports = {
171
+ APP_LAYOUT,
172
+ APP_PAGE_LAYOUTS,
173
+ APP_PAGE_NAV,
97
174
  AppContext,
175
+ HANDLER_NAMES,
176
+ conventionalPaths,
98
177
  defineDeployer,
99
178
  defineDriftDetector,
100
179
  defineHealthChecker,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/pipeline/define-validator.ts","../src/pipeline/define-deployer.ts","../src/pipeline/define-rollback-handler.ts","../src/pipeline/define-health-checker.ts","../src/pipeline/define-drift-detector.ts","../src/hooks/use-app-context.ts","../src/hooks/use-pipeline-status.ts"],"sourcesContent":["// ========================================================================\n// @veltrixsecops/app-sdk\n//\n// The official SDK for building Veltrix Security-as-Code apps.\n// Import pipeline helpers, hooks, and types to build your app.\n//\n// Usage:\n// import { defineValidator, defineDeployer } from '@veltrixsecops/app-sdk/pipeline'\n// import { useAppContext, usePipelineStatus } from '@veltrixsecops/app-sdk/hooks'\n// import type { PipelineContext, DeployContext } from '@veltrixsecops/app-sdk'\n// ========================================================================\n\n// Pipeline handler helpers\nexport {\n defineValidator,\n defineDeployer,\n defineRollbackHandler,\n defineHealthChecker,\n defineDriftDetector,\n} from './pipeline'\n\n// React hooks\nexport {\n useAppContext,\n AppContext,\n usePipelineStatus,\n} from './hooks'\n\n// Pipeline types\nexport type {\n PipelineContext,\n DeployContext,\n RollbackContext,\n HealthCheckContext,\n DriftContext,\n ValidationResult,\n ValidationError,\n ValidationWarning,\n DeployResult,\n RollbackResult,\n HealthCheckResult,\n HealthCheck,\n DriftResult,\n DriftDiff,\n ConfigStatus,\n ComponentConfigStatus,\n CanvasSnapshot,\n CanvasSectionSnapshot,\n DeploymentStrategy,\n EnvironmentRef,\n UserRef,\n ComponentRef,\n CredentialRef,\n ConnectivityRef,\n ConnectivityProviderRef,\n PlatformDataApi,\n DeploymentSummary,\n ValidateHandler,\n DeployHandler,\n RollbackHandler,\n HealthCheckHandler,\n DriftDetectHandler,\n GetStatusHandler,\n} from './types/pipeline'\n\n// Platform types\nexport type {\n Component,\n Credential,\n Connectivity,\n Tag,\n User,\n Customer,\n PlatformDatabaseClient,\n AppHookContext,\n AppRouteContext,\n} from './types/platform'\n\n// Manifest types\nexport type {\n AppManifest,\n AppConfigurationTypeManifest,\n AppPermissionDeclaration,\n AppPageDeclaration,\n AppSettingDeclaration,\n AppSource,\n AppStatusType,\n AppInstallationStatus,\n AppListItem,\n AppDetail,\n AppInstallationDetail,\n} from './types/manifest'\n\n// Hooks types\nexport type { AppContextValue, PipelineStatusData } from './hooks'\n","import type { PipelineContext, ValidationResult } from '../types/pipeline'\n\n/**\n * Define a validation handler for a configuration type.\n *\n * @example\n * ```ts\n * import { defineValidator } from '@veltrixsecops/app-sdk/pipeline'\n *\n * export default defineValidator(async (ctx) => {\n * const errors = []\n * const name = ctx.canvas.sections[0]?.fields['name']\n * if (!name) {\n * errors.push({ field: 'name', message: 'Name is required', code: 'REQUIRED' })\n * }\n * return { valid: errors.length === 0, errors, warnings: [] }\n * })\n * ```\n */\nexport function defineValidator(\n handler: (ctx: PipelineContext) => Promise<ValidationResult>,\n): (ctx: PipelineContext) => Promise<ValidationResult> {\n return handler\n}\n","import type { DeployContext, DeployResult } from '../types/pipeline'\n\n/**\n * Define a deploy handler for a configuration type.\n *\n * @example\n * ```ts\n * import { defineDeployer } from '@veltrixsecops/app-sdk/pipeline'\n *\n * export default defineDeployer(async (ctx) => {\n * const { component, credential, connectivity, canvas } = ctx\n * // Push configuration to the tool\n * return { success: true, message: 'Deployed', rollbackData: previousState }\n * })\n * ```\n */\nexport function defineDeployer(\n handler: (ctx: DeployContext) => Promise<DeployResult>,\n): (ctx: DeployContext) => Promise<DeployResult> {\n return handler\n}\n","import type { RollbackContext, RollbackResult } from '../types/pipeline'\r\n\r\n/**\r\n * Define a rollback handler for a configuration type.\r\n */\r\nexport function defineRollbackHandler(\r\n handler: (ctx: RollbackContext) => Promise<RollbackResult>,\r\n): (ctx: RollbackContext) => Promise<RollbackResult> {\r\n return handler\r\n}\r\n","import type { HealthCheckContext, HealthCheckResult } from '../types/pipeline'\r\n\r\n/**\r\n * Define a health check handler for a configuration type.\r\n */\r\nexport function defineHealthChecker(\r\n handler: (ctx: HealthCheckContext) => Promise<HealthCheckResult>,\r\n): (ctx: HealthCheckContext) => Promise<HealthCheckResult> {\r\n return handler\r\n}\r\n","import type { DriftContext, DriftResult } from '../types/pipeline'\r\n\r\n/**\r\n * Define a drift detection handler for a configuration type.\r\n */\r\nexport function defineDriftDetector(\r\n handler: (ctx: DriftContext) => Promise<DriftResult>,\r\n): (ctx: DriftContext) => Promise<DriftResult> {\r\n return handler\r\n}\r\n","// ========================================================================\n// React hooks for app developers\n// These provide access to platform data and pipeline state\n// ========================================================================\n\nimport { createContext, useContext } from 'react'\nimport type { Component, Credential, Tag, User, Customer } from '../types/platform'\n\nexport interface AppContextValue {\n appId: string\n customerId: string\n user: User | null\n customer: Customer | null\n\n // Platform data accessors\n getComponents: () => Promise<Component[]>\n getCredentials: () => Promise<Credential[]>\n getTags: () => Promise<Tag[]>\n\n // App settings\n settings: Record<string, unknown>\n}\n\nexport const AppContext = createContext<AppContextValue | null>(null)\n\n/**\n * Access the app context in any app component.\n *\n * @example\n * ```tsx\n * import { useAppContext } from '@veltrixsecops/app-sdk/hooks'\n *\n * function MyComponent() {\n * const { appId, settings, getComponents } = useAppContext()\n * // ...\n * }\n * ```\n */\nexport function useAppContext(): AppContextValue {\n const ctx = useContext(AppContext)\n if (!ctx) {\n throw new Error('useAppContext must be used within an AppContextProvider')\n }\n return ctx\n}\n","import { useState, useEffect, useCallback } from 'react'\n\nexport interface PipelineStatusData {\n pendingApprovals: number\n activeDeployments: number\n failedDeployments: number\n unresolvedDrifts: number\n recentDeployments: Array<{\n id: string\n canvasName: string\n environment: string\n status: string\n startedAt: string\n completedAt?: string\n }>\n}\n\n/**\n * Hook to access pipeline status for the current app/customer.\n *\n * @example\n * ```tsx\n * import { usePipelineStatus } from '@veltrixsecops/app-sdk/hooks'\n *\n * function Dashboard() {\n * const { data, isLoading } = usePipelineStatus('my-app')\n * if (isLoading) return <Spinner />\n * return <div>{data.activeDeployments} active deployments</div>\n * }\n * ```\n */\nexport function usePipelineStatus(appId: string) {\n const [data, setData] = useState<PipelineStatusData | null>(null)\n const [isLoading, setIsLoading] = useState(true)\n const [error, setError] = useState<Error | null>(null)\n\n const refresh = useCallback(async () => {\n try {\n setIsLoading(true)\n const response = await fetch(`/api/pipeline/summary?appId=${appId}`)\n if (!response.ok) throw new Error(`Failed to fetch pipeline status`)\n const result = await response.json()\n setData(result)\n setError(null)\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Unknown error'))\n } finally {\n setIsLoading(false)\n }\n }, [appId])\n\n useEffect(() => {\n refresh()\n }, [refresh])\n\n return { data, isLoading, error, refresh }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACmBO,SAAS,gBACd,SACqD;AACrD,SAAO;AACT;;;ACPO,SAAS,eACd,SAC+C;AAC/C,SAAO;AACT;;;ACfO,SAAS,sBACd,SACmD;AACnD,SAAO;AACT;;;ACJO,SAAS,oBACd,SACyD;AACzD,SAAO;AACT;;;ACJO,SAAS,oBACd,SAC6C;AAC7C,SAAO;AACT;;;ACJA,mBAA0C;AAkBnC,IAAM,iBAAa,4BAAsC,IAAI;AAe7D,SAAS,gBAAiC;AAC/C,QAAM,UAAM,yBAAW,UAAU;AACjC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,yDAAyD;AAAA,EAC3E;AACA,SAAO;AACT;;;AC5CA,IAAAA,gBAAiD;AA+B1C,SAAS,kBAAkB,OAAe;AAC/C,QAAM,CAAC,MAAM,OAAO,QAAI,wBAAoC,IAAI;AAChE,QAAM,CAAC,WAAW,YAAY,QAAI,wBAAS,IAAI;AAC/C,QAAM,CAAC,OAAO,QAAQ,QAAI,wBAAuB,IAAI;AAErD,QAAM,cAAU,2BAAY,YAAY;AACtC,QAAI;AACF,mBAAa,IAAI;AACjB,YAAM,WAAW,MAAM,MAAM,+BAA+B,KAAK,EAAE;AACnE,UAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,iCAAiC;AACnE,YAAM,SAAS,MAAM,SAAS,KAAK;AACnC,cAAQ,MAAM;AACd,eAAS,IAAI;AAAA,IACf,SAAS,KAAK;AACZ,eAAS,eAAe,QAAQ,MAAM,IAAI,MAAM,eAAe,CAAC;AAAA,IAClE,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,KAAK,CAAC;AAEV,+BAAU,MAAM;AACd,YAAQ;AAAA,EACV,GAAG,CAAC,OAAO,CAAC;AAEZ,SAAO,EAAE,MAAM,WAAW,OAAO,QAAQ;AAC3C;","names":["import_react"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/pipeline/define-validator.ts","../src/pipeline/define-deployer.ts","../src/pipeline/define-rollback-handler.ts","../src/pipeline/define-health-checker.ts","../src/pipeline/define-drift-detector.ts","../src/hooks/use-app-context.ts","../src/hooks/use-pipeline-status.ts","../src/client/index.ts","../src/types/manifest.ts","../src/structure.ts"],"sourcesContent":["// ========================================================================\n// @veltrixsecops/app-sdk\n//\n// The official SDK for building Veltrix Security-as-Code apps.\n// Import pipeline helpers, hooks, and types to build your app.\n//\n// Usage:\n// import { defineValidator, defineDeployer } from '@veltrixsecops/app-sdk/pipeline'\n// import { useAppContext, usePipelineStatus } from '@veltrixsecops/app-sdk/hooks'\n// import type { PipelineContext, DeployContext } from '@veltrixsecops/app-sdk'\n// ========================================================================\n\n// Pipeline handler helpers\nexport {\n defineValidator,\n defineDeployer,\n defineRollbackHandler,\n defineHealthChecker,\n defineDriftDetector,\n} from './pipeline'\n\n// React hooks\nexport {\n useAppContext,\n AppContext,\n usePipelineStatus,\n} from './hooks'\n\n// Pipeline types\nexport type {\n PipelineContext,\n DeployContext,\n RollbackContext,\n HealthCheckContext,\n DriftContext,\n ValidationResult,\n ValidationError,\n ValidationWarning,\n DeployResult,\n RollbackResult,\n HealthCheckResult,\n HealthCheck,\n DriftResult,\n DriftDiff,\n ConfigStatus,\n ComponentConfigStatus,\n CanvasSnapshot,\n CanvasSectionSnapshot,\n DeploymentStrategy,\n EnvironmentRef,\n UserRef,\n ComponentRef,\n CredentialRef,\n ConnectivityRef,\n ConnectivityProviderRef,\n PlatformDataApi,\n DeploymentSummary,\n ValidateHandler,\n DeployHandler,\n RollbackHandler,\n HealthCheckHandler,\n DriftDetectHandler,\n GetStatusHandler,\n} from './types/pipeline'\n\n// Platform types\nexport type {\n Component,\n Credential,\n Connectivity,\n Tag,\n User,\n Customer,\n PlatformDatabaseClient,\n AppHookContext,\n AppRouteContext,\n} from './types/platform'\n\n// App UI & navigation contract\nexport { APP_PAGE_LAYOUTS, APP_PAGE_NAV } from './types/manifest'\nexport type { AppPageLayout, AppPageNav, AppPagePermission } from './types/manifest'\n\n// Manifest types\nexport type {\n AppManifest,\n AppConfigurationTypeManifest,\n AppPermissionDeclaration,\n AppPageDeclaration,\n AppSettingDeclaration,\n AppSource,\n AppStatusType,\n AppInstallationStatus,\n AppListItem,\n AppDetail,\n AppInstallationDetail,\n} from './types/manifest'\n\n// Canonical app layout\nexport { APP_LAYOUT, HANDLER_NAMES, conventionalPaths } from './structure'\nexport type { HandlerName } from './structure'\n\n// Hooks types\nexport type { AppContextValue, PipelineStatusData } from './hooks'\n","import type { PipelineContext, ValidationResult } from '../types/pipeline'\n\n/**\n * Define a validation handler for a configuration type.\n *\n * @example\n * ```ts\n * import { defineValidator } from '@veltrixsecops/app-sdk/pipeline'\n *\n * export default defineValidator(async (ctx) => {\n * const errors = []\n * const name = ctx.canvas.sections[0]?.fields['name']\n * if (!name) {\n * errors.push({ field: 'name', message: 'Name is required', code: 'REQUIRED' })\n * }\n * return { valid: errors.length === 0, errors, warnings: [] }\n * })\n * ```\n */\nexport function defineValidator(\n handler: (ctx: PipelineContext) => Promise<ValidationResult>,\n): (ctx: PipelineContext) => Promise<ValidationResult> {\n return handler\n}\n","import type { DeployContext, DeployResult } from '../types/pipeline'\n\n/**\n * Define a deploy handler for a configuration type.\n *\n * @example\n * ```ts\n * import { defineDeployer } from '@veltrixsecops/app-sdk/pipeline'\n *\n * export default defineDeployer(async (ctx) => {\n * const { component, credential, connectivity, canvas } = ctx\n * // Push configuration to the tool\n * return { success: true, message: 'Deployed', rollbackData: previousState }\n * })\n * ```\n */\nexport function defineDeployer(\n handler: (ctx: DeployContext) => Promise<DeployResult>,\n): (ctx: DeployContext) => Promise<DeployResult> {\n return handler\n}\n","import type { RollbackContext, RollbackResult } from '../types/pipeline'\r\n\r\n/**\r\n * Define a rollback handler for a configuration type.\r\n */\r\nexport function defineRollbackHandler(\r\n handler: (ctx: RollbackContext) => Promise<RollbackResult>,\r\n): (ctx: RollbackContext) => Promise<RollbackResult> {\r\n return handler\r\n}\r\n","import type { HealthCheckContext, HealthCheckResult } from '../types/pipeline'\r\n\r\n/**\r\n * Define a health check handler for a configuration type.\r\n */\r\nexport function defineHealthChecker(\r\n handler: (ctx: HealthCheckContext) => Promise<HealthCheckResult>,\r\n): (ctx: HealthCheckContext) => Promise<HealthCheckResult> {\r\n return handler\r\n}\r\n","import type { DriftContext, DriftResult } from '../types/pipeline'\r\n\r\n/**\r\n * Define a drift detection handler for a configuration type.\r\n */\r\nexport function defineDriftDetector(\r\n handler: (ctx: DriftContext) => Promise<DriftResult>,\r\n): (ctx: DriftContext) => Promise<DriftResult> {\r\n return handler\r\n}\r\n","// ========================================================================\n// React hooks for app developers\n// These provide access to platform data and pipeline state\n// ========================================================================\n\nimport { createContext, useContext, type Context } from 'react'\nimport type { Component, Credential, Tag, User, Customer } from '../types/platform'\n\nexport interface AppContextValue {\n appId: string\n customerId: string\n user: User | null\n customer: Customer | null\n\n // Platform data accessors\n getComponents: () => Promise<Component[]>\n getCredentials: () => Promise<Credential[]>\n getTags: () => Promise<Tag[]>\n\n // App settings\n settings: Record<string, unknown>\n}\n\n// Anchor the context on the host runtime when running inside the platform,\n// so a bundle that inlined its own SDK copy still shares the host's context\n// (two createContext objects never match, even with identical shapes).\nconst hostContext = (\n (globalThis as Record<string, unknown>).__VELTRIX_APP_RUNTIME__ as\n | { AppContext?: Context<AppContextValue | null> }\n | undefined\n)?.AppContext\n\nexport const AppContext: Context<AppContextValue | null> =\n hostContext ?? createContext<AppContextValue | null>(null)\n\n/**\n * Access the app context in any app component.\n *\n * @example\n * ```tsx\n * import { useAppContext } from '@veltrixsecops/app-sdk/hooks'\n *\n * function MyComponent() {\n * const { appId, settings, getComponents } = useAppContext()\n * // ...\n * }\n * ```\n */\nexport function useAppContext(): AppContextValue {\n const ctx = useContext(AppContext)\n if (!ctx) {\n throw new Error('useAppContext must be used within an AppContextProvider')\n }\n return ctx\n}\n","import { useState, useEffect, useCallback } from 'react'\nimport { authFetch } from '../client'\n\nexport interface PipelineStatusData {\n pendingApprovals: number\n activeDeployments: number\n failedDeployments: number\n unresolvedDrifts: number\n recentDeployments: Array<{\n id: string\n canvasName: string\n environment: string\n status: string\n startedAt: string\n completedAt?: string\n }>\n}\n\n/**\n * Hook to access pipeline status for the current app/customer.\n *\n * @example\n * ```tsx\n * import { usePipelineStatus } from '@veltrixsecops/app-sdk/hooks'\n *\n * function Dashboard() {\n * const { data, isLoading } = usePipelineStatus('my-app')\n * if (isLoading) return <Spinner />\n * return <div>{data.activeDeployments} active deployments</div>\n * }\n * ```\n */\nexport function usePipelineStatus(appId: string) {\n const [data, setData] = useState<PipelineStatusData | null>(null)\n const [isLoading, setIsLoading] = useState(true)\n const [error, setError] = useState<Error | null>(null)\n\n const refresh = useCallback(async () => {\n try {\n setIsLoading(true)\n // Platform APIs are bearer-token protected — plain fetch would 401\n const response = await authFetch(`/api/pipeline/summary?appId=${appId}`)\n if (!response.ok) throw new Error(`Failed to fetch pipeline status`)\n const result = await response.json()\n setData(result)\n setError(null)\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Unknown error'))\n } finally {\n setIsLoading(false)\n }\n }, [appId])\n\n useEffect(() => {\n refresh()\n }, [refresh])\n\n return { data, isLoading, error, refresh }\n}\n","// ========================================================================\n// Client runtime contract — how app client bundles talk to the host.\n//\n// App client code is packaged as a hermetic ESM bundle in which `react`,\n// `react-dom`, `react/jsx-runtime`, and every `@veltrixsecops/app-sdk`\n// subpath are compile-time shims that read the host-provided runtime from\n// `globalThis.__VELTRIX_APP_RUNTIME__`. The platform installs that global\n// (with ITS React instance, the shared AppContext, and an authenticated\n// fetch) before dynamically importing any app bundle, so app components\n// render inside the host React tree with working hooks and context.\n//\n// App authors: import from '@veltrixsecops/app-sdk/client' (and /hooks) —\n// never bundle your own copy of react. Use `authFetch` for calls to your\n// app's server routes (/api/apps/<app-id>/...): plain fetch() lacks the\n// platform's Authorization header and will receive 401s.\n// ========================================================================\n\nimport type { ComponentType, Context, LazyExoticComponent } from 'react'\nimport type { AppContextValue } from '../hooks/use-app-context'\n\n/** Name of the global the platform installs before loading app bundles. */\nexport const HOST_RUNTIME_GLOBAL = '__VELTRIX_APP_RUNTIME__'\n\n/**\n * The runtime surface the platform exposes to app client bundles.\n * The react/reactDom/jsxRuntime members are the host's own module objects —\n * app bundles are compiled with shims that re-export them, guaranteeing a\n * single React instance per page.\n */\nexport interface VeltrixHostRuntime {\n /** The host's `react` module object. */\n react: unknown\n /** The host's `react-dom` module object. */\n reactDom: unknown\n /** The host's `react-dom/client` module object. */\n reactDomClient?: unknown\n /** The host's `react/jsx-runtime` module object. */\n jsxRuntime: unknown\n /** Shared app context — the host wraps app pages in its Provider. */\n AppContext: Context<AppContextValue | null>\n /** fetch() with the platform's Authorization header attached. */\n authFetch: (input: string, init?: RequestInit) => Promise<Response>\n /**\n * The SDK surface app bundles receive for `@veltrixsecops/app-sdk`,\n * `.../hooks`, and `.../client` imports (useAppContext, AppContext,\n * usePipelineStatus, authFetch, getHostRuntime, ...).\n */\n sdk: Record<string, unknown>\n}\n\n/** Read the host runtime, or null outside the platform (tests, storybook). */\nexport function getHostRuntime(): VeltrixHostRuntime | null {\n const runtime = (globalThis as Record<string, unknown>)[HOST_RUNTIME_GLOBAL]\n return (runtime as VeltrixHostRuntime) ?? null\n}\n\n/** Read the host runtime, throwing a diagnosable error when absent. */\nexport function requireHostRuntime(): VeltrixHostRuntime {\n const runtime = getHostRuntime()\n if (!runtime) {\n throw new Error(\n 'Veltrix host runtime not found — app client bundles only run inside the ' +\n `Veltrix platform (missing globalThis.${HOST_RUNTIME_GLOBAL})`,\n )\n }\n return runtime\n}\n\n/**\n * fetch() that carries the platform's Authorization header. Required for an\n * app page to call its own server routes (/api/apps/<app-id>/...), which are\n * bearer-token protected. Falls back to plain fetch outside the platform.\n */\nexport function authFetch(input: string, init?: RequestInit): Promise<Response> {\n const runtime = getHostRuntime()\n if (runtime) return runtime.authFetch(input, init)\n return fetch(input, init)\n}\n\n/** A sidebar entry contributed by the app's client entry module. */\nexport interface AppSidebarItem {\n path: string\n label: string\n icon?: string\n}\n\n/**\n * Shape of the default export of an app's `client/index.tsx`.\n * `pages` keys must match `manifest.client.pages[].component`.\n */\nexport interface AppClientModule {\n id: string\n pages: Record<string, ComponentType | LazyExoticComponent<ComponentType>>\n sidebarItems?: AppSidebarItem[]\n}\n","// ========================================================================\n// App manifest types\n//\n// Self-contained copy of the platform's manifest contract so the SDK can\n// be published and consumed outside the platform monorepo. The platform\n// keeps its own copy in shared/types/app.ts — changes to the manifest\n// contract must be applied to both files.\n// ========================================================================\n\nexport type AppSource = 'BUILT_IN' | 'MARKETPLACE' | 'CUSTOM'\nexport type AppStatusType = 'AVAILABLE' | 'DEPRECATED' | 'REMOVED'\nexport type AppInstallationStatus =\n | 'INSTALLING'\n | 'INSTALLED'\n | 'ENABLED'\n | 'DISABLED'\n | 'FAILED'\n | 'UNINSTALLING'\n\n// --- Manifest Types (parsed from manifest.yaml) ---\n\nexport interface AppManifest {\n id: string\n name: string\n version: string\n vendor: string\n description: string\n category: string\n license?: string\n homepage?: string\n icon?: string\n logo?: string\n\n platform: {\n minVersion: string\n }\n\n permissions: {\n platform: string[] // Platform permissions the app needs\n app: AppPermissionDeclaration[] // Permissions the app exposes\n }\n\n database?: {\n migrations: string\n tablePrefix: string\n }\n\n pipeline: {\n configurationTypes: AppConfigurationTypeManifest[]\n pipelineEvents?: string[]\n }\n\n server: {\n entry: string\n routes?: {\n prefix: string\n }\n }\n\n client?: {\n entry: string\n pages?: AppPageDeclaration[]\n }\n\n hooks?: {\n onInstall?: string\n onUninstall?: string\n onEnable?: string\n onDisable?: string\n onUpgrade?: string\n }\n\n events?: string[] // Platform events this app subscribes to\n\n settings?: AppSettingDeclaration[]\n}\n\nexport interface AppConfigurationTypeManifest {\n id: string\n name: string\n description?: string\n canvasTemplate: string // Path to canvas template YAML\n defaultConfig?: string // Path to default config YAML\n\n handlers: {\n validate: string\n deploy: string\n rollback: string\n healthCheck: string\n driftDetect?: string | null\n getStatus: string\n }\n\n targets: {\n componentTypes: string[]\n requiresCredential: boolean\n requiresConnectivity: boolean\n }\n}\n\nexport interface AppPermissionDeclaration {\n resource: string\n actions: string[]\n description?: string\n}\n\n// --- App UI & navigation contract ---\n//\n// The platform owns the chrome: breadcrumb, app header, navigation, permission\n// gating, error boundary and loading states are rendered identically for every\n// app. Apps own the page body and compose it from @veltrixsecops/ui.\n// Predictable shell, flexible body.\n\n/**\n * How the platform frames an app page.\n * - `standard` — page header + padded content area (default; use for most pages)\n * - `full-bleed` — content area with no padding/toolbar (custom canvases, maps)\n * - `canvas` — Configuration Canvas chrome (section rail + save/validate bar)\n */\nexport type AppPageLayout = 'standard' | 'full-bleed' | 'canvas'\nexport const APP_PAGE_LAYOUTS = ['standard', 'full-bleed', 'canvas'] as const\n\n/**\n * Where the page surfaces in navigation.\n * - `sidebar` — an entry beneath the app in the sidebar\n * - `tab` — a tab within its `parent` page\n * - `hidden` — routable but not linked (details/drill-down pages)\n */\nexport type AppPageNav = 'sidebar' | 'tab' | 'hidden'\nexport const APP_PAGE_NAV = ['sidebar', 'tab', 'hidden'] as const\n\n/** An app-scoped permission (declared in `permissions.app`) required to see a page. */\nexport interface AppPagePermission {\n resource: string\n action: string\n}\n\nexport interface AppPageDeclaration {\n /** Route beneath the app, e.g. `/indexes` → `/apps/<app-id>/indexes` */\n path: string\n /** Exported component name from the app's client entry */\n component: string\n label: string\n description?: string\n /** Icon name from the platform icon set; falls back to the app icon */\n icon?: string\n /** @deprecated use `nav: 'sidebar' | 'hidden'` — kept for backward compatibility */\n sidebar?: boolean\n /** Navigation placement. Defaults to `sidebar` when `sidebar: true`, else `hidden`. */\n nav?: AppPageNav\n /** Parent page `path` — required for `nav: 'tab'`, optional nesting for sidebar entries */\n parent?: string\n /** Optional sidebar section label, for apps with many pages */\n group?: string\n /** Deterministic ordering within its group/parent (ascending; ties break on label) */\n order?: number\n /** Layout preset the platform renders around the page body */\n layout?: AppPageLayout\n /** Hide the page (and its nav entry) unless the user holds this app permission */\n requiresPermission?: AppPagePermission\n}\n\nexport interface AppSettingDeclaration {\n key: string\n type: 'string' | 'number' | 'boolean' | 'select'\n label: string\n description?: string\n default?: string | number | boolean\n required?: boolean\n options?: Array<{ label: string; value: string }>\n}\n\n// --- API Response Types ---\n\nexport interface AppListItem {\n id: string\n appId: string\n name: string\n version: string\n vendor: string\n description: string\n category: string\n icon?: string\n logo?: string\n source: AppSource\n isDefault: boolean\n status: AppStatusType\n installed?: boolean\n enabled?: boolean\n}\n\nexport interface AppDetail extends AppListItem {\n license?: string\n homepage?: string\n repository?: string\n configurationTypes: Array<{\n id: string\n name: string\n description?: string\n componentTypes: string[]\n }>\n permissions: AppPermissionDeclaration[]\n settings: AppSettingDeclaration[]\n}\n\nexport interface AppInstallationDetail {\n id: string\n appId: string\n customerId: string\n version: string\n enabled: boolean\n installedBy: string\n installedAt: string\n settings: Record<string, unknown>\n status: AppInstallationStatus\n app: AppListItem\n}\n","// ========================================================================\n// Canonical app layout\n//\n// Every Veltrix app follows one predictable folder structure so apps are\n// easy to set up, review, and load. The `veltrix` CLI scaffolds it,\n// `veltrix validate` (and repo CI) warn on deviations, and the platform's\n// app engine resolves manifest references against it.\n// ========================================================================\n\n/** Canonical locations inside an app directory. */\nexport const APP_LAYOUT = {\n /** The app contract. Always at the app root. */\n manifest: 'manifest.yaml',\n /**\n * The unit of extension: config-types/<configTypeId>/ colocates everything\n * for one configuration type — canvas.yaml, defaults.yaml, the six pipeline\n * handlers (extensionless in the manifest), and __tests__/.\n */\n configTypesDir: 'config-types',\n /** Canvas form schema filename inside a config-type folder. */\n canvasFile: 'canvas.yaml',\n /** Default field values filename inside a config-type folder. */\n defaultsFile: 'defaults.yaml',\n /** Lifecycle hooks (camelCase): hooks/onInstall, hooks/onUninstall, ... */\n hooksDir: 'hooks',\n /** Shared app code used by multiple handlers (API clients, parsers). */\n libDir: 'lib',\n /** SQL migrations (requires manifest `database.tablePrefix`). */\n migrationsDir: 'migrations',\n /** Fastify route module receiving (fastify, AppRouteContext). */\n serverEntry: 'server/index',\n /** Client entry registering pages/sidebar items (optional). */\n clientEntry: 'client/index',\n /** Icons and logos (optional). */\n assetsDir: 'assets',\n /** Tests live next to the code they cover: handlers/<id>/__tests__/ */\n testsDirName: '__tests__',\n} as const\n\n/** The six pipeline handler names, in lifecycle order. */\nexport const HANDLER_NAMES = [\n 'validate',\n 'deploy',\n 'rollback',\n 'healthCheck',\n 'driftDetect',\n 'getStatus',\n] as const\n\nexport type HandlerName = (typeof HANDLER_NAMES)[number]\n\n/**\n * Conventional manifest paths for one configuration type — handy when\n * generating or checking a manifest programmatically.\n *\n * @example\n * conventionalPaths('indexes').handlers.deploy // 'config-types/indexes/deploy'\n */\nexport function conventionalPaths(configTypeId: string): {\n canvasTemplate: string\n defaultConfig: string\n handlers: Record<HandlerName, string>\n} {\n const base = `${APP_LAYOUT.configTypesDir}/${configTypeId}`\n const handlers = {} as Record<HandlerName, string>\n for (const name of HANDLER_NAMES) {\n handlers[name] = `${base}/${name}`\n }\n return {\n canvasTemplate: `${base}/${APP_LAYOUT.canvasFile}`,\n defaultConfig: `${base}/${APP_LAYOUT.defaultsFile}`,\n handlers,\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACmBO,SAAS,gBACd,SACqD;AACrD,SAAO;AACT;;;ACPO,SAAS,eACd,SAC+C;AAC/C,SAAO;AACT;;;ACfO,SAAS,sBACd,SACmD;AACnD,SAAO;AACT;;;ACJO,SAAS,oBACd,SACyD;AACzD,SAAO;AACT;;;ACJO,SAAS,oBACd,SAC6C;AAC7C,SAAO;AACT;;;ACJA,mBAAwD;AAqBxD,IAAM,cACH,WAAuC,yBAGvC;AAEI,IAAM,aACX,mBAAe,4BAAsC,IAAI;AAepD,SAAS,gBAAiC;AAC/C,QAAM,UAAM,yBAAW,UAAU;AACjC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,yDAAyD;AAAA,EAC3E;AACA,SAAO;AACT;;;ACtDA,IAAAA,gBAAiD;;;ACqB1C,IAAM,sBAAsB;AA8B5B,SAAS,iBAA4C;AAC1D,QAAM,UAAW,WAAuC,mBAAmB;AAC3E,SAAQ,WAAkC;AAC5C;AAmBO,SAAS,UAAU,OAAe,MAAuC;AAC9E,QAAM,UAAU,eAAe;AAC/B,MAAI,QAAS,QAAO,QAAQ,UAAU,OAAO,IAAI;AACjD,SAAO,MAAM,OAAO,IAAI;AAC1B;;;AD7CO,SAAS,kBAAkB,OAAe;AAC/C,QAAM,CAAC,MAAM,OAAO,QAAI,wBAAoC,IAAI;AAChE,QAAM,CAAC,WAAW,YAAY,QAAI,wBAAS,IAAI;AAC/C,QAAM,CAAC,OAAO,QAAQ,QAAI,wBAAuB,IAAI;AAErD,QAAM,cAAU,2BAAY,YAAY;AACtC,QAAI;AACF,mBAAa,IAAI;AAEjB,YAAM,WAAW,MAAM,UAAU,+BAA+B,KAAK,EAAE;AACvE,UAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,iCAAiC;AACnE,YAAM,SAAS,MAAM,SAAS,KAAK;AACnC,cAAQ,MAAM;AACd,eAAS,IAAI;AAAA,IACf,SAAS,KAAK;AACZ,eAAS,eAAe,QAAQ,MAAM,IAAI,MAAM,eAAe,CAAC;AAAA,IAClE,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,KAAK,CAAC;AAEV,+BAAU,MAAM;AACd,YAAQ;AAAA,EACV,GAAG,CAAC,OAAO,CAAC;AAEZ,SAAO,EAAE,MAAM,WAAW,OAAO,QAAQ;AAC3C;;;AE8DO,IAAM,mBAAmB,CAAC,YAAY,cAAc,QAAQ;AAS5D,IAAM,eAAe,CAAC,WAAW,OAAO,QAAQ;;;ACvHhD,IAAM,aAAa;AAAA;AAAA,EAExB,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMV,gBAAgB;AAAA;AAAA,EAEhB,YAAY;AAAA;AAAA,EAEZ,cAAc;AAAA;AAAA,EAEd,UAAU;AAAA;AAAA,EAEV,QAAQ;AAAA;AAAA,EAER,eAAe;AAAA;AAAA,EAEf,aAAa;AAAA;AAAA,EAEb,aAAa;AAAA;AAAA,EAEb,WAAW;AAAA;AAAA,EAEX,cAAc;AAChB;AAGO,IAAM,gBAAgB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAWO,SAAS,kBAAkB,cAIhC;AACA,QAAM,OAAO,GAAG,WAAW,cAAc,IAAI,YAAY;AACzD,QAAM,WAAW,CAAC;AAClB,aAAW,QAAQ,eAAe;AAChC,aAAS,IAAI,IAAI,GAAG,IAAI,IAAI,IAAI;AAAA,EAClC;AACA,SAAO;AAAA,IACL,gBAAgB,GAAG,IAAI,IAAI,WAAW,UAAU;AAAA,IAChD,eAAe,GAAG,IAAI,IAAI,WAAW,YAAY;AAAA,IACjD;AAAA,EACF;AACF;","names":["import_react"]}
package/dist/index.d.cts CHANGED
@@ -1,3 +1,51 @@
1
1
  export { C as CanvasSectionSnapshot, a as CanvasSnapshot, b as ComponentConfigStatus, c as ComponentRef, d as ConfigStatus, e as ConnectivityProviderRef, f as ConnectivityRef, g as CredentialRef, D as DeployContext, h as DeployHandler, i as DeployResult, j as DeploymentStrategy, k as DeploymentSummary, l as DriftContext, m as DriftDetectHandler, n as DriftDiff, o as DriftResult, E as EnvironmentRef, G as GetStatusHandler, H as HealthCheck, p as HealthCheckContext, q as HealthCheckHandler, r as HealthCheckResult, P as PipelineContext, s as PlatformDataApi, R as RollbackContext, t as RollbackHandler, u as RollbackResult, U as UserRef, V as ValidateHandler, v as ValidationError, w as ValidationResult, x as ValidationWarning, y as defineDeployer, z as defineDriftDetector, A as defineHealthChecker, B as defineRollbackHandler, F as defineValidator } from './index-CKeIf4Jx.cjs';
2
- export { A as AppConfigurationTypeManifest, a as AppContext, b as AppContextValue, c as AppDetail, d as AppHookContext, e as AppInstallationDetail, f as AppInstallationStatus, g as AppListItem, h as AppManifest, i as AppPageDeclaration, j as AppPermissionDeclaration, k as AppRouteContext, l as AppSettingDeclaration, m as AppSource, n as AppStatusType, C as Component, o as Connectivity, p as Credential, q as Customer, P as PipelineStatusData, r as PlatformDatabaseClient, T as Tag, U as User, u as useAppContext, s as usePipelineStatus } from './index-BI8f_mBw.cjs';
2
+ export { a as APP_PAGE_LAYOUTS, b as APP_PAGE_NAV, c as AppConfigurationTypeManifest, d as AppContext, A as AppContextValue, e as AppDetail, f as AppHookContext, g as AppInstallationDetail, h as AppInstallationStatus, i as AppListItem, j as AppManifest, k as AppPageDeclaration, l as AppPageLayout, m as AppPageNav, n as AppPagePermission, o as AppPermissionDeclaration, p as AppRouteContext, q as AppSettingDeclaration, r as AppSource, s as AppStatusType, C as Component, t as Connectivity, u as Credential, v as Customer, P as PlatformDatabaseClient, T as Tag, U as User, w as useAppContext } from './use-app-context-PBuG144m.cjs';
3
+ export { PipelineStatusData, usePipelineStatus } from './hooks/index.cjs';
3
4
  import 'react';
5
+
6
+ /** Canonical locations inside an app directory. */
7
+ declare const APP_LAYOUT: {
8
+ /** The app contract. Always at the app root. */
9
+ readonly manifest: "manifest.yaml";
10
+ /**
11
+ * The unit of extension: config-types/<configTypeId>/ colocates everything
12
+ * for one configuration type — canvas.yaml, defaults.yaml, the six pipeline
13
+ * handlers (extensionless in the manifest), and __tests__/.
14
+ */
15
+ readonly configTypesDir: "config-types";
16
+ /** Canvas form schema filename inside a config-type folder. */
17
+ readonly canvasFile: "canvas.yaml";
18
+ /** Default field values filename inside a config-type folder. */
19
+ readonly defaultsFile: "defaults.yaml";
20
+ /** Lifecycle hooks (camelCase): hooks/onInstall, hooks/onUninstall, ... */
21
+ readonly hooksDir: "hooks";
22
+ /** Shared app code used by multiple handlers (API clients, parsers). */
23
+ readonly libDir: "lib";
24
+ /** SQL migrations (requires manifest `database.tablePrefix`). */
25
+ readonly migrationsDir: "migrations";
26
+ /** Fastify route module receiving (fastify, AppRouteContext). */
27
+ readonly serverEntry: "server/index";
28
+ /** Client entry registering pages/sidebar items (optional). */
29
+ readonly clientEntry: "client/index";
30
+ /** Icons and logos (optional). */
31
+ readonly assetsDir: "assets";
32
+ /** Tests live next to the code they cover: handlers/<id>/__tests__/ */
33
+ readonly testsDirName: "__tests__";
34
+ };
35
+ /** The six pipeline handler names, in lifecycle order. */
36
+ declare const HANDLER_NAMES: readonly ["validate", "deploy", "rollback", "healthCheck", "driftDetect", "getStatus"];
37
+ type HandlerName = (typeof HANDLER_NAMES)[number];
38
+ /**
39
+ * Conventional manifest paths for one configuration type — handy when
40
+ * generating or checking a manifest programmatically.
41
+ *
42
+ * @example
43
+ * conventionalPaths('indexes').handlers.deploy // 'config-types/indexes/deploy'
44
+ */
45
+ declare function conventionalPaths(configTypeId: string): {
46
+ canvasTemplate: string;
47
+ defaultConfig: string;
48
+ handlers: Record<HandlerName, string>;
49
+ };
50
+
51
+ export { APP_LAYOUT, HANDLER_NAMES, type HandlerName, conventionalPaths };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,51 @@
1
1
  export { C as CanvasSectionSnapshot, a as CanvasSnapshot, b as ComponentConfigStatus, c as ComponentRef, d as ConfigStatus, e as ConnectivityProviderRef, f as ConnectivityRef, g as CredentialRef, D as DeployContext, h as DeployHandler, i as DeployResult, j as DeploymentStrategy, k as DeploymentSummary, l as DriftContext, m as DriftDetectHandler, n as DriftDiff, o as DriftResult, E as EnvironmentRef, G as GetStatusHandler, H as HealthCheck, p as HealthCheckContext, q as HealthCheckHandler, r as HealthCheckResult, P as PipelineContext, s as PlatformDataApi, R as RollbackContext, t as RollbackHandler, u as RollbackResult, U as UserRef, V as ValidateHandler, v as ValidationError, w as ValidationResult, x as ValidationWarning, y as defineDeployer, z as defineDriftDetector, A as defineHealthChecker, B as defineRollbackHandler, F as defineValidator } from './index-CKeIf4Jx.js';
2
- export { A as AppConfigurationTypeManifest, a as AppContext, b as AppContextValue, c as AppDetail, d as AppHookContext, e as AppInstallationDetail, f as AppInstallationStatus, g as AppListItem, h as AppManifest, i as AppPageDeclaration, j as AppPermissionDeclaration, k as AppRouteContext, l as AppSettingDeclaration, m as AppSource, n as AppStatusType, C as Component, o as Connectivity, p as Credential, q as Customer, P as PipelineStatusData, r as PlatformDatabaseClient, T as Tag, U as User, u as useAppContext, s as usePipelineStatus } from './index-BI8f_mBw.js';
2
+ export { a as APP_PAGE_LAYOUTS, b as APP_PAGE_NAV, c as AppConfigurationTypeManifest, d as AppContext, A as AppContextValue, e as AppDetail, f as AppHookContext, g as AppInstallationDetail, h as AppInstallationStatus, i as AppListItem, j as AppManifest, k as AppPageDeclaration, l as AppPageLayout, m as AppPageNav, n as AppPagePermission, o as AppPermissionDeclaration, p as AppRouteContext, q as AppSettingDeclaration, r as AppSource, s as AppStatusType, C as Component, t as Connectivity, u as Credential, v as Customer, P as PlatformDatabaseClient, T as Tag, U as User, w as useAppContext } from './use-app-context-PBuG144m.js';
3
+ export { PipelineStatusData, usePipelineStatus } from './hooks/index.js';
3
4
  import 'react';
5
+
6
+ /** Canonical locations inside an app directory. */
7
+ declare const APP_LAYOUT: {
8
+ /** The app contract. Always at the app root. */
9
+ readonly manifest: "manifest.yaml";
10
+ /**
11
+ * The unit of extension: config-types/<configTypeId>/ colocates everything
12
+ * for one configuration type — canvas.yaml, defaults.yaml, the six pipeline
13
+ * handlers (extensionless in the manifest), and __tests__/.
14
+ */
15
+ readonly configTypesDir: "config-types";
16
+ /** Canvas form schema filename inside a config-type folder. */
17
+ readonly canvasFile: "canvas.yaml";
18
+ /** Default field values filename inside a config-type folder. */
19
+ readonly defaultsFile: "defaults.yaml";
20
+ /** Lifecycle hooks (camelCase): hooks/onInstall, hooks/onUninstall, ... */
21
+ readonly hooksDir: "hooks";
22
+ /** Shared app code used by multiple handlers (API clients, parsers). */
23
+ readonly libDir: "lib";
24
+ /** SQL migrations (requires manifest `database.tablePrefix`). */
25
+ readonly migrationsDir: "migrations";
26
+ /** Fastify route module receiving (fastify, AppRouteContext). */
27
+ readonly serverEntry: "server/index";
28
+ /** Client entry registering pages/sidebar items (optional). */
29
+ readonly clientEntry: "client/index";
30
+ /** Icons and logos (optional). */
31
+ readonly assetsDir: "assets";
32
+ /** Tests live next to the code they cover: handlers/<id>/__tests__/ */
33
+ readonly testsDirName: "__tests__";
34
+ };
35
+ /** The six pipeline handler names, in lifecycle order. */
36
+ declare const HANDLER_NAMES: readonly ["validate", "deploy", "rollback", "healthCheck", "driftDetect", "getStatus"];
37
+ type HandlerName = (typeof HANDLER_NAMES)[number];
38
+ /**
39
+ * Conventional manifest paths for one configuration type — handy when
40
+ * generating or checking a manifest programmatically.
41
+ *
42
+ * @example
43
+ * conventionalPaths('indexes').handlers.deploy // 'config-types/indexes/deploy'
44
+ */
45
+ declare function conventionalPaths(configTypeId: string): {
46
+ canvasTemplate: string;
47
+ defaultConfig: string;
48
+ handlers: Record<HandlerName, string>;
49
+ };
50
+
51
+ export { APP_LAYOUT, HANDLER_NAMES, type HandlerName, conventionalPaths };
package/dist/index.js CHANGED
@@ -9,9 +9,69 @@ import {
9
9
  AppContext,
10
10
  useAppContext,
11
11
  usePipelineStatus
12
- } from "./chunk-TSEIWO6T.js";
12
+ } from "./chunk-JDEA2F7I.js";
13
+ import "./chunk-I4JJX4LL.js";
14
+
15
+ // src/types/manifest.ts
16
+ var APP_PAGE_LAYOUTS = ["standard", "full-bleed", "canvas"];
17
+ var APP_PAGE_NAV = ["sidebar", "tab", "hidden"];
18
+
19
+ // src/structure.ts
20
+ var APP_LAYOUT = {
21
+ /** The app contract. Always at the app root. */
22
+ manifest: "manifest.yaml",
23
+ /**
24
+ * The unit of extension: config-types/<configTypeId>/ colocates everything
25
+ * for one configuration type — canvas.yaml, defaults.yaml, the six pipeline
26
+ * handlers (extensionless in the manifest), and __tests__/.
27
+ */
28
+ configTypesDir: "config-types",
29
+ /** Canvas form schema filename inside a config-type folder. */
30
+ canvasFile: "canvas.yaml",
31
+ /** Default field values filename inside a config-type folder. */
32
+ defaultsFile: "defaults.yaml",
33
+ /** Lifecycle hooks (camelCase): hooks/onInstall, hooks/onUninstall, ... */
34
+ hooksDir: "hooks",
35
+ /** Shared app code used by multiple handlers (API clients, parsers). */
36
+ libDir: "lib",
37
+ /** SQL migrations (requires manifest `database.tablePrefix`). */
38
+ migrationsDir: "migrations",
39
+ /** Fastify route module receiving (fastify, AppRouteContext). */
40
+ serverEntry: "server/index",
41
+ /** Client entry registering pages/sidebar items (optional). */
42
+ clientEntry: "client/index",
43
+ /** Icons and logos (optional). */
44
+ assetsDir: "assets",
45
+ /** Tests live next to the code they cover: handlers/<id>/__tests__/ */
46
+ testsDirName: "__tests__"
47
+ };
48
+ var HANDLER_NAMES = [
49
+ "validate",
50
+ "deploy",
51
+ "rollback",
52
+ "healthCheck",
53
+ "driftDetect",
54
+ "getStatus"
55
+ ];
56
+ function conventionalPaths(configTypeId) {
57
+ const base = `${APP_LAYOUT.configTypesDir}/${configTypeId}`;
58
+ const handlers = {};
59
+ for (const name of HANDLER_NAMES) {
60
+ handlers[name] = `${base}/${name}`;
61
+ }
62
+ return {
63
+ canvasTemplate: `${base}/${APP_LAYOUT.canvasFile}`,
64
+ defaultConfig: `${base}/${APP_LAYOUT.defaultsFile}`,
65
+ handlers
66
+ };
67
+ }
13
68
  export {
69
+ APP_LAYOUT,
70
+ APP_PAGE_LAYOUTS,
71
+ APP_PAGE_NAV,
14
72
  AppContext,
73
+ HANDLER_NAMES,
74
+ conventionalPaths,
15
75
  defineDeployer,
16
76
  defineDriftDetector,
17
77
  defineHealthChecker,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
1
+ {"version":3,"sources":["../src/types/manifest.ts","../src/structure.ts"],"sourcesContent":["// ========================================================================\n// App manifest types\n//\n// Self-contained copy of the platform's manifest contract so the SDK can\n// be published and consumed outside the platform monorepo. The platform\n// keeps its own copy in shared/types/app.ts — changes to the manifest\n// contract must be applied to both files.\n// ========================================================================\n\nexport type AppSource = 'BUILT_IN' | 'MARKETPLACE' | 'CUSTOM'\nexport type AppStatusType = 'AVAILABLE' | 'DEPRECATED' | 'REMOVED'\nexport type AppInstallationStatus =\n | 'INSTALLING'\n | 'INSTALLED'\n | 'ENABLED'\n | 'DISABLED'\n | 'FAILED'\n | 'UNINSTALLING'\n\n// --- Manifest Types (parsed from manifest.yaml) ---\n\nexport interface AppManifest {\n id: string\n name: string\n version: string\n vendor: string\n description: string\n category: string\n license?: string\n homepage?: string\n icon?: string\n logo?: string\n\n platform: {\n minVersion: string\n }\n\n permissions: {\n platform: string[] // Platform permissions the app needs\n app: AppPermissionDeclaration[] // Permissions the app exposes\n }\n\n database?: {\n migrations: string\n tablePrefix: string\n }\n\n pipeline: {\n configurationTypes: AppConfigurationTypeManifest[]\n pipelineEvents?: string[]\n }\n\n server: {\n entry: string\n routes?: {\n prefix: string\n }\n }\n\n client?: {\n entry: string\n pages?: AppPageDeclaration[]\n }\n\n hooks?: {\n onInstall?: string\n onUninstall?: string\n onEnable?: string\n onDisable?: string\n onUpgrade?: string\n }\n\n events?: string[] // Platform events this app subscribes to\n\n settings?: AppSettingDeclaration[]\n}\n\nexport interface AppConfigurationTypeManifest {\n id: string\n name: string\n description?: string\n canvasTemplate: string // Path to canvas template YAML\n defaultConfig?: string // Path to default config YAML\n\n handlers: {\n validate: string\n deploy: string\n rollback: string\n healthCheck: string\n driftDetect?: string | null\n getStatus: string\n }\n\n targets: {\n componentTypes: string[]\n requiresCredential: boolean\n requiresConnectivity: boolean\n }\n}\n\nexport interface AppPermissionDeclaration {\n resource: string\n actions: string[]\n description?: string\n}\n\n// --- App UI & navigation contract ---\n//\n// The platform owns the chrome: breadcrumb, app header, navigation, permission\n// gating, error boundary and loading states are rendered identically for every\n// app. Apps own the page body and compose it from @veltrixsecops/ui.\n// Predictable shell, flexible body.\n\n/**\n * How the platform frames an app page.\n * - `standard` — page header + padded content area (default; use for most pages)\n * - `full-bleed` — content area with no padding/toolbar (custom canvases, maps)\n * - `canvas` — Configuration Canvas chrome (section rail + save/validate bar)\n */\nexport type AppPageLayout = 'standard' | 'full-bleed' | 'canvas'\nexport const APP_PAGE_LAYOUTS = ['standard', 'full-bleed', 'canvas'] as const\n\n/**\n * Where the page surfaces in navigation.\n * - `sidebar` — an entry beneath the app in the sidebar\n * - `tab` — a tab within its `parent` page\n * - `hidden` — routable but not linked (details/drill-down pages)\n */\nexport type AppPageNav = 'sidebar' | 'tab' | 'hidden'\nexport const APP_PAGE_NAV = ['sidebar', 'tab', 'hidden'] as const\n\n/** An app-scoped permission (declared in `permissions.app`) required to see a page. */\nexport interface AppPagePermission {\n resource: string\n action: string\n}\n\nexport interface AppPageDeclaration {\n /** Route beneath the app, e.g. `/indexes` → `/apps/<app-id>/indexes` */\n path: string\n /** Exported component name from the app's client entry */\n component: string\n label: string\n description?: string\n /** Icon name from the platform icon set; falls back to the app icon */\n icon?: string\n /** @deprecated use `nav: 'sidebar' | 'hidden'` — kept for backward compatibility */\n sidebar?: boolean\n /** Navigation placement. Defaults to `sidebar` when `sidebar: true`, else `hidden`. */\n nav?: AppPageNav\n /** Parent page `path` — required for `nav: 'tab'`, optional nesting for sidebar entries */\n parent?: string\n /** Optional sidebar section label, for apps with many pages */\n group?: string\n /** Deterministic ordering within its group/parent (ascending; ties break on label) */\n order?: number\n /** Layout preset the platform renders around the page body */\n layout?: AppPageLayout\n /** Hide the page (and its nav entry) unless the user holds this app permission */\n requiresPermission?: AppPagePermission\n}\n\nexport interface AppSettingDeclaration {\n key: string\n type: 'string' | 'number' | 'boolean' | 'select'\n label: string\n description?: string\n default?: string | number | boolean\n required?: boolean\n options?: Array<{ label: string; value: string }>\n}\n\n// --- API Response Types ---\n\nexport interface AppListItem {\n id: string\n appId: string\n name: string\n version: string\n vendor: string\n description: string\n category: string\n icon?: string\n logo?: string\n source: AppSource\n isDefault: boolean\n status: AppStatusType\n installed?: boolean\n enabled?: boolean\n}\n\nexport interface AppDetail extends AppListItem {\n license?: string\n homepage?: string\n repository?: string\n configurationTypes: Array<{\n id: string\n name: string\n description?: string\n componentTypes: string[]\n }>\n permissions: AppPermissionDeclaration[]\n settings: AppSettingDeclaration[]\n}\n\nexport interface AppInstallationDetail {\n id: string\n appId: string\n customerId: string\n version: string\n enabled: boolean\n installedBy: string\n installedAt: string\n settings: Record<string, unknown>\n status: AppInstallationStatus\n app: AppListItem\n}\n","// ========================================================================\n// Canonical app layout\n//\n// Every Veltrix app follows one predictable folder structure so apps are\n// easy to set up, review, and load. The `veltrix` CLI scaffolds it,\n// `veltrix validate` (and repo CI) warn on deviations, and the platform's\n// app engine resolves manifest references against it.\n// ========================================================================\n\n/** Canonical locations inside an app directory. */\nexport const APP_LAYOUT = {\n /** The app contract. Always at the app root. */\n manifest: 'manifest.yaml',\n /**\n * The unit of extension: config-types/<configTypeId>/ colocates everything\n * for one configuration type — canvas.yaml, defaults.yaml, the six pipeline\n * handlers (extensionless in the manifest), and __tests__/.\n */\n configTypesDir: 'config-types',\n /** Canvas form schema filename inside a config-type folder. */\n canvasFile: 'canvas.yaml',\n /** Default field values filename inside a config-type folder. */\n defaultsFile: 'defaults.yaml',\n /** Lifecycle hooks (camelCase): hooks/onInstall, hooks/onUninstall, ... */\n hooksDir: 'hooks',\n /** Shared app code used by multiple handlers (API clients, parsers). */\n libDir: 'lib',\n /** SQL migrations (requires manifest `database.tablePrefix`). */\n migrationsDir: 'migrations',\n /** Fastify route module receiving (fastify, AppRouteContext). */\n serverEntry: 'server/index',\n /** Client entry registering pages/sidebar items (optional). */\n clientEntry: 'client/index',\n /** Icons and logos (optional). */\n assetsDir: 'assets',\n /** Tests live next to the code they cover: handlers/<id>/__tests__/ */\n testsDirName: '__tests__',\n} as const\n\n/** The six pipeline handler names, in lifecycle order. */\nexport const HANDLER_NAMES = [\n 'validate',\n 'deploy',\n 'rollback',\n 'healthCheck',\n 'driftDetect',\n 'getStatus',\n] as const\n\nexport type HandlerName = (typeof HANDLER_NAMES)[number]\n\n/**\n * Conventional manifest paths for one configuration type — handy when\n * generating or checking a manifest programmatically.\n *\n * @example\n * conventionalPaths('indexes').handlers.deploy // 'config-types/indexes/deploy'\n */\nexport function conventionalPaths(configTypeId: string): {\n canvasTemplate: string\n defaultConfig: string\n handlers: Record<HandlerName, string>\n} {\n const base = `${APP_LAYOUT.configTypesDir}/${configTypeId}`\n const handlers = {} as Record<HandlerName, string>\n for (const name of HANDLER_NAMES) {\n handlers[name] = `${base}/${name}`\n }\n return {\n canvasTemplate: `${base}/${APP_LAYOUT.canvasFile}`,\n defaultConfig: `${base}/${APP_LAYOUT.defaultsFile}`,\n handlers,\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAwHO,IAAM,mBAAmB,CAAC,YAAY,cAAc,QAAQ;AAS5D,IAAM,eAAe,CAAC,WAAW,OAAO,QAAQ;;;ACvHhD,IAAM,aAAa;AAAA;AAAA,EAExB,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMV,gBAAgB;AAAA;AAAA,EAEhB,YAAY;AAAA;AAAA,EAEZ,cAAc;AAAA;AAAA,EAEd,UAAU;AAAA;AAAA,EAEV,QAAQ;AAAA;AAAA,EAER,eAAe;AAAA;AAAA,EAEf,aAAa;AAAA;AAAA,EAEb,aAAa;AAAA;AAAA,EAEb,WAAW;AAAA;AAAA,EAEX,cAAc;AAChB;AAGO,IAAM,gBAAgB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAWO,SAAS,kBAAkB,cAIhC;AACA,QAAM,OAAO,GAAG,WAAW,cAAc,IAAI,YAAY;AACzD,QAAM,WAAW,CAAC;AAClB,aAAW,QAAQ,eAAe;AAChC,aAAS,IAAI,IAAI,GAAG,IAAI,IAAI,IAAI;AAAA,EAClC;AACA,SAAO;AAAA,IACL,gBAAgB,GAAG,IAAI,IAAI,WAAW,UAAU;AAAA,IAChD,eAAe,GAAG,IAAI,IAAI,WAAW,YAAY;AAAA,IACjD;AAAA,EACF;AACF;","names":[]}
@@ -1,4 +1,4 @@
1
- import * as react from 'react';
1
+ import { Context } from 'react';
2
2
 
3
3
  type AppSource = 'BUILT_IN' | 'MARKETPLACE' | 'CUSTOM';
4
4
  type AppStatusType = 'AVAILABLE' | 'DEPRECATED' | 'REMOVED';
@@ -74,13 +74,50 @@ interface AppPermissionDeclaration {
74
74
  actions: string[];
75
75
  description?: string;
76
76
  }
77
+ /**
78
+ * How the platform frames an app page.
79
+ * - `standard` — page header + padded content area (default; use for most pages)
80
+ * - `full-bleed` — content area with no padding/toolbar (custom canvases, maps)
81
+ * - `canvas` — Configuration Canvas chrome (section rail + save/validate bar)
82
+ */
83
+ type AppPageLayout = 'standard' | 'full-bleed' | 'canvas';
84
+ declare const APP_PAGE_LAYOUTS: readonly ["standard", "full-bleed", "canvas"];
85
+ /**
86
+ * Where the page surfaces in navigation.
87
+ * - `sidebar` — an entry beneath the app in the sidebar
88
+ * - `tab` — a tab within its `parent` page
89
+ * - `hidden` — routable but not linked (details/drill-down pages)
90
+ */
91
+ type AppPageNav = 'sidebar' | 'tab' | 'hidden';
92
+ declare const APP_PAGE_NAV: readonly ["sidebar", "tab", "hidden"];
93
+ /** An app-scoped permission (declared in `permissions.app`) required to see a page. */
94
+ interface AppPagePermission {
95
+ resource: string;
96
+ action: string;
97
+ }
77
98
  interface AppPageDeclaration {
99
+ /** Route beneath the app, e.g. `/indexes` → `/apps/<app-id>/indexes` */
78
100
  path: string;
101
+ /** Exported component name from the app's client entry */
79
102
  component: string;
80
103
  label: string;
104
+ description?: string;
105
+ /** Icon name from the platform icon set; falls back to the app icon */
81
106
  icon?: string;
107
+ /** @deprecated use `nav: 'sidebar' | 'hidden'` — kept for backward compatibility */
82
108
  sidebar?: boolean;
109
+ /** Navigation placement. Defaults to `sidebar` when `sidebar: true`, else `hidden`. */
110
+ nav?: AppPageNav;
111
+ /** Parent page `path` — required for `nav: 'tab'`, optional nesting for sidebar entries */
83
112
  parent?: string;
113
+ /** Optional sidebar section label, for apps with many pages */
114
+ group?: string;
115
+ /** Deterministic ordering within its group/parent (ascending; ties break on label) */
116
+ order?: number;
117
+ /** Layout preset the platform renders around the page body */
118
+ layout?: AppPageLayout;
119
+ /** Hide the page (and its nav entry) unless the user holds this app permission */
120
+ requiresPermission?: AppPagePermission;
84
121
  }
85
122
  interface AppSettingDeclaration {
86
123
  key: string;
@@ -232,7 +269,7 @@ interface AppContextValue {
232
269
  getTags: () => Promise<Tag[]>;
233
270
  settings: Record<string, unknown>;
234
271
  }
235
- declare const AppContext: react.Context<AppContextValue | null>;
272
+ declare const AppContext: Context<AppContextValue | null>;
236
273
  /**
237
274
  * Access the app context in any app component.
238
275
  *
@@ -248,39 +285,4 @@ declare const AppContext: react.Context<AppContextValue | null>;
248
285
  */
249
286
  declare function useAppContext(): AppContextValue;
250
287
 
251
- interface PipelineStatusData {
252
- pendingApprovals: number;
253
- activeDeployments: number;
254
- failedDeployments: number;
255
- unresolvedDrifts: number;
256
- recentDeployments: Array<{
257
- id: string;
258
- canvasName: string;
259
- environment: string;
260
- status: string;
261
- startedAt: string;
262
- completedAt?: string;
263
- }>;
264
- }
265
- /**
266
- * Hook to access pipeline status for the current app/customer.
267
- *
268
- * @example
269
- * ```tsx
270
- * import { usePipelineStatus } from '@veltrixsecops/app-sdk/hooks'
271
- *
272
- * function Dashboard() {
273
- * const { data, isLoading } = usePipelineStatus('my-app')
274
- * if (isLoading) return <Spinner />
275
- * return <div>{data.activeDeployments} active deployments</div>
276
- * }
277
- * ```
278
- */
279
- declare function usePipelineStatus(appId: string): {
280
- data: PipelineStatusData | null;
281
- isLoading: boolean;
282
- error: Error | null;
283
- refresh: () => Promise<void>;
284
- };
285
-
286
- export { type AppConfigurationTypeManifest as A, type Component as C, type PipelineStatusData as P, type Tag as T, type User as U, AppContext as a, type AppContextValue as b, type AppDetail as c, type AppHookContext as d, type AppInstallationDetail as e, type AppInstallationStatus as f, type AppListItem as g, type AppManifest as h, type AppPageDeclaration as i, type AppPermissionDeclaration as j, type AppRouteContext as k, type AppSettingDeclaration as l, type AppSource as m, type AppStatusType as n, type Connectivity as o, type Credential as p, type Customer as q, type PlatformDatabaseClient as r, usePipelineStatus as s, useAppContext as u };
288
+ export { type AppContextValue as A, type Component as C, type PlatformDatabaseClient as P, type Tag as T, type User as U, APP_PAGE_LAYOUTS as a, APP_PAGE_NAV as b, type AppConfigurationTypeManifest as c, AppContext as d, type AppDetail as e, type AppHookContext as f, type AppInstallationDetail as g, type AppInstallationStatus as h, type AppListItem as i, type AppManifest as j, type AppPageDeclaration as k, type AppPageLayout as l, type AppPageNav as m, type AppPagePermission as n, type AppPermissionDeclaration as o, type AppRouteContext as p, type AppSettingDeclaration as q, type AppSource as r, type AppStatusType as s, type Connectivity as t, type Credential as u, type Customer as v, useAppContext as w };
@@ -1,4 +1,4 @@
1
- import * as react from 'react';
1
+ import { Context } from 'react';
2
2
 
3
3
  type AppSource = 'BUILT_IN' | 'MARKETPLACE' | 'CUSTOM';
4
4
  type AppStatusType = 'AVAILABLE' | 'DEPRECATED' | 'REMOVED';
@@ -74,13 +74,50 @@ interface AppPermissionDeclaration {
74
74
  actions: string[];
75
75
  description?: string;
76
76
  }
77
+ /**
78
+ * How the platform frames an app page.
79
+ * - `standard` — page header + padded content area (default; use for most pages)
80
+ * - `full-bleed` — content area with no padding/toolbar (custom canvases, maps)
81
+ * - `canvas` — Configuration Canvas chrome (section rail + save/validate bar)
82
+ */
83
+ type AppPageLayout = 'standard' | 'full-bleed' | 'canvas';
84
+ declare const APP_PAGE_LAYOUTS: readonly ["standard", "full-bleed", "canvas"];
85
+ /**
86
+ * Where the page surfaces in navigation.
87
+ * - `sidebar` — an entry beneath the app in the sidebar
88
+ * - `tab` — a tab within its `parent` page
89
+ * - `hidden` — routable but not linked (details/drill-down pages)
90
+ */
91
+ type AppPageNav = 'sidebar' | 'tab' | 'hidden';
92
+ declare const APP_PAGE_NAV: readonly ["sidebar", "tab", "hidden"];
93
+ /** An app-scoped permission (declared in `permissions.app`) required to see a page. */
94
+ interface AppPagePermission {
95
+ resource: string;
96
+ action: string;
97
+ }
77
98
  interface AppPageDeclaration {
99
+ /** Route beneath the app, e.g. `/indexes` → `/apps/<app-id>/indexes` */
78
100
  path: string;
101
+ /** Exported component name from the app's client entry */
79
102
  component: string;
80
103
  label: string;
104
+ description?: string;
105
+ /** Icon name from the platform icon set; falls back to the app icon */
81
106
  icon?: string;
107
+ /** @deprecated use `nav: 'sidebar' | 'hidden'` — kept for backward compatibility */
82
108
  sidebar?: boolean;
109
+ /** Navigation placement. Defaults to `sidebar` when `sidebar: true`, else `hidden`. */
110
+ nav?: AppPageNav;
111
+ /** Parent page `path` — required for `nav: 'tab'`, optional nesting for sidebar entries */
83
112
  parent?: string;
113
+ /** Optional sidebar section label, for apps with many pages */
114
+ group?: string;
115
+ /** Deterministic ordering within its group/parent (ascending; ties break on label) */
116
+ order?: number;
117
+ /** Layout preset the platform renders around the page body */
118
+ layout?: AppPageLayout;
119
+ /** Hide the page (and its nav entry) unless the user holds this app permission */
120
+ requiresPermission?: AppPagePermission;
84
121
  }
85
122
  interface AppSettingDeclaration {
86
123
  key: string;
@@ -232,7 +269,7 @@ interface AppContextValue {
232
269
  getTags: () => Promise<Tag[]>;
233
270
  settings: Record<string, unknown>;
234
271
  }
235
- declare const AppContext: react.Context<AppContextValue | null>;
272
+ declare const AppContext: Context<AppContextValue | null>;
236
273
  /**
237
274
  * Access the app context in any app component.
238
275
  *
@@ -248,39 +285,4 @@ declare const AppContext: react.Context<AppContextValue | null>;
248
285
  */
249
286
  declare function useAppContext(): AppContextValue;
250
287
 
251
- interface PipelineStatusData {
252
- pendingApprovals: number;
253
- activeDeployments: number;
254
- failedDeployments: number;
255
- unresolvedDrifts: number;
256
- recentDeployments: Array<{
257
- id: string;
258
- canvasName: string;
259
- environment: string;
260
- status: string;
261
- startedAt: string;
262
- completedAt?: string;
263
- }>;
264
- }
265
- /**
266
- * Hook to access pipeline status for the current app/customer.
267
- *
268
- * @example
269
- * ```tsx
270
- * import { usePipelineStatus } from '@veltrixsecops/app-sdk/hooks'
271
- *
272
- * function Dashboard() {
273
- * const { data, isLoading } = usePipelineStatus('my-app')
274
- * if (isLoading) return <Spinner />
275
- * return <div>{data.activeDeployments} active deployments</div>
276
- * }
277
- * ```
278
- */
279
- declare function usePipelineStatus(appId: string): {
280
- data: PipelineStatusData | null;
281
- isLoading: boolean;
282
- error: Error | null;
283
- refresh: () => Promise<void>;
284
- };
285
-
286
- export { type AppConfigurationTypeManifest as A, type Component as C, type PipelineStatusData as P, type Tag as T, type User as U, AppContext as a, type AppContextValue as b, type AppDetail as c, type AppHookContext as d, type AppInstallationDetail as e, type AppInstallationStatus as f, type AppListItem as g, type AppManifest as h, type AppPageDeclaration as i, type AppPermissionDeclaration as j, type AppRouteContext as k, type AppSettingDeclaration as l, type AppSource as m, type AppStatusType as n, type Connectivity as o, type Credential as p, type Customer as q, type PlatformDatabaseClient as r, usePipelineStatus as s, useAppContext as u };
288
+ export { type AppContextValue as A, type Component as C, type PlatformDatabaseClient as P, type Tag as T, type User as U, APP_PAGE_LAYOUTS as a, APP_PAGE_NAV as b, type AppConfigurationTypeManifest as c, AppContext as d, type AppDetail as e, type AppHookContext as f, type AppInstallationDetail as g, type AppInstallationStatus as h, type AppListItem as i, type AppManifest as j, type AppPageDeclaration as k, type AppPageLayout as l, type AppPageNav as m, type AppPagePermission as n, type AppPermissionDeclaration as o, type AppRouteContext as p, type AppSettingDeclaration as q, type AppSource as r, type AppStatusType as s, type Connectivity as t, type Credential as u, type Customer as v, useAppContext as w };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veltrixsecops/app-sdk",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "description": "Official SDK for building Veltrix Security-as-Code apps — typed pipeline handler contracts, lifecycle hook types, manifest types, and React hooks.",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",
@@ -38,6 +38,16 @@
38
38
  "default": "./dist/hooks/index.cjs"
39
39
  }
40
40
  },
41
+ "./client": {
42
+ "import": {
43
+ "types": "./dist/client/index.d.ts",
44
+ "default": "./dist/client/index.js"
45
+ },
46
+ "require": {
47
+ "types": "./dist/client/index.d.cts",
48
+ "default": "./dist/client/index.cjs"
49
+ }
50
+ },
41
51
  "./package.json": "./package.json"
42
52
  },
43
53
  "files": [
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/hooks/use-app-context.ts","../src/hooks/use-pipeline-status.ts"],"sourcesContent":["// ========================================================================\n// React hooks for app developers\n// These provide access to platform data and pipeline state\n// ========================================================================\n\nimport { createContext, useContext } from 'react'\nimport type { Component, Credential, Tag, User, Customer } from '../types/platform'\n\nexport interface AppContextValue {\n appId: string\n customerId: string\n user: User | null\n customer: Customer | null\n\n // Platform data accessors\n getComponents: () => Promise<Component[]>\n getCredentials: () => Promise<Credential[]>\n getTags: () => Promise<Tag[]>\n\n // App settings\n settings: Record<string, unknown>\n}\n\nexport const AppContext = createContext<AppContextValue | null>(null)\n\n/**\n * Access the app context in any app component.\n *\n * @example\n * ```tsx\n * import { useAppContext } from '@veltrixsecops/app-sdk/hooks'\n *\n * function MyComponent() {\n * const { appId, settings, getComponents } = useAppContext()\n * // ...\n * }\n * ```\n */\nexport function useAppContext(): AppContextValue {\n const ctx = useContext(AppContext)\n if (!ctx) {\n throw new Error('useAppContext must be used within an AppContextProvider')\n }\n return ctx\n}\n","import { useState, useEffect, useCallback } from 'react'\n\nexport interface PipelineStatusData {\n pendingApprovals: number\n activeDeployments: number\n failedDeployments: number\n unresolvedDrifts: number\n recentDeployments: Array<{\n id: string\n canvasName: string\n environment: string\n status: string\n startedAt: string\n completedAt?: string\n }>\n}\n\n/**\n * Hook to access pipeline status for the current app/customer.\n *\n * @example\n * ```tsx\n * import { usePipelineStatus } from '@veltrixsecops/app-sdk/hooks'\n *\n * function Dashboard() {\n * const { data, isLoading } = usePipelineStatus('my-app')\n * if (isLoading) return <Spinner />\n * return <div>{data.activeDeployments} active deployments</div>\n * }\n * ```\n */\nexport function usePipelineStatus(appId: string) {\n const [data, setData] = useState<PipelineStatusData | null>(null)\n const [isLoading, setIsLoading] = useState(true)\n const [error, setError] = useState<Error | null>(null)\n\n const refresh = useCallback(async () => {\n try {\n setIsLoading(true)\n const response = await fetch(`/api/pipeline/summary?appId=${appId}`)\n if (!response.ok) throw new Error(`Failed to fetch pipeline status`)\n const result = await response.json()\n setData(result)\n setError(null)\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Unknown error'))\n } finally {\n setIsLoading(false)\n }\n }, [appId])\n\n useEffect(() => {\n refresh()\n }, [refresh])\n\n return { data, isLoading, error, refresh }\n}\n"],"mappings":";AAKA,SAAS,eAAe,kBAAkB;AAkBnC,IAAM,aAAa,cAAsC,IAAI;AAe7D,SAAS,gBAAiC;AAC/C,QAAM,MAAM,WAAW,UAAU;AACjC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,yDAAyD;AAAA,EAC3E;AACA,SAAO;AACT;;;AC5CA,SAAS,UAAU,WAAW,mBAAmB;AA+B1C,SAAS,kBAAkB,OAAe;AAC/C,QAAM,CAAC,MAAM,OAAO,IAAI,SAAoC,IAAI;AAChE,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,IAAI;AAC/C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,IAAI;AAErD,QAAM,UAAU,YAAY,YAAY;AACtC,QAAI;AACF,mBAAa,IAAI;AACjB,YAAM,WAAW,MAAM,MAAM,+BAA+B,KAAK,EAAE;AACnE,UAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,iCAAiC;AACnE,YAAM,SAAS,MAAM,SAAS,KAAK;AACnC,cAAQ,MAAM;AACd,eAAS,IAAI;AAAA,IACf,SAAS,KAAK;AACZ,eAAS,eAAe,QAAQ,MAAM,IAAI,MAAM,eAAe,CAAC;AAAA,IAClE,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,KAAK,CAAC;AAEV,YAAU,MAAM;AACd,YAAQ;AAAA,EACV,GAAG,CAAC,OAAO,CAAC;AAEZ,SAAO,EAAE,MAAM,WAAW,OAAO,QAAQ;AAC3C;","names":[]}