@directus/api 33.3.1 → 34.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.
Files changed (98) hide show
  1. package/dist/ai/chat/lib/create-ui-stream.js +2 -1
  2. package/dist/ai/chat/lib/transform-file-parts.d.ts +12 -0
  3. package/dist/ai/chat/lib/transform-file-parts.js +36 -0
  4. package/dist/ai/files/adapters/anthropic.d.ts +3 -0
  5. package/dist/ai/files/adapters/anthropic.js +25 -0
  6. package/dist/ai/files/adapters/google.d.ts +3 -0
  7. package/dist/ai/files/adapters/google.js +58 -0
  8. package/dist/ai/files/adapters/index.d.ts +3 -0
  9. package/dist/ai/files/adapters/index.js +3 -0
  10. package/dist/ai/files/adapters/openai.d.ts +3 -0
  11. package/dist/ai/files/adapters/openai.js +22 -0
  12. package/dist/ai/files/controllers/upload.d.ts +2 -0
  13. package/dist/ai/files/controllers/upload.js +101 -0
  14. package/dist/ai/files/lib/fetch-provider.d.ts +1 -0
  15. package/dist/ai/files/lib/fetch-provider.js +23 -0
  16. package/dist/ai/files/lib/upload-to-provider.d.ts +4 -0
  17. package/dist/ai/files/lib/upload-to-provider.js +26 -0
  18. package/dist/ai/files/router.d.ts +1 -0
  19. package/dist/ai/files/router.js +5 -0
  20. package/dist/ai/files/types.d.ts +5 -0
  21. package/dist/ai/files/types.js +1 -0
  22. package/dist/ai/providers/anthropic-file-support.d.ts +12 -0
  23. package/dist/ai/providers/anthropic-file-support.js +94 -0
  24. package/dist/ai/providers/registry.js +3 -6
  25. package/dist/ai/tools/flows/index.d.ts +16 -16
  26. package/dist/ai/tools/schema.d.ts +8 -8
  27. package/dist/ai/tools/schema.js +2 -2
  28. package/dist/app.js +10 -1
  29. package/dist/controllers/deployment-webhooks.d.ts +2 -0
  30. package/dist/controllers/deployment-webhooks.js +95 -0
  31. package/dist/controllers/deployment.js +61 -165
  32. package/dist/controllers/files.js +2 -1
  33. package/dist/database/get-ast-from-query/lib/parse-fields.js +52 -26
  34. package/dist/database/helpers/date/dialects/oracle.js +2 -0
  35. package/dist/database/helpers/date/dialects/sqlite.js +2 -0
  36. package/dist/database/helpers/date/types.d.ts +1 -1
  37. package/dist/database/helpers/date/types.js +3 -1
  38. package/dist/database/helpers/fn/dialects/mssql.d.ts +1 -0
  39. package/dist/database/helpers/fn/dialects/mssql.js +21 -0
  40. package/dist/database/helpers/fn/dialects/mysql.d.ts +2 -0
  41. package/dist/database/helpers/fn/dialects/mysql.js +30 -0
  42. package/dist/database/helpers/fn/dialects/oracle.d.ts +1 -0
  43. package/dist/database/helpers/fn/dialects/oracle.js +21 -0
  44. package/dist/database/helpers/fn/dialects/postgres.d.ts +14 -0
  45. package/dist/database/helpers/fn/dialects/postgres.js +40 -0
  46. package/dist/database/helpers/fn/dialects/sqlite.d.ts +1 -0
  47. package/dist/database/helpers/fn/dialects/sqlite.js +12 -0
  48. package/dist/database/helpers/fn/json/parse-function.d.ts +19 -0
  49. package/dist/database/helpers/fn/json/parse-function.js +66 -0
  50. package/dist/database/helpers/fn/types.d.ts +8 -0
  51. package/dist/database/helpers/fn/types.js +19 -0
  52. package/dist/database/helpers/schema/dialects/mysql.d.ts +1 -0
  53. package/dist/database/helpers/schema/dialects/mysql.js +11 -0
  54. package/dist/database/helpers/schema/types.d.ts +1 -0
  55. package/dist/database/helpers/schema/types.js +3 -0
  56. package/dist/database/index.js +2 -1
  57. package/dist/database/migrations/20260211A-add-deployment-webhooks.d.ts +3 -0
  58. package/dist/database/migrations/20260211A-add-deployment-webhooks.js +37 -0
  59. package/dist/database/run-ast/lib/apply-query/filter/get-filter-type.d.ts +2 -2
  60. package/dist/database/run-ast/lib/apply-query/filter/operator.js +17 -7
  61. package/dist/database/run-ast/lib/parse-current-level.js +8 -1
  62. package/dist/database/run-ast/run-ast.js +11 -1
  63. package/dist/database/run-ast/utils/apply-function-to-column-name.js +7 -1
  64. package/dist/database/run-ast/utils/get-column.js +13 -2
  65. package/dist/deployment/deployment.d.ts +25 -2
  66. package/dist/deployment/drivers/netlify.d.ts +6 -2
  67. package/dist/deployment/drivers/netlify.js +114 -12
  68. package/dist/deployment/drivers/vercel.d.ts +5 -2
  69. package/dist/deployment/drivers/vercel.js +84 -5
  70. package/dist/deployment.d.ts +5 -0
  71. package/dist/deployment.js +34 -0
  72. package/dist/permissions/utils/get-unaliased-field-key.js +9 -1
  73. package/dist/request/is-denied-ip.js +24 -23
  74. package/dist/services/authentication.js +27 -22
  75. package/dist/services/collections.js +1 -0
  76. package/dist/services/deployment-projects.d.ts +31 -2
  77. package/dist/services/deployment-projects.js +109 -5
  78. package/dist/services/deployment-runs.d.ts +19 -1
  79. package/dist/services/deployment-runs.js +86 -0
  80. package/dist/services/deployment.d.ts +44 -3
  81. package/dist/services/deployment.js +263 -15
  82. package/dist/services/files/utils/get-metadata.js +6 -6
  83. package/dist/services/files.d.ts +3 -1
  84. package/dist/services/files.js +26 -3
  85. package/dist/services/graphql/resolvers/query.js +23 -6
  86. package/dist/services/payload.d.ts +6 -0
  87. package/dist/services/payload.js +27 -2
  88. package/dist/services/server.js +1 -1
  89. package/dist/services/users.js +6 -1
  90. package/dist/utils/get-field-relational-depth.d.ts +13 -0
  91. package/dist/utils/get-field-relational-depth.js +22 -0
  92. package/dist/utils/parse-value.d.ts +4 -0
  93. package/dist/utils/parse-value.js +11 -0
  94. package/dist/utils/sanitize-query.js +3 -2
  95. package/dist/utils/split-fields.d.ts +4 -0
  96. package/dist/utils/split-fields.js +32 -0
  97. package/dist/utils/validate-query.js +2 -1
  98. package/package.json +29 -29
