@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.
- package/README.md +116 -0
- package/admin/jsconfig.json +10 -0
- package/admin/src/components/Initializer.jsx +18 -0
- package/admin/src/components/PluginIcon.jsx +5 -0
- package/admin/src/index.js +49 -0
- package/admin/src/pages/App.jsx +17 -0
- package/admin/src/pages/HomePage.jsx +241 -0
- package/admin/src/pages/SettingsPage.jsx +370 -0
- package/admin/src/pluginId.js +1 -0
- package/admin/src/translations/en.json +3 -0
- package/admin/src/utils/getTranslation.js +5 -0
- package/dist/_chunks/App-3JntxPYv.js +520 -0
- package/dist/_chunks/App-C0Byi5W1.mjs +520 -0
- package/dist/_chunks/en-BDvOU5UD.js +6 -0
- package/dist/_chunks/en-DdBZuj6F.mjs +6 -0
- package/dist/_chunks/index-C18aSW5z.mjs +70 -0
- package/dist/_chunks/index-CqpMwL_C.js +69 -0
- package/dist/admin/index.js +3 -0
- package/dist/admin/index.mjs +4 -0
- package/dist/server/index.js +264 -0
- package/dist/server/index.mjs +265 -0
- package/package.json +84 -0
- package/server/jsconfig.json +10 -0
- package/server/src/bootstrap.js +5 -0
- package/server/src/config/index.js +4 -0
- package/server/src/content-types/index.js +1 -0
- package/server/src/controllers/controller.js +95 -0
- package/server/src/controllers/index.js +5 -0
- package/server/src/destroy.js +5 -0
- package/server/src/index.js +31 -0
- package/server/src/middlewares/index.js +1 -0
- package/server/src/policies/index.js +1 -0
- package/server/src/register.js +5 -0
- package/server/src/routes/admin/index.js +37 -0
- package/server/src/routes/content-api/index.js +14 -0
- package/server/src/routes/index.js +9 -0
- package/server/src/services/index.js +5 -0
- 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,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,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};
|