@orange-soft/strapi-deployment-trigger 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -68,6 +68,30 @@ const controller = ({ strapi }) => ({
68
68
  },
69
69
  // Trigger deployment for a specific target
70
70
  async trigger(ctx) {
71
+ const service2 = getService(strapi);
72
+ const { targetId } = ctx.request.body || {};
73
+ let target;
74
+ if (targetId) {
75
+ target = await service2.getTarget(targetId);
76
+ if (!target) {
77
+ return ctx.badRequest("Target not found");
78
+ }
79
+ } else {
80
+ const settings = await service2.getSettings();
81
+ target = settings.targets?.[0];
82
+ if (!target) {
83
+ return ctx.badRequest("No deployment targets configured");
84
+ }
85
+ }
86
+ const targetType = target.type || "github";
87
+ if (targetType === "vercel") {
88
+ return this.triggerVercel(ctx, target);
89
+ } else {
90
+ return this.triggerGitHub(ctx, target);
91
+ }
92
+ },
93
+ // Trigger GitHub Actions deployment
94
+ async triggerGitHub(ctx, target) {
71
95
  const service2 = getService(strapi);
72
96
  const githubToken = await service2.getToken();
73
97
  if (!githubToken) {
@@ -78,22 +102,9 @@ const controller = ({ strapi }) => ({
78
102
  if (!owner || !repo) {
79
103
  return ctx.badRequest("GitHub repository URL is not configured or invalid. Please configure it in Settings.");
80
104
  }
81
- const { targetId, workflow: overrideWorkflow, branch: overrideBranch } = ctx.request.body || {};
82
- let workflow, branch, targetName;
83
- if (targetId) {
84
- const target = await service2.getTarget(targetId);
85
- if (!target) {
86
- return ctx.badRequest("Target not found");
87
- }
88
- workflow = overrideWorkflow || target.workflow;
89
- branch = overrideBranch || target.branch;
90
- targetName = target.name;
91
- } else {
92
- const firstTarget = settings.targets?.[0];
93
- workflow = overrideWorkflow || firstTarget?.workflow || "deploy.yml";
94
- branch = overrideBranch || firstTarget?.branch || "master";
95
- targetName = firstTarget?.name || "Default";
96
- }
105
+ const workflow = target.workflow || "deploy.yml";
106
+ const branch = target.branch || "master";
107
+ const targetName = target.name;
97
108
  const url = `https://api.github.com/repos/${owner}/${repo}/actions/workflows/${workflow}/dispatches`;
98
109
  try {
99
110
  const response = await fetch(url, {
@@ -110,11 +121,12 @@ const controller = ({ strapi }) => ({
110
121
  strapi.log.error(`GitHub API error: ${response.status} - ${errorText}`);
111
122
  return ctx.badRequest(`GitHub API error: ${response.status} - ${errorText}`);
112
123
  }
113
- strapi.log.info(`Deployment triggered for ${owner}/${repo} [${targetName}] on branch ${branch}`);
124
+ strapi.log.info(`GitHub deployment triggered for ${owner}/${repo} [${targetName}] on branch ${branch}`);
114
125
  const actionsUrl = `https://github.com/${owner}/${repo}/actions`;
115
126
  ctx.body = {
116
127
  data: {
117
128
  success: true,
129
+ type: "github",
118
130
  message: `Deployment triggered successfully for ${owner}/${repo}`,
119
131
  repository: `${owner}/${repo}`,
120
132
  targetName,
@@ -124,9 +136,39 @@ const controller = ({ strapi }) => ({
124
136
  }
125
137
  };
126
138
  } catch (error) {
127
- strapi.log.error("Error triggering deployment:", error);
139
+ strapi.log.error("Error triggering GitHub deployment:", error);
128
140
  return ctx.badRequest(`Failed to trigger deployment: ${error.message}`);
129
141
  }
142
+ },
143
+ // Trigger Vercel deployment via webhook
144
+ async triggerVercel(ctx, target) {
145
+ const webhookUrl = target.webhookUrl;
146
+ const targetName = target.name;
147
+ if (!webhookUrl) {
148
+ return ctx.badRequest("Vercel webhook URL is not configured for this target.");
149
+ }
150
+ try {
151
+ const response = await fetch(webhookUrl, {
152
+ method: "POST"
153
+ });
154
+ if (!response.ok) {
155
+ const errorText = await response.text();
156
+ strapi.log.error(`Vercel webhook error: ${response.status} - ${errorText}`);
157
+ return ctx.badRequest(`Vercel webhook error: ${response.status} - ${errorText}`);
158
+ }
159
+ strapi.log.info(`Vercel deployment triggered [${targetName}]`);
160
+ ctx.body = {
161
+ data: {
162
+ success: true,
163
+ type: "vercel",
164
+ message: "Vercel deployment triggered successfully",
165
+ targetName
166
+ }
167
+ };
168
+ } catch (error) {
169
+ strapi.log.error("Error triggering Vercel deployment:", error);
170
+ return ctx.badRequest(`Failed to trigger Vercel deployment: ${error.message}`);
171
+ }
130
172
  }
131
173
  });
132
174
  const controllers = {
@@ -248,7 +290,14 @@ const service = ({ strapi }) => ({
248
290
  migrateSettings(settings) {
249
291
  if (!settings) return null;
250
292
  if (Array.isArray(settings.targets)) {
251
- return settings;
293
+ const targetsWithType = settings.targets.map((target) => ({
294
+ ...target,
295
+ type: target.type || "github"
296
+ }));
297
+ return {
298
+ ...settings,
299
+ targets: targetsWithType
300
+ };
252
301
  }
253
302
  if (settings.workflow || settings.branch) {
254
303
  return {
@@ -256,6 +305,7 @@ const service = ({ strapi }) => ({
256
305
  githubToken: settings.githubToken || "",
257
306
  targets: [{
258
307
  id: generateId(),
308
+ type: "github",
259
309
  name: "Default",
260
310
  workflow: settings.workflow || "deploy.yml",
261
311
  branch: settings.branch || "master"
@@ -267,7 +317,11 @@ const service = ({ strapi }) => ({
267
317
  async getSettings() {
268
318
  const store = strapi.store({ type: "plugin", name: PLUGIN_ID });
269
319
  let dbSettings = await store.get({ key: STORE_KEY });
320
+ const needsMigration = dbSettings && !Array.isArray(dbSettings.targets) && (dbSettings.workflow || dbSettings.branch);
270
321
  dbSettings = this.migrateSettings(dbSettings);
322
+ if (needsMigration && dbSettings) {
323
+ await store.set({ key: STORE_KEY, value: dbSettings });
324
+ }
271
325
  if (!dbSettings) {
272
326
  return {
273
327
  ...DEFAULT_SETTINGS,
@@ -308,12 +362,18 @@ const service = ({ strapi }) => ({
308
362
  const store = strapi.store({ type: "plugin", name: PLUGIN_ID });
309
363
  let settings = await store.get({ key: STORE_KEY }) || DEFAULT_SETTINGS;
310
364
  settings = this.migrateSettings(settings) || DEFAULT_SETTINGS;
365
+ const targetType = target.type || "github";
311
366
  const newTarget = {
312
367
  id: generateId(),
313
- name: target.name || "New Target",
314
- workflow: target.workflow || "deploy.yml",
315
- branch: target.branch || "master"
368
+ type: targetType,
369
+ name: target.name || "New Target"
316
370
  };
371
+ if (targetType === "github") {
372
+ newTarget.workflow = target.workflow || "deploy.yml";
373
+ newTarget.branch = target.branch || "master";
374
+ } else if (targetType === "vercel") {
375
+ newTarget.webhookUrl = target.webhookUrl || "";
376
+ }
317
377
  settings.targets = [...settings.targets || [], newTarget];
318
378
  await store.set({ key: STORE_KEY, value: settings });
319
379
  return newTarget;
@@ -325,12 +385,20 @@ const service = ({ strapi }) => ({
325
385
  if (!settings?.targets) return null;
326
386
  const targetIndex = settings.targets.findIndex((t) => t.id === targetId);
327
387
  if (targetIndex === -1) return null;
328
- settings.targets[targetIndex] = {
329
- ...settings.targets[targetIndex],
330
- ...updates,
331
- id: targetId
332
- // Preserve ID
388
+ const existingTarget = settings.targets[targetIndex];
389
+ const targetType = updates.type || existingTarget.type || "github";
390
+ const updatedTarget = {
391
+ id: targetId,
392
+ type: targetType,
393
+ name: updates.name !== void 0 ? updates.name : existingTarget.name
333
394
  };
395
+ if (targetType === "github") {
396
+ updatedTarget.workflow = updates.workflow !== void 0 ? updates.workflow : existingTarget.workflow;
397
+ updatedTarget.branch = updates.branch !== void 0 ? updates.branch : existingTarget.branch;
398
+ } else if (targetType === "vercel") {
399
+ updatedTarget.webhookUrl = updates.webhookUrl !== void 0 ? updates.webhookUrl : existingTarget.webhookUrl;
400
+ }
401
+ settings.targets[targetIndex] = updatedTarget;
334
402
  await store.set({ key: STORE_KEY, value: settings });
335
403
  return settings.targets[targetIndex];
336
404
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orange-soft/strapi-deployment-trigger",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Strapi v5 plugin to trigger GitHub Actions workflow deployments from the admin panel",
5
5
  "license": "MIT",
6
6
  "author": "Justin Moh <moh@os.my>",
@@ -70,6 +70,37 @@ const controller = ({ strapi }) => ({
70
70
 
71
71
  // Trigger deployment for a specific target
72
72
  async trigger(ctx) {
73
+ const service = getService(strapi);
74
+ const { targetId } = ctx.request.body || {};
75
+
76
+ // Get target
77
+ let target;
78
+ if (targetId) {
79
+ target = await service.getTarget(targetId);
80
+ if (!target) {
81
+ return ctx.badRequest('Target not found');
82
+ }
83
+ } else {
84
+ // Fallback for backwards compatibility - use first target
85
+ const settings = await service.getSettings();
86
+ target = settings.targets?.[0];
87
+ if (!target) {
88
+ return ctx.badRequest('No deployment targets configured');
89
+ }
90
+ }
91
+
92
+ const targetType = target.type || 'github';
93
+
94
+ // Branch by target type
95
+ if (targetType === 'vercel') {
96
+ return this.triggerVercel(ctx, target);
97
+ } else {
98
+ return this.triggerGitHub(ctx, target);
99
+ }
100
+ },
101
+
102
+ // Trigger GitHub Actions deployment
103
+ async triggerGitHub(ctx, target) {
73
104
  const service = getService(strapi);
74
105
  const githubToken = await service.getToken();
75
106
 
@@ -84,27 +115,9 @@ const controller = ({ strapi }) => ({
84
115
  return ctx.badRequest('GitHub repository URL is not configured or invalid. Please configure it in Settings.');
85
116
  }
86
117
 
87
- // Get target from request body or params
88
- const { targetId, workflow: overrideWorkflow, branch: overrideBranch } = ctx.request.body || {};
89
-
90
- let workflow, branch, targetName;
91
-
92
- if (targetId) {
93
- // Trigger specific target
94
- const target = await service.getTarget(targetId);
95
- if (!target) {
96
- return ctx.badRequest('Target not found');
97
- }
98
- workflow = overrideWorkflow || target.workflow;
99
- branch = overrideBranch || target.branch;
100
- targetName = target.name;
101
- } else {
102
- // Fallback for backwards compatibility - use first target or provided values
103
- const firstTarget = settings.targets?.[0];
104
- workflow = overrideWorkflow || firstTarget?.workflow || 'deploy.yml';
105
- branch = overrideBranch || firstTarget?.branch || 'master';
106
- targetName = firstTarget?.name || 'Default';
107
- }
118
+ const workflow = target.workflow || 'deploy.yml';
119
+ const branch = target.branch || 'master';
120
+ const targetName = target.name;
108
121
 
109
122
  const url = `https://api.github.com/repos/${owner}/${repo}/actions/workflows/${workflow}/dispatches`;
110
123
 
@@ -125,13 +138,14 @@ const controller = ({ strapi }) => ({
125
138
  return ctx.badRequest(`GitHub API error: ${response.status} - ${errorText}`);
126
139
  }
127
140
 
128
- strapi.log.info(`Deployment triggered for ${owner}/${repo} [${targetName}] on branch ${branch}`);
141
+ strapi.log.info(`GitHub deployment triggered for ${owner}/${repo} [${targetName}] on branch ${branch}`);
129
142
 
130
143
  const actionsUrl = `https://github.com/${owner}/${repo}/actions`;
131
144
 
132
145
  ctx.body = {
133
146
  data: {
134
147
  success: true,
148
+ type: 'github',
135
149
  message: `Deployment triggered successfully for ${owner}/${repo}`,
136
150
  repository: `${owner}/${repo}`,
137
151
  targetName,
@@ -141,10 +155,46 @@ const controller = ({ strapi }) => ({
141
155
  },
142
156
  };
143
157
  } catch (error) {
144
- strapi.log.error('Error triggering deployment:', error);
158
+ strapi.log.error('Error triggering GitHub deployment:', error);
145
159
  return ctx.badRequest(`Failed to trigger deployment: ${error.message}`);
146
160
  }
147
161
  },
162
+
163
+ // Trigger Vercel deployment via webhook
164
+ async triggerVercel(ctx, target) {
165
+ const webhookUrl = target.webhookUrl;
166
+ const targetName = target.name;
167
+
168
+ if (!webhookUrl) {
169
+ return ctx.badRequest('Vercel webhook URL is not configured for this target.');
170
+ }
171
+
172
+ try {
173
+ const response = await fetch(webhookUrl, {
174
+ method: 'POST',
175
+ });
176
+
177
+ if (!response.ok) {
178
+ const errorText = await response.text();
179
+ strapi.log.error(`Vercel webhook error: ${response.status} - ${errorText}`);
180
+ return ctx.badRequest(`Vercel webhook error: ${response.status} - ${errorText}`);
181
+ }
182
+
183
+ strapi.log.info(`Vercel deployment triggered [${targetName}]`);
184
+
185
+ ctx.body = {
186
+ data: {
187
+ success: true,
188
+ type: 'vercel',
189
+ message: 'Vercel deployment triggered successfully',
190
+ targetName,
191
+ },
192
+ };
193
+ } catch (error) {
194
+ strapi.log.error('Error triggering Vercel deployment:', error);
195
+ return ctx.badRequest(`Failed to trigger Vercel deployment: ${error.message}`);
196
+ }
197
+ },
148
198
  });
149
199
 
150
200
  export default controller;
@@ -39,9 +39,17 @@ const service = ({ strapi }) => ({
39
39
  migrateSettings(settings) {
40
40
  if (!settings) return null;
41
41
 
42
- // Already in new format
42
+ // Already in new format with targets array
43
43
  if (Array.isArray(settings.targets)) {
44
- return settings;
44
+ // Ensure all targets have a type field (backward compatibility)
45
+ const targetsWithType = settings.targets.map(target => ({
46
+ ...target,
47
+ type: target.type || 'github',
48
+ }));
49
+ return {
50
+ ...settings,
51
+ targets: targetsWithType,
52
+ };
45
53
  }
46
54
 
47
55
  // Old format detected - migrate
@@ -51,6 +59,7 @@ const service = ({ strapi }) => ({
51
59
  githubToken: settings.githubToken || '',
52
60
  targets: [{
53
61
  id: generateId(),
62
+ type: 'github',
54
63
  name: 'Default',
55
64
  workflow: settings.workflow || 'deploy.yml',
56
65
  branch: settings.branch || 'master',
@@ -65,9 +74,17 @@ const service = ({ strapi }) => ({
65
74
  const store = strapi.store({ type: 'plugin', name: PLUGIN_ID });
66
75
  let dbSettings = await store.get({ key: STORE_KEY });
67
76
 
77
+ // Check if migration is needed (old format has workflow/branch but no targets array)
78
+ const needsMigration = dbSettings && !Array.isArray(dbSettings.targets) && (dbSettings.workflow || dbSettings.branch);
79
+
68
80
  // Migrate if needed
69
81
  dbSettings = this.migrateSettings(dbSettings);
70
82
 
83
+ // Persist the migration so IDs remain stable
84
+ if (needsMigration && dbSettings) {
85
+ await store.set({ key: STORE_KEY, value: dbSettings });
86
+ }
87
+
71
88
  if (!dbSettings) {
72
89
  return {
73
90
  ...DEFAULT_SETTINGS,
@@ -116,13 +133,22 @@ const service = ({ strapi }) => ({
116
133
  let settings = await store.get({ key: STORE_KEY }) || DEFAULT_SETTINGS;
117
134
  settings = this.migrateSettings(settings) || DEFAULT_SETTINGS;
118
135
 
136
+ const targetType = target.type || 'github';
137
+
119
138
  const newTarget = {
120
139
  id: generateId(),
140
+ type: targetType,
121
141
  name: target.name || 'New Target',
122
- workflow: target.workflow || 'deploy.yml',
123
- branch: target.branch || 'master',
124
142
  };
125
143
 
144
+ // Add type-specific fields
145
+ if (targetType === 'github') {
146
+ newTarget.workflow = target.workflow || 'deploy.yml';
147
+ newTarget.branch = target.branch || 'master';
148
+ } else if (targetType === 'vercel') {
149
+ newTarget.webhookUrl = target.webhookUrl || '';
150
+ }
151
+
126
152
  settings.targets = [...(settings.targets || []), newTarget];
127
153
  await store.set({ key: STORE_KEY, value: settings });
128
154
 
@@ -139,12 +165,25 @@ const service = ({ strapi }) => ({
139
165
  const targetIndex = settings.targets.findIndex(t => t.id === targetId);
140
166
  if (targetIndex === -1) return null;
141
167
 
142
- settings.targets[targetIndex] = {
143
- ...settings.targets[targetIndex],
144
- ...updates,
145
- id: targetId, // Preserve ID
168
+ const existingTarget = settings.targets[targetIndex];
169
+ const targetType = updates.type || existingTarget.type || 'github';
170
+
171
+ // Build updated target with type-specific fields
172
+ const updatedTarget = {
173
+ id: targetId,
174
+ type: targetType,
175
+ name: updates.name !== undefined ? updates.name : existingTarget.name,
146
176
  };
147
177
 
178
+ if (targetType === 'github') {
179
+ updatedTarget.workflow = updates.workflow !== undefined ? updates.workflow : existingTarget.workflow;
180
+ updatedTarget.branch = updates.branch !== undefined ? updates.branch : existingTarget.branch;
181
+ } else if (targetType === 'vercel') {
182
+ updatedTarget.webhookUrl = updates.webhookUrl !== undefined ? updates.webhookUrl : existingTarget.webhookUrl;
183
+ }
184
+
185
+ settings.targets[targetIndex] = updatedTarget;
186
+
148
187
  await store.set({ key: STORE_KEY, value: settings });
149
188
  return settings.targets[targetIndex];
150
189
  },