@@ -42,11 +42,25 @@ export class AuthenticationService {
42
42
  const STALL_TIME = env['LOGIN_STALL_TIME'];
43
43
  const timeStart = performance.now();
44
44
  const provider = getAuthProvider(providerName);
45
+ const emitStatus = (status, loginPayload, loginUser, error) => {
46
+ emitter.emitAction('auth.login', {
47
+ payload: loginPayload,
48
+ status,
49
+ user: loginUser?.id,
50
+ provider: providerName,
51
+ error,
52
+ }, {
53
+ database: this.knex,
54
+ schema: this.schema,
55
+ accountability: this.accountability,
56
+ });
57
+ };
45
58
  let userId;
46
59
  try {
47
60
  userId = await provider.getUserID(cloneDeep(payload));
48
61
  }
49
62
  catch (err) {
63
+ emitStatus('fail', payload, undefined, err);
50
64
  await stall(STALL_TIME, timeStart);
51
65
  throw err;
52
66
  }
@@ -64,22 +78,11 @@ export class AuthenticationService {
64
78
  schema: this.schema,
65
79
  accountability: this.accountability,
66
80
  });
67
- const emitStatus = (status) => {
68
- emitter.emitAction('auth.login', {
69
- payload: updatedPayload,
70
- status,
71
- user: user?.id,
72
- provider: providerName,
73
- }, {
74
- database: this.knex,
75
- schema: this.schema,
76
- accountability: this.accountability,
77
- });
78
- };
79
81
  if (user?.status !== 'active' || user?.provider !== providerName) {
80
- emitStatus('fail');
82
+ const loginError = new InvalidCredentialsError();
83
+ emitStatus('fail', updatedPayload, user, loginError);
81
84
  await stall(STALL_TIME, timeStart);
82
- throw new InvalidCredentialsError();
85
+ throw loginError;
83
86
  }
