@veltrixsecops/app-sdk 1.0.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 +92 -0
- package/dist/chunk-PJCHU6ZZ.js +33 -0
- package/dist/chunk-PJCHU6ZZ.js.map +1 -0
- package/dist/chunk-TSEIWO6T.js +43 -0
- package/dist/chunk-TSEIWO6T.js.map +1 -0
- package/dist/hooks/index.cjs +71 -0
- package/dist/hooks/index.cjs.map +1 -0
- package/dist/hooks/index.d.cts +2 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.js +11 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/index-BI8f_mBw.d.cts +286 -0
- package/dist/index-BI8f_mBw.d.ts +286 -0
- package/dist/index-CKeIf4Jx.d.cts +239 -0
- package/dist/index-CKeIf4Jx.d.ts +239 -0
- package/dist/index.cjs +106 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/pipeline/index.cjs +63 -0
- package/dist/pipeline/index.cjs.map +1 -0
- package/dist/pipeline/index.d.cts +1 -0
- package/dist/pipeline/index.d.ts +1 -0
- package/dist/pipeline/index.js +15 -0
- package/dist/pipeline/index.js.map +1 -0
- package/package.json +84 -0
package/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# @veltrixsecops/app-sdk
|
|
2
|
+
|
|
3
|
+
The official SDK for building [Veltrix](https://github.com/captivatortechnologies/Veltrix) Security-as-Code apps.
|
|
4
|
+
|
|
5
|
+
A Veltrix app packages everything needed to manage one security tool's configuration as code: pipeline handlers (validate → deploy → rollback → health-check → drift-detect → status), canvas templates, database migrations, lifecycle hooks, and optional client pages. This SDK provides the typed contracts and helpers for all of it.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install --save-dev @veltrixsecops/app-sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The SDK is types-first: pipeline handlers typically only need `import type`, so it can be a dev dependency.
|
|
14
|
+
|
|
15
|
+
## Writing pipeline handlers
|
|
16
|
+
|
|
17
|
+
Every configuration type your app declares in `manifest.yaml` implements six handlers. Each receives a typed context built by the platform:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
// handlers/indexes/validate.ts
|
|
21
|
+
import type { PipelineContext, ValidationResult } from '@veltrixsecops/app-sdk'
|
|
22
|
+
|
|
23
|
+
export default async function validate(ctx: PipelineContext): Promise<ValidationResult> {
|
|
24
|
+
const errors = []
|
|
25
|
+
for (const section of ctx.canvas.sections) {
|
|
26
|
+
if (!section.fields['name']) {
|
|
27
|
+
errors.push({ field: 'name', message: 'Name is required', code: 'required' })
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return { valid: errors.length === 0, errors, warnings: [] }
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Or use the `define*` helpers for inference:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { defineDeployer } from '@veltrixsecops/app-sdk/pipeline'
|
|
38
|
+
|
|
39
|
+
export default defineDeployer(async (ctx) => {
|
|
40
|
+
// ctx.component, ctx.credential, ctx.connectivity, ctx.connectivityProvider
|
|
41
|
+
// ctx.previousConfig, ctx.strategy, ctx.canaryPercent
|
|
42
|
+
return { success: true, message: 'Deployed', rollbackData: {/* prior state */} }
|
|
43
|
+
})
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Reading platform data
|
|
47
|
+
|
|
48
|
+
Handlers must never query the platform database directly. Use the tenant-scoped
|
|
49
|
+
data API on every context instead:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import type { PipelineContext, ConfigStatus } from '@veltrixsecops/app-sdk'
|
|
53
|
+
|
|
54
|
+
export default async function getStatus(ctx: PipelineContext): Promise<ConfigStatus> {
|
|
55
|
+
const latest = await ctx.platform.getLatestDeployment(ctx.canvas.canvasId, { status: 'SUCCEEDED' })
|
|
56
|
+
const components = await ctx.platform.listComponents({ types: ['indexer'] })
|
|
57
|
+
// ...
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Lifecycle hooks
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
// hooks/on-install.ts
|
|
65
|
+
import type { AppHookContext } from '@veltrixsecops/app-sdk'
|
|
66
|
+
|
|
67
|
+
export default async function onInstall({ db, appId }: AppHookContext): Promise<void> {
|
|
68
|
+
// Seed your app's own (prefixed) tables here.
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Client pages
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
import { useAppContext, usePipelineStatus } from '@veltrixsecops/app-sdk/hooks'
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Package layout
|
|
79
|
+
|
|
80
|
+
| Entry point | Contents |
|
|
81
|
+
|---|---|
|
|
82
|
+
| `@veltrixsecops/app-sdk` | All types: handler contexts/results, manifest, platform refs, hook contexts |
|
|
83
|
+
| `@veltrixsecops/app-sdk/pipeline` | `defineValidator`, `defineDeployer`, `defineRollbackHandler`, `defineHealthChecker`, `defineDriftDetector` |
|
|
84
|
+
| `@veltrixsecops/app-sdk/hooks` | React hooks for app client pages (requires `react`) |
|
|
85
|
+
|
|
86
|
+
## Building an app
|
|
87
|
+
|
|
88
|
+
Start from the [`_template`](https://github.com/captivatortechnologies/Veltrix/tree/main/apps-and-tools/_template) app and see the contribution guide in `apps-and-tools/CONTRIBUTING.md`.
|
|
89
|
+
|
|
90
|
+
## License
|
|
91
|
+
|
|
92
|
+
MIT
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// src/pipeline/define-validator.ts
|
|
2
|
+
function defineValidator(handler) {
|
|
3
|
+
return handler;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// src/pipeline/define-deployer.ts
|
|
7
|
+
function defineDeployer(handler) {
|
|
8
|
+
return handler;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// src/pipeline/define-rollback-handler.ts
|
|
12
|
+
function defineRollbackHandler(handler) {
|
|
13
|
+
return handler;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/pipeline/define-health-checker.ts
|
|
17
|
+
function defineHealthChecker(handler) {
|
|
18
|
+
return handler;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// src/pipeline/define-drift-detector.ts
|
|
22
|
+
function defineDriftDetector(handler) {
|
|
23
|
+
return handler;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export {
|
|
27
|
+
defineValidator,
|
|
28
|
+
defineDeployer,
|
|
29
|
+
defineRollbackHandler,
|
|
30
|
+
defineHealthChecker,
|
|
31
|
+
defineDriftDetector
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=chunk-PJCHU6ZZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../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"],"sourcesContent":["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"],"mappings":";AAmBO,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;","names":[]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// src/hooks/use-app-context.ts
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
var AppContext = createContext(null);
|
|
4
|
+
function useAppContext() {
|
|
5
|
+
const ctx = useContext(AppContext);
|
|
6
|
+
if (!ctx) {
|
|
7
|
+
throw new Error("useAppContext must be used within an AppContextProvider");
|
|
8
|
+
}
|
|
9
|
+
return ctx;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// src/hooks/use-pipeline-status.ts
|
|
13
|
+
import { useState, useEffect, useCallback } from "react";
|
|
14
|
+
function usePipelineStatus(appId) {
|
|
15
|
+
const [data, setData] = useState(null);
|
|
16
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
17
|
+
const [error, setError] = useState(null);
|
|
18
|
+
const refresh = useCallback(async () => {
|
|
19
|
+
try {
|
|
20
|
+
setIsLoading(true);
|
|
21
|
+
const response = await fetch(`/api/pipeline/summary?appId=${appId}`);
|
|
22
|
+
if (!response.ok) throw new Error(`Failed to fetch pipeline status`);
|
|
23
|
+
const result = await response.json();
|
|
24
|
+
setData(result);
|
|
25
|
+
setError(null);
|
|
26
|
+
} catch (err) {
|
|
27
|
+
setError(err instanceof Error ? err : new Error("Unknown error"));
|
|
28
|
+
} finally {
|
|
29
|
+
setIsLoading(false);
|
|
30
|
+
}
|
|
31
|
+
}, [appId]);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
refresh();
|
|
34
|
+
}, [refresh]);
|
|
35
|
+
return { data, isLoading, error, refresh };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export {
|
|
39
|
+
AppContext,
|
|
40
|
+
useAppContext,
|
|
41
|
+
usePipelineStatus
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=chunk-TSEIWO6T.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 } 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":[]}
|
|
@@ -0,0 +1,71 @@
|
|
|
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/hooks/index.ts
|
|
21
|
+
var hooks_exports = {};
|
|
22
|
+
__export(hooks_exports, {
|
|
23
|
+
AppContext: () => AppContext,
|
|
24
|
+
useAppContext: () => useAppContext,
|
|
25
|
+
usePipelineStatus: () => usePipelineStatus
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(hooks_exports);
|
|
28
|
+
|
|
29
|
+
// src/hooks/use-app-context.ts
|
|
30
|
+
var import_react = require("react");
|
|
31
|
+
var AppContext = (0, import_react.createContext)(null);
|
|
32
|
+
function useAppContext() {
|
|
33
|
+
const ctx = (0, import_react.useContext)(AppContext);
|
|
34
|
+
if (!ctx) {
|
|
35
|
+
throw new Error("useAppContext must be used within an AppContextProvider");
|
|
36
|
+
}
|
|
37
|
+
return ctx;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/hooks/use-pipeline-status.ts
|
|
41
|
+
var import_react2 = require("react");
|
|
42
|
+
function usePipelineStatus(appId) {
|
|
43
|
+
const [data, setData] = (0, import_react2.useState)(null);
|
|
44
|
+
const [isLoading, setIsLoading] = (0, import_react2.useState)(true);
|
|
45
|
+
const [error, setError] = (0, import_react2.useState)(null);
|
|
46
|
+
const refresh = (0, import_react2.useCallback)(async () => {
|
|
47
|
+
try {
|
|
48
|
+
setIsLoading(true);
|
|
49
|
+
const response = await fetch(`/api/pipeline/summary?appId=${appId}`);
|
|
50
|
+
if (!response.ok) throw new Error(`Failed to fetch pipeline status`);
|
|
51
|
+
const result = await response.json();
|
|
52
|
+
setData(result);
|
|
53
|
+
setError(null);
|
|
54
|
+
} catch (err) {
|
|
55
|
+
setError(err instanceof Error ? err : new Error("Unknown error"));
|
|
56
|
+
} finally {
|
|
57
|
+
setIsLoading(false);
|
|
58
|
+
}
|
|
59
|
+
}, [appId]);
|
|
60
|
+
(0, import_react2.useEffect)(() => {
|
|
61
|
+
refresh();
|
|
62
|
+
}, [refresh]);
|
|
63
|
+
return { data, isLoading, error, refresh };
|
|
64
|
+
}
|
|
65
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
66
|
+
0 && (module.exports = {
|
|
67
|
+
AppContext,
|
|
68
|
+
useAppContext,
|
|
69
|
+
usePipelineStatus
|
|
70
|
+
});
|
|
71
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
type AppSource = 'BUILT_IN' | 'MARKETPLACE' | 'CUSTOM';
|
|
4
|
+
type AppStatusType = 'AVAILABLE' | 'DEPRECATED' | 'REMOVED';
|
|
5
|
+
type AppInstallationStatus = 'INSTALLING' | 'INSTALLED' | 'ENABLED' | 'DISABLED' | 'FAILED' | 'UNINSTALLING';
|
|
6
|
+
interface AppManifest {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
version: string;
|
|
10
|
+
vendor: string;
|
|
11
|
+
description: string;
|
|
12
|
+
category: string;
|
|
13
|
+
license?: string;
|
|
14
|
+
homepage?: string;
|
|
15
|
+
icon?: string;
|
|
16
|
+
logo?: string;
|
|
17
|
+
platform: {
|
|
18
|
+
minVersion: string;
|
|
19
|
+
};
|
|
20
|
+
permissions: {
|
|
21
|
+
platform: string[];
|
|
22
|
+
app: AppPermissionDeclaration[];
|
|
23
|
+
};
|
|
24
|
+
database?: {
|
|
25
|
+
migrations: string;
|
|
26
|
+
tablePrefix: string;
|
|
27
|
+
};
|
|
28
|
+
pipeline: {
|
|
29
|
+
configurationTypes: AppConfigurationTypeManifest[];
|
|
30
|
+
pipelineEvents?: string[];
|
|
31
|
+
};
|
|
32
|
+
server: {
|
|
33
|
+
entry: string;
|
|
34
|
+
routes?: {
|
|
35
|
+
prefix: string;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
client?: {
|
|
39
|
+
entry: string;
|
|
40
|
+
pages?: AppPageDeclaration[];
|
|
41
|
+
};
|
|
42
|
+
hooks?: {
|
|
43
|
+
onInstall?: string;
|
|
44
|
+
onUninstall?: string;
|
|
45
|
+
onEnable?: string;
|
|
46
|
+
onDisable?: string;
|
|
47
|
+
onUpgrade?: string;
|
|
48
|
+
};
|
|
49
|
+
events?: string[];
|
|
50
|
+
settings?: AppSettingDeclaration[];
|
|
51
|
+
}
|
|
52
|
+
interface AppConfigurationTypeManifest {
|
|
53
|
+
id: string;
|
|
54
|
+
name: string;
|
|
55
|
+
description?: string;
|
|
56
|
+
canvasTemplate: string;
|
|
57
|
+
defaultConfig?: string;
|
|
58
|
+
handlers: {
|
|
59
|
+
validate: string;
|
|
60
|
+
deploy: string;
|
|
61
|
+
rollback: string;
|
|
62
|
+
healthCheck: string;
|
|
63
|
+
driftDetect?: string | null;
|
|
64
|
+
getStatus: string;
|
|
65
|
+
};
|
|
66
|
+
targets: {
|
|
67
|
+
componentTypes: string[];
|
|
68
|
+
requiresCredential: boolean;
|
|
69
|
+
requiresConnectivity: boolean;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
interface AppPermissionDeclaration {
|
|
73
|
+
resource: string;
|
|
74
|
+
actions: string[];
|
|
75
|
+
description?: string;
|
|
76
|
+
}
|
|
77
|
+
interface AppPageDeclaration {
|
|
78
|
+
path: string;
|
|
79
|
+
component: string;
|
|
80
|
+
label: string;
|
|
81
|
+
icon?: string;
|
|
82
|
+
sidebar?: boolean;
|
|
83
|
+
parent?: string;
|
|
84
|
+
}
|
|
85
|
+
interface AppSettingDeclaration {
|
|
86
|
+
key: string;
|
|
87
|
+
type: 'string' | 'number' | 'boolean' | 'select';
|
|
88
|
+
label: string;
|
|
89
|
+
description?: string;
|
|
90
|
+
default?: string | number | boolean;
|
|
91
|
+
required?: boolean;
|
|
92
|
+
options?: Array<{
|
|
93
|
+
label: string;
|
|
94
|
+
value: string;
|
|
95
|
+
}>;
|
|
96
|
+
}
|
|
97
|
+
interface AppListItem {
|
|
98
|
+
id: string;
|
|
99
|
+
appId: string;
|
|
100
|
+
name: string;
|
|
101
|
+
version: string;
|
|
102
|
+
vendor: string;
|
|
103
|
+
description: string;
|
|
104
|
+
category: string;
|
|
105
|
+
icon?: string;
|
|
106
|
+
logo?: string;
|
|
107
|
+
source: AppSource;
|
|
108
|
+
isDefault: boolean;
|
|
109
|
+
status: AppStatusType;
|
|
110
|
+
installed?: boolean;
|
|
111
|
+
enabled?: boolean;
|
|
112
|
+
}
|
|
113
|
+
interface AppDetail extends AppListItem {
|
|
114
|
+
license?: string;
|
|
115
|
+
homepage?: string;
|
|
116
|
+
repository?: string;
|
|
117
|
+
configurationTypes: Array<{
|
|
118
|
+
id: string;
|
|
119
|
+
name: string;
|
|
120
|
+
description?: string;
|
|
121
|
+
componentTypes: string[];
|
|
122
|
+
}>;
|
|
123
|
+
permissions: AppPermissionDeclaration[];
|
|
124
|
+
settings: AppSettingDeclaration[];
|
|
125
|
+
}
|
|
126
|
+
interface AppInstallationDetail {
|
|
127
|
+
id: string;
|
|
128
|
+
appId: string;
|
|
129
|
+
customerId: string;
|
|
130
|
+
version: string;
|
|
131
|
+
enabled: boolean;
|
|
132
|
+
installedBy: string;
|
|
133
|
+
installedAt: string;
|
|
134
|
+
settings: Record<string, unknown>;
|
|
135
|
+
status: AppInstallationStatus;
|
|
136
|
+
app: AppListItem;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
interface Component {
|
|
140
|
+
id: string;
|
|
141
|
+
hostname: string;
|
|
142
|
+
port: string;
|
|
143
|
+
type: string[];
|
|
144
|
+
toolId: string;
|
|
145
|
+
customerId: string;
|
|
146
|
+
}
|
|
147
|
+
interface Credential {
|
|
148
|
+
id: string;
|
|
149
|
+
name: string;
|
|
150
|
+
username: string;
|
|
151
|
+
password: string;
|
|
152
|
+
apiToken: string | null;
|
|
153
|
+
certificate: string | null;
|
|
154
|
+
toolId: string;
|
|
155
|
+
customerId: string;
|
|
156
|
+
}
|
|
157
|
+
interface Connectivity {
|
|
158
|
+
id: string;
|
|
159
|
+
componentId: string;
|
|
160
|
+
status: string;
|
|
161
|
+
sshCommand: string | null;
|
|
162
|
+
httpsUrl: string | null;
|
|
163
|
+
tailscaleDeviceIP: string | null;
|
|
164
|
+
}
|
|
165
|
+
interface Tag {
|
|
166
|
+
id: string;
|
|
167
|
+
name: string;
|
|
168
|
+
customerId: string;
|
|
169
|
+
}
|
|
170
|
+
interface User {
|
|
171
|
+
id: string;
|
|
172
|
+
email: string;
|
|
173
|
+
name: string | null;
|
|
174
|
+
customerId: string;
|
|
175
|
+
roleId: string;
|
|
176
|
+
}
|
|
177
|
+
interface Customer {
|
|
178
|
+
id: string;
|
|
179
|
+
name: string;
|
|
180
|
+
domain: string | null;
|
|
181
|
+
isActive: boolean;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Loosely-typed handle to the platform database, passed to lifecycle hooks.
|
|
185
|
+
* At runtime this is the platform's Prisma client; model delegates are
|
|
186
|
+
* accessed by name (e.g. `db.splunkVersion.upsert(...)` for an app table).
|
|
187
|
+
* Apps should only touch their own prefixed tables and the raw-query
|
|
188
|
+
* escape hatches — anything else is unsupported and may break between
|
|
189
|
+
* platform versions.
|
|
190
|
+
*/
|
|
191
|
+
interface PlatformDatabaseClient {
|
|
192
|
+
$queryRawUnsafe<T = unknown>(query: string, ...values: unknown[]): Promise<T>;
|
|
193
|
+
$executeRawUnsafe(query: string, ...values: unknown[]): Promise<number>;
|
|
194
|
+
[modelDelegate: string]: any;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Context passed to app lifecycle hooks
|
|
198
|
+
* (onInstall / onUninstall / onEnable / onDisable / onUpgrade).
|
|
199
|
+
* `customerId` is present only for customer-scoped hooks (onEnable/onDisable).
|
|
200
|
+
*/
|
|
201
|
+
interface AppHookContext {
|
|
202
|
+
db: PlatformDatabaseClient;
|
|
203
|
+
appId: string;
|
|
204
|
+
customerId?: string;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Context passed to an app's server route module (the `server.entry`
|
|
208
|
+
* declared in manifest.yaml). The platform mounts the module as a Fastify
|
|
209
|
+
* plugin under `/api/apps/<appId>` with auth + app-enabled checks applied;
|
|
210
|
+
* `hasPermission` returns a preHandler enforcing an app-scoped permission.
|
|
211
|
+
*/
|
|
212
|
+
interface AppRouteContext {
|
|
213
|
+
appId: string;
|
|
214
|
+
appDir: string;
|
|
215
|
+
manifest: AppManifest;
|
|
216
|
+
db: PlatformDatabaseClient;
|
|
217
|
+
/**
|
|
218
|
+
* Returns a Fastify preHandler enforcing an app-scoped permission.
|
|
219
|
+
* Typed `any` so the SDK does not depend on Fastify's hook types —
|
|
220
|
+
* pass it straight into a route's `preHandler` array.
|
|
221
|
+
*/
|
|
222
|
+
hasPermission: (resource: string, action: string) => any;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
interface AppContextValue {
|
|
226
|
+
appId: string;
|
|
227
|
+
customerId: string;
|
|
228
|
+
user: User | null;
|
|
229
|
+
customer: Customer | null;
|
|
230
|
+
getComponents: () => Promise<Component[]>;
|
|
231
|
+
getCredentials: () => Promise<Credential[]>;
|
|
232
|
+
getTags: () => Promise<Tag[]>;
|
|
233
|
+
settings: Record<string, unknown>;
|
|
234
|
+
}
|
|
235
|
+
declare const AppContext: react.Context<AppContextValue | null>;
|
|
236
|
+
/**
|
|
237
|
+
* Access the app context in any app component.
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* ```tsx
|
|
241
|
+
* import { useAppContext } from '@veltrixsecops/app-sdk/hooks'
|
|
242
|
+
*
|
|
243
|
+
* function MyComponent() {
|
|
244
|
+
* const { appId, settings, getComponents } = useAppContext()
|
|
245
|
+
* // ...
|
|
246
|
+
* }
|
|
247
|
+
* ```
|
|
248
|
+
*/
|
|
249
|
+
declare function useAppContext(): AppContextValue;
|
|
250
|
+
|
|
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 };
|