@orange-soft/strapi-deployment-trigger 1.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 (38) hide show
  1. package/README.md +116 -0
  2. package/admin/jsconfig.json +10 -0
  3. package/admin/src/components/Initializer.jsx +18 -0
  4. package/admin/src/components/PluginIcon.jsx +5 -0
  5. package/admin/src/index.js +49 -0
  6. package/admin/src/pages/App.jsx +17 -0
  7. package/admin/src/pages/HomePage.jsx +241 -0
  8. package/admin/src/pages/SettingsPage.jsx +370 -0
  9. package/admin/src/pluginId.js +1 -0
  10. package/admin/src/translations/en.json +3 -0
  11. package/admin/src/utils/getTranslation.js +5 -0
  12. package/dist/_chunks/App-3JntxPYv.js +520 -0
  13. package/dist/_chunks/App-C0Byi5W1.mjs +520 -0
  14. package/dist/_chunks/en-BDvOU5UD.js +6 -0
  15. package/dist/_chunks/en-DdBZuj6F.mjs +6 -0
  16. package/dist/_chunks/index-C18aSW5z.mjs +70 -0
  17. package/dist/_chunks/index-CqpMwL_C.js +69 -0
  18. package/dist/admin/index.js +3 -0
  19. package/dist/admin/index.mjs +4 -0
  20. package/dist/server/index.js +264 -0
  21. package/dist/server/index.mjs +265 -0
  22. package/package.json +84 -0
  23. package/server/jsconfig.json +10 -0
  24. package/server/src/bootstrap.js +5 -0
  25. package/server/src/config/index.js +4 -0
  26. package/server/src/content-types/index.js +1 -0
  27. package/server/src/controllers/controller.js +95 -0
  28. package/server/src/controllers/index.js +5 -0
  29. package/server/src/destroy.js +5 -0
  30. package/server/src/index.js +31 -0
  31. package/server/src/middlewares/index.js +1 -0
  32. package/server/src/policies/index.js +1 -0
  33. package/server/src/register.js +5 -0
  34. package/server/src/routes/admin/index.js +37 -0
  35. package/server/src/routes/content-api/index.js +14 -0
  36. package/server/src/routes/index.js +9 -0
  37. package/server/src/services/index.js +5 -0
  38. package/server/src/services/service.js +124 -0
