@orange-soft/strapi-deployment-trigger 1.0.1 → 1.1.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.
@@ -37,7 +37,7 @@ const index = {
37
37
  defaultMessage: PLUGIN_ID
38
38
  },
39
39
  Component: async () => {
40
- const { App } = await Promise.resolve().then(() => require("./App-3JntxPYv.js"));
40
+ const { App } = await Promise.resolve().then(() => require("./App-CuSCtdH7.js"));
41
41
  return App;
42
42
  }
43
43
  });
@@ -1,3 +1,3 @@
1
1
  "use strict";
2
- const index = require("../_chunks/index-CqpMwL_C.js");
2
+ const index = require("../_chunks/index-DGatQB-9.js");
3
3
  module.exports = index.index;
@@ -1,4 +1,4 @@
1
- import { i } from "../_chunks/index-C18aSW5z.mjs";
1
+ import { i } from "../_chunks/index-CZWWYGR3.mjs";
2
2
  export {
3
3
  i as default
4
4
  };
@@ -38,10 +38,36 @@ const controller = ({ strapi }) => ({
38
38
  hasToken,
39
39
  settings,
40
40
  parsed: { owner, repo }
41
- // Include parsed values for display
42
41
  }
43
42
  };
44
43
  },
44
+ // Target CRUD operations
45
+ async addTarget(ctx) {
46
+ const { data } = ctx.request.body;
47
+ const service2 = getService(strapi);
48
+ const target = await service2.addTarget(data);
49
+ ctx.body = { data: target };
50
+ },
51
+ async updateTarget(ctx) {
52
+ const { id } = ctx.params;
53
+ const { data } = ctx.request.body;
54
+ const service2 = getService(strapi);
55
+ const target = await service2.updateTarget(id, data);
56
+ if (!target) {
57
+ return ctx.notFound("Target not found");
58
+ }
59
+ ctx.body = { data: target };
60
+ },
61
+ async deleteTarget(ctx) {
62
+ const { id } = ctx.params;
63
+ const service2 = getService(strapi);
64
+ const deleted = await service2.deleteTarget(id);
65
+ if (!deleted) {
66
+ return ctx.notFound("Target not found");
67
+ }
68
+ ctx.body = { data: { success: true } };
69
+ },
70
+ // Trigger deployment for a specific target
45
71
  async trigger(ctx) {
46
72
  const service2 = getService(strapi);
47
73
  const githubToken = await service2.getToken();
@@ -53,7 +79,22 @@ const controller = ({ strapi }) => ({
53
79
  if (!owner || !repo) {
54
80
  return ctx.badRequest("GitHub repository URL is not configured or invalid. Please configure it in Settings.");
55
81
  }
56
- const { workflow = settings.workflow, branch = settings.branch } = ctx.request.body || {};
82
+ const { targetId, workflow: overrideWorkflow, branch: overrideBranch } = ctx.request.body || {};
83
+ let workflow, branch, targetName;
84
+ if (targetId) {
85
+ const target = await service2.getTarget(targetId);
86
+ if (!target) {
87
+ return ctx.badRequest("Target not found");
88
+ }
89
+ workflow = overrideWorkflow || target.workflow;
90
+ branch = overrideBranch || target.branch;
91
+ targetName = target.name;
92
+ } else {
93
+ const firstTarget = settings.targets?.[0];
94
+ workflow = overrideWorkflow || firstTarget?.workflow || "deploy.yml";
95
+ branch = overrideBranch || firstTarget?.branch || "master";
96
+ targetName = firstTarget?.name || "Default";
97
+ }
57
98
  const url = `https://api.github.com/repos/${owner}/${repo}/actions/workflows/${workflow}/dispatches`;
58
99
  try {
59
100
  const response = await fetch(url, {
@@ -70,13 +111,14 @@ const controller = ({ strapi }) => ({
70
111
  strapi.log.error(`GitHub API error: ${response.status} - ${errorText}`);
71
112
  return ctx.badRequest(`GitHub API error: ${response.status} - ${errorText}`);
72
113
  }
73
- strapi.log.info(`Deployment triggered for ${owner}/${repo} on branch ${branch}`);
114
+ strapi.log.info(`Deployment triggered for ${owner}/${repo} [${targetName}] on branch ${branch}`);
74
115
  const actionsUrl = `https://github.com/${owner}/${repo}/actions`;
75
116
  ctx.body = {
76
117
  data: {
77
118
  success: true,
78
119
  message: `Deployment triggered successfully for ${owner}/${repo}`,
79
120
  repository: `${owner}/${repo}`,
121
+ targetName,
80
122
  workflow,
81
123
  branch,
82
124
  actionsUrl
@@ -141,6 +183,31 @@ const adminAPIRoutes = () => ({
141
183
  config: {
142
184
  policies: []
143
185
  }
186
+ },
187
+ // Target CRUD routes
188
+ {
189
+ method: "POST",
190
+ path: "/targets",
191
+ handler: "controller.addTarget",
192
+ config: {
193
+ policies: []
194
+ }
195
+ },
196
+ {
197
+ method: "PUT",
198
+ path: "/targets/:id",
199
+ handler: "controller.updateTarget",
200
+ config: {
201
+ policies: []
202
+ }
203
+ },
204
+ {
205
+ method: "DELETE",
206
+ path: "/targets/:id",
207
+ handler: "controller.deleteTarget",
208
+ config: {
209
+ policies: []
210
+ }
144
211
  }
145
212
  ]
146
213
  });
@@ -152,9 +219,10 @@ const PLUGIN_ID = "deployment-trigger";
152
219
  const STORE_KEY = "settings";
153
220
  const DEFAULT_SETTINGS = {
154
221
  repoUrl: "",
155
- workflow: "deploy.yml",
156
- branch: "master"
222
+ githubToken: "",
223
+ targets: []
157
224
  };
225
+ const generateId = () => Math.random().toString(36).substring(2, 9);
158
226
  const service = ({ strapi }) => ({
159
227
  getWelcomeMessage() {
160
228
  return "Welcome to Strapi Deployment Trigger";
@@ -177,24 +245,43 @@ const service = ({ strapi }) => ({
177
245
  }
178
246
  return { owner: null, repo: null };
179
247
  },
248
+ // Migrate old single-config format to new multi-target format
249
+ migrateSettings(settings) {
250
+ if (!settings) return null;
251
+ if (Array.isArray(settings.targets)) {
252
+ return settings;
253
+ }
254
+ if (settings.workflow || settings.branch) {
255
+ return {
256
+ repoUrl: settings.repoUrl || "",
257
+ githubToken: settings.githubToken || "",
258
+ targets: [{
259
+ id: generateId(),
260
+ name: "Default",
261
+ workflow: settings.workflow || "deploy.yml",
262
+ branch: settings.branch || "master"
263
+ }]
264
+ };
265
+ }
266
+ return settings;
267
+ },
180
268
  async getSettings() {
181
269
  const store = strapi.store({ type: "plugin", name: PLUGIN_ID });
182
- const dbSettings = await store.get({ key: STORE_KEY });
183
- if (dbSettings && dbSettings.repoUrl) {
184
- const token2 = dbSettings.githubToken;
270
+ let dbSettings = await store.get({ key: STORE_KEY });
271
+ dbSettings = this.migrateSettings(dbSettings);
272
+ if (!dbSettings) {
185
273
  return {
186
- ...dbSettings,
187
- githubToken: void 0,
188
- // Never expose full token
189
- hasToken: !!token2,
190
- maskedToken: this.maskToken(token2)
274
+ ...DEFAULT_SETTINGS,
275
+ hasToken: false,
276
+ maskedToken: null
191
277
  };
192
278
  }
193
- const token = dbSettings?.githubToken;
279
+ const token = dbSettings.githubToken;
194
280
  return {
195
- ...DEFAULT_SETTINGS,
196
- ...dbSettings,
281
+ repoUrl: dbSettings.repoUrl || "",
282
+ targets: dbSettings.targets || [],
197
283
  githubToken: void 0,
284
+ // Never expose full token
198
285
  hasToken: !!token,
199
286
  maskedToken: this.maskToken(token)
200
287
  };
@@ -202,30 +289,79 @@ const service = ({ strapi }) => ({
202
289
  async saveSettings(settings) {
203
290
  const store = strapi.store({ type: "plugin", name: PLUGIN_ID });
204
291
  const existingSettings = await store.get({ key: STORE_KEY }) || {};
292
+ const migrated = this.migrateSettings(existingSettings);
205
293
  const settingsToSave = {
206
- repoUrl: settings.repoUrl,
207
- workflow: settings.workflow || DEFAULT_SETTINGS.workflow,
208
- branch: settings.branch || DEFAULT_SETTINGS.branch,
294
+ repoUrl: settings.repoUrl !== void 0 ? settings.repoUrl : migrated?.repoUrl || "",
295
+ targets: settings.targets !== void 0 ? settings.targets : migrated?.targets || [],
209
296
  // Only update token if a new one is provided
210
- githubToken: settings.githubToken || existingSettings.githubToken
297
+ githubToken: settings.githubToken || migrated?.githubToken || ""
211
298
  };
212
299
  await store.set({ key: STORE_KEY, value: settingsToSave });
213
300
  return {
214
- ...settingsToSave,
301
+ repoUrl: settingsToSave.repoUrl,
302
+ targets: settingsToSave.targets,
215
303
  githubToken: void 0,
216
- hasToken: !!settingsToSave.githubToken
304
+ hasToken: !!settingsToSave.githubToken,
305
+ maskedToken: this.maskToken(settingsToSave.githubToken)
306
+ };
307
+ },
308
+ async addTarget(target) {
309
+ const store = strapi.store({ type: "plugin", name: PLUGIN_ID });
310
+ let settings = await store.get({ key: STORE_KEY }) || DEFAULT_SETTINGS;
311
+ settings = this.migrateSettings(settings) || DEFAULT_SETTINGS;
312
+ const newTarget = {
313
+ id: generateId(),
314
+ name: target.name || "New Target",
315
+ workflow: target.workflow || "deploy.yml",
316
+ branch: target.branch || "master"
217
317
  };
318
+ settings.targets = [...settings.targets || [], newTarget];
319
+ await store.set({ key: STORE_KEY, value: settings });
320
+ return newTarget;
321
+ },
322
+ async updateTarget(targetId, updates) {
323
+ const store = strapi.store({ type: "plugin", name: PLUGIN_ID });
324
+ let settings = await store.get({ key: STORE_KEY });
325
+ settings = this.migrateSettings(settings);
326
+ if (!settings?.targets) return null;
327
+ const targetIndex = settings.targets.findIndex((t) => t.id === targetId);
328
+ if (targetIndex === -1) return null;
329
+ settings.targets[targetIndex] = {
330
+ ...settings.targets[targetIndex],
331
+ ...updates,
332
+ id: targetId
333
+ // Preserve ID
334
+ };
335
+ await store.set({ key: STORE_KEY, value: settings });
336
+ return settings.targets[targetIndex];
337
+ },
338
+ async deleteTarget(targetId) {
339
+ const store = strapi.store({ type: "plugin", name: PLUGIN_ID });
340
+ let settings = await store.get({ key: STORE_KEY });
341
+ settings = this.migrateSettings(settings);
342
+ if (!settings?.targets) return false;
343
+ const initialLength = settings.targets.length;
344
+ settings.targets = settings.targets.filter((t) => t.id !== targetId);
345
+ if (settings.targets.length === initialLength) return false;
346
+ await store.set({ key: STORE_KEY, value: settings });
347
+ return true;
348
+ },
349
+ async getTarget(targetId) {
350
+ const settings = await this.getSettings();
351
+ return settings.targets?.find((t) => t.id === targetId) || null;
218
352
  },
219
353
  async getToken() {
220
354
  const store = strapi.store({ type: "plugin", name: PLUGIN_ID });
221
- const dbSettings = await store.get({ key: STORE_KEY });
355
+ let dbSettings = await store.get({ key: STORE_KEY });
356
+ dbSettings = this.migrateSettings(dbSettings);
222
357
  return dbSettings?.githubToken || null;
223
358
  },
224
359
  async isConfigured() {
225
360
  const settings = await this.getSettings();
226
361
  const hasToken = await this.getToken();
227
362
  const { owner, repo } = this.parseRepoUrl(settings.repoUrl);
228
- return !!(owner && repo && hasToken);
363
+ const hasTargets = settings.targets && settings.targets.length > 0;
364
+ return !!(owner && repo && hasToken && hasTargets);
229
365
  },
230
366
  async hasToken() {
231
367
  const token = await this.getToken();
@@ -37,10 +37,36 @@ const controller = ({ strapi }) => ({
37
37
  hasToken,
38
38
  settings,
39
39
  parsed: { owner, repo }
40
- // Include parsed values for display
41
40
  }
42
41
  };
43
42
  },
43
+ // Target CRUD operations
44
+ async addTarget(ctx) {
45
+ const { data } = ctx.request.body;
46
+ const service2 = getService(strapi);
47
+ const target = await service2.addTarget(data);
48
+ ctx.body = { data: target };
49
+ },
50
+ async updateTarget(ctx) {
51
+ const { id } = ctx.params;
52
+ const { data } = ctx.request.body;
53
+ const service2 = getService(strapi);
54
+ const target = await service2.updateTarget(id, data);
55
+ if (!target) {
56
+ return ctx.notFound("Target not found");
57
+ }
58
+ ctx.body = { data: target };
59
+ },
60
+ async deleteTarget(ctx) {
61
+ const { id } = ctx.params;
62
+ const service2 = getService(strapi);
63
+ const deleted = await service2.deleteTarget(id);
64
+ if (!deleted) {
65
+ return ctx.notFound("Target not found");
66
+ }
67
+ ctx.body = { data: { success: true } };
68
+ },
69
+ // Trigger deployment for a specific target
44
70
  async trigger(ctx) {
45
71
  const service2 = getService(strapi);
46
72
  const githubToken = await service2.getToken();
@@ -52,7 +78,22 @@ const controller = ({ strapi }) => ({
52
78
  if (!owner || !repo) {
53
79
  return ctx.badRequest("GitHub repository URL is not configured or invalid. Please configure it in Settings.");
54
80
  }
55
- const { workflow = settings.workflow, branch = settings.branch } = ctx.request.body || {};
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
+ }
56
97
  const url = `https://api.github.com/repos/${owner}/${repo}/actions/workflows/${workflow}/dispatches`;
57
98
  try {
58
99
  const response = await fetch(url, {
@@ -69,13 +110,14 @@ const controller = ({ strapi }) => ({
69
110
  strapi.log.error(`GitHub API error: ${response.status} - ${errorText}`);
70
111
  return ctx.badRequest(`GitHub API error: ${response.status} - ${errorText}`);
71
112
  }
72
- strapi.log.info(`Deployment triggered for ${owner}/${repo} on branch ${branch}`);
113
+ strapi.log.info(`Deployment triggered for ${owner}/${repo} [${targetName}] on branch ${branch}`);
73
114
  const actionsUrl = `https://github.com/${owner}/${repo}/actions`;
74
115
  ctx.body = {
75
116
  data: {
76
117
  success: true,
77
118
  message: `Deployment triggered successfully for ${owner}/${repo}`,
78
119
  repository: `${owner}/${repo}`,
120
+ targetName,
79
121
  workflow,
80
122
  branch,
81
123
  actionsUrl
@@ -140,6 +182,31 @@ const adminAPIRoutes = () => ({
140
182
  config: {
141
183
  policies: []
142
184
  }
185
+ },
186
+ // Target CRUD routes
187
+ {
188
+ method: "POST",
189
+ path: "/targets",
190
+ handler: "controller.addTarget",
191
+ config: {
192
+ policies: []
193
+ }
194
+ },
195
+ {
196
+ method: "PUT",
197
+ path: "/targets/:id",
198
+ handler: "controller.updateTarget",
199
+ config: {
200
+ policies: []
201
+ }
202
+ },
203
+ {
204
+ method: "DELETE",
205
+ path: "/targets/:id",
206
+ handler: "controller.deleteTarget",
207
+ config: {
208
+ policies: []
209
+ }
143
210
  }
144
211
  ]
145
212
  });
@@ -151,9 +218,10 @@ const PLUGIN_ID = "deployment-trigger";
151
218
  const STORE_KEY = "settings";
152
219
  const DEFAULT_SETTINGS = {
153
220
  repoUrl: "",
154
- workflow: "deploy.yml",
155
- branch: "master"
221
+ githubToken: "",
222
+ targets: []
156
223
  };
224
+ const generateId = () => Math.random().toString(36).substring(2, 9);
157
225
  const service = ({ strapi }) => ({
158
226
  getWelcomeMessage() {
159
227
  return "Welcome to Strapi Deployment Trigger";
@@ -176,24 +244,43 @@ const service = ({ strapi }) => ({
176
244
  }
177
245
  return { owner: null, repo: null };
178
246
  },
247
+ // Migrate old single-config format to new multi-target format
248
+ migrateSettings(settings) {
249
+ if (!settings) return null;
250
+ if (Array.isArray(settings.targets)) {
251
+ return settings;
252
+ }
253
+ if (settings.workflow || settings.branch) {
254
+ return {
255
+ repoUrl: settings.repoUrl || "",
256
+ githubToken: settings.githubToken || "",
257
+ targets: [{
258
+ id: generateId(),
259
+ name: "Default",
260
+ workflow: settings.workflow || "deploy.yml",
261
+ branch: settings.branch || "master"
262
+ }]
263
+ };
264
+ }
265
+ return settings;
266
+ },
179
267
  async getSettings() {
180
268
  const store = strapi.store({ type: "plugin", name: PLUGIN_ID });
181
- const dbSettings = await store.get({ key: STORE_KEY });
182
- if (dbSettings && dbSettings.repoUrl) {
183
- const token2 = dbSettings.githubToken;
269
+ let dbSettings = await store.get({ key: STORE_KEY });
270
+ dbSettings = this.migrateSettings(dbSettings);
271
+ if (!dbSettings) {
184
272
  return {
185
- ...dbSettings,
186
- githubToken: void 0,
187
- // Never expose full token
188
- hasToken: !!token2,
189
- maskedToken: this.maskToken(token2)
273
+ ...DEFAULT_SETTINGS,
274
+ hasToken: false,
275
+ maskedToken: null
190
276
  };
191
277
  }
192
- const token = dbSettings?.githubToken;
278
+ const token = dbSettings.githubToken;
193
279
  return {
194
- ...DEFAULT_SETTINGS,
195
- ...dbSettings,
280
+ repoUrl: dbSettings.repoUrl || "",
281
+ targets: dbSettings.targets || [],
196
282
  githubToken: void 0,
283
+ // Never expose full token
197
284
  hasToken: !!token,
198
285
  maskedToken: this.maskToken(token)
199
286
  };
@@ -201,30 +288,79 @@ const service = ({ strapi }) => ({
201
288
  async saveSettings(settings) {
202
289
  const store = strapi.store({ type: "plugin", name: PLUGIN_ID });
203
290
  const existingSettings = await store.get({ key: STORE_KEY }) || {};
291
+ const migrated = this.migrateSettings(existingSettings);
204
292
  const settingsToSave = {
205
- repoUrl: settings.repoUrl,
206
- workflow: settings.workflow || DEFAULT_SETTINGS.workflow,
207
- branch: settings.branch || DEFAULT_SETTINGS.branch,
293
+ repoUrl: settings.repoUrl !== void 0 ? settings.repoUrl : migrated?.repoUrl || "",
294
+ targets: settings.targets !== void 0 ? settings.targets : migrated?.targets || [],
208
295
  // Only update token if a new one is provided
209
- githubToken: settings.githubToken || existingSettings.githubToken
296
+ githubToken: settings.githubToken || migrated?.githubToken || ""
210
297
  };
211
298
  await store.set({ key: STORE_KEY, value: settingsToSave });
212
299
  return {
213
- ...settingsToSave,
300
+ repoUrl: settingsToSave.repoUrl,
301
+ targets: settingsToSave.targets,
214
302
  githubToken: void 0,
215
- hasToken: !!settingsToSave.githubToken
303
+ hasToken: !!settingsToSave.githubToken,
304
+ maskedToken: this.maskToken(settingsToSave.githubToken)
305
+ };
306
+ },
307
+ async addTarget(target) {
308
+ const store = strapi.store({ type: "plugin", name: PLUGIN_ID });
309
+ let settings = await store.get({ key: STORE_KEY }) || DEFAULT_SETTINGS;
310
+ settings = this.migrateSettings(settings) || DEFAULT_SETTINGS;
311
+ const newTarget = {
312
+ id: generateId(),
313
+ name: target.name || "New Target",
314
+ workflow: target.workflow || "deploy.yml",
315
+ branch: target.branch || "master"
216
316
  };
317
+ settings.targets = [...settings.targets || [], newTarget];
318
+ await store.set({ key: STORE_KEY, value: settings });
319
+ return newTarget;
320
+ },
321
+ async updateTarget(targetId, updates) {
322
+ const store = strapi.store({ type: "plugin", name: PLUGIN_ID });
323
+ let settings = await store.get({ key: STORE_KEY });
324
+ settings = this.migrateSettings(settings);
325
+ if (!settings?.targets) return null;
326
+ const targetIndex = settings.targets.findIndex((t) => t.id === targetId);
327
+ if (targetIndex === -1) return null;
328
+ settings.targets[targetIndex] = {
329
+ ...settings.targets[targetIndex],
330
+ ...updates,
331
+ id: targetId
332
+ // Preserve ID
333
+ };
334
+ await store.set({ key: STORE_KEY, value: settings });
335
+ return settings.targets[targetIndex];
336
+ },
337
+ async deleteTarget(targetId) {
338
+ const store = strapi.store({ type: "plugin", name: PLUGIN_ID });
339
+ let settings = await store.get({ key: STORE_KEY });
340
+ settings = this.migrateSettings(settings);
341
+ if (!settings?.targets) return false;
342
+ const initialLength = settings.targets.length;
343
+ settings.targets = settings.targets.filter((t) => t.id !== targetId);
344
+ if (settings.targets.length === initialLength) return false;
345
+ await store.set({ key: STORE_KEY, value: settings });
346
+ return true;
347
+ },
348
+ async getTarget(targetId) {
349
+ const settings = await this.getSettings();
350
+ return settings.targets?.find((t) => t.id === targetId) || null;
217
351
  },
218
352
  async getToken() {
219
353
  const store = strapi.store({ type: "plugin", name: PLUGIN_ID });
220
- const dbSettings = await store.get({ key: STORE_KEY });
354
+ let dbSettings = await store.get({ key: STORE_KEY });
355
+ dbSettings = this.migrateSettings(dbSettings);
221
356
  return dbSettings?.githubToken || null;
222
357
  },
223
358
  async isConfigured() {
224
359
  const settings = await this.getSettings();
225
360
  const hasToken = await this.getToken();
226
361
  const { owner, repo } = this.parseRepoUrl(settings.repoUrl);
227
- return !!(owner && repo && hasToken);
362
+ const hasTargets = settings.targets && settings.targets.length > 0;
363
+ return !!(owner && repo && hasToken && hasTargets);
228
364
  },
229
365
  async hasToken() {
230
366
  const token = await this.getToken();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orange-soft/strapi-deployment-trigger",
3
- "version": "1.0.1",
3
+ "version": "1.1.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>",
@@ -30,11 +30,45 @@ const controller = ({ strapi }) => ({
30
30
  configured,
31
31
  hasToken,
32
32
  settings,
33
- parsed: { owner, repo }, // Include parsed values for display
33
+ parsed: { owner, repo },
34
34
  },
35
35
  };
36
36
  },
37
37
 
38
+ // Target CRUD operations
39
+ async addTarget(ctx) {
40
+ const { data } = ctx.request.body;
41
+ const service = getService(strapi);
42
+ const target = await service.addTarget(data);
43
+ ctx.body = { data: target };
44
+ },
45
+
46
+ async updateTarget(ctx) {
47
+ const { id } = ctx.params;
48
+ const { data } = ctx.request.body;
49
+ const service = getService(strapi);
50
+ const target = await service.updateTarget(id, data);
51
+
52
+ if (!target) {
53
+ return ctx.notFound('Target not found');
54
+ }
55
+
56
+ ctx.body = { data: target };
57
+ },
58
+
59
+ async deleteTarget(ctx) {
60
+ const { id } = ctx.params;
61
+ const service = getService(strapi);
62
+ const deleted = await service.deleteTarget(id);
63
+
64
+ if (!deleted) {
65
+ return ctx.notFound('Target not found');
66
+ }
67
+
68
+ ctx.body = { data: { success: true } };
69
+ },
70
+
71
+ // Trigger deployment for a specific target
38
72
  async trigger(ctx) {
39
73
  const service = getService(strapi);
40
74
  const githubToken = await service.getToken();
@@ -50,7 +84,27 @@ const controller = ({ strapi }) => ({
50
84
  return ctx.badRequest('GitHub repository URL is not configured or invalid. Please configure it in Settings.');
51
85
  }
52
86
 
53
- const { workflow = settings.workflow, branch = settings.branch } = ctx.request.body || {};
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
+ }
54
108
 
55
109
  const url = `https://api.github.com/repos/${owner}/${repo}/actions/workflows/${workflow}/dispatches`;
56
110
 
@@ -71,7 +125,7 @@ const controller = ({ strapi }) => ({
71
125
  return ctx.badRequest(`GitHub API error: ${response.status} - ${errorText}`);
72
126
  }
73
127
 
74
- strapi.log.info(`Deployment triggered for ${owner}/${repo} on branch ${branch}`);
128
+ strapi.log.info(`Deployment triggered for ${owner}/${repo} [${targetName}] on branch ${branch}`);
75
129
 
76
130
  const actionsUrl = `https://github.com/${owner}/${repo}/actions`;
77
131
 
@@ -80,6 +134,7 @@ const controller = ({ strapi }) => ({
80
134
  success: true,
81
135
  message: `Deployment triggered successfully for ${owner}/${repo}`,
82
136
  repository: `${owner}/${repo}`,
137
+ targetName,
83
138
  workflow,
84
139
  branch,
85
140
  actionsUrl,
@@ -33,5 +33,30 @@ export default () => ({
33
33
  policies: [],
34
34
  },
35
35
  },
36
+ // Target CRUD routes
37
+ {
38
+ method: 'POST',
39
+ path: '/targets',
40
+ handler: 'controller.addTarget',
41
+ config: {
42
+ policies: [],
43
+ },
44
+ },
45
+ {
46
+ method: 'PUT',
47
+ path: '/targets/:id',
48
+ handler: 'controller.updateTarget',
49
+ config: {
50
+ policies: [],
51
+ },
52
+ },
53
+ {
54
+ method: 'DELETE',
55
+ path: '/targets/:id',
56
+ handler: 'controller.deleteTarget',
57
+ config: {
58
+ policies: [],
59
+ },
60
+ },
36
61
  ],
37
62
  });