84
87
  const settingsService = new SettingsService({
85
88
  knex: this.knex,
@@ -130,23 +133,25 @@ export class AuthenticationService {
130
133
  try {
131
134
  await provider.login(clone(user), cloneDeep(updatedPayload));
132
135
  }
133
- catch (e) {
134
- emitStatus('fail');
136
+ catch (err) {
137
+ emitStatus('fail', updatedPayload, user, err);
135
138
  await stall(STALL_TIME, timeStart);
136
- throw e;
139
+ throw err;
137
140
  }
138
141
  if (user.tfa_secret && !options?.otp) {
139
- emitStatus('fail');
142
+ const loginError = new InvalidOtpError();
143
+ emitStatus('fail', updatedPayload, user, loginError);
140
144
  await stall(STALL_TIME, timeStart);
141
- throw new InvalidOtpError();
145
+ throw loginError;
142
146
  }
143
147
  if (user.tfa_secret && options?.otp) {
144
148
  const tfaService = new TFAService({ knex: this.knex, schema: this.schema });
145
149
  const otpValid = await tfaService.verifyOTP(user.id, options?.otp);
146
150
  if (otpValid === false) {
147
- emitStatus('fail');
151
+ const loginError = new InvalidOtpError();
152
+ emitStatus('fail', updatedPayload, user, loginError);
148
153
  await stall(STALL_TIME, timeStart);
149
- throw new InvalidOtpError();
154
+ throw loginError;
150
155
  }
151
156
  }
152
157
  const roles = await fetchRolesTree(user.role, { knex: this.knex });
@@ -214,7 +219,7 @@ export class AuthenticationService {
214
219
  });
215
220
  }
216
221
  await this.knex('directus_users').update({ last_access: new Date() }).where({ id: user.id });
217
- emitStatus('success');
222
+ emitStatus('success', updatedPayload, user);
218
223
  if (allowedAttempts !== null) {
219
224
  await loginAttemptsLimiter.set(user.id, 0, 0);
220
225
  }
@@ -52,6 +52,7 @@ export class CollectionsService {
52
52
  if (payload.collection.startsWith('directus_')) {
53
53
  throw new InvalidPayloadError({ reason: `Collections can't start with "directus_"` });
54
54
  }
55
+ payload.collection = await this.helpers.schema.parseCollectionName(payload.collection);
55
56
  const nestedActionEvents = [];
56
57
  try {
57
58
  const existingCollections = [
@@ -1,20 +1,49 @@
1
- import type { AbstractServiceOptions, PrimaryKey } from '@directus/types';
1
+ import type { AbstractServiceOptions, PrimaryKey, Project as ProviderProject, ProviderType } from '@directus/types';
2
2
  import { ItemsService } from './items.js';
3
3
  export interface DeploymentProject {
4
4
  id: string;
5
5
  deployment: string;
6
6
  external_id: string;
7
7
  name: string;
8
+ url: string | null;
9
+ framework: string | null;
10
+ deployable: boolean;
8
11
  date_created: string;
9
12
  user_created: string;
10
13
  }
11
14
  export declare class DeploymentProjectsService extends ItemsService<DeploymentProject> {
12
15
  constructor(options: AbstractServiceOptions);
16
+ /**
17
+ * Find a project by its provider-side external ID. Returns null if not tracked.
18
+ */
19
+ readByExternalId(externalId: string): Promise<DeploymentProject | null>;
20
+ /**
21
+ * List provider projects merged with DB selection, syncing metadata.
22
+ */
23
+ listWithSync(deploymentId: string, providerProjects: ProviderProject[]): Promise<{
24
+ id: string | null;
25
+ external_id: string;
26
+ name: string;
27
+ deployable: boolean;
28
+ framework: string | undefined;
29
+ }[]>;
30
+ /**
31
+ * Validate that all projects to create are deployable.
32
+ */
33
+ validateDeployable(provider: ProviderType, projectsToCreate: {
34
+ external_id: string;
35
+ name: string;
36
+ }[]): Promise<void>;
13
37
  /**
14
38
  * Update project selection (create/delete)
15
39
  */
16
- updateSelection(deploymentId: string, create: {
40
+ updateSelection(provider: ProviderType, create: {
17
41
  external_id: string;
18
42
  name: string;
19
43
  }[], deleteIds: PrimaryKey[]): Promise<DeploymentProject[]>;
44
+ /**
45
+ * Read deployment config by provider (null accountability for internal use).
46
+ */
47
+ private readConfig;
48
+ private createDriver;
20
49
  }
@@ -1,15 +1,92 @@
1
+ import { InvalidPayloadError } from '@directus/errors';
1
2
  import getDatabase from '../database/index.js';
3
+ import { getDeploymentDriver } from '../deployment.js';
4
+ import { parseValue } from '../utils/parse-value.js';
2
5
  import { transaction } from '../utils/transaction.js';
3
6
  import { ItemsService } from './items.js';
4
7
  export class DeploymentProjectsService extends ItemsService {
5
8
  constructor(options) {
6
9
  super('directus_deployment_projects', options);
7
10
  }
11
+ /**
12
+ * Find a project by its provider-side external ID. Returns null if not tracked.
13
+ */
14
+ async readByExternalId(externalId) {
15
+ const results = await this.readByQuery({
16
+ filter: { external_id: { _eq: externalId } },
17
+ limit: 1,
18
+ });
19
+ return results?.[0] ?? null;
20
+ }
21
+ /**
22
+ * List provider projects merged with DB selection, syncing metadata.
23
+ */
24
+ async listWithSync(deploymentId, providerProjects) {
25
+ const selectedProjects = await this.readByQuery({
26
+ filter: { deployment: { _eq: deploymentId } },
27
+ limit: -1,
28
+ });
29
+ const selectedMap = new Map(selectedProjects.map((p) => [p.external_id, p]));
30
+ // Sync name and deployable
31
+ const toUpdate = selectedProjects
32
+ .map((dbProject) => {
33
+ const providerProject = providerProjects.find((p) => p.id === dbProject.external_id);
34
+ if (!providerProject)
35
+ return null;
36
+ return {
37
+ id: dbProject.id,
38
+ name: providerProject.name,
39
+ deployable: providerProject.deployable,
40
+ };
41
+ })
42
+ .filter((update) => update !== null);
43
+ if (toUpdate.length > 0) {
44
+ await this.updateBatch(toUpdate);
45
+ }
46
+ return providerProjects.map((project) => ({
47
+ id: selectedMap.get(project.id)?.id ?? null,
48
+ external_id: project.id,
49
+ name: project.name,
50
+ deployable: project.deployable,
51
+ framework: project.framework,
52
+ }));
53
+ }
54
+ /**
55
+ * Validate that all projects to create are deployable.
56
+ */
57
+ async validateDeployable(provider, projectsToCreate) {
58
+ if (projectsToCreate.length === 0)
59
+ return;
60
+ const config = await this.readConfig(provider);
61
+ const driver = this.createDriver(config);
62
+ const providerProjects = await driver.listProjects();
63
+ const projectsMap = new Map(providerProjects.map((p) => [p.id, p]));
64
+ const nonDeployable = projectsToCreate.filter((p) => !projectsMap.get(p.external_id)?.deployable);
65
+ if (nonDeployable.length > 0) {
66
+ const names = nonDeployable.map((p) => projectsMap.get(p.external_id)?.name || p.external_id).join(', ');
67
+ throw new InvalidPayloadError({
68
+ reason: `Cannot add non-deployable projects: ${names}`,
69
+ });
70
+ }
71
+ }
8
72
  /**
9
73
  * Update project selection (create/delete)
10
74
  */
11
- async updateSelection(deploymentId, create, deleteIds) {
75
+ async updateSelection(provider, create, deleteIds) {
76
+ const config = await this.readConfig(provider);
12
77
  const db = getDatabase();
78
+ const driver = this.createDriver(config);
79
+ // Fetch metadata for new projects
80
+ const enrichedCreate = await Promise.all(create.map(async (p) => {
81
+ const details = await driver.getProject(p.external_id);
82
+ return {
83
+ external_id: p.external_id,
84
+ name: p.name,
85
+ url: details.url ?? null,
86
+ framework: details.framework ?? null,
87
+ deployable: details.deployable,
88
+ };
89
+ }));
13
90
  return transaction(db, async (trx) => {
14
91
  const trxService = new DeploymentProjectsService({
15
92
  accountability: this.accountability,
@@ -19,16 +96,43 @@ export class DeploymentProjectsService extends ItemsService {
19
96
  if (deleteIds.length > 0) {
20
97
  await trxService.deleteMany(deleteIds);
21
98
  }
22
- if (create.length > 0) {
23
- await trxService.createMany(create.map((p) => ({
24
- deployment: deploymentId,
99
+ if (enrichedCreate.length > 0) {
100
+ await trxService.createMany(enrichedCreate.map((p) => ({
101
+ deployment: config.id,
25
102
  external_id: p.external_id,
26
103
  name: p.name,
104
+ url: p.url,
105
+ framework: p.framework,
106
+ deployable: p.deployable,
27
107
  })));
28
108
  }
29
109
  return trxService.readByQuery({
30
- filter: { deployment: { _eq: deploymentId } },
110
+ filter: { deployment: { _eq: config.id } },
111
+ limit: -1,
31
112
  });
32
113
  });
33
114
  }
115
+ /**
116
+ * Read deployment config by provider (null accountability for internal use).
117
+ */
118
+ async readConfig(provider) {
119
+ const internalService = new ItemsService('directus_deployments', {
120
+ knex: this.knex,
121
+ schema: this.schema,
122
+ accountability: null,
123
+ });
124
+ const results = await internalService.readByQuery({
125
+ filter: { provider: { _eq: provider } },
126
+ limit: 1,
127
+ });
128
+ if (!results || results.length === 0) {
129
+ throw new Error(`Deployment config for "${provider}" not found`);
130
+ }
131
+ return results[0];
132
+ }
133
+ createDriver(config) {
134
+ const credentials = parseValue(config.credentials, {});
135
+ const options = parseValue(config.options, {});
136
+ return getDeploymentDriver(config.provider, credentials, options);
137
+ }
34
138
  }
@@ -1,13 +1,31 @@
1
- import type { AbstractServiceOptions } from '@directus/types';
1
+ import type { AbstractServiceOptions, DeploymentWebhookEvent } from '@directus/types';
2
2
  import { ItemsService } from './items.js';
3
3
  export interface DeploymentRun {
4
4
  id: string;
5
5
  project: string;
6
6
  external_id: string;
7
7
  target: string;
8
+ status: string | null;
9
+ url: string | null;
10
+ started_at: string | null;
11
+ completed_at: string | null;
8
12
  date_created: string;
9
13
  user_created: string;
10
14
  }
11
15
  export declare class DeploymentRunsService extends ItemsService<DeploymentRun> {
12
16
  constructor(options: AbstractServiceOptions);
17
+ /**
18
+ * Process a webhook event: create or update a run based on the event data.
19
+ * Returns the run ID.
20
+ */
21
+ processWebhookEvent(projectId: string, event: DeploymentWebhookEvent): Promise<string>;
22
+ /**
23
+ * Get run stats for a project within a date range
24
+ */
25
+ getStats(projectId: string, sinceDate: string): Promise<{
26
+ total_deployments: number;
27
+ average_build_time: number | null;
28
+ failed_builds: number;
29
+ successful_builds: number;
30
+ }>;
13
31
  }
@@ -3,4 +3,90 @@ export class DeploymentRunsService extends ItemsService {
3
3
  constructor(options) {
4
4
  super('directus_deployment_runs', options);
5
5
  }
6
+ /**
7
+ * Process a webhook event: create or update a run based on the event data.
8
+ * Returns the run ID.
9
+ */
10
+ async processWebhookEvent(projectId, event) {
11
+ const existingRuns = await this.readByQuery({
12
+ filter: {
13
+ project: { _eq: projectId },
14
+ external_id: { _eq: event.deployment_external_id },
15
+ },
16
+ limit: 1,
17
+ });
18
+ const isTerminal = event.type === 'deployment.succeeded' ||
19
+ event.type === 'deployment.error' ||
20
+ event.type === 'deployment.canceled';
21
+ if (existingRuns && existingRuns.length > 0) {
22
+ const existingRun = existingRuns[0];
23
+ await this.updateOne(existingRun.id, {
24
+ status: event.status,
25
+ ...(event.url ? { url: event.url } : {}),
26
+ ...(event.type === 'deployment.created' && !existingRun.started_at
27
+ ? { started_at: event.timestamp.toISOString() }
28
+ : {}),
29
+ ...(isTerminal ? { completed_at: event.timestamp.toISOString() } : {}),
30
+ });
31
+ return existingRun.id;
32
+ }
33
+ return (await this.createOne({
34
+ project: projectId,
35
+ external_id: event.deployment_external_id,
36
+ target: event.target || 'production',
37
+ status: event.status,
38
+ ...(event.url ? { url: event.url } : {}),
39
+ started_at: event.type === 'deployment.created' ? event.timestamp.toISOString() : null,
40
+ ...(isTerminal ? { completed_at: event.timestamp.toISOString() } : {}),
41
+ }));
42
+ }
43
+ /**
44
+ * Get run stats for a project within a date range
45
+ */
46
+ async getStats(projectId, sinceDate) {
47
+ const dateFilter = {
48
+ _and: [{ project: { _eq: projectId } }, { date_created: { _gte: sinceDate } }],
49
+ };
50
+ const [countResult, completedRuns, statusCounts] = await Promise.all([
51
+ this.readByQuery({
52
+ filter: dateFilter,
53
+ aggregate: { count: ['*'] },
54
+ }),
55
+ this.readByQuery({
56
+ filter: {
57
+ _and: [
58
+ { project: { _eq: projectId } },
59
+ { date_created: { _gte: sinceDate } },
60
+ { started_at: { _nnull: true } },
61
+ { completed_at: { _nnull: true } },
62
+ ],
63
+ },
64
+ fields: ['started_at', 'completed_at'],
65
+ limit: -1,
66
+ }),
67
+ this.readByQuery({
68
+ filter: {
69
+ _and: [
70
+ { project: { _eq: projectId } },
71
+ { date_created: { _gte: sinceDate } },
72
+ { status: { _in: ['ready', 'error'] } },
73
+ ],
74
+ },
75
+ aggregate: { count: ['*'] },
76
+ group: ['status'],
77
+ }),
78
+ ]);
79
+ let averageBuildTime = null;
80
+ if (completedRuns.length > 0) {
81
+ const durations = completedRuns.map((r) => new Date(r.completed_at).getTime() - new Date(r.started_at).getTime());
82
+ averageBuildTime = Math.round(durations.reduce((a, b) => a + b, 0) / durations.length);
83
+ }
84
+ const statusMap = new Map(statusCounts.map((r) => [r.status, Number(r.count)]));
85
+ return {
86
+ total_deployments: Number(countResult[0]?.['count'] ?? 0),
87
+ average_build_time: averageBuildTime,
88
+ failed_builds: statusMap.get('error') ?? 0,
89
+ successful_builds: statusMap.get('ready') ?? 0,
90
+ };
91
+ }
6
92
  }
@@ -1,5 +1,6 @@
1
- import type { AbstractServiceOptions, CachedResult, DeploymentConfig, PrimaryKey, Project, ProviderType, Query } from '@directus/types';
1
+ import type { AbstractServiceOptions, CachedResult, Credentials, DeploymentConfig, Options, PrimaryKey, Project, ProviderType, Query } from '@directus/types';
2
2
  import type { DeploymentDriver } from '../deployment/deployment.js';
3
+ import type { DeploymentRun } from './deployment-runs.js';
3
4
  import { ItemsService } from './items.js';
4
5
  export declare class DeploymentService extends ItemsService<DeploymentConfig> {
5
6
  constructor(options: AbstractServiceOptions);
@@ -22,13 +23,21 @@ export declare class DeploymentService extends ItemsService<DeploymentConfig> {
22
23
  */
23
24
  private readConfig;
24
25
  /**
25
- * Parse JSON string or return value as-is
26
+ * Get webhook config for a provider
26
27
  */
27
- private parseValue;
28
+ getWebhookConfig(provider: ProviderType): Promise<{
29
+ webhook_secret: string | null;
30
+ credentials: Credentials;
31
+ options: Options;
32
+ }>;
28
33
  /**
29
34
  * Get a deployment driver instance with decrypted credentials
30
35
  */
31
36
  getDriver(provider: ProviderType): Promise<DeploymentDriver>;
37
+ /**
38
+ * Sync webhook registration with current tracked projects.
39
+ */
40
+ syncWebhook(provider: ProviderType): Promise<void>;
32
41
  /**
33
42
  * List projects from provider with caching
34
43
  */
@@ -37,4 +46,36 @@ export declare class DeploymentService extends ItemsService<DeploymentConfig> {
37
46
  * Get project details from provider with caching
38
47
  */
39
48
  getProviderProject(provider: ProviderType, projectId: string): Promise<CachedResult<Project>>;
49
+ /**
50
+ * Dashboard: projects + latest run status + stats
51
+ */
52
+ getDashboard(provider: ProviderType, sinceDate: Date): Promise<{
53
+ projects: any[];
54
+ stats: {
55
+ active_deployments: number;
56
+ successful_builds: number;
57
+ failed_builds: number;
58
+ };
59
+ }>;
60
+ /**
61
+ * Refresh project metadata (name, url, framework, deployable) if stale.
62
+ */
63
+ private syncProjectMetadataIfStale;
64
+ /**
65
+ * Trigger a deployment for a project
66
+ */
67
+ triggerDeployment(provider: ProviderType, projectId: string, options: {
68
+ preview: boolean;
69
+ clearCache: boolean;
70
+ }): Promise<DeploymentRun>;
71
+ /**
72
+ * Cancel a deployment run
73
+ */
74
+ cancelDeployment(provider: ProviderType, runId: string): Promise<DeploymentRun>;
75
+ /**
76
+ * Get a run with its logs from the provider
77
+ */
78
+ getRunWithLogs(provider: ProviderType, runId: string, since?: Date): Promise<DeploymentRun & {
79
+ logs: any;
80
+ }>;
40
81
  }