package/README.md ADDED
@@ -0,0 +1,116 @@
1
+ # Strapi Plugin: Deployment Trigger
2
+
3
+ A Strapi v5 plugin that allows you to trigger GitHub Actions workflow deployments directly from the Strapi admin panel.
4
+
5
+ ## Features
6
+
7
+ - Trigger GitHub Actions `workflow_dispatch` events from the admin UI
8
+ - Configure repository URL, workflow file, and branch
9
+ - Secure token storage in database (or via environment variable)
10
+ - Token masking for security
11
+ - Direct link to GitHub Actions to monitor deployment progress
12
+
13
+ ## Requirements
14
+
15
+ - Strapi v5.x
16
+ - Node.js >= 18.x
17
+ - A GitHub repository with a workflow that supports `workflow_dispatch`
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install @orange-soft/strapi-deployment-trigger
23
+ # or
24
+ yarn add @orange-soft/strapi-deployment-trigger
25
+ ```
26
+
27
+ ## Configuration
28
+
29
+ ### 1. Enable the plugin
30
+
31
+ Add the plugin to your `config/plugins.ts` (or `config/plugins.js`):
32
+
33
+ ```typescript
34
+ export default () => ({
35
+ 'deployment-trigger': {
36
+ enabled: true,
37
+ },
38
+ });
39
+ ```
40
+
41
+ ### 2. Configure via Admin UI (Recommended)
42
+
43
+ 1. Go to **Plugins > Deployment Trigger > Settings** in your Strapi admin
44
+ 2. Enter your GitHub repository URL (e.g., `https://github.com/owner/repo`)
45
+ 3. Enter the workflow filename (e.g., `deploy.yml`)
46
+ 4. Enter the branch name to trigger (e.g., `main` or `master`)
47
+ 5. Enter your GitHub Personal Access Token
48
+
49
+ ### 3. Alternative: Environment Variable for Token
50
+
51
+ You can set the GitHub token via environment variable instead of the UI:
52
+
53
+ ```bash
54
+ GITHUB_PERSONAL_ACCESS_TOKEN=github_pat_xxxxxxxxxxxx
55
+ ```
56
+
57
+ The plugin will use the database-stored token first, falling back to the environment variable.
58
+
59
+ ## GitHub Token Setup
60
+
61
+ You need a GitHub **Fine-grained Personal Access Token** with the following permissions:
62
+
63
+ 1. Go to GitHub > Settings > Developer settings > Personal access tokens > Fine-grained tokens
64
+ 2. Click "Generate new token"
65
+ 3. Set token name and expiration
66
+ 4. Select the target repository under "Repository access"
67
+ 5. Under "Repository permissions", set **Actions** to "Read and write"
68
+ 6. Click "Generate token"
69
+
70
+ ## GitHub Workflow Setup
71
+
72
+ Your workflow file (e.g., `.github/workflows/deploy.yml`) must include the `workflow_dispatch` trigger:
73
+
74
+ ```yaml
75
+ name: Deploy
76
+
77
+ on:
78
+ workflow_dispatch:
79
+
80
+ jobs:
81
+ deploy:
82
+ runs-on: ubuntu-latest
83
+ steps:
84
+ - uses: actions/checkout@v4
85
+ # ... your deployment steps
86
+ ```
87
+
88
+ ## Usage
89
+
90
+ 1. Navigate to **Plugins > Deployment Trigger** in your Strapi admin
91
+ 2. Verify your configuration is correct
92
+ 3. Click **Trigger Deployment**
93
+ 4. Monitor the deployment progress via the provided GitHub Actions link
94
+
95
+ ## API
96
+
97
+ The plugin exposes the following admin API endpoints:
98
+
99
+ | Method | Endpoint | Description |
100
+ |--------|----------|-------------|
101
+ | GET | `/deployment-trigger/settings` | Get current settings |
102
+ | PUT | `/deployment-trigger/settings` | Update settings |
103
+ | GET | `/deployment-trigger/status` | Get configuration status |
104
+ | POST | `/deployment-trigger/trigger` | Trigger deployment |
105
+
106
+ ## License
107
+
108
+ MIT
109
+
110
+ ## Author
111
+
112
+ Justin Moh <moh@os.my>
113
+
114
+ ## Contributing
115
+
116
+ Contributions are welcome! Please feel free to submit a Pull Request.
@@ -0,0 +1,10 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es6",
4
+ "jsx": "react",
5
+ "module": "esnext",
6
+ "allowSyntheticDefaultImports": true,
7
+ "esModuleInterop": true
8
+ },
9
+ "include": ["./src/**/*.js", "./src/**/*.jsx"]
10
+ }
@@ -0,0 +1,18 @@
1
+ import { useEffect, useRef } from 'react';
2
+
3
+ import { PLUGIN_ID } from '../pluginId';
4
+
5
+ /**
6
+ * @type {import('react').FC<{ setPlugin: (id: string) => void }>}
7
+ */
8
+ const Initializer = ({ setPlugin }) => {
9
+ const ref = useRef(setPlugin);
10
+
11
+ useEffect(() => {
12
+ ref.current(PLUGIN_ID);
13
+ }, []);
14
+
15
+ return null;
16
+ };
17
+
18
+ export { Initializer };
@@ -0,0 +1,5 @@
1
+ import { CloudUpload } from '@strapi/icons';
2
+
3
+ const PluginIcon = () => <CloudUpload />;
4
+
5
+ export { PluginIcon };
@@ -0,0 +1,49 @@
1
+ import { getTranslation } from './utils/getTranslation';
2
+ import { PLUGIN_ID } from './pluginId';
3
+ import { Initializer } from './components/Initializer';
4
+ import { PluginIcon } from './components/PluginIcon';
5
+
6
+ export default {
7
+ register(app) {
8
+ app.addMenuLink({
9
+ to: `plugins/${PLUGIN_ID}`,
10
+ icon: PluginIcon,
11
+ intlLabel: {
12
+ id: `${PLUGIN_ID}.plugin.name`,
13
+ defaultMessage: PLUGIN_ID,
14
+ },
15
+ Component: async () => {
16
+ const { App } = await import('./pages/App');
17
+
18
+ return App;
19
+ },
20
+ });
21
+
22
+ app.registerPlugin({
23
+ id: PLUGIN_ID,
24
+ initializer: Initializer,
25
+ isReady: false,
26
+ name: PLUGIN_ID,
27
+ });
28
+ },
29
+
30
+ async registerTrads({ locales }) {
31
+ return Promise.all(
32
+ locales.map(async (locale) => {
33
+ try {
34
+ const { default: data } = await import(`./translations/${locale}.json`);
35
+
36
+ // Prefix all keys with plugin ID
37
+ const prefixedData = Object.keys(data).reduce((acc, key) => {
38
+ acc[`${PLUGIN_ID}.${key}`] = data[key];
39
+ return acc;
40
+ }, {});
41
+
42
+ return { data: prefixedData, locale };
43
+ } catch {
44
+ return { data: {}, locale };
45
+ }
46
+ })
47
+ );
48
+ },
49
+ };
@@ -0,0 +1,17 @@
1
+ import { Page } from '@strapi/strapi/admin';
2
+ import { Routes, Route } from 'react-router-dom';
3
+
4
+ import { HomePage } from './HomePage';
5
+ import { SettingsPage } from './SettingsPage';
6
+
7
+ const App = () => {
8
+ return (
9
+ <Routes>
10
+ <Route index element={<HomePage />} />
11
+ <Route path="settings" element={<SettingsPage />} />
12
+ <Route path="*" element={<Page.Error />} />
13
+ </Routes>
14
+ );
15
+ };
16
+
17
+ export { App };
@@ -0,0 +1,241 @@
1
+ import {useState, useEffect} from 'react';
2
+ import {useIntl} from 'react-intl';
3
+ import {Layouts, useFetchClient} from '@strapi/strapi/admin';
4
+ import {Link} from 'react-router-dom';
5
+ import {
6
+ Box,
7
+ Button,
8
+ Flex,
9
+ Typography,
10
+ Alert,
11
+ Loader,
12
+ } from '@strapi/design-system';
13
+ import {Rocket, Cog} from '@strapi/icons';
14
+
15
+ import {PLUGIN_ID} from '../pluginId';
16
+ import {getTranslation} from '../utils/getTranslation';
17
+
18
+ const HomePage = () => {
19
+ const {formatMessage} = useIntl();
20
+ const {get, post} = useFetchClient();
21
+ const [status, setStatus] = useState(null);
22
+ const [loading, setLoading] = useState(true);
23
+ const [deploying, setDeploying] = useState(false);
24
+ const [notification, setNotification] = useState(null);
25
+
26
+ useEffect(() => {
27
+ fetchStatus();
28
+ }, []);
29
+
30
+ const fetchStatus = async () => {
31
+ try {
32
+ const {data} = await get(`/${PLUGIN_ID}/status`);
33
+ setStatus(data.data);
34
+ } catch (error) {
35
+ console.error('Failed to fetch status:', error);
36
+ } finally {
37
+ setLoading(false);
38
+ }
39
+ };
40
+
41
+ const handleDeploy = async () => {
42
+ setDeploying(true);
43
+ setNotification(null);
44
+ try {
45
+ const {data} = await post(`/${PLUGIN_ID}/trigger`, {});
46
+ setNotification({
47
+ type: 'success',
48
+ message: data.data?.message || 'Deployment triggered successfully!',
49
+ actionsUrl: data.data?.actionsUrl,
50
+ });
51
+ } catch (error) {
52
+ setNotification({
53
+ type: 'danger',
54
+ message: error?.response?.data?.error?.message || error.message || 'Deployment failed',
55
+ });
56
+ } finally {
57
+ setDeploying(false);
58
+ }
59
+ };
60
+
61
+ if (loading) {
62
+ return (
63
+ <Layouts.Root>
64
+ <Layouts.Header
65
+ title={formatMessage({id: getTranslation('plugin.name')})}
66
+ subtitle="Loading..."
67
+ />
68
+ <Layouts.Content>
69
+ <Flex justifyContent="center" padding={8}>
70
+ <Loader>Loading...</Loader>
71
+ </Flex>
72
+ </Layouts.Content>
73
+ </Layouts.Root>
74
+ );
75
+ }
76
+
77
+ const settings = status?.settings || {};
78
+ const parsed = status?.parsed || {};
79
+ const isConfigured = status?.configured;
80
+ const hasToken = status?.hasToken;
81
+
82
+ return (
83
+ <Layouts.Root>
84
+ <Layouts.Header
85
+ title={formatMessage({id: getTranslation('plugin.name')})}
86
+ subtitle="Trigger GitHub Actions deployment to production"
87
+ secondaryAction={
88
+ <Link to={`/plugins/${PLUGIN_ID}/settings`}>
89
+ <Button variant="tertiary" startIcon={<Cog />}>
90
+ Settings
91
+ </Button>
92
+ </Link>
93
+ }
94
+ />
95
+ <Layouts.Content>
96
+ {notification && (
97
+ <Box paddingBottom={4}>
98
+ <Alert
99
+ closeLabel="Close"
100
+ title={notification.message}
101
+ variant={notification.type}
102
+ onClose={() => setNotification(null)}
103
+ >
104
+ {notification.actionsUrl && (
105
+ <Typography variant="pi">
106
+ Check the deployment status at{' '}
107
+ <Typography
108
+ variant="pi"
109
+ tag="a"
110
+ href={notification.actionsUrl}
111
+ target="_blank"
112
+ rel="noopener noreferrer"
113
+ textColor="primary600"
114
+ >
115
+ GitHub Actions
116
+ </Typography>
117
+ </Typography>
118
+ )}
119
+ </Alert>
120
+ </Box>
121
+ )}
122
+
123
+ {!hasToken && (
124
+ <Box paddingBottom={4}>
125
+ <Alert title="Token Missing" variant="danger">
126
+ GitHub Personal Access Token is not configured. Please add it in Settings.
127
+ </Alert>
128
+ </Box>
129
+ )}
130
+
131
+ {!settings.repoUrl && (
132
+ <Box paddingBottom={4}>
133
+ <Alert title="Configuration Required" variant="warning">
134
+ Please configure your GitHub repository in the Settings page before triggering deployments.
135
+ </Alert>
136
+ </Box>
137
+ )}
138
+
139
+ <Flex direction="column" alignItems="stretch" gap={4}>
140
+ {/* Repository Configuration Card */}
141
+ <Box
142
+ background="neutral0"
143
+ hasRadius
144
+ shadow="filterShadow"
145
+ paddingTop={6}
146
+ paddingBottom={6}
147
+ paddingLeft={7}
148
+ paddingRight={7}
149
+ >
150
+ <Flex direction="column" alignItems="stretch" gap={4}>
151
+ <Typography variant="delta" tag="h2">
152
+ Configuration
153
+ </Typography>
154
+
155
+ <Flex justifyContent="space-between" alignItems="center" gap={6}>
156
+ {/* Labels and Values */}
157
+ <Box style={{ display: 'grid', gridTemplateColumns: '120px 1fr', gap: '12px 16px', alignItems: 'center' }}>
158
+ <Typography variant="sigma" textColor="neutral600">Repository</Typography>
159
+ {parsed.owner && parsed.repo ? (
160
+ <Typography
161
+ variant="pi"
162
+ tag="a"
163
+ href={settings.repoUrl}
164
+ target="_blank"
165
+ rel="noopener noreferrer"
166
+ textColor="primary600"
167
+ style={{ textDecoration: 'none' }}
168
+ >
169
+ {parsed.owner}/{parsed.repo}
170
+ </Typography>
171
+ ) : (
172
+ <Typography variant="pi" textColor="neutral500">Not configured</Typography>
173
+ )}
174
+
175
+ <Typography variant="sigma" textColor="neutral600">Workflow</Typography>
176
+ <Typography variant="pi">
177
+ {settings.workflow || 'deploy.yml (default)'}
178
+ </Typography>
179
+
180
+ <Typography variant="sigma" textColor="neutral600">Branch</Typography>
181
+ <Typography variant="pi">
182
+ {settings.branch || 'master (default)'}
183
+ </Typography>
184
+
185
+ <Typography variant="sigma" textColor="neutral600">GitHub Token</Typography>
186
+ <Typography variant="pi" textColor={hasToken ? 'success600' : 'danger600'}>
187
+ {hasToken ? 'Configured' : 'Missing'}
188
+ </Typography>
189
+ </Box>
190
+
191
+ {/* Action Button */}
192
+ {isConfigured && (
193
+ <Button
194
+ onClick={handleDeploy}
195
+ loading={deploying}
196
+ disabled={deploying}
197
+ startIcon={<Rocket />}
198
+ size="L"
199
+ >
200
+ {deploying ? 'Triggering...' : 'Trigger Deployment'}
201
+ </Button>
202
+ )}
203
+ </Flex>
204
+ </Flex>
205
+ </Box>
206
+
207
+ {/* Instructions Card */}
208
+ {!isConfigured && (
209
+ <Box
210
+ background="neutral0"
211
+ hasRadius
212
+ shadow="filterShadow"
213
+ paddingTop={8}
214
+ paddingBottom={8}
215
+ paddingLeft={7}
216
+ paddingRight={7}
217
+ >
218
+ <Flex direction="column" alignItems="center" justifyContent="center" gap={3}>
219
+ <Typography variant="beta" textColor="neutral600" textAlign="center">
220
+ Setup Required
221
+ </Typography>
222
+ <Typography variant="epsilon" textColor="neutral600" textAlign="center">
223
+ Configure your GitHub repository settings to enable deployment triggering.
224
+ </Typography>
225
+ <Box paddingTop={2}>
226
+ <Link to={`/plugins/${PLUGIN_ID}/settings`}>
227
+ <Button variant="default" startIcon={<Cog />}>
228
+ Go to Settings
229
+ </Button>
230
+ </Link>
231
+ </Box>
232
+ </Flex>
233
+ </Box>
234
+ )}
235
+ </Flex>
236
+ </Layouts.Content>
237
+ </Layouts.Root>
238
+ );
239
+ };
240
+
241
+ export {HomePage};