@ninedeploy/sdk 0.10.6 → 0.16.1

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.
Files changed (129) hide show
  1. package/LICENSE +21 -21
  2. package/dist/apiPath.d.ts +5 -0
  3. package/dist/apiPath.d.ts.map +1 -0
  4. package/dist/apiPath.js +18 -0
  5. package/dist/apiPath.js.map +1 -0
  6. package/dist/client.d.ts +29 -0
  7. package/dist/client.d.ts.map +1 -0
  8. package/dist/client.js +51 -0
  9. package/dist/client.js.map +1 -0
  10. package/dist/databaseImport.d.ts +138 -0
  11. package/dist/databaseImport.d.ts.map +1 -0
  12. package/dist/databaseImport.js +126 -0
  13. package/dist/databaseImport.js.map +1 -0
  14. package/dist/errors.js +1 -1
  15. package/dist/errors.js.map +1 -1
  16. package/dist/index.d.ts +15 -1973
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +9 -641
  19. package/dist/index.js.map +1 -1
  20. package/dist/manifest.d.ts.map +1 -1
  21. package/dist/manifest.js +25 -21
  22. package/dist/manifest.js.map +1 -1
  23. package/dist/resources/auth.d.ts +282 -0
  24. package/dist/resources/auth.d.ts.map +1 -0
  25. package/dist/resources/auth.js +111 -0
  26. package/dist/resources/auth.js.map +1 -0
  27. package/dist/resources/databases.d.ts +178 -0
  28. package/dist/resources/databases.d.ts.map +1 -0
  29. package/dist/resources/databases.js +82 -0
  30. package/dist/resources/databases.js.map +1 -0
  31. package/dist/resources/deploys.d.ts +125 -0
  32. package/dist/resources/deploys.d.ts.map +1 -0
  33. package/dist/resources/deploys.js +46 -0
  34. package/dist/resources/deploys.js.map +1 -0
  35. package/dist/resources/domains.d.ts +109 -0
  36. package/dist/resources/domains.d.ts.map +1 -0
  37. package/dist/resources/domains.js +29 -0
  38. package/dist/resources/domains.js.map +1 -0
  39. package/dist/resources/env.d.ts +55 -0
  40. package/dist/resources/env.d.ts.map +1 -0
  41. package/dist/resources/env.js +33 -0
  42. package/dist/resources/env.js.map +1 -0
  43. package/dist/resources/logs.d.ts +27 -0
  44. package/dist/resources/logs.d.ts.map +1 -0
  45. package/dist/resources/logs.js +18 -0
  46. package/dist/resources/logs.js.map +1 -0
  47. package/dist/resources/network.d.ts +169 -0
  48. package/dist/resources/network.d.ts.map +1 -0
  49. package/dist/resources/network.js +39 -0
  50. package/dist/resources/network.js.map +1 -0
  51. package/dist/resources/plugins.d.ts +42 -0
  52. package/dist/resources/plugins.d.ts.map +1 -0
  53. package/dist/resources/plugins.js +22 -0
  54. package/dist/resources/plugins.js.map +1 -0
  55. package/dist/resources/servers.d.ts +130 -0
  56. package/dist/resources/servers.d.ts.map +1 -0
  57. package/dist/resources/servers.js +37 -0
  58. package/dist/resources/servers.js.map +1 -0
  59. package/dist/resources/services.d.ts +240 -0
  60. package/dist/resources/services.d.ts.map +1 -0
  61. package/dist/resources/services.js +93 -0
  62. package/dist/resources/services.js.map +1 -0
  63. package/dist/resources/settings.d.ts +373 -0
  64. package/dist/resources/settings.d.ts.map +1 -0
  65. package/dist/resources/settings.js +94 -0
  66. package/dist/resources/settings.js.map +1 -0
  67. package/dist/resources/sources.d.ts +87 -0
  68. package/dist/resources/sources.d.ts.map +1 -0
  69. package/dist/resources/sources.js +39 -0
  70. package/dist/resources/sources.js.map +1 -0
  71. package/dist/resources/system.d.ts +240 -0
  72. package/dist/resources/system.d.ts.map +1 -0
  73. package/dist/resources/system.js +86 -0
  74. package/dist/resources/system.js.map +1 -0
  75. package/dist/resources/templates.d.ts +36 -0
  76. package/dist/resources/templates.d.ts.map +1 -0
  77. package/dist/resources/templates.js +17 -0
  78. package/dist/resources/templates.js.map +1 -0
  79. package/dist/resources/terminals.d.ts +60 -0
  80. package/dist/resources/terminals.d.ts.map +1 -0
  81. package/dist/resources/terminals.js +35 -0
  82. package/dist/resources/terminals.js.map +1 -0
  83. package/dist/resources/traefik.d.ts +93 -0
  84. package/dist/resources/traefik.d.ts.map +1 -0
  85. package/dist/resources/traefik.js +39 -0
  86. package/dist/resources/traefik.js.map +1 -0
  87. package/dist/resources/volumes.d.ts +114 -0
  88. package/dist/resources/volumes.d.ts.map +1 -0
  89. package/dist/resources/volumes.js +47 -0
  90. package/dist/resources/volumes.js.map +1 -0
  91. package/dist/terminal.d.ts +98 -0
  92. package/dist/terminal.d.ts.map +1 -0
  93. package/dist/terminal.js +180 -0
  94. package/dist/terminal.js.map +1 -0
  95. package/dist/transport.d.ts +61 -0
  96. package/dist/transport.d.ts.map +1 -0
  97. package/dist/transport.js +90 -0
  98. package/dist/transport.js.map +1 -0
  99. package/dist/types.d.ts +539 -0
  100. package/dist/types.d.ts.map +1 -0
  101. package/dist/types.js +5 -0
  102. package/dist/types.js.map +1 -0
  103. package/package.json +5 -4
  104. package/src/apiPath.ts +23 -0
  105. package/src/client.ts +70 -0
  106. package/src/databaseImport.ts +174 -0
  107. package/src/errors.ts +27 -0
  108. package/src/index.ts +121 -0
  109. package/src/manifest.ts +414 -0
  110. package/src/resources/auth.ts +357 -0
  111. package/src/resources/databases.ts +227 -0
  112. package/src/resources/deploys.ts +161 -0
  113. package/src/resources/domains.ts +149 -0
  114. package/src/resources/env.ts +84 -0
  115. package/src/resources/logs.ts +48 -0
  116. package/src/resources/network.ts +154 -0
  117. package/src/resources/plugins.ts +52 -0
  118. package/src/resources/servers.ts +141 -0
  119. package/src/resources/services.ts +296 -0
  120. package/src/resources/settings.ts +334 -0
  121. package/src/resources/sources.ts +131 -0
  122. package/src/resources/system.ts +281 -0
  123. package/src/resources/templates.ts +55 -0
  124. package/src/resources/terminals.ts +112 -0
  125. package/src/resources/traefik.ts +125 -0
  126. package/src/resources/volumes.ts +138 -0
  127. package/src/terminal.ts +254 -0
  128. package/src/transport.ts +154 -0
  129. package/src/types.ts +588 -0
