@ninedeploy/sdk 0.10.5 → 0.16.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/LICENSE +21 -21
- package/dist/apiPath.d.ts +5 -0
- package/dist/apiPath.d.ts.map +1 -0
- package/dist/apiPath.js +18 -0
- package/dist/apiPath.js.map +1 -0
- package/dist/client.d.ts +29 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +51 -0
- package/dist/client.js.map +1 -0
- package/dist/databaseImport.d.ts +138 -0
- package/dist/databaseImport.d.ts.map +1 -0
- package/dist/databaseImport.js +126 -0
- package/dist/databaseImport.js.map +1 -0
- package/dist/errors.js +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +12 -1973
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -641
- package/dist/index.js.map +1 -1
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +25 -21
- package/dist/manifest.js.map +1 -1
- package/dist/resources/auth.d.ts +282 -0
- package/dist/resources/auth.d.ts.map +1 -0
- package/dist/resources/auth.js +111 -0
- package/dist/resources/auth.js.map +1 -0
- package/dist/resources/databases.d.ts +178 -0
- package/dist/resources/databases.d.ts.map +1 -0
- package/dist/resources/databases.js +82 -0
- package/dist/resources/databases.js.map +1 -0
- package/dist/resources/deploys.d.ts +125 -0
- package/dist/resources/deploys.d.ts.map +1 -0
- package/dist/resources/deploys.js +46 -0
- package/dist/resources/deploys.js.map +1 -0
- package/dist/resources/domains.d.ts +101 -0
- package/dist/resources/domains.d.ts.map +1 -0
- package/dist/resources/domains.js +28 -0
- package/dist/resources/domains.js.map +1 -0
- package/dist/resources/env.d.ts +55 -0
- package/dist/resources/env.d.ts.map +1 -0
- package/dist/resources/env.js +33 -0
- package/dist/resources/env.js.map +1 -0
- package/dist/resources/logs.d.ts +27 -0
- package/dist/resources/logs.d.ts.map +1 -0
- package/dist/resources/logs.js +18 -0
- package/dist/resources/logs.js.map +1 -0
- package/dist/resources/network.d.ts +134 -0
- package/dist/resources/network.d.ts.map +1 -0
- package/dist/resources/network.js +33 -0
- package/dist/resources/network.js.map +1 -0
- package/dist/resources/plugins.d.ts +42 -0
- package/dist/resources/plugins.d.ts.map +1 -0
- package/dist/resources/plugins.js +22 -0
- package/dist/resources/plugins.js.map +1 -0
- package/dist/resources/servers.d.ts +130 -0
- package/dist/resources/servers.d.ts.map +1 -0
- package/dist/resources/servers.js +37 -0
- package/dist/resources/servers.js.map +1 -0
- package/dist/resources/services.d.ts +185 -0
- package/dist/resources/services.d.ts.map +1 -0
- package/dist/resources/services.js +83 -0
- package/dist/resources/services.js.map +1 -0
- package/dist/resources/settings.d.ts +312 -0
- package/dist/resources/settings.d.ts.map +1 -0
- package/dist/resources/settings.js +85 -0
- package/dist/resources/settings.js.map +1 -0
- package/dist/resources/sources.d.ts +87 -0
- package/dist/resources/sources.d.ts.map +1 -0
- package/dist/resources/sources.js +39 -0
- package/dist/resources/sources.js.map +1 -0
- package/dist/resources/system.d.ts +240 -0
- package/dist/resources/system.d.ts.map +1 -0
- package/dist/resources/system.js +86 -0
- package/dist/resources/system.js.map +1 -0
- package/dist/resources/templates.d.ts +36 -0
- package/dist/resources/templates.d.ts.map +1 -0
- package/dist/resources/templates.js +17 -0
- package/dist/resources/templates.js.map +1 -0
- package/dist/resources/terminals.d.ts +60 -0
- package/dist/resources/terminals.d.ts.map +1 -0
- package/dist/resources/terminals.js +35 -0
- package/dist/resources/terminals.js.map +1 -0
- package/dist/resources/traefik.d.ts +84 -0
- package/dist/resources/traefik.d.ts.map +1 -0
- package/dist/resources/traefik.js +35 -0
- package/dist/resources/traefik.js.map +1 -0
- package/dist/resources/volumes.d.ts +114 -0
- package/dist/resources/volumes.d.ts.map +1 -0
- package/dist/resources/volumes.js +47 -0
- package/dist/resources/volumes.js.map +1 -0
- package/dist/terminal.d.ts +98 -0
- package/dist/terminal.d.ts.map +1 -0
- package/dist/terminal.js +180 -0
- package/dist/terminal.js.map +1 -0
- package/dist/transport.d.ts +61 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +90 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +539 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +5 -4
- package/src/apiPath.ts +23 -0
- package/src/client.ts +70 -0
- package/src/databaseImport.ts +174 -0
- package/src/errors.ts +27 -0
- package/src/index.ts +118 -0
- package/src/manifest.ts +414 -0
- package/src/resources/auth.ts +357 -0
- package/src/resources/databases.ts +227 -0
- package/src/resources/deploys.ts +161 -0
- package/src/resources/domains.ts +142 -0
- package/src/resources/env.ts +84 -0
- package/src/resources/logs.ts +48 -0
- package/src/resources/network.ts +126 -0
- package/src/resources/plugins.ts +52 -0
- package/src/resources/servers.ts +141 -0
- package/src/resources/services.ts +250 -0
- package/src/resources/settings.ts +275 -0
- package/src/resources/sources.ts +131 -0
- package/src/resources/system.ts +281 -0
- package/src/resources/templates.ts +55 -0
- package/src/resources/terminals.ts +112 -0
- package/src/resources/traefik.ts +116 -0
- package/src/resources/volumes.ts +138 -0
- package/src/terminal.ts +254 -0
- package/src/transport.ts +154 -0
- package/src/types.ts +588 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// Client surface: settings, branding, configPresets, config, emailTemplates, notifications.
|
|
2
|
+
// Split out of the former single-file SDK entry (src/index.ts); the public
|
|
3
|
+
// surface is re-exported unchanged from there.
|
|
4
|
+
import type {
|
|
5
|
+
ConfigItem,
|
|
6
|
+
ConfigListResponse,
|
|
7
|
+
SetConfigInput,
|
|
8
|
+
SecretProviderKind,
|
|
9
|
+
SecretProviderTestRequest,
|
|
10
|
+
SecretProviderTestResult,
|
|
11
|
+
SecretProviderView,
|
|
12
|
+
VaultProviderPut,
|
|
13
|
+
AwsProviderPut,
|
|
14
|
+
} from '@ninedeploy/schemas';
|
|
15
|
+
import type { VaultAllowlistView, VaultSettings, EmailTemplateName, EmailTemplateEntry, EmailTemplateRender } from '../types.js';
|
|
16
|
+
import type { Transport } from '../transport.js';
|
|
17
|
+
|
|
18
|
+
export interface SettingsApi {
|
|
19
|
+
settings: {
|
|
20
|
+
get: () => Promise<{ allowRegistration: boolean; acmeEmail: string | null; templatesSource: string | null; dnsProvider: string | null; hasDnsToken: boolean; wildcardApex: string | null; panelDomain: string | null }>;
|
|
21
|
+
setPanelDomain: (domain: string) => Promise<{ ok: boolean; panelDomain: string | null }>;
|
|
22
|
+
setAllowRegistration: (enabled: boolean) => Promise<{ ok: boolean; allowRegistration: boolean }>;
|
|
23
|
+
setAcmeEmail: (email: string) => Promise<{ ok: boolean; acmeEmail: string | null; applied: string }>;
|
|
24
|
+
setTemplatesSource: (source: string) => Promise<{ ok: boolean; templatesSource: string | null }>;
|
|
25
|
+
/** Configure the ACME DNS-01 challenge (wildcard certificates). */
|
|
26
|
+
setDns: (input: { provider: string; token?: string; wildcardApex: string }) => Promise<{ ok: boolean; dnsProvider: string | null; wildcardApex: string | null; applied: string }>;
|
|
27
|
+
/** Vault provider (deploy-time secret resolution). */
|
|
28
|
+
vault: {
|
|
29
|
+
get: () => Promise<VaultSettings>;
|
|
30
|
+
set: (input: { provider: '' | 'infisical' | 'doppler'; token?: string; projectId?: string; environment?: string }) => Promise<{ ok: boolean; provider: string | null }>;
|
|
31
|
+
test: () => Promise<{ ok: boolean; secrets: number }>;
|
|
32
|
+
/** Which workspaces (and legacy un-tagged services) may resolve vault references. */
|
|
33
|
+
setAllowlist: (input: { workspaceIds: number[]; serviceIds?: number[] }) => Promise<{ ok: boolean } & VaultAllowlistView>;
|
|
34
|
+
};
|
|
35
|
+
/** Cloudflare DNS-record auto-provisioning for added domains. */
|
|
36
|
+
dnsRecords: {
|
|
37
|
+
get: () => Promise<{ enabled: boolean; hasToken: boolean; content: string | null }>;
|
|
38
|
+
set: (input: { enabled: boolean; token?: string; content?: string }) => Promise<{ ok: boolean; enabled: boolean }>;
|
|
39
|
+
test: () => Promise<{ ok: boolean; status?: string; error?: string }>;
|
|
40
|
+
};
|
|
41
|
+
/** Namecheap DNS-record auto-provisioning for added domains (G-07 PR-A). */
|
|
42
|
+
namecheap: {
|
|
43
|
+
get: () => Promise<{ configured: boolean; apiUser: string | null; clientIp: string | null; hasKey: boolean }>;
|
|
44
|
+
set: (input: { apiUser: string; apiKey: string; clientIp: string }) => Promise<{ ok: boolean; apiUser: string }>;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Master-key rotation. `rotate` re-encrypts every stored secret onto the
|
|
48
|
+
* highest key version in `NINEDEPLOY_MASTER_KEYS`; it does NOT rewrite
|
|
49
|
+
* backup envelopes, which is what `warning` reports.
|
|
50
|
+
*/
|
|
51
|
+
masterKey: {
|
|
52
|
+
get: () => Promise<{ activeVersion: number; knownVersions: number[]; rotatable: boolean }>;
|
|
53
|
+
rotate: () => Promise<{ rotated: number; activeVersion: number; backupsNotRotated: number; warning: string | null }>;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Node-enrolment token (r463): the secret an operator pastes into a
|
|
57
|
+
* remote agent's NINEDEPLOY_ENROLMENT_TOKEN. Returned in clear — the
|
|
58
|
+
* server routes are admin-only for exactly that reason.
|
|
59
|
+
*/
|
|
60
|
+
enrolment: {
|
|
61
|
+
get: () => Promise<{ enabled: boolean; token: string | null }>;
|
|
62
|
+
rotate: () => Promise<{ ok: boolean; enabled: boolean; token: string }>;
|
|
63
|
+
disable: () => Promise<{ ok: boolean; enabled: boolean }>;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* 0.14: HashiCorp Vault / OpenBao and AWS Secrets Manager (operator-only).
|
|
67
|
+
* Credentials are write-only: `list` never returns them, and an omitted
|
|
68
|
+
* credential field on `set` keeps the stored value.
|
|
69
|
+
*/
|
|
70
|
+
secretProviders: {
|
|
71
|
+
list: () => Promise<SecretProviderView[]>;
|
|
72
|
+
set: <K extends SecretProviderKind>(kind: K, input: K extends 'vault' ? VaultProviderPut : AwsProviderPut) => Promise<SecretProviderView>;
|
|
73
|
+
delete: (kind: SecretProviderKind) => Promise<{ ok: boolean; deleted: boolean }>;
|
|
74
|
+
test: (kind: SecretProviderKind, probe?: SecretProviderTestRequest) => Promise<SecretProviderTestResult>;
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
branding: {
|
|
78
|
+
/** Read the four operator-overridable branding fields. G-30. */
|
|
79
|
+
get: () => Promise<{
|
|
80
|
+
logoUrl: string | null;
|
|
81
|
+
primaryColor: string | null;
|
|
82
|
+
supportEmail: string | null;
|
|
83
|
+
footerHtml: string | null;
|
|
84
|
+
}>;
|
|
85
|
+
/** Set one or more branding fields. Empty strings are stored as `null`. */
|
|
86
|
+
set: (input: {
|
|
87
|
+
logoUrl?: string | null;
|
|
88
|
+
primaryColor?: string | null;
|
|
89
|
+
supportEmail?: string | null;
|
|
90
|
+
footerHtml?: string | null;
|
|
91
|
+
}) => Promise<{ ok: boolean }>;
|
|
92
|
+
};
|
|
93
|
+
configPresets: {
|
|
94
|
+
/** Every preset the kernel has registered. */
|
|
95
|
+
list: () => Promise<{ presets: string[] }>;
|
|
96
|
+
/** Fetch a single preset's values + description. */
|
|
97
|
+
get: (id: string) => Promise<{
|
|
98
|
+
id: string;
|
|
99
|
+
description: string | null;
|
|
100
|
+
values: Record<string, unknown>;
|
|
101
|
+
createdAt: string;
|
|
102
|
+
}>;
|
|
103
|
+
/** Register a new preset. Throws if `id` already exists. */
|
|
104
|
+
register: (input: { id: string; description?: string; values: Record<string, unknown> }) => Promise<{ ok: boolean; id: string; keyCount: number }>;
|
|
105
|
+
/** Write every value in the preset to the live configCenter. */
|
|
106
|
+
apply: (id: string, opts?: { override?: Record<string, unknown> }) => Promise<{
|
|
107
|
+
ok: boolean;
|
|
108
|
+
id: string;
|
|
109
|
+
keyCount: number;
|
|
110
|
+
failureCount?: number;
|
|
111
|
+
failures?: Array<{ key: string; status: 'failed'; reason?: string }>;
|
|
112
|
+
}>;
|
|
113
|
+
/** Unregister a preset. Does NOT undo the apply. */
|
|
114
|
+
remove: (id: string) => Promise<{ ok: boolean; id: string }>;
|
|
115
|
+
};
|
|
116
|
+
config: {
|
|
117
|
+
list: (query?: { category?: string; pluginId?: string; reveal?: boolean }) => Promise<ConfigListResponse>;
|
|
118
|
+
get: (key: string) => Promise<ConfigItem>;
|
|
119
|
+
set: (key: string, input: SetConfigInput) => Promise<{ ok: boolean; key: string }>;
|
|
120
|
+
delete: (key: string) => Promise<{ ok: boolean; key: string }>;
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Per-workspace email template overrides (G-30).
|
|
124
|
+
* Read-side is open to members; write-side requires the
|
|
125
|
+
* workspace's admin role. The list / preview are safe
|
|
126
|
+
* to call from a CI script; `set` / `reset` mutate the
|
|
127
|
+
* panel's outbound email and should be operator-only.
|
|
128
|
+
*/
|
|
129
|
+
emailTemplates: {
|
|
130
|
+
list: (workspaceId: number) => Promise<{ workspaceId: number; templates: EmailTemplateEntry[] }>;
|
|
131
|
+
preview: (workspaceId: number, name: EmailTemplateName, vars?: Record<string, string | number | null>) => Promise<EmailTemplateRender>;
|
|
132
|
+
set: (workspaceId: number, name: EmailTemplateName, subject: string, text: string) => Promise<{ ok: boolean }>;
|
|
133
|
+
reset: (workspaceId: number, name: EmailTemplateName) => Promise<{ ok: boolean }>;
|
|
134
|
+
};
|
|
135
|
+
notifications: {
|
|
136
|
+
/**
|
|
137
|
+
* r473: the provider config blob is encrypted at rest and WRITE-ONLY over
|
|
138
|
+
* the API for everything but the secret-free Discord shape — `configJson`
|
|
139
|
+
* is null with `hasConfig: true` for webhook/FCM channels. Never PATCH
|
|
140
|
+
* the read value back: send the full config, `''` to clear it, or omit
|
|
141
|
+
* the field.
|
|
142
|
+
*
|
|
143
|
+
* r552: the write side is `configJson?: string` — the server schema is
|
|
144
|
+
* `z.string().optional()`, so the `null` the type used to allow was a 400.
|
|
145
|
+
*/
|
|
146
|
+
listChannels: () => Promise<Array<{ id: number; name: string; type: string; eventFilter: string; active: boolean; configJson: string | null; hasConfig: boolean; createdAt: string }>>;
|
|
147
|
+
createChannel: (input: { name: string; type: string; target: string; eventFilter?: string; configJson?: string }) => Promise<{ id: number; name: string; type: string }>;
|
|
148
|
+
updateChannel: (id: number, input: { name?: string; target?: string; eventFilter?: string; active?: boolean; configJson?: string }) => Promise<{ id: number; active: boolean }>;
|
|
149
|
+
removeChannel: (id: number) => Promise<void>;
|
|
150
|
+
testChannel: (id: number) => Promise<{ ok: boolean }>;
|
|
151
|
+
log: () => Promise<Array<{ id: number; channelId: number | null; event: string; entity: string | null; status: string; error: string | null; ts: string }>>;
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function settingsApi(t: Transport): SettingsApi {
|
|
156
|
+
const { get, send, request } = t;
|
|
157
|
+
return {
|
|
158
|
+
settings: {
|
|
159
|
+
get: () => get<{ allowRegistration: boolean; acmeEmail: string | null; templatesSource: string | null; dnsProvider: string | null; hasDnsToken: boolean; wildcardApex: string | null; panelDomain: string | null }>('/v1/settings'),
|
|
160
|
+
setPanelDomain: (domain) =>
|
|
161
|
+
send<{ ok: boolean; panelDomain: string | null }>('PUT', '/v1/settings/panel-domain', { domain }),
|
|
162
|
+
setAllowRegistration: (enabled) =>
|
|
163
|
+
send<{ ok: boolean; allowRegistration: boolean }>('PUT', '/v1/settings/allow-registration', { enabled }),
|
|
164
|
+
setAcmeEmail: (email) =>
|
|
165
|
+
send<{ ok: boolean; acmeEmail: string | null; applied: string }>('PUT', '/v1/settings/acme-email', { email }),
|
|
166
|
+
setTemplatesSource: (source) =>
|
|
167
|
+
send<{ ok: boolean; templatesSource: string | null }>('PUT', '/v1/settings/templates-source', { source }),
|
|
168
|
+
setDns: (input) =>
|
|
169
|
+
send<{ ok: boolean; dnsProvider: string | null; wildcardApex: string | null; applied: string }>('PUT', '/v1/settings/dns', input),
|
|
170
|
+
vault: {
|
|
171
|
+
get: () => get<VaultSettings>('/v1/settings/vault'),
|
|
172
|
+
set: (input) => send<{ ok: boolean; provider: string | null }>('PUT', '/v1/settings/vault', input),
|
|
173
|
+
test: () => send<{ ok: boolean; secrets: number }>('POST', '/v1/settings/vault/test'),
|
|
174
|
+
setAllowlist: (input) => send<{ ok: boolean } & VaultAllowlistView>('PUT', '/v1/settings/vault/allowlist', input),
|
|
175
|
+
},
|
|
176
|
+
dnsRecords: {
|
|
177
|
+
get: () => get<{ enabled: boolean; hasToken: boolean; content: string | null }>('/v1/settings/dns-records'),
|
|
178
|
+
set: (input) => send<{ ok: boolean; enabled: boolean }>('PUT', '/v1/settings/dns-records', input),
|
|
179
|
+
test: () => send<{ ok: boolean; status?: string; error?: string }>('POST', '/v1/settings/dns-records/test'),
|
|
180
|
+
},
|
|
181
|
+
namecheap: {
|
|
182
|
+
get: () => get<{ configured: boolean; apiUser: string | null; clientIp: string | null; hasKey: boolean }>('/v1/settings/dns-records/namecheap'),
|
|
183
|
+
set: (input) => send<{ ok: boolean; apiUser: string }>('PUT', '/v1/settings/dns-records/namecheap', input),
|
|
184
|
+
},
|
|
185
|
+
masterKey: {
|
|
186
|
+
get: () => get<{ activeVersion: number; knownVersions: number[]; rotatable: boolean }>('/v1/settings/master-key'),
|
|
187
|
+
rotate: () =>
|
|
188
|
+
send<{ rotated: number; activeVersion: number; backupsNotRotated: number; warning: string | null }>(
|
|
189
|
+
'POST',
|
|
190
|
+
'/v1/settings/master-key/rotate',
|
|
191
|
+
),
|
|
192
|
+
},
|
|
193
|
+
enrolment: {
|
|
194
|
+
get: () => get<{ enabled: boolean; token: string | null }>('/v1/settings/enrolment'),
|
|
195
|
+
rotate: () => send<{ ok: boolean; enabled: boolean; token: string }>('POST', '/v1/settings/enrolment/rotate'),
|
|
196
|
+
disable: () => send<{ ok: boolean; enabled: boolean }>('DELETE', '/v1/settings/enrolment'),
|
|
197
|
+
},
|
|
198
|
+
secretProviders: {
|
|
199
|
+
list: () => get<SecretProviderView[]>('/v1/settings/secret-providers'),
|
|
200
|
+
set: (kind, input) => send<SecretProviderView>('PUT', `/v1/settings/secret-providers/${kind}`, input),
|
|
201
|
+
delete: (kind) => send<{ ok: boolean; deleted: boolean }>('DELETE', `/v1/settings/secret-providers/${kind}`),
|
|
202
|
+
test: (kind, probe) =>
|
|
203
|
+
send<SecretProviderTestResult>('POST', `/v1/settings/secret-providers/${kind}/test`, probe ?? {}),
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
branding: {
|
|
207
|
+
get: () => get<{
|
|
208
|
+
logoUrl: string | null;
|
|
209
|
+
primaryColor: string | null;
|
|
210
|
+
supportEmail: string | null;
|
|
211
|
+
footerHtml: string | null;
|
|
212
|
+
}>('/v1/branding'),
|
|
213
|
+
set: (input) => send<{ ok: boolean }>('PATCH', '/v1/branding', input),
|
|
214
|
+
},
|
|
215
|
+
configPresets: {
|
|
216
|
+
list: () => get<{ presets: string[] }>('/v1/config-presets'),
|
|
217
|
+
get: (id) => get<{
|
|
218
|
+
id: string;
|
|
219
|
+
description: string | null;
|
|
220
|
+
values: Record<string, unknown>;
|
|
221
|
+
createdAt: string;
|
|
222
|
+
}>(`/v1/config-presets/${encodeURIComponent(id)}`),
|
|
223
|
+
register: (input) => send<{ ok: boolean; id: string; keyCount: number }>('POST', '/v1/config-presets', input),
|
|
224
|
+
apply: (id, opts) => send<{
|
|
225
|
+
ok: boolean;
|
|
226
|
+
id: string;
|
|
227
|
+
keyCount: number;
|
|
228
|
+
failureCount?: number;
|
|
229
|
+
failures?: Array<{ key: string; status: 'failed'; reason?: string }>;
|
|
230
|
+
}>('PUT', `/v1/config-presets/${encodeURIComponent(id)}/apply`, opts ?? {}),
|
|
231
|
+
remove: (id) => send<{ ok: boolean; id: string }>('DELETE', `/v1/config-presets/${encodeURIComponent(id)}`),
|
|
232
|
+
},
|
|
233
|
+
config: {
|
|
234
|
+
list: (query) => {
|
|
235
|
+
const parts: string[] = [];
|
|
236
|
+
if (query?.category) parts.push(`category=${encodeURIComponent(query.category)}`);
|
|
237
|
+
if (query?.pluginId) parts.push(`pluginId=${encodeURIComponent(query.pluginId)}`);
|
|
238
|
+
if (query?.reveal) parts.push('reveal=true');
|
|
239
|
+
const qs = parts.length ? `?${parts.join('&')}` : '';
|
|
240
|
+
return get<ConfigListResponse>(`/v1/config${qs}`);
|
|
241
|
+
},
|
|
242
|
+
get: (key) => get<ConfigItem>(`/v1/config/${encodeURIComponent(key)}`),
|
|
243
|
+
set: (key, input) => send<{ ok: boolean; key: string }>('POST', `/v1/config/${encodeURIComponent(key)}`, input),
|
|
244
|
+
delete: (key) => send<{ ok: boolean; key: string }>('DELETE', `/v1/config/${encodeURIComponent(key)}`),
|
|
245
|
+
},
|
|
246
|
+
emailTemplates: {
|
|
247
|
+
list: (workspaceId) =>
|
|
248
|
+
get<{ workspaceId: number; templates: EmailTemplateEntry[] }>(
|
|
249
|
+
`/v1/workspaces/${workspaceId}/email-templates`,
|
|
250
|
+
),
|
|
251
|
+
preview: (workspaceId, name, vars) =>
|
|
252
|
+
send<EmailTemplateRender>(
|
|
253
|
+
'POST',
|
|
254
|
+
`/v1/workspaces/${workspaceId}/email-templates/preview`,
|
|
255
|
+
{ name, vars: vars ?? {} },
|
|
256
|
+
),
|
|
257
|
+
set: (workspaceId, name, subject, text) =>
|
|
258
|
+
send<{ ok: boolean }>(
|
|
259
|
+
'PUT',
|
|
260
|
+
`/v1/workspaces/${workspaceId}/email-templates/${name}`,
|
|
261
|
+
{ subject, text },
|
|
262
|
+
),
|
|
263
|
+
reset: (workspaceId, name) =>
|
|
264
|
+
send<{ ok: boolean }>('DELETE', `/v1/workspaces/${workspaceId}/email-templates/${name}`, {}),
|
|
265
|
+
},
|
|
266
|
+
notifications: {
|
|
267
|
+
listChannels: () => get<Array<{ id: number; name: string; type: string; eventFilter: string; active: boolean; configJson: string | null; hasConfig: boolean; createdAt: string }>>('/v1/notifications/channels'),
|
|
268
|
+
createChannel: (input) => send('POST', '/v1/notifications/channels', input),
|
|
269
|
+
updateChannel: (id, input) => send('PATCH', `/v1/notifications/channels/${id}`, input),
|
|
270
|
+
removeChannel: async (id) => { await request(`/v1/notifications/channels/${id}`, { method: 'DELETE' }); },
|
|
271
|
+
testChannel: (id) => send('POST', `/v1/notifications/channels/${id}/test`),
|
|
272
|
+
log: () => get('/v1/notifications/log'),
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// Client surface: sources, githubApps.
|
|
2
|
+
// Split out of the former single-file SDK entry (src/index.ts); the public
|
|
3
|
+
// surface is re-exported unchanged from there.
|
|
4
|
+
import type {
|
|
5
|
+
CreateSourceInput,
|
|
6
|
+
Source,
|
|
7
|
+
GithubApp,
|
|
8
|
+
GithubAppCreate,
|
|
9
|
+
GithubAppInstallation,
|
|
10
|
+
GithubAppManifestRequest,
|
|
11
|
+
GithubAppManifestResponse,
|
|
12
|
+
GithubAppPatch,
|
|
13
|
+
} from '@ninedeploy/schemas';
|
|
14
|
+
import type { GithubAppInstallationSync, SourceRepo, SourceUpdateInput } from '../types.js';
|
|
15
|
+
import type { Transport } from '../transport.js';
|
|
16
|
+
|
|
17
|
+
export interface SourcesApi {
|
|
18
|
+
sources: {
|
|
19
|
+
list: () => Promise<Source[]>;
|
|
20
|
+
create: (input: CreateSourceInput) => Promise<Source>;
|
|
21
|
+
update: (id: number, input: SourceUpdateInput) => Promise<Source>;
|
|
22
|
+
remove: (id: number) => Promise<void>;
|
|
23
|
+
repos: (id: number) => Promise<SourceRepo[]>;
|
|
24
|
+
/**
|
|
25
|
+
* `repos` plus the server's diagnostic for the list (F1010): a list capped
|
|
26
|
+
* at the page limit, a later page that failed, a classic token without the
|
|
27
|
+
* `repo` scope, or a provider error. The server sends it in the
|
|
28
|
+
* `x-nd-source-error` response header; `warning` is null when absent.
|
|
29
|
+
*/
|
|
30
|
+
reposWithDiagnostics: (id: number) => Promise<{
|
|
31
|
+
repos: Awaited<ReturnType<SourcesApi['sources']['repos']>>;
|
|
32
|
+
warning: string | null;
|
|
33
|
+
}>;
|
|
34
|
+
branches: (id: number, repo: string) => Promise<string[]>;
|
|
35
|
+
/**
|
|
36
|
+
* Live credential check — proves a stored token still authenticates.
|
|
37
|
+
* GitHub successes also say what the token can see (F1008): `tokenKind`,
|
|
38
|
+
* the classic token's `scopes` (from `x-oauth-scopes`) and `warnings`
|
|
39
|
+
* explaining why private repositories may be missing. Never the token.
|
|
40
|
+
*/
|
|
41
|
+
test: (id: number) => Promise<{
|
|
42
|
+
ok: boolean;
|
|
43
|
+
provider?: string;
|
|
44
|
+
login?: string;
|
|
45
|
+
name?: string | null;
|
|
46
|
+
status?: number;
|
|
47
|
+
error?: string;
|
|
48
|
+
tokenKind?: 'classic' | 'fine-grained' | 'oauth' | 'unknown';
|
|
49
|
+
scopes?: string[];
|
|
50
|
+
warnings?: string[];
|
|
51
|
+
/** GitHub App sources (0.13): the installation's repository selection, permissions and state. */
|
|
52
|
+
repositorySelection?: string;
|
|
53
|
+
permissions?: Record<string, string>;
|
|
54
|
+
suspended?: boolean;
|
|
55
|
+
}>;
|
|
56
|
+
/**
|
|
57
|
+
* Server-side generation of an ed25519 deploy key pair. The private key
|
|
58
|
+
* is encrypted into the source row and never leaves the server; only the
|
|
59
|
+
* public key and fingerprint are returned (the operator pastes the public
|
|
60
|
+
* key into the Git provider's "Deploy keys" UI).
|
|
61
|
+
*/
|
|
62
|
+
generateDeployKey: (id: number) => Promise<{ publicKey: string; fingerprint: string }>;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* GitHub App registration and installations (0.13). Operator-only. No
|
|
66
|
+
* response carries the private key, webhook secret or client secret.
|
|
67
|
+
*/
|
|
68
|
+
githubApps: {
|
|
69
|
+
list: () => Promise<GithubApp[]>;
|
|
70
|
+
get: (id: number) => Promise<GithubApp>;
|
|
71
|
+
/** Manual entry (GitHub Enterprise Server, or an existing App). Omit both base URLs for github.com. */
|
|
72
|
+
create: (input: GithubAppCreate) => Promise<GithubApp>;
|
|
73
|
+
/**
|
|
74
|
+
* Start the one-click manifest flow. Submit `manifest` (JSON-encoded) as
|
|
75
|
+
* the `manifest` field of a form POSTed to `postUrl`; GitHub then sends
|
|
76
|
+
* the browser to `/github-apps/callback?code&state`.
|
|
77
|
+
*/
|
|
78
|
+
manifest: (input: GithubAppManifestRequest) => Promise<GithubAppManifestResponse>;
|
|
79
|
+
/** Finish the manifest flow with the `code` and `state` GitHub returned. Same user only, single use. */
|
|
80
|
+
completeManifest: (input: { code: string; state: string }) => Promise<GithubApp>;
|
|
81
|
+
patch: (id: number, input: GithubAppPatch) => Promise<GithubApp>;
|
|
82
|
+
/** Replace the private key; the server proves it with `GET /app` first. */
|
|
83
|
+
rotateKey: (id: number, privateKey: string) => Promise<GithubApp>;
|
|
84
|
+
/** Re-point the App's webhook at this panel (after a panel domain change). */
|
|
85
|
+
webhookSync: (id: number) => Promise<GithubApp>;
|
|
86
|
+
rotateWebhookSecret: (id: number) => Promise<GithubApp>;
|
|
87
|
+
/** Forget the App; its generated sources stay and their clones fail closed. */
|
|
88
|
+
remove: (id: number) => Promise<void>;
|
|
89
|
+
syncInstallations: (id: number) => Promise<GithubAppInstallationSync>;
|
|
90
|
+
installations: (id: number) => Promise<GithubAppInstallation[]>;
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function sourcesApi(t: Transport): SourcesApi {
|
|
95
|
+
const { get, send, request, requestWithHeaders } = t;
|
|
96
|
+
return {
|
|
97
|
+
sources: {
|
|
98
|
+
list: () => get<Source[]>('/v1/sources'),
|
|
99
|
+
create: (input) => send<Source>('POST', '/v1/sources', input),
|
|
100
|
+
update: (id, input) => send<Source>('PATCH', `/v1/sources/${id}`, input),
|
|
101
|
+
remove: async (id) => {
|
|
102
|
+
await request(`/v1/sources/${id}`, { method: 'DELETE' });
|
|
103
|
+
},
|
|
104
|
+
repos: (id) => get<SourceRepo[]>(`/v1/sources/${id}/repos`),
|
|
105
|
+
reposWithDiagnostics: async (id) => {
|
|
106
|
+
const { data, header } = await requestWithHeaders<Awaited<ReturnType<SourcesApi['sources']['repos']>>>(`/v1/sources/${id}/repos`);
|
|
107
|
+
const warning = header('x-nd-source-error')?.trim();
|
|
108
|
+
return { repos: Array.isArray(data) ? data : [], warning: warning ? warning : null };
|
|
109
|
+
},
|
|
110
|
+
branches: (id, repo) => get<string[]>(`/v1/sources/${id}/branches?repo=${encodeURIComponent(repo)}`),
|
|
111
|
+
test: (id) => get<Awaited<ReturnType<SourcesApi['sources']['test']>>>(`/v1/sources/${id}/test`),
|
|
112
|
+
generateDeployKey: (id) => send<{ publicKey: string; fingerprint: string }>('POST', `/v1/sources/${id}/generate-deploy-key`),
|
|
113
|
+
},
|
|
114
|
+
githubApps: {
|
|
115
|
+
list: () => get<GithubApp[]>('/v1/github-apps'),
|
|
116
|
+
get: (id) => get<GithubApp>(`/v1/github-apps/${id}`),
|
|
117
|
+
create: (input) => send<GithubApp>('POST', '/v1/github-apps', input),
|
|
118
|
+
manifest: (input) => send<GithubAppManifestResponse>('POST', '/v1/github-apps/manifest', input),
|
|
119
|
+
completeManifest: (input) => send<GithubApp>('POST', '/v1/github-apps/manifest/complete', input),
|
|
120
|
+
patch: (id, input) => send<GithubApp>('PATCH', `/v1/github-apps/${id}`, input),
|
|
121
|
+
rotateKey: (id, privateKey) => send<GithubApp>('PUT', `/v1/github-apps/${id}/private-key`, { privateKey }),
|
|
122
|
+
webhookSync: (id) => send<GithubApp>('POST', `/v1/github-apps/${id}/webhook/sync`),
|
|
123
|
+
rotateWebhookSecret: (id) => send<GithubApp>('POST', `/v1/github-apps/${id}/webhook-secret/rotate`),
|
|
124
|
+
remove: async (id) => {
|
|
125
|
+
await request(`/v1/github-apps/${id}`, { method: 'DELETE' });
|
|
126
|
+
},
|
|
127
|
+
syncInstallations: (id) => send<GithubAppInstallationSync>('POST', `/v1/github-apps/${id}/installations/sync`),
|
|
128
|
+
installations: (id) => get<GithubAppInstallation[]>(`/v1/github-apps/${id}/installations`),
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|