@orange-soft/strapi-deployment-trigger 1.0.0 → 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.
@@ -0,0 +1,666 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useFetchClient, Layouts, BackButton, Page } from "@strapi/strapi/admin";
3
+ import { Link, Routes, Route } from "react-router-dom";
4
+ import { useState, useEffect } from "react";
5
+ import { useIntl } from "react-intl";
6
+ import { Flex, Loader, Button, Box, Alert, Typography, Table, Thead, Tr, Th, Tbody, Td, Field, Grid, IconButton, Dialog } from "@strapi/design-system";
7
+ import { Cog, Rocket, Check, Plus, Pencil, Trash } from "@strapi/icons";
8
+ import { P as PLUGIN_ID } from "./index-CZWWYGR3.mjs";
9
+ const getTranslation = (id) => `${PLUGIN_ID}.${id}`;
10
+ const HomePage = () => {
11
+ const { formatMessage } = useIntl();
12
+ const { get, post } = useFetchClient();
13
+ const [status, setStatus] = useState(null);
14
+ const [loading, setLoading] = useState(true);
15
+ const [deployingTargetId, setDeployingTargetId] = useState(null);
16
+ const [notification, setNotification] = useState(null);
17
+ useEffect(() => {
18
+ fetchStatus();
19
+ }, []);
20
+ const fetchStatus = async () => {
21
+ try {
22
+ const { data } = await get(`/${PLUGIN_ID}/status`);
23
+ setStatus(data.data);
24
+ } catch (error) {
25
+ console.error("Failed to fetch status:", error);
26
+ } finally {
27
+ setLoading(false);
28
+ }
29
+ };
30
+ const handleDeploy = async (targetId, targetName) => {
31
+ setDeployingTargetId(targetId);
32
+ setNotification(null);
33
+ try {
34
+ const { data } = await post(`/${PLUGIN_ID}/trigger`, { targetId });
35
+ setNotification({
36
+ type: "success",
37
+ message: `${targetName}: ${data.data?.message || "Deployment triggered successfully!"}`,
38
+ actionsUrl: data.data?.actionsUrl
39
+ });
40
+ } catch (error) {
41
+ setNotification({
42
+ type: "danger",
43
+ message: `${targetName}: ${error?.response?.data?.error?.message || error.message || "Deployment failed"}`
44
+ });
45
+ } finally {
46
+ setDeployingTargetId(null);
47
+ }
48
+ };
49
+ if (loading) {
50
+ return /* @__PURE__ */ jsxs(Layouts.Root, { children: [
51
+ /* @__PURE__ */ jsx(
52
+ Layouts.Header,
53
+ {
54
+ title: formatMessage({ id: getTranslation("plugin.name") }),
55
+ subtitle: "Loading..."
56
+ }
57
+ ),
58
+ /* @__PURE__ */ jsx(Layouts.Content, { children: /* @__PURE__ */ jsx(Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsx(Loader, { children: "Loading..." }) }) })
59
+ ] });
60
+ }
61
+ const settings = status?.settings || {};
62
+ const parsed = status?.parsed || {};
63
+ const isConfigured = status?.configured;
64
+ const hasToken = status?.hasToken;
65
+ const targets = settings.targets || [];
66
+ return /* @__PURE__ */ jsxs(Layouts.Root, { children: [
67
+ /* @__PURE__ */ jsx(
68
+ Layouts.Header,
69
+ {
70
+ title: formatMessage({ id: getTranslation("plugin.name") }),
71
+ subtitle: "Trigger GitHub Actions deployment",
72
+ secondaryAction: /* @__PURE__ */ jsx(Link, { to: `/plugins/${PLUGIN_ID}/settings`, children: /* @__PURE__ */ jsx(Button, { variant: "tertiary", startIcon: /* @__PURE__ */ jsx(Cog, {}), children: "Settings" }) })
73
+ }
74
+ ),
75
+ /* @__PURE__ */ jsxs(Layouts.Content, { children: [
76
+ notification && /* @__PURE__ */ jsx(Box, { paddingBottom: 4, children: /* @__PURE__ */ jsx(
77
+ Alert,
78
+ {
79
+ closeLabel: "Close",
80
+ title: notification.message,
81
+ variant: notification.type,
82
+ onClose: () => setNotification(null),
83
+ children: notification.actionsUrl && /* @__PURE__ */ jsxs(Typography, { variant: "pi", children: [
84
+ "Check the deployment status at",
85
+ " ",
86
+ /* @__PURE__ */ jsx(
87
+ Typography,
88
+ {
89
+ variant: "pi",
90
+ tag: "a",
91
+ href: notification.actionsUrl,
92
+ target: "_blank",
93
+ rel: "noopener noreferrer",
94
+ textColor: "primary600",
95
+ children: "GitHub Actions"
96
+ }
97
+ )
98
+ ] })
99
+ }
100
+ ) }),
101
+ !hasToken && /* @__PURE__ */ jsx(Box, { paddingBottom: 4, children: /* @__PURE__ */ jsx(Alert, { title: "Token Missing", variant: "danger", children: "GitHub Personal Access Token is not configured. Please add it in Settings." }) }),
102
+ !settings.repoUrl && /* @__PURE__ */ jsx(Box, { paddingBottom: 4, children: /* @__PURE__ */ jsx(Alert, { title: "Configuration Required", variant: "warning", children: "Please configure your GitHub repository in the Settings page before triggering deployments." }) }),
103
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
104
+ /* @__PURE__ */ jsx(
105
+ Box,
106
+ {
107
+ background: "neutral0",
108
+ hasRadius: true,
109
+ shadow: "filterShadow",
110
+ paddingTop: 6,
111
+ paddingBottom: 6,
112
+ paddingLeft: 7,
113
+ paddingRight: 7,
114
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
115
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", tag: "h2", children: "Repository" }),
116
+ /* @__PURE__ */ jsxs(Box, { style: { display: "grid", gridTemplateColumns: "120px 1fr", gap: "12px 16px", alignItems: "center" }, children: [
117
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: "Repository" }),
118
+ parsed.owner && parsed.repo ? /* @__PURE__ */ jsxs(
119
+ Typography,
120
+ {
121
+ variant: "pi",
122
+ tag: "a",
123
+ href: settings.repoUrl,
124
+ target: "_blank",
125
+ rel: "noopener noreferrer",
126
+ textColor: "primary600",
127
+ style: { textDecoration: "none" },
128
+ children: [
129
+ parsed.owner,
130
+ "/",
131
+ parsed.repo
132
+ ]
133
+ }
134
+ ) : /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: "Not configured" }),
135
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: "GitHub Token" }),
136
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: hasToken ? "success600" : "danger600", children: hasToken ? "Configured" : "Missing" })
137
+ ] })
138
+ ] })
139
+ }
140
+ ),
141
+ /* @__PURE__ */ jsx(
142
+ Box,
143
+ {
144
+ background: "neutral0",
145
+ hasRadius: true,
146
+ shadow: "filterShadow",
147
+ paddingTop: 6,
148
+ paddingBottom: 6,
149
+ paddingLeft: 7,
150
+ paddingRight: 7,
151
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
152
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", tag: "h2", children: "Deployment Targets" }),
153
+ targets.length > 0 ? /* @__PURE__ */ jsxs(Table, { children: [
154
+ /* @__PURE__ */ jsx(Thead, { children: /* @__PURE__ */ jsxs(Tr, { children: [
155
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Name" }) }),
156
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Workflow" }) }),
157
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Branch" }) }),
158
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Action" }) })
159
+ ] }) }),
160
+ /* @__PURE__ */ jsx(Tbody, { children: targets.map((target) => /* @__PURE__ */ jsxs(Tr, { children: [
161
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "bold", children: target.name }) }),
162
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "omega", children: target.workflow }) }),
163
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "omega", children: target.branch }) }),
164
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(
165
+ Button,
166
+ {
167
+ onClick: () => handleDeploy(target.id, target.name),
168
+ loading: deployingTargetId === target.id,
169
+ disabled: !isConfigured || deployingTargetId !== null,
170
+ startIcon: /* @__PURE__ */ jsx(Rocket, {}),
171
+ size: "S",
172
+ children: deployingTargetId === target.id ? "Triggering..." : "Trigger"
173
+ }
174
+ ) })
175
+ ] }, target.id)) })
176
+ ] }) : /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "center", justifyContent: "center", gap: 3, padding: 6, children: [
177
+ /* @__PURE__ */ jsx(Typography, { variant: "epsilon", textColor: "neutral600", textAlign: "center", children: "No deployment targets configured" }),
178
+ /* @__PURE__ */ jsx(Link, { to: `/plugins/${PLUGIN_ID}/settings`, children: /* @__PURE__ */ jsx(Button, { variant: "default", startIcon: /* @__PURE__ */ jsx(Cog, {}), children: "Add Targets in Settings" }) })
179
+ ] })
180
+ ] })
181
+ }
182
+ ),
183
+ !isConfigured && targets.length > 0 && /* @__PURE__ */ jsx(
184
+ Box,
185
+ {
186
+ background: "neutral0",
187
+ hasRadius: true,
188
+ shadow: "filterShadow",
189
+ paddingTop: 6,
190
+ paddingBottom: 6,
191
+ paddingLeft: 7,
192
+ paddingRight: 7,
193
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "center", justifyContent: "center", gap: 3, children: [
194
+ /* @__PURE__ */ jsx(Typography, { variant: "beta", textColor: "neutral600", textAlign: "center", children: "Setup Incomplete" }),
195
+ /* @__PURE__ */ jsx(Typography, { variant: "epsilon", textColor: "neutral600", textAlign: "center", children: "Please ensure repository URL and GitHub token are configured in Settings." }),
196
+ /* @__PURE__ */ jsx(Box, { paddingTop: 2, children: /* @__PURE__ */ jsx(Link, { to: `/plugins/${PLUGIN_ID}/settings`, children: /* @__PURE__ */ jsx(Button, { variant: "default", startIcon: /* @__PURE__ */ jsx(Cog, {}), children: "Go to Settings" }) }) })
197
+ ] })
198
+ }
199
+ )
200
+ ] })
201
+ ] })
202
+ ] });
203
+ };
204
+ const TOKEN_PATTERN = /^github_pat_[a-zA-Z0-9_]+$/;
205
+ const REPO_URL_PATTERN = /^https:\/\/github\.com\/[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+\/?$/;
206
+ const WORKFLOW_PATTERN = /^[a-zA-Z0-9_.-]+\.ya?ml$/;
207
+ const validateToken = (value) => {
208
+ if (!value) return null;
209
+ if (!TOKEN_PATTERN.test(value)) {
210
+ return 'Token must start with "github_pat_" followed by alphanumeric characters';
211
+ }
212
+ return null;
213
+ };
214
+ const validateRepoUrl = (value) => {
215
+ if (!value) return "Repository URL is required";
216
+ if (!REPO_URL_PATTERN.test(value)) {
217
+ return "Must be a valid GitHub URL (e.g., https://github.com/owner/repo)";
218
+ }
219
+ return null;
220
+ };
221
+ const validateWorkflow = (value) => {
222
+ if (!value) return "Workflow file is required";
223
+ if (!WORKFLOW_PATTERN.test(value)) {
224
+ return "Workflow file must end with .yml or .yaml";
225
+ }
226
+ return null;
227
+ };
228
+ const SettingsPage = () => {
229
+ const { get, put, post, del } = useFetchClient();
230
+ const [settings, setSettings] = useState({
231
+ githubToken: "",
232
+ repoUrl: "",
233
+ targets: []
234
+ });
235
+ const [errors, setErrors] = useState({});
236
+ const [hasExistingToken, setHasExistingToken] = useState(false);
237
+ const [maskedToken, setMaskedToken] = useState(null);
238
+ const [loading, setLoading] = useState(true);
239
+ const [saving, setSaving] = useState(false);
240
+ const [notification, setNotification] = useState(null);
241
+ const [editingTarget, setEditingTarget] = useState(null);
242
+ const [targetForm, setTargetForm] = useState({ name: "", workflow: "", branch: "" });
243
+ const [targetErrors, setTargetErrors] = useState({});
244
+ const [showAddForm, setShowAddForm] = useState(false);
245
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
246
+ const [targetToDelete, setTargetToDelete] = useState(null);
247
+ useEffect(() => {
248
+ fetchSettings();
249
+ }, []);
250
+ const fetchSettings = async () => {
251
+ setLoading(true);
252
+ try {
253
+ const { data } = await get(`/${PLUGIN_ID}/settings`);
254
+ const fetchedSettings = data.data || {};
255
+ setSettings({
256
+ githubToken: "",
257
+ repoUrl: fetchedSettings.repoUrl || "",
258
+ targets: fetchedSettings.targets || []
259
+ });
260
+ setHasExistingToken(fetchedSettings.hasToken || false);
261
+ setMaskedToken(fetchedSettings.maskedToken || null);
262
+ } catch (error) {
263
+ console.error("Error fetching settings:", error);
264
+ setNotification({ type: "danger", message: "Failed to load settings" });
265
+ }
266
+ setLoading(false);
267
+ };
268
+ const handleSaveSettings = async () => {
269
+ const newErrors = {};
270
+ const repoError = validateRepoUrl(settings.repoUrl);
271
+ if (repoError) newErrors.repoUrl = repoError;
272
+ const tokenError = validateToken(settings.githubToken);
273
+ if (tokenError && settings.githubToken) newErrors.githubToken = tokenError;
274
+ setErrors(newErrors);
275
+ if (Object.keys(newErrors).length > 0) {
276
+ setNotification({ type: "warning", message: "Please fix the validation errors" });
277
+ return;
278
+ }
279
+ setSaving(true);
280
+ try {
281
+ const dataToSave = {
282
+ repoUrl: settings.repoUrl,
283
+ targets: settings.targets
284
+ };
285
+ if (settings.githubToken) {
286
+ dataToSave.githubToken = settings.githubToken;
287
+ }
288
+ const { data } = await put(`/${PLUGIN_ID}/settings`, { data: dataToSave });
289
+ if (settings.githubToken) {
290
+ setHasExistingToken(true);
291
+ setMaskedToken(data.data?.maskedToken);
292
+ }
293
+ setSettings((prev) => ({ ...prev, githubToken: "" }));
294
+ setNotification({ type: "success", message: "Settings saved successfully" });
295
+ } catch (error) {
296
+ console.error("Error saving settings:", error);
297
+ setNotification({ type: "danger", message: "Failed to save settings" });
298
+ }
299
+ setSaving(false);
300
+ };
301
+ const handleChange = (field) => (e) => {
302
+ const value = e.target.value;
303
+ setSettings((prev) => ({ ...prev, [field]: value }));
304
+ if (errors[field]) {
305
+ setErrors((prev) => ({ ...prev, [field]: null }));
306
+ }
307
+ };
308
+ const resetTargetForm = () => {
309
+ setTargetForm({ name: "", workflow: "deploy.yml", branch: "master" });
310
+ setTargetErrors({});
311
+ setEditingTarget(null);
312
+ setShowAddForm(false);
313
+ };
314
+ const validateTargetForm = () => {
315
+ const newErrors = {};
316
+ if (!targetForm.name.trim()) newErrors.name = "Name is required";
317
+ const workflowError = validateWorkflow(targetForm.workflow);
318
+ if (workflowError) newErrors.workflow = workflowError;
319
+ if (!targetForm.branch.trim()) newErrors.branch = "Branch is required";
320
+ setTargetErrors(newErrors);
321
+ return Object.keys(newErrors).length === 0;
322
+ };
323
+ const handleAddTarget = async () => {
324
+ if (!validateTargetForm()) return;
325
+ try {
326
+ const { data } = await post(`/${PLUGIN_ID}/targets`, { data: targetForm });
327
+ setSettings((prev) => ({
328
+ ...prev,
329
+ targets: [...prev.targets, data.data]
330
+ }));
331
+ resetTargetForm();
332
+ setNotification({ type: "success", message: "Target added successfully" });
333
+ } catch (error) {
334
+ console.error("Error adding target:", error);
335
+ setNotification({ type: "danger", message: "Failed to add target" });
336
+ }
337
+ };
338
+ const handleEditTarget = (target) => {
339
+ setEditingTarget(target.id);
340
+ setTargetForm({ name: target.name, workflow: target.workflow, branch: target.branch });
341
+ setShowAddForm(false);
342
+ };
343
+ const handleUpdateTarget = async () => {
344
+ if (!validateTargetForm()) return;
345
+ try {
346
+ const { data } = await put(`/${PLUGIN_ID}/targets/${editingTarget}`, { data: targetForm });
347
+ setSettings((prev) => ({
348
+ ...prev,
349
+ targets: prev.targets.map((t) => t.id === editingTarget ? data.data : t)
350
+ }));
351
+ resetTargetForm();
352
+ setNotification({ type: "success", message: "Target updated successfully" });
353
+ } catch (error) {
354
+ console.error("Error updating target:", error);
355
+ setNotification({ type: "danger", message: "Failed to update target" });
356
+ }
357
+ };
358
+ const handleDeleteTarget = async () => {
359
+ if (!targetToDelete) return;
360
+ try {
361
+ await del(`/${PLUGIN_ID}/targets/${targetToDelete}`);
362
+ setSettings((prev) => ({
363
+ ...prev,
364
+ targets: prev.targets.filter((t) => t.id !== targetToDelete)
365
+ }));
366
+ setDeleteDialogOpen(false);
367
+ setTargetToDelete(null);
368
+ setNotification({ type: "success", message: "Target deleted successfully" });
369
+ } catch (error) {
370
+ console.error("Error deleting target:", error);
371
+ setNotification({ type: "danger", message: "Failed to delete target" });
372
+ }
373
+ };
374
+ const handleTargetFormChange = (field) => (e) => {
375
+ const value = e.target.value;
376
+ setTargetForm((prev) => ({ ...prev, [field]: value }));
377
+ if (targetErrors[field]) {
378
+ setTargetErrors((prev) => ({ ...prev, [field]: null }));
379
+ }
380
+ };
381
+ if (loading) {
382
+ return /* @__PURE__ */ jsxs(Layouts.Root, { children: [
383
+ /* @__PURE__ */ jsx(
384
+ Layouts.Header,
385
+ {
386
+ title: "Settings",
387
+ subtitle: "Configure GitHub Actions deployment",
388
+ navigationAction: /* @__PURE__ */ jsx(BackButton, { fallback: `/plugins/${PLUGIN_ID}` })
389
+ }
390
+ ),
391
+ /* @__PURE__ */ jsx(Layouts.Content, { children: /* @__PURE__ */ jsx(Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsx(Loader, { children: "Loading settings..." }) }) })
392
+ ] });
393
+ }
394
+ const isValid = settings.repoUrl && !errors.repoUrl && !errors.githubToken;
395
+ return /* @__PURE__ */ jsxs(Layouts.Root, { children: [
396
+ /* @__PURE__ */ jsx(
397
+ Layouts.Header,
398
+ {
399
+ title: "Settings",
400
+ subtitle: "Configure GitHub Actions deployment",
401
+ navigationAction: /* @__PURE__ */ jsx(BackButton, { fallback: `/plugins/${PLUGIN_ID}` }),
402
+ primaryAction: /* @__PURE__ */ jsx(
403
+ Button,
404
+ {
405
+ onClick: handleSaveSettings,
406
+ loading: saving,
407
+ disabled: loading || !isValid,
408
+ startIcon: /* @__PURE__ */ jsx(Check, {}),
409
+ size: "L",
410
+ children: "Save Settings"
411
+ }
412
+ )
413
+ }
414
+ ),
415
+ /* @__PURE__ */ jsxs(Layouts.Content, { children: [
416
+ notification && /* @__PURE__ */ jsx(Box, { paddingBottom: 4, children: /* @__PURE__ */ jsx(
417
+ Alert,
418
+ {
419
+ closeLabel: "Close",
420
+ title: notification.message,
421
+ variant: notification.type,
422
+ onClose: () => setNotification(null)
423
+ }
424
+ ) }),
425
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 6, children: [
426
+ /* @__PURE__ */ jsx(
427
+ Box,
428
+ {
429
+ background: "neutral0",
430
+ hasRadius: true,
431
+ shadow: "filterShadow",
432
+ paddingTop: 6,
433
+ paddingBottom: 6,
434
+ paddingLeft: 7,
435
+ paddingRight: 7,
436
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
437
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", tag: "h2", children: "Repository" }),
438
+ /* @__PURE__ */ jsxs(
439
+ Field.Root,
440
+ {
441
+ name: "repoUrl",
442
+ required: true,
443
+ error: errors.repoUrl,
444
+ hint: "Copy the URL from your browser when viewing the repository",
445
+ children: [
446
+ /* @__PURE__ */ jsx(Field.Label, { children: "Repository URL" }),
447
+ /* @__PURE__ */ jsx(
448
+ Field.Input,
449
+ {
450
+ placeholder: "https://github.com/{owner}/{repo}",
451
+ value: settings.repoUrl,
452
+ onChange: handleChange("repoUrl")
453
+ }
454
+ ),
455
+ /* @__PURE__ */ jsx(Field.Hint, {}),
456
+ /* @__PURE__ */ jsx(Field.Error, {})
457
+ ]
458
+ }
459
+ )
460
+ ] })
461
+ }
462
+ ),
463
+ /* @__PURE__ */ jsx(
464
+ Box,
465
+ {
466
+ background: "neutral0",
467
+ hasRadius: true,
468
+ shadow: "filterShadow",
469
+ paddingTop: 6,
470
+ paddingBottom: 6,
471
+ paddingLeft: 7,
472
+ paddingRight: 7,
473
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
474
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
475
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", tag: "h2", children: "Deployment Targets" }),
476
+ !showAddForm && !editingTarget && /* @__PURE__ */ jsx(
477
+ Button,
478
+ {
479
+ startIcon: /* @__PURE__ */ jsx(Plus, {}),
480
+ onClick: () => {
481
+ setShowAddForm(true);
482
+ setTargetForm({ name: "", workflow: "deploy.yml", branch: "master" });
483
+ },
484
+ size: "S",
485
+ children: "Add Target"
486
+ }
487
+ )
488
+ ] }),
489
+ (showAddForm || editingTarget) && /* @__PURE__ */ jsx(
490
+ Box,
491
+ {
492
+ background: "neutral100",
493
+ padding: 4,
494
+ hasRadius: true,
495
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 4, children: [
496
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "bold", children: editingTarget ? "Edit Target" : "Add New Target" }),
497
+ /* @__PURE__ */ jsxs(Grid.Root, { gap: 4, children: [
498
+ /* @__PURE__ */ jsx(Grid.Item, { col: 4, s: 12, children: /* @__PURE__ */ jsxs(Field.Root, { name: "targetName", required: true, error: targetErrors.name, children: [
499
+ /* @__PURE__ */ jsx(Field.Label, { children: "Name" }),
500
+ /* @__PURE__ */ jsx(
501
+ Field.Input,
502
+ {
503
+ placeholder: "e.g., Production",
504
+ value: targetForm.name,
505
+ onChange: handleTargetFormChange("name")
506
+ }
507
+ ),
508
+ /* @__PURE__ */ jsx(Field.Error, {})
509
+ ] }) }),
510
+ /* @__PURE__ */ jsx(Grid.Item, { col: 4, s: 12, children: /* @__PURE__ */ jsxs(Field.Root, { name: "targetWorkflow", required: true, error: targetErrors.workflow, children: [
511
+ /* @__PURE__ */ jsx(Field.Label, { children: "Workflow File" }),
512
+ /* @__PURE__ */ jsx(
513
+ Field.Input,
514
+ {
515
+ placeholder: "deploy.yml",
516
+ value: targetForm.workflow,
517
+ onChange: handleTargetFormChange("workflow")
518
+ }
519
+ ),
520
+ /* @__PURE__ */ jsx(Field.Error, {})
521
+ ] }) }),
522
+ /* @__PURE__ */ jsx(Grid.Item, { col: 4, s: 12, children: /* @__PURE__ */ jsxs(Field.Root, { name: "targetBranch", required: true, error: targetErrors.branch, children: [
523
+ /* @__PURE__ */ jsx(Field.Label, { children: "Branch" }),
524
+ /* @__PURE__ */ jsx(
525
+ Field.Input,
526
+ {
527
+ placeholder: "main",
528
+ value: targetForm.branch,
529
+ onChange: handleTargetFormChange("branch")
530
+ }
531
+ ),
532
+ /* @__PURE__ */ jsx(Field.Error, {})
533
+ ] }) })
534
+ ] }),
535
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, justifyContent: "flex-end", children: [
536
+ /* @__PURE__ */ jsx(Button, { variant: "tertiary", onClick: resetTargetForm, children: "Cancel" }),
537
+ /* @__PURE__ */ jsx(
538
+ Button,
539
+ {
540
+ onClick: editingTarget ? handleUpdateTarget : handleAddTarget,
541
+ startIcon: /* @__PURE__ */ jsx(Check, {}),
542
+ children: editingTarget ? "Update" : "Add"
543
+ }
544
+ )
545
+ ] })
546
+ ] })
547
+ }
548
+ ),
549
+ settings.targets.length > 0 ? /* @__PURE__ */ jsxs(Table, { children: [
550
+ /* @__PURE__ */ jsx(Thead, { children: /* @__PURE__ */ jsxs(Tr, { children: [
551
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Name" }) }),
552
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Workflow" }) }),
553
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Branch" }) }),
554
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Actions" }) })
555
+ ] }) }),
556
+ /* @__PURE__ */ jsx(Tbody, { children: settings.targets.map((target) => /* @__PURE__ */ jsxs(Tr, { children: [
557
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "omega", children: target.name }) }),
558
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "omega", children: target.workflow }) }),
559
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "omega", children: target.branch }) }),
560
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsxs(Flex, { gap: 1, children: [
561
+ /* @__PURE__ */ jsx(
562
+ IconButton,
563
+ {
564
+ onClick: () => handleEditTarget(target),
565
+ label: "Edit",
566
+ variant: "ghost",
567
+ children: /* @__PURE__ */ jsx(Pencil, {})
568
+ }
569
+ ),
570
+ /* @__PURE__ */ jsx(
571
+ IconButton,
572
+ {
573
+ onClick: () => {
574
+ setTargetToDelete(target.id);
575
+ setDeleteDialogOpen(true);
576
+ },
577
+ label: "Delete",
578
+ variant: "ghost",
579
+ children: /* @__PURE__ */ jsx(Trash, {})
580
+ }
581
+ )
582
+ ] }) })
583
+ ] }, target.id)) })
584
+ ] }) : !showAddForm && /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: 'No deployment targets configured. Click "Add Target" to create one.' })
585
+ ] })
586
+ }
587
+ ),
588
+ /* @__PURE__ */ jsx(
589
+ Box,
590
+ {
591
+ background: "neutral0",
592
+ hasRadius: true,
593
+ shadow: "filterShadow",
594
+ paddingTop: 6,
595
+ paddingBottom: 6,
596
+ paddingLeft: 7,
597
+ paddingRight: 7,
598
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
599
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", tag: "h2", children: "Authentication" }),
600
+ /* @__PURE__ */ jsxs(
601
+ Field.Root,
602
+ {
603
+ name: "githubToken",
604
+ required: true,
605
+ error: errors.githubToken,
606
+ hint: !hasExistingToken || !maskedToken ? "Create a fine-grained token with Actions (Read and write) permission" : void 0,
607
+ children: [
608
+ /* @__PURE__ */ jsx(Field.Label, { children: "GitHub Personal Access Token" }),
609
+ /* @__PURE__ */ jsx(
610
+ Field.Input,
611
+ {
612
+ type: "password",
613
+ placeholder: hasExistingToken ? "••••••••••••••••" : "github_pat_xxxxxxxxxxxx",
614
+ value: settings.githubToken,
615
+ onChange: handleChange("githubToken")
616
+ }
617
+ ),
618
+ hasExistingToken && maskedToken ? /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
619
+ "Existing token:",
620
+ " ",
621
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", textColor: "success600", tag: "span", children: maskedToken }),
622
+ ". Leave empty to keep existing, or enter new to replace."
623
+ ] }) : /* @__PURE__ */ jsx(Field.Hint, {}),
624
+ /* @__PURE__ */ jsx(Field.Error, {})
625
+ ]
626
+ }
627
+ ),
628
+ /* @__PURE__ */ jsxs(Box, { paddingTop: 2, children: [
629
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: "How to get a GitHub Token:" }),
630
+ /* @__PURE__ */ jsx(Box, { paddingTop: 2, children: /* @__PURE__ */ jsxs(Typography, { variant: "pi", tag: "ol", textColor: "neutral600", children: [
631
+ /* @__PURE__ */ jsx("li", { children: "Go to GitHub → Settings → Developer settings → Personal access tokens → Fine-grained tokens" }),
632
+ /* @__PURE__ */ jsx("li", { children: 'Click "Generate new token"' }),
633
+ /* @__PURE__ */ jsx("li", { children: "Set token name, expiration, and select the target repository" }),
634
+ /* @__PURE__ */ jsxs("li", { children: [
635
+ 'Under "Repository permissions", set ',
636
+ /* @__PURE__ */ jsx("strong", { children: "Actions" }),
637
+ ' to "Read and write"'
638
+ ] }),
639
+ /* @__PURE__ */ jsx("li", { children: 'Click "Generate token" and paste it above' })
640
+ ] }) })
641
+ ] })
642
+ ] })
643
+ }
644
+ )
645
+ ] }),
646
+ /* @__PURE__ */ jsx(Dialog.Root, { open: deleteDialogOpen, onOpenChange: setDeleteDialogOpen, children: /* @__PURE__ */ jsxs(Dialog.Content, { children: [
647
+ /* @__PURE__ */ jsx(Dialog.Header, { children: "Delete Target" }),
648
+ /* @__PURE__ */ jsx(Dialog.Body, { children: "Are you sure you want to delete this deployment target? This action cannot be undone." }),
649
+ /* @__PURE__ */ jsxs(Dialog.Footer, { children: [
650
+ /* @__PURE__ */ jsx(Dialog.Cancel, { children: /* @__PURE__ */ jsx(Button, { variant: "tertiary", children: "Cancel" }) }),
651
+ /* @__PURE__ */ jsx(Dialog.Action, { children: /* @__PURE__ */ jsx(Button, { variant: "danger-light", onClick: handleDeleteTarget, children: "Delete" }) })
652
+ ] })
653
+ ] }) })
654
+ ] })
655
+ ] });
656
+ };
657
+ const App = () => {
658
+ return /* @__PURE__ */ jsxs(Routes, { children: [
659
+ /* @__PURE__ */ jsx(Route, { index: true, element: /* @__PURE__ */ jsx(HomePage, {}) }),
660
+ /* @__PURE__ */ jsx(Route, { path: "settings", element: /* @__PURE__ */ jsx(SettingsPage, {}) }),
661
+ /* @__PURE__ */ jsx(Route, { path: "*", element: /* @__PURE__ */ jsx(Page.Error, {}) })
662
+ ] });
663
+ };
664
+ export {
665
+ App
666
+ };
@@ -36,7 +36,7 @@ const index = {
36
36
  defaultMessage: PLUGIN_ID
37
37
  },
38
38
  Component: async () => {
39
- const { App } = await import("./App-C0Byi5W1.mjs");
39
+ const { App } = await import("./App-vakyp6FE.mjs");
40
40
  return App;
41
41
  }
42
42
  });