@@ -0,0 +1,334 @@
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 type AutoUrlMode = 'off' | 'domain' | 'sslip';
19
+ export interface AutoUrlEffective {
20
+ mode: AutoUrlMode;
21
+ domain: string | null;
22
+ ip: string | null;
23
+ source: 'env' | 'setting';
24
+ }
25
+ export interface AutoUrlSettings {
26
+ effective: AutoUrlEffective;
27
+ stored: { mode: string; domain: string | null; ip: string | null };
28
+ envWildcard: string | null;
29
+ /** The installer's address (NINEDEPLOY_PUBLIC_URL), offered for sslip.io. */
30
+ suggestedIp: string | null;
31
+ }
32
+
33
+ export type OnboardingStepId =
34
+ | 'auto_url'
35
+ | 'git_source'
36
+ | 'first_service'
37
+ | 'first_deploy'
38
+ | 'custom_domain'
39
+ | 'https'
40
+ | 'notifications'
41
+ | 'backups';
42
+ export interface OnboardingStatus {
43
+ steps: { id: OnboardingStepId; done: boolean; optional: boolean }[];
44
+ completed: number;
45
+ total: number;
46
+ /** Every required step is done. */
47
+ ready: boolean;
48
+ dismissed: boolean;
49
+ autoUrl: AutoUrlEffective;
50
+ }
51
+
52
+ export interface SettingsApi {
53
+ settings: {
54
+ get: () => Promise<{ allowRegistration: boolean; acmeEmail: string | null; templatesSource: string | null; dnsProvider: string | null; hasDnsToken: boolean; wildcardApex: string | null; panelDomain: string | null }>;
55
+ setPanelDomain: (domain: string) => Promise<{ ok: boolean; panelDomain: string | null }>;
56
+ setAllowRegistration: (enabled: boolean) => Promise<{ ok: boolean; allowRegistration: boolean }>;
57
+ setAcmeEmail: (email: string) => Promise<{ ok: boolean; acmeEmail: string | null; applied: string }>;
58
+ setTemplatesSource: (source: string) => Promise<{ ok: boolean; templatesSource: string | null }>;
59
+ /** Configure the ACME DNS-01 challenge (wildcard certificates). */
60
+ setDns: (input: { provider: string; token?: string; wildcardApex: string }) => Promise<{ ok: boolean; dnsProvider: string | null; wildcardApex: string | null; applied: string }>;
61
+ /**
62
+ * 0.16.1 Automatic URLs: how a service gets its own URL with no DNS work —
63
+ * `<slug>.<domain>` or a free `<slug>.<ip>.sslip.io`. `envWildcard`
64
+ * (NINEDEPLOY_WILDCARD_DOMAIN) wins over the stored choice when set.
65
+ */
66
+ autoUrl: {
67
+ get: () => Promise<AutoUrlSettings>;
68
+ set: (input: { mode: AutoUrlMode; domain?: string; ip?: string }) => Promise<{ ok: boolean; effective: AutoUrlEffective }>;
69
+ /** Asks a public echo service which IPv4 this host egresses from. */
70
+ detectIp: () => Promise<{ ip: string; public: boolean }>;
71
+ };
72
+ /** Vault provider (deploy-time secret resolution). */
73
+ vault: {
74
+ get: () => Promise<VaultSettings>;
75
+ set: (input: { provider: '' | 'infisical' | 'doppler'; token?: string; projectId?: string; environment?: string }) => Promise<{ ok: boolean; provider: string | null }>;
76
+ test: () => Promise<{ ok: boolean; secrets: number }>;
77
+ /** Which workspaces (and legacy un-tagged services) may resolve vault references. */
78
+ setAllowlist: (input: { workspaceIds: number[]; serviceIds?: number[] }) => Promise<{ ok: boolean } & VaultAllowlistView>;
79
+ };
80
+ /** Cloudflare DNS-record auto-provisioning for added domains. */
81
+ dnsRecords: {
82
+ get: () => Promise<{ enabled: boolean; hasToken: boolean; content: string | null }>;
83
+ set: (input: { enabled: boolean; token?: string; content?: string }) => Promise<{ ok: boolean; enabled: boolean }>;
84
+ test: () => Promise<{ ok: boolean; status?: string; error?: string }>;
85
+ };
86
+ /** Namecheap DNS-record auto-provisioning for added domains (G-07 PR-A). */
87
+ namecheap: {
88
+ get: () => Promise<{ configured: boolean; apiUser: string | null; clientIp: string | null; hasKey: boolean }>;
89
+ set: (input: { apiUser: string; apiKey: string; clientIp: string }) => Promise<{ ok: boolean; apiUser: string }>;
90
+ };
91
+ /**
92
+ * Master-key rotation. `rotate` re-encrypts every stored secret onto the
93
+ * highest key version in `NINEDEPLOY_MASTER_KEYS`; it does NOT rewrite
94
+ * backup envelopes, which is what `warning` reports.
95
+ */
96
+ masterKey: {
97
+ get: () => Promise<{ activeVersion: number; knownVersions: number[]; rotatable: boolean }>;
98
+ rotate: () => Promise<{ rotated: number; activeVersion: number; backupsNotRotated: number; warning: string | null }>;
99
+ };
100
+ /**
101
+ * Node-enrolment token (r463): the secret an operator pastes into a
102
+ * remote agent's NINEDEPLOY_ENROLMENT_TOKEN. Returned in clear — the
103
+ * server routes are admin-only for exactly that reason.
104
+ */
105
+ enrolment: {
106
+ get: () => Promise<{ enabled: boolean; token: string | null }>;
107
+ rotate: () => Promise<{ ok: boolean; enabled: boolean; token: string }>;
108
+ disable: () => Promise<{ ok: boolean; enabled: boolean }>;
109
+ };
110
+ /**
111
+ * 0.14: HashiCorp Vault / OpenBao and AWS Secrets Manager (operator-only).
112
+ * Credentials are write-only: `list` never returns them, and an omitted
113
+ * credential field on `set` keeps the stored value.
114
+ */
115
+ secretProviders: {
116
+ list: () => Promise<SecretProviderView[]>;
117
+ set: <K extends SecretProviderKind>(kind: K, input: K extends 'vault' ? VaultProviderPut : AwsProviderPut) => Promise<SecretProviderView>;
118
+ delete: (kind: SecretProviderKind) => Promise<{ ok: boolean; deleted: boolean }>;
119
+ test: (kind: SecretProviderKind, probe?: SecretProviderTestRequest) => Promise<SecretProviderTestResult>;
120
+ };
121
+ };
122
+ branding: {
123
+ /** Read the four operator-overridable branding fields. G-30. */
124
+ get: () => Promise<{
125
+ logoUrl: string | null;
126
+ primaryColor: string | null;
127
+ supportEmail: string | null;
128
+ footerHtml: string | null;
129
+ }>;
130
+ /** Set one or more branding fields. Empty strings are stored as `null`. */
131
+ set: (input: {
132
+ logoUrl?: string | null;
133
+ primaryColor?: string | null;
134
+ supportEmail?: string | null;
135
+ footerHtml?: string | null;
136
+ }) => Promise<{ ok: boolean }>;
137
+ };
138
+ configPresets: {
139
+ /** Every preset the kernel has registered. */
140
+ list: () => Promise<{ presets: string[] }>;
141
+ /** Fetch a single preset's values + description. */
142
+ get: (id: string) => Promise<{
143
+ id: string;
144
+ description: string | null;
145
+ values: Record<string, unknown>;
146
+ createdAt: string;
147
+ }>;
148
+ /** Register a new preset. Throws if `id` already exists. */
149
+ register: (input: { id: string; description?: string; values: Record<string, unknown> }) => Promise<{ ok: boolean; id: string; keyCount: number }>;
150
+ /** Write every value in the preset to the live configCenter. */
151
+ apply: (id: string, opts?: { override?: Record<string, unknown> }) => Promise<{
152
+ ok: boolean;
153
+ id: string;
154
+ keyCount: number;
155
+ failureCount?: number;
156
+ failures?: Array<{ key: string; status: 'failed'; reason?: string }>;
157
+ }>;
158
+ /** Unregister a preset. Does NOT undo the apply. */
159
+ remove: (id: string) => Promise<{ ok: boolean; id: string }>;
160
+ };
161
+ config: {
162
+ list: (query?: { category?: string; pluginId?: string; reveal?: boolean }) => Promise<ConfigListResponse>;
163
+ get: (key: string) => Promise<ConfigItem>;
164
+ set: (key: string, input: SetConfigInput) => Promise<{ ok: boolean; key: string }>;
165
+ delete: (key: string) => Promise<{ ok: boolean; key: string }>;
166
+ };
167
+ /**
168
+ * Per-workspace email template overrides (G-30).
169
+ * Read-side is open to members; write-side requires the
170
+ * workspace's admin role. The list / preview are safe
171
+ * to call from a CI script; `set` / `reset` mutate the
172
+ * panel's outbound email and should be operator-only.
173
+ */
174
+ emailTemplates: {
175
+ list: (workspaceId: number) => Promise<{ workspaceId: number; templates: EmailTemplateEntry[] }>;
176
+ preview: (workspaceId: number, name: EmailTemplateName, vars?: Record<string, string | number | null>) => Promise<EmailTemplateRender>;
177
+ set: (workspaceId: number, name: EmailTemplateName, subject: string, text: string) => Promise<{ ok: boolean }>;
178
+ reset: (workspaceId: number, name: EmailTemplateName) => Promise<{ ok: boolean }>;
179
+ };
180
+ notifications: {
181
+ /**
182
+ * r473: the provider config blob is encrypted at rest and WRITE-ONLY over
183
+ * the API for everything but the secret-free Discord shape — `configJson`
184
+ * is null with `hasConfig: true` for webhook/FCM channels. Never PATCH
185
+ * the read value back: send the full config, `''` to clear it, or omit
186
+ * the field.
187
+ *
188
+ * r552: the write side is `configJson?: string` — the server schema is
189
+ * `z.string().optional()`, so the `null` the type used to allow was a 400.
190
+ */
191
+ listChannels: () => Promise<Array<{ id: number; name: string; type: string; eventFilter: string; active: boolean; configJson: string | null; hasConfig: boolean; createdAt: string }>>;
192
+ createChannel: (input: { name: string; type: string; target: string; eventFilter?: string; configJson?: string }) => Promise<{ id: number; name: string; type: string }>;
193
+ updateChannel: (id: number, input: { name?: string; target?: string; eventFilter?: string; active?: boolean; configJson?: string }) => Promise<{ id: number; active: boolean }>;
194
+ removeChannel: (id: number) => Promise<void>;
195
+ testChannel: (id: number) => Promise<{ ok: boolean }>;
196
+ log: () => Promise<Array<{ id: number; channelId: number | null; event: string; entity: string | null; status: string; error: string | null; ts: string }>>;
197
+ };
198
+ /** 0.16.1: the operator's Getting started checklist (Dashboard card). */
199
+ onboarding: {
200
+ get: () => Promise<OnboardingStatus>;
201
+ setDismissed: (dismissed: boolean) => Promise<{ ok: boolean; dismissed: boolean }>;
202
+ };
203
+ }
204
+
205
+ export function settingsApi(t: Transport): SettingsApi {
206
+ const { get, send, request } = t;
207
+ return {
208
+ settings: {
209
+ get: () => get<{ allowRegistration: boolean; acmeEmail: string | null; templatesSource: string | null; dnsProvider: string | null; hasDnsToken: boolean; wildcardApex: string | null; panelDomain: string | null }>('/v1/settings'),
210
+ setPanelDomain: (domain) =>
211
+ send<{ ok: boolean; panelDomain: string | null }>('PUT', '/v1/settings/panel-domain', { domain }),
212
+ setAllowRegistration: (enabled) =>
213
+ send<{ ok: boolean; allowRegistration: boolean }>('PUT', '/v1/settings/allow-registration', { enabled }),
214
+ setAcmeEmail: (email) =>
215
+ send<{ ok: boolean; acmeEmail: string | null; applied: string }>('PUT', '/v1/settings/acme-email', { email }),
216
+ setTemplatesSource: (source) =>
217
+ send<{ ok: boolean; templatesSource: string | null }>('PUT', '/v1/settings/templates-source', { source }),
218
+ setDns: (input) =>
219
+ send<{ ok: boolean; dnsProvider: string | null; wildcardApex: string | null; applied: string }>('PUT', '/v1/settings/dns', input),
220
+ autoUrl: {
221
+ get: () => get<AutoUrlSettings>('/v1/settings/auto-url'),
222
+ set: (input) => send<{ ok: boolean; effective: AutoUrlEffective }>('PUT', '/v1/settings/auto-url', input),
223
+ detectIp: () => send<{ ip: string; public: boolean }>('POST', '/v1/settings/auto-url/detect-ip', {}),
224
+ },
225
+ vault: {
226
+ get: () => get<VaultSettings>('/v1/settings/vault'),
227
+ set: (input) => send<{ ok: boolean; provider: string | null }>('PUT', '/v1/settings/vault', input),
228
+ test: () => send<{ ok: boolean; secrets: number }>('POST', '/v1/settings/vault/test'),
229
+ setAllowlist: (input) => send<{ ok: boolean } & VaultAllowlistView>('PUT', '/v1/settings/vault/allowlist', input),
230
+ },
231
+ dnsRecords: {
232
+ get: () => get<{ enabled: boolean; hasToken: boolean; content: string | null }>('/v1/settings/dns-records'),
233
+ set: (input) => send<{ ok: boolean; enabled: boolean }>('PUT', '/v1/settings/dns-records', input),
234
+ test: () => send<{ ok: boolean; status?: string; error?: string }>('POST', '/v1/settings/dns-records/test'),
235
+ },
236
+ namecheap: {
237
+ get: () => get<{ configured: boolean; apiUser: string | null; clientIp: string | null; hasKey: boolean }>('/v1/settings/dns-records/namecheap'),
238
+ set: (input) => send<{ ok: boolean; apiUser: string }>('PUT', '/v1/settings/dns-records/namecheap', input),
239
+ },
240
+ masterKey: {
241
+ get: () => get<{ activeVersion: number; knownVersions: number[]; rotatable: boolean }>('/v1/settings/master-key'),
242
+ rotate: () =>
243
+ send<{ rotated: number; activeVersion: number; backupsNotRotated: number; warning: string | null }>(
244
+ 'POST',
245
+ '/v1/settings/master-key/rotate',
246
+ ),
247
+ },
248
+ enrolment: {
249
+ get: () => get<{ enabled: boolean; token: string | null }>('/v1/settings/enrolment'),
250
+ rotate: () => send<{ ok: boolean; enabled: boolean; token: string }>('POST', '/v1/settings/enrolment/rotate'),
251
+ disable: () => send<{ ok: boolean; enabled: boolean }>('DELETE', '/v1/settings/enrolment'),
252
+ },
253
+ secretProviders: {
254
+ list: () => get<SecretProviderView[]>('/v1/settings/secret-providers'),
255
+ set: (kind, input) => send<SecretProviderView>('PUT', `/v1/settings/secret-providers/${kind}`, input),
256
+ delete: (kind) => send<{ ok: boolean; deleted: boolean }>('DELETE', `/v1/settings/secret-providers/${kind}`),
257
+ test: (kind, probe) =>
258
+ send<SecretProviderTestResult>('POST', `/v1/settings/secret-providers/${kind}/test`, probe ?? {}),
259
+ },
260
+ },
261
+ branding: {
262
+ get: () => get<{
263
+ logoUrl: string | null;
264
+ primaryColor: string | null;
265
+ supportEmail: string | null;
266
+ footerHtml: string | null;
267
+ }>('/v1/branding'),
268
+ set: (input) => send<{ ok: boolean }>('PATCH', '/v1/branding', input),
269
+ },
270
+ configPresets: {
271
+ list: () => get<{ presets: string[] }>('/v1/config-presets'),
272
+ get: (id) => get<{
273
+ id: string;
274
+ description: string | null;
275
+ values: Record<string, unknown>;
276
+ createdAt: string;
277
+ }>(`/v1/config-presets/${encodeURIComponent(id)}`),
278
+ register: (input) => send<{ ok: boolean; id: string; keyCount: number }>('POST', '/v1/config-presets', input),
279
+ apply: (id, opts) => send<{
280
+ ok: boolean;
281
+ id: string;
282
+ keyCount: number;
283
+ failureCount?: number;
284
+ failures?: Array<{ key: string; status: 'failed'; reason?: string }>;
285
+ }>('PUT', `/v1/config-presets/${encodeURIComponent(id)}/apply`, opts ?? {}),
286
+ remove: (id) => send<{ ok: boolean; id: string }>('DELETE', `/v1/config-presets/${encodeURIComponent(id)}`),
287
+ },
288
+ config: {
289
+ list: (query) => {
290
+ const parts: string[] = [];
291
+ if (query?.category) parts.push(`category=${encodeURIComponent(query.category)}`);
292
+ if (query?.pluginId) parts.push(`pluginId=${encodeURIComponent(query.pluginId)}`);
293
+ if (query?.reveal) parts.push('reveal=true');
294
+ const qs = parts.length ? `?${parts.join('&')}` : '';
295
+ return get<ConfigListResponse>(`/v1/config${qs}`);
296
+ },
297
+ get: (key) => get<ConfigItem>(`/v1/config/${encodeURIComponent(key)}`),
298
+ set: (key, input) => send<{ ok: boolean; key: string }>('POST', `/v1/config/${encodeURIComponent(key)}`, input),
299
+ delete: (key) => send<{ ok: boolean; key: string }>('DELETE', `/v1/config/${encodeURIComponent(key)}`),
300
+ },
301
+ emailTemplates: {
302
+ list: (workspaceId) =>
303
+ get<{ workspaceId: number; templates: EmailTemplateEntry[] }>(
304
+ `/v1/workspaces/${workspaceId}/email-templates`,
305
+ ),
306
+ preview: (workspaceId, name, vars) =>
307
+ send<EmailTemplateRender>(
308
+ 'POST',
309
+ `/v1/workspaces/${workspaceId}/email-templates/preview`,
310
+ { name, vars: vars ?? {} },
311
+ ),
312
+ set: (workspaceId, name, subject, text) =>
313
+ send<{ ok: boolean }>(
314
+ 'PUT',
315
+ `/v1/workspaces/${workspaceId}/email-templates/${name}`,
316
+ { subject, text },
317
+ ),
318
+ reset: (workspaceId, name) =>
319
+ send<{ ok: boolean }>('DELETE', `/v1/workspaces/${workspaceId}/email-templates/${name}`, {}),
320
+ },
321
+ notifications: {
322
+ listChannels: () => get<Array<{ id: number; name: string; type: string; eventFilter: string; active: boolean; configJson: string | null; hasConfig: boolean; createdAt: string }>>('/v1/notifications/channels'),
323
+ createChannel: (input) => send('POST', '/v1/notifications/channels', input),
324
+ updateChannel: (id, input) => send('PATCH', `/v1/notifications/channels/${id}`, input),
325
+ removeChannel: async (id) => { await request(`/v1/notifications/channels/${id}`, { method: 'DELETE' }); },
326
+ testChannel: (id) => send('POST', `/v1/notifications/channels/${id}/test`),
327
+ log: () => get('/v1/notifications/log'),
328
+ },
329
+ onboarding: {
330
+ get: () => get<OnboardingStatus>('/v1/onboarding'),
331
+ setDismissed: (dismissed) => send<{ ok: boolean; dismissed: boolean }>('PUT', '/v1/onboarding/dismissed', { dismissed }),
332
+ },
333
+ };
334
+ }
@@ -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
+ }