@fils/sanity-components 0.1.0 → 0.1.2
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/HOSTED.md +205 -0
- package/lib/components/deploy/DeployButton.d.ts +23 -0
- package/lib/components/deploy/DeployButton.js +246 -0
- package/lib/components/deploy/EnvUtils.d.ts +24 -0
- package/lib/components/deploy/EnvUtils.js +49 -0
- package/lib/components/deploy/GithubTokenConfig.d.ts +14 -0
- package/lib/components/deploy/GithubTokenConfig.js +29 -0
- package/lib/components/video/VideoSchemas.d.ts +1 -1
- package/lib/components/video/VideoSchemas.js +1 -1
- package/lib/main.d.ts +3 -1
- package/lib/main.js +3 -1
- package/package.json +1 -1
- package/src/components/{ui → deploy}/DeployButton.tsx +33 -10
- package/src/components/deploy/EnvUtils.tsx +57 -0
- package/src/components/deploy/GithubTokenConfig.tsx +93 -0
- package/src/components/video/VideoSchemas.ts +1 -1
- package/src/main.ts +3 -1
package/HOSTED.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Sanity Dashboard Widgets
|
|
2
|
+
|
|
3
|
+
Collection of custom Sanity Studio dashboard widgets for deployment automation.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
### DeployButton
|
|
8
|
+
A deployment button that triggers GitHub Actions workflows and monitors their status.
|
|
9
|
+
|
|
10
|
+
**Features:**
|
|
11
|
+
- Triggers GitHub Actions via repository dispatch
|
|
12
|
+
- Real-time workflow status monitoring
|
|
13
|
+
- Fast polling (3-second intervals)
|
|
14
|
+
- Retry logic for workflow detection
|
|
15
|
+
- Success/error state handling
|
|
16
|
+
- Links to live deployment
|
|
17
|
+
|
|
18
|
+
**Props:**
|
|
19
|
+
```typescript
|
|
20
|
+
interface DeployButtonProps {
|
|
21
|
+
config: {
|
|
22
|
+
owner: string; // GitHub org/user
|
|
23
|
+
repo: string; // Repository name
|
|
24
|
+
token?: string; // Optional: hardcoded token (use secrets instead)
|
|
25
|
+
branch?: string; // Default: 'main'
|
|
26
|
+
eventType?: string; // Default: 'deploy-site'
|
|
27
|
+
};
|
|
28
|
+
title?: string; // Default: '🚀 Site Deployment'
|
|
29
|
+
deploymentUrl?: string; // Optional: URL to deployed site
|
|
30
|
+
environment?: string; // Default: 'production'
|
|
31
|
+
secretsNamespace?: string; // Default: 'deployButton'
|
|
32
|
+
secretKey?: string; // Default: 'github_token'
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### GitHubTokenConfig
|
|
37
|
+
Configuration widget for managing GitHub tokens (dev-only).
|
|
38
|
+
|
|
39
|
+
**Props:**
|
|
40
|
+
```typescript
|
|
41
|
+
interface TokenConfigProps {
|
|
42
|
+
title?: string; // Default: '🔐 GitHub Token Configuration'
|
|
43
|
+
description?: string; // Custom description text
|
|
44
|
+
secretsNamespace?: string; // Default: 'deployButton'
|
|
45
|
+
secretKey?: string; // Default: 'github_token'
|
|
46
|
+
environment?: string; // Default: 'production'
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Environment Utilities
|
|
51
|
+
|
|
52
|
+
**envUtils.ts** provides helpers for conditional rendering:
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { isDevelopment, isProduction, withDevOnly } from './envUtils';
|
|
56
|
+
|
|
57
|
+
// Check environment
|
|
58
|
+
if (isDevelopment()) {
|
|
59
|
+
// Show dev tools
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Wrap components
|
|
63
|
+
const DevOnlyWidget = withDevOnly(GitHubTokenConfig);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Usage
|
|
67
|
+
|
|
68
|
+
### Basic Setup
|
|
69
|
+
|
|
70
|
+
1. **Install dependencies:**
|
|
71
|
+
```bash
|
|
72
|
+
npm install @sanity/dashboard @sanity/studio-secrets
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
2. **Add to your dashboard config:**
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { isDevelopment } from './envUtils';
|
|
79
|
+
import DeployButton from './DeployButton';
|
|
80
|
+
import GitHubTokenConfig from './GitHubTokenConfig';
|
|
81
|
+
|
|
82
|
+
export default {
|
|
83
|
+
widgets: [
|
|
84
|
+
// Production deploy button (always visible)
|
|
85
|
+
{
|
|
86
|
+
name: 'deploy-prod',
|
|
87
|
+
component: DeployButton,
|
|
88
|
+
options: {
|
|
89
|
+
config: {
|
|
90
|
+
owner: 'your-org',
|
|
91
|
+
repo: 'your-repo',
|
|
92
|
+
branch: 'main'
|
|
93
|
+
},
|
|
94
|
+
title: '🚀 Deploy to Production',
|
|
95
|
+
deploymentUrl: 'https://your-site.com',
|
|
96
|
+
environment: 'production'
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
// Token config (dev only)
|
|
101
|
+
...(isDevelopment() ? [
|
|
102
|
+
{
|
|
103
|
+
name: 'github-config',
|
|
104
|
+
component: GitHubTokenConfig
|
|
105
|
+
}
|
|
106
|
+
] : [])
|
|
107
|
+
]
|
|
108
|
+
};
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### GitHub Setup
|
|
112
|
+
|
|
113
|
+
1. **Create a Personal Access Token:**
|
|
114
|
+
- Go to GitHub Settings → Developer settings → Personal access tokens
|
|
115
|
+
- Generate new token (classic)
|
|
116
|
+
- Select scopes: `repo` (for private repos) or `public_repo` (for public)
|
|
117
|
+
- Copy the token
|
|
118
|
+
|
|
119
|
+
2. **Configure in Sanity:**
|
|
120
|
+
- In local dev, the GitHubTokenConfig widget will appear
|
|
121
|
+
- Paste your token
|
|
122
|
+
- Save
|
|
123
|
+
|
|
124
|
+
3. **Create GitHub Workflow:**
|
|
125
|
+
|
|
126
|
+
```yaml
|
|
127
|
+
# .github/workflows/deploy.yml
|
|
128
|
+
name: Deploy Site
|
|
129
|
+
|
|
130
|
+
on:
|
|
131
|
+
repository_dispatch:
|
|
132
|
+
types: [deploy-site]
|
|
133
|
+
|
|
134
|
+
jobs:
|
|
135
|
+
deploy:
|
|
136
|
+
runs-on: ubuntu-latest
|
|
137
|
+
steps:
|
|
138
|
+
- uses: actions/checkout@v3
|
|
139
|
+
|
|
140
|
+
- name: Deploy to AWS
|
|
141
|
+
run: |
|
|
142
|
+
# Your deployment script
|
|
143
|
+
echo "Deploying to ${{ github.event.client_payload.environment }}"
|
|
144
|
+
# ... your AWS deploy commands
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Conditional Dev Tools
|
|
148
|
+
|
|
149
|
+
Hide sensitive tools from clients:
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
const dashboardConfig = {
|
|
153
|
+
widgets: [
|
|
154
|
+
// Always visible
|
|
155
|
+
{ name: 'deploy', component: DeployButton, options: {...} },
|
|
156
|
+
|
|
157
|
+
// Dev only
|
|
158
|
+
...(isDevelopment() ? [
|
|
159
|
+
{ name: 'token-config', component: GitHubTokenConfig },
|
|
160
|
+
{ name: 'vision', component: VisionTool },
|
|
161
|
+
{ name: 'debug-panel', component: DebugPanel }
|
|
162
|
+
] : [])
|
|
163
|
+
]
|
|
164
|
+
};
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Environment Detection
|
|
168
|
+
|
|
169
|
+
The `isDevelopment()` function checks:
|
|
170
|
+
1. `window.location.hostname` for localhost/127.0.0.1/192.168.x.x
|
|
171
|
+
2. `process.env.NODE_ENV === 'development'`
|
|
172
|
+
|
|
173
|
+
This means:
|
|
174
|
+
- ✅ Local dev: widgets visible
|
|
175
|
+
- ❌ Production deploy: widgets hidden
|
|
176
|
+
- ❌ Client access: widgets hidden
|
|
177
|
+
|
|
178
|
+
## Security Notes
|
|
179
|
+
|
|
180
|
+
- **Never commit tokens** to your repository
|
|
181
|
+
- Use Sanity Studio Secrets to store tokens securely
|
|
182
|
+
- Tokens are stored in your Sanity project dataset
|
|
183
|
+
- Each user can configure their own token in local dev
|
|
184
|
+
- Production deployments should use GitHub Actions secrets, not personal tokens
|
|
185
|
+
|
|
186
|
+
## Troubleshooting
|
|
187
|
+
|
|
188
|
+
**"Could not find workflow run"**
|
|
189
|
+
- Check that your workflow file exists
|
|
190
|
+
- Verify the `eventType` matches your workflow's `repository_dispatch.types`
|
|
191
|
+
- Ensure the token has correct permissions
|
|
192
|
+
|
|
193
|
+
**"GitHub API delays"**
|
|
194
|
+
- The GitHub Actions API can lag 10-30 seconds behind the UI
|
|
195
|
+
- This is normal and expected
|
|
196
|
+
- The widget polls every 3 seconds for updates
|
|
197
|
+
|
|
198
|
+
**"CORS errors"**
|
|
199
|
+
- Make sure you're not adding custom cache headers
|
|
200
|
+
- GitHub's API has strict CORS policies
|
|
201
|
+
- The widget handles this correctly
|
|
202
|
+
|
|
203
|
+
## License
|
|
204
|
+
|
|
205
|
+
MIT
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fil's Hosted Deploy Button
|
|
3
|
+
* Dependencies: React, @sanity/ui, @sanity/icons, @sanity/studio-secrets
|
|
4
|
+
* Recommended to use inside the dashboard
|
|
5
|
+
* Uses studio secrets to store Github token
|
|
6
|
+
*/
|
|
7
|
+
interface DeployButtonConfig {
|
|
8
|
+
owner: string;
|
|
9
|
+
repo: string;
|
|
10
|
+
token?: string;
|
|
11
|
+
branch?: string;
|
|
12
|
+
eventType?: string;
|
|
13
|
+
}
|
|
14
|
+
interface DeployButtonProps {
|
|
15
|
+
config: DeployButtonConfig;
|
|
16
|
+
title?: string;
|
|
17
|
+
deploymentUrl?: string;
|
|
18
|
+
environment?: string;
|
|
19
|
+
secretsNamespace?: string;
|
|
20
|
+
secretKey?: string;
|
|
21
|
+
}
|
|
22
|
+
declare const DeployButton: ({ config, title, deploymentUrl, environment, secretsNamespace, secretKey }: DeployButtonProps) => import("react/jsx-runtime").JSX.Element;
|
|
23
|
+
export default DeployButton;
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect, useRef } from 'react';
|
|
3
|
+
import { Button, Card, Stack, Text, Spinner, Flex } from '@sanity/ui';
|
|
4
|
+
import { PlayIcon, CheckmarkIcon, CloseIcon } from '@sanity/icons';
|
|
5
|
+
import { useSecrets, SettingsView } from '@sanity/studio-secrets';
|
|
6
|
+
const DeployButton = ({ config, title = "🚀 Site Deployment", deploymentUrl, environment = "production", secretsNamespace = "deployButton", secretKey = "github_token" }) => {
|
|
7
|
+
// ... rest of code
|
|
8
|
+
// ALL HOOKS MUST BE CALLED FIRST - NEVER CONDITIONALLY
|
|
9
|
+
const [deployState, setDeployState] = useState('idle');
|
|
10
|
+
const [message, setMessage] = useState('');
|
|
11
|
+
const [showSettings, setShowSettings] = useState(false);
|
|
12
|
+
const intervalRef = useRef(null);
|
|
13
|
+
const monitoringRef = useRef(false);
|
|
14
|
+
// Use Sanity secrets - ALWAYS call this hook
|
|
15
|
+
const { secrets } = useSecrets(secretsNamespace);
|
|
16
|
+
const token = secrets?.[secretKey];
|
|
17
|
+
// Get the effective token (secrets take priority over config)
|
|
18
|
+
const effectiveToken = token || config?.token;
|
|
19
|
+
// Only show settings if we don't have a token AND we have valid config
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!effectiveToken && config?.owner && config?.repo && !showSettings) {
|
|
22
|
+
setShowSettings(true);
|
|
23
|
+
}
|
|
24
|
+
else if (effectiveToken && showSettings) {
|
|
25
|
+
// Close settings if we now have a token
|
|
26
|
+
setShowSettings(false);
|
|
27
|
+
}
|
|
28
|
+
}, [effectiveToken, config?.owner, config?.repo, showSettings]);
|
|
29
|
+
// Clean up on unmount
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
return () => {
|
|
32
|
+
if (intervalRef.current) {
|
|
33
|
+
clearInterval(intervalRef.current);
|
|
34
|
+
intervalRef.current = null;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}, []);
|
|
38
|
+
// Plugin config for secrets
|
|
39
|
+
const pluginConfigKeys = [
|
|
40
|
+
{
|
|
41
|
+
key: secretKey,
|
|
42
|
+
title: `GitHub Token for ${environment}`,
|
|
43
|
+
description: `Personal access token for ${config?.owner}/${config?.repo} deployment`,
|
|
44
|
+
type: 'string',
|
|
45
|
+
inputType: 'password'
|
|
46
|
+
}
|
|
47
|
+
];
|
|
48
|
+
// Poll workflow status
|
|
49
|
+
const pollWorkflowStatus = async (runId) => {
|
|
50
|
+
try {
|
|
51
|
+
console.log('Polling workflow:', runId);
|
|
52
|
+
const cacheBuster = `_=${Date.now()}`;
|
|
53
|
+
const response = await fetch(`https://api.github.com/repos/${config.owner}/${config.repo}/actions/runs/${runId}?${cacheBuster}`, {
|
|
54
|
+
headers: {
|
|
55
|
+
'Authorization': `token ${effectiveToken}`,
|
|
56
|
+
'Accept': 'application/vnd.github.v3+json'
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
throw new Error(`HTTP ${response.status}`);
|
|
61
|
+
}
|
|
62
|
+
const run = await response.json();
|
|
63
|
+
console.log('Run status:', run.status, 'conclusion:', run.conclusion);
|
|
64
|
+
if (run.status === 'completed') {
|
|
65
|
+
// Stop polling
|
|
66
|
+
if (intervalRef.current) {
|
|
67
|
+
clearInterval(intervalRef.current);
|
|
68
|
+
intervalRef.current = null;
|
|
69
|
+
}
|
|
70
|
+
monitoringRef.current = false;
|
|
71
|
+
if (run.conclusion === 'success') {
|
|
72
|
+
setDeployState('success');
|
|
73
|
+
setMessage('🎉 Deployment completed successfully!');
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
setDeployState('error');
|
|
77
|
+
setMessage(`❌ Deployment failed: ${run.conclusion}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else if (run.status === 'in_progress' || run.status === 'queued') {
|
|
81
|
+
const startTime = new Date(run.run_started_at || run.created_at);
|
|
82
|
+
const elapsed = Math.floor((Date.now() - startTime.getTime()) / 1000);
|
|
83
|
+
setDeployState('deploying');
|
|
84
|
+
setMessage(`🔄 Deployment running... (${elapsed}s)`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
console.error('Polling error:', error);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
// Find the workflow run with retry logic
|
|
92
|
+
const findWorkflowRun = async (retryCount = 0) => {
|
|
93
|
+
const maxRetries = 6; // Try for 30 seconds (5s * 6)
|
|
94
|
+
try {
|
|
95
|
+
console.log(`Looking for workflow run... (attempt ${retryCount + 1}/${maxRetries})`);
|
|
96
|
+
const cacheBuster = `_=${Date.now()}`;
|
|
97
|
+
const response = await fetch(`https://api.github.com/repos/${config.owner}/${config.repo}/actions/runs?per_page=10&${cacheBuster}`, {
|
|
98
|
+
headers: {
|
|
99
|
+
'Authorization': `token ${effectiveToken}`,
|
|
100
|
+
'Accept': 'application/vnd.github.v3+json'
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
if (!response.ok) {
|
|
104
|
+
throw new Error(`HTTP ${response.status}`);
|
|
105
|
+
}
|
|
106
|
+
const data = await response.json();
|
|
107
|
+
console.log('Found runs:', data.workflow_runs.length);
|
|
108
|
+
// Find the most recent run (within last 90 seconds)
|
|
109
|
+
const recentRun = data.workflow_runs.find(run => {
|
|
110
|
+
const ageInSeconds = (Date.now() - new Date(run.created_at).getTime()) / 1000;
|
|
111
|
+
console.log(`Run ${run.id}: age ${ageInSeconds}s, event: ${run.event}, status: ${run.status}`);
|
|
112
|
+
return ageInSeconds < 90;
|
|
113
|
+
});
|
|
114
|
+
if (recentRun) {
|
|
115
|
+
console.log('Found recent run:', recentRun.id, 'Event:', recentRun.event);
|
|
116
|
+
setDeployState('deploying');
|
|
117
|
+
setMessage('🚀 Deployment started...');
|
|
118
|
+
// Start polling every 3 seconds (faster response)
|
|
119
|
+
intervalRef.current = setInterval(() => {
|
|
120
|
+
pollWorkflowStatus(recentRun.id);
|
|
121
|
+
}, 3000);
|
|
122
|
+
// Initial poll
|
|
123
|
+
pollWorkflowStatus(recentRun.id);
|
|
124
|
+
}
|
|
125
|
+
else if (retryCount < maxRetries) {
|
|
126
|
+
// Retry after 5 seconds
|
|
127
|
+
console.log(`Workflow not found yet, retrying in 5s... (${retryCount + 1}/${maxRetries})`);
|
|
128
|
+
setMessage(`⏳ Waiting for workflow to start... (${retryCount + 1}/${maxRetries})`);
|
|
129
|
+
setTimeout(() => {
|
|
130
|
+
findWorkflowRun(retryCount + 1);
|
|
131
|
+
}, 5000);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
console.log('No recent workflow found after retries');
|
|
135
|
+
setDeployState('error');
|
|
136
|
+
setMessage('❌ Could not find workflow run - check if workflow exists');
|
|
137
|
+
monitoringRef.current = false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
console.error('Error finding workflow:', error);
|
|
142
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
143
|
+
if (retryCount < maxRetries) {
|
|
144
|
+
console.log(`Error finding workflow, retrying... (${retryCount + 1}/${maxRetries})`);
|
|
145
|
+
setTimeout(() => {
|
|
146
|
+
findWorkflowRun(retryCount + 1);
|
|
147
|
+
}, 5000);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
setDeployState('error');
|
|
151
|
+
setMessage(`❌ Error finding workflow: ${errorMessage}`);
|
|
152
|
+
monitoringRef.current = false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
const triggerDeploy = async () => {
|
|
157
|
+
// Prevent multiple deployments
|
|
158
|
+
if (deployState !== 'idle' && deployState !== 'success' && deployState !== 'error')
|
|
159
|
+
return;
|
|
160
|
+
setDeployState('triggering');
|
|
161
|
+
setMessage('⚡ Triggering deployment...');
|
|
162
|
+
try {
|
|
163
|
+
console.log('Triggering deployment...');
|
|
164
|
+
const response = await fetch(`https://api.github.com/repos/${config.owner}/${config.repo}/dispatches`, {
|
|
165
|
+
method: 'POST',
|
|
166
|
+
headers: {
|
|
167
|
+
'Authorization': `token ${effectiveToken}`,
|
|
168
|
+
'Accept': 'application/vnd.github.v3+json',
|
|
169
|
+
'Content-Type': 'application/json'
|
|
170
|
+
},
|
|
171
|
+
body: JSON.stringify({
|
|
172
|
+
event_type: config.eventType || 'deploy-site',
|
|
173
|
+
client_payload: {
|
|
174
|
+
environment: environment,
|
|
175
|
+
triggered_by: 'sanity_studio',
|
|
176
|
+
timestamp: new Date().toISOString()
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
});
|
|
180
|
+
if (response.ok) {
|
|
181
|
+
console.log('Deployment triggered successfully');
|
|
182
|
+
setMessage('✅ Deployment triggered! Looking for workflow...');
|
|
183
|
+
// Wait 5 seconds then look for the workflow
|
|
184
|
+
setTimeout(() => {
|
|
185
|
+
if (!monitoringRef.current) {
|
|
186
|
+
monitoringRef.current = true;
|
|
187
|
+
findWorkflowRun();
|
|
188
|
+
}
|
|
189
|
+
}, 5000);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
const error = await response.json();
|
|
193
|
+
throw new Error(error.message || `HTTP ${response.status}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
console.error('Deploy trigger failed:', error);
|
|
198
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
199
|
+
setDeployState('error');
|
|
200
|
+
setMessage(`❌ Failed to trigger: ${errorMessage}`);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
const getButtonProps = () => {
|
|
204
|
+
switch (deployState) {
|
|
205
|
+
case 'triggering':
|
|
206
|
+
return { tone: 'primary', disabled: true, icon: Spinner, text: 'Triggering...' };
|
|
207
|
+
case 'deploying':
|
|
208
|
+
return { tone: 'primary', disabled: true, icon: Spinner, text: 'Deploying...' };
|
|
209
|
+
case 'success':
|
|
210
|
+
return { tone: 'positive', disabled: false, icon: CheckmarkIcon, text: 'Deploy Again' };
|
|
211
|
+
case 'error':
|
|
212
|
+
return { tone: 'critical', disabled: false, icon: CloseIcon, text: 'Try Again' };
|
|
213
|
+
default:
|
|
214
|
+
return { tone: 'primary', disabled: false, icon: PlayIcon, text: 'Deploy Site' };
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
// NOW WE CAN HANDLE CONDITIONAL RENDERING AFTER ALL HOOKS
|
|
218
|
+
// Check for missing config
|
|
219
|
+
if (!config || !config.owner || !config.repo) {
|
|
220
|
+
return (_jsx(Card, { padding: 4, radius: 2, shadow: 1, tone: "critical", children: _jsxs(Stack, { space: 3, children: [_jsx(Text, { size: 2, weight: "semibold", children: "\u274C Deploy Button Configuration Error" }), _jsx(Text, { size: 1, children: "Missing required config: owner and repo are required" })] }) }));
|
|
221
|
+
}
|
|
222
|
+
// If showing settings, render the settings view
|
|
223
|
+
if (showSettings && !effectiveToken) {
|
|
224
|
+
return (_jsx(Card, { padding: 4, radius: 2, shadow: 1, children: _jsxs(Stack, { space: 3, children: [_jsxs(Text, { size: 2, weight: "semibold", children: ["\uD83D\uDD10 ", title, " - Setup Required"] }), _jsx(Text, { size: 1, muted: true, children: "Please configure your GitHub token to enable deployments." }), _jsx(SettingsView, { title: `${title} Configuration`, namespace: secretsNamespace, keys: pluginConfigKeys, onClose: () => {
|
|
225
|
+
setShowSettings(false);
|
|
226
|
+
} })] }) }));
|
|
227
|
+
}
|
|
228
|
+
// Check for missing token after settings
|
|
229
|
+
if (!effectiveToken) {
|
|
230
|
+
return (_jsx(Card, { padding: 4, radius: 2, shadow: 1, tone: "critical", children: _jsxs(Stack, { space: 3, children: [_jsx(Text, { size: 2, weight: "semibold", children: "\u274C Authentication Required" }), _jsx(Text, { size: 1, children: "No GitHub token configured." }), _jsx(Button, { tone: "primary", mode: "ghost", size: 1, onClick: () => setShowSettings(true), children: "Configure Token" })] }) }));
|
|
231
|
+
}
|
|
232
|
+
const buttonProps = getButtonProps();
|
|
233
|
+
return (_jsx(Card, { padding: 4, radius: 2, shadow: 1, children: _jsxs(Stack, { space: 3, children: [_jsx(Text, { size: 2, weight: "semibold", children: title }), _jsx(Flex, { justify: "flex-start", children: _jsx(Button, { tone: buttonProps.tone, disabled: buttonProps.disabled, onClick: triggerDeploy, size: 3, style: {
|
|
234
|
+
width: '100%',
|
|
235
|
+
maxWidth: '360px',
|
|
236
|
+
}, children: _jsxs(Flex, { align: "center", justify: "center", style: {
|
|
237
|
+
gap: '6px',
|
|
238
|
+
padding: '16px 0px 16px 0px'
|
|
239
|
+
}, children: [buttonProps.icon && (_jsx("span", { style: {
|
|
240
|
+
display: 'flex',
|
|
241
|
+
alignItems: 'center',
|
|
242
|
+
fontSize: '16px',
|
|
243
|
+
lineHeight: '1'
|
|
244
|
+
}, children: _jsx(buttonProps.icon, {}) })), _jsx(Text, { size: 2, weight: "medium", style: { lineHeight: '1' }, children: buttonProps.text })] }) }) }), message && (_jsx(Card, { padding: 3, radius: 1, tone: deployState === 'error' ? 'critical' : deployState === 'success' ? 'positive' : 'primary', children: _jsx(Text, { size: 1, children: message }) })), deploymentUrl && deployState === 'success' && (_jsx(Button, { as: "a", href: deploymentUrl, target: "_blank", rel: "noopener noreferrer", tone: "primary", mode: "ghost", size: 3, children: "\uD83C\uDF10 View Live Site" })), _jsxs(Text, { size: 1, muted: true, children: ["Deploys from ", config.branch || 'main', " branch to ", environment] })] }) }));
|
|
245
|
+
};
|
|
246
|
+
export default DeployButton;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment Detection Utilities
|
|
3
|
+
* Helper functions to determine if we're in development mode
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Check if running in local development
|
|
7
|
+
* @returns true if in development, false otherwise
|
|
8
|
+
*/
|
|
9
|
+
export declare const isDevelopment: () => boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Check if running in production
|
|
12
|
+
* @returns true if in production, false otherwise
|
|
13
|
+
*/
|
|
14
|
+
export declare const isProduction: () => boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Conditionally render component only in development
|
|
17
|
+
* Usage: withDevOnly(MyComponent)
|
|
18
|
+
*/
|
|
19
|
+
export declare const withDevOnly: <P extends object>(Component: React.ComponentType<P>) => React.FC<P>;
|
|
20
|
+
/**
|
|
21
|
+
* Conditionally render component only in production
|
|
22
|
+
* Usage: withProdOnly(MyComponent)
|
|
23
|
+
*/
|
|
24
|
+
export declare const withProdOnly: <P extends object>(Component: React.ComponentType<P>) => React.FC<P>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Environment Detection Utilities
|
|
4
|
+
* Helper functions to determine if we're in development mode
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Check if running in local development
|
|
8
|
+
* @returns true if in development, false otherwise
|
|
9
|
+
*/
|
|
10
|
+
export const isDevelopment = () => {
|
|
11
|
+
// Check for localhost
|
|
12
|
+
if (typeof window !== 'undefined') {
|
|
13
|
+
const hostname = window.location.hostname;
|
|
14
|
+
return hostname === 'localhost' || hostname === '127.0.0.1' || hostname.startsWith('192.168.');
|
|
15
|
+
}
|
|
16
|
+
// Fallback to NODE_ENV if available
|
|
17
|
+
return process.env.NODE_ENV === 'development';
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Check if running in production
|
|
21
|
+
* @returns true if in production, false otherwise
|
|
22
|
+
*/
|
|
23
|
+
export const isProduction = () => {
|
|
24
|
+
return !isDevelopment();
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Conditionally render component only in development
|
|
28
|
+
* Usage: withDevOnly(MyComponent)
|
|
29
|
+
*/
|
|
30
|
+
export const withDevOnly = (Component) => {
|
|
31
|
+
return (props) => {
|
|
32
|
+
if (!isDevelopment()) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
return _jsx(Component, { ...props });
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Conditionally render component only in production
|
|
40
|
+
* Usage: withProdOnly(MyComponent)
|
|
41
|
+
*/
|
|
42
|
+
export const withProdOnly = (Component) => {
|
|
43
|
+
return (props) => {
|
|
44
|
+
if (!isProduction()) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return _jsx(Component, { ...props });
|
|
48
|
+
};
|
|
49
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Token Configuration Widget
|
|
3
|
+
* Only show this in local development environment
|
|
4
|
+
* Dependencies: React, @sanity/ui, @sanity/icons, @sanity/studio-secrets
|
|
5
|
+
*/
|
|
6
|
+
interface TokenConfigProps {
|
|
7
|
+
title?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
secretsNamespace?: string;
|
|
10
|
+
secretKey?: string;
|
|
11
|
+
environment?: string;
|
|
12
|
+
}
|
|
13
|
+
declare const GitHubTokenConfig: ({ title, description, secretsNamespace, secretKey, environment }: TokenConfigProps) => import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
export default GitHubTokenConfig;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Card, Stack, Text, Button } from '@sanity/ui';
|
|
4
|
+
import { useSecrets, SettingsView } from '@sanity/studio-secrets';
|
|
5
|
+
import { CogIcon } from '@sanity/icons';
|
|
6
|
+
const GitHubTokenConfig = ({ title = "🔐 GitHub Token Configuration", description = "Configure your GitHub personal access token for deployment automation.", secretsNamespace = "deployButton", secretKey = "github_token", environment = "production" }) => {
|
|
7
|
+
const [showSettings, setShowSettings] = useState(false);
|
|
8
|
+
// Use Sanity secrets to check if token exists
|
|
9
|
+
const { secrets } = useSecrets(secretsNamespace);
|
|
10
|
+
const token = secrets?.[secretKey];
|
|
11
|
+
const hasToken = Boolean(token);
|
|
12
|
+
// Plugin config for secrets
|
|
13
|
+
const pluginConfigKeys = [
|
|
14
|
+
{
|
|
15
|
+
key: secretKey,
|
|
16
|
+
title: `GitHub Token for ${environment}`,
|
|
17
|
+
description: `Personal access token for GitHub Actions deployment`,
|
|
18
|
+
type: 'string',
|
|
19
|
+
inputType: 'password'
|
|
20
|
+
}
|
|
21
|
+
];
|
|
22
|
+
if (showSettings) {
|
|
23
|
+
return (_jsx(Card, { padding: 4, radius: 2, shadow: 1, children: _jsxs(Stack, { space: 3, children: [_jsx(Text, { size: 2, weight: "semibold", children: title }), _jsx(SettingsView, { title: title, namespace: secretsNamespace, keys: pluginConfigKeys, onClose: () => {
|
|
24
|
+
setShowSettings(false);
|
|
25
|
+
} })] }) }));
|
|
26
|
+
}
|
|
27
|
+
return (_jsx(Card, { padding: 4, radius: 2, shadow: 1, children: _jsxs(Stack, { space: 3, children: [_jsx(Text, { size: 2, weight: "semibold", children: title }), _jsx(Text, { size: 1, muted: true, children: description }), _jsxs(Stack, { space: 2, children: [_jsxs(Text, { size: 1, children: ["Status: ", hasToken ? '✅ Token configured' : '⚠️ No token configured'] }), _jsx(Button, { tone: hasToken ? 'default' : 'primary', icon: CogIcon, onClick: () => setShowSettings(true), text: hasToken ? 'Update Token' : 'Configure Token' })] })] }) }));
|
|
28
|
+
};
|
|
29
|
+
export default GitHubTokenConfig;
|
|
@@ -52,7 +52,7 @@ export declare const VideoFileNoThumb: {
|
|
|
52
52
|
};
|
|
53
53
|
export declare const VideoURLNoThumb: {
|
|
54
54
|
type: "object";
|
|
55
|
-
name: "
|
|
55
|
+
name: "videoURLNoThumb";
|
|
56
56
|
} & Omit<import("sanity").ObjectDefinition, "preview"> & {
|
|
57
57
|
preview?: import("sanity").PreviewConfig<Record<string, string>, Record<never, any>> | undefined;
|
|
58
58
|
};
|
package/lib/main.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export * from './components/core/SEOImage';
|
|
2
2
|
export * from './components/core/SEO';
|
|
3
3
|
export * from './validators/utils';
|
|
4
|
-
export * from './components/
|
|
4
|
+
export * from './components/deploy/EnvUtils';
|
|
5
|
+
export * from './components/deploy/DeployButton';
|
|
6
|
+
export * from './components/deploy/GithubTokenConfig';
|
|
5
7
|
export * from './components/video/VideoSchemas';
|
package/lib/main.js
CHANGED
|
@@ -2,5 +2,7 @@ export * from './components/core/SEOImage';
|
|
|
2
2
|
export * from './components/core/SEO';
|
|
3
3
|
// export * from './config/utils';
|
|
4
4
|
export * from './validators/utils';
|
|
5
|
-
export * from './components/
|
|
5
|
+
export * from './components/deploy/EnvUtils';
|
|
6
|
+
export * from './components/deploy/DeployButton';
|
|
7
|
+
export * from './components/deploy/GithubTokenConfig';
|
|
6
8
|
export * from './components/video/VideoSchemas';
|
package/package.json
CHANGED
|
@@ -102,8 +102,9 @@ const DeployButton = ({
|
|
|
102
102
|
try {
|
|
103
103
|
console.log('Polling workflow:', runId);
|
|
104
104
|
|
|
105
|
+
const cacheBuster = `_=${Date.now()}`;
|
|
105
106
|
const response = await fetch(
|
|
106
|
-
`https://api.github.com/repos/${config.owner}/${config.repo}/actions/runs/${runId}`,
|
|
107
|
+
`https://api.github.com/repos/${config.owner}/${config.repo}/actions/runs/${runId}?${cacheBuster}`,
|
|
107
108
|
{
|
|
108
109
|
headers: {
|
|
109
110
|
'Authorization': `token ${effectiveToken}`,
|
|
@@ -145,13 +146,16 @@ const DeployButton = ({
|
|
|
145
146
|
}
|
|
146
147
|
};
|
|
147
148
|
|
|
148
|
-
// Find the workflow run
|
|
149
|
-
const findWorkflowRun = async (): Promise<void> => {
|
|
149
|
+
// Find the workflow run with retry logic
|
|
150
|
+
const findWorkflowRun = async (retryCount = 0): Promise<void> => {
|
|
151
|
+
const maxRetries = 6; // Try for 30 seconds (5s * 6)
|
|
152
|
+
|
|
150
153
|
try {
|
|
151
|
-
console.log(
|
|
154
|
+
console.log(`Looking for workflow run... (attempt ${retryCount + 1}/${maxRetries})`);
|
|
152
155
|
|
|
156
|
+
const cacheBuster = `_=${Date.now()}`;
|
|
153
157
|
const response = await fetch(
|
|
154
|
-
`https://api.github.com/repos/${config.owner}/${config.repo}/actions/runs?per_page=10`,
|
|
158
|
+
`https://api.github.com/repos/${config.owner}/${config.repo}/actions/runs?per_page=10&${cacheBuster}`,
|
|
155
159
|
{
|
|
156
160
|
headers: {
|
|
157
161
|
'Authorization': `token ${effectiveToken}`,
|
|
@@ -179,23 +183,42 @@ const DeployButton = ({
|
|
|
179
183
|
setDeployState('deploying');
|
|
180
184
|
setMessage('🚀 Deployment started...');
|
|
181
185
|
|
|
182
|
-
// Start polling every
|
|
186
|
+
// Start polling every 3 seconds (faster response)
|
|
183
187
|
intervalRef.current = setInterval(() => {
|
|
184
188
|
pollWorkflowStatus(recentRun.id);
|
|
185
|
-
},
|
|
189
|
+
}, 3000);
|
|
186
190
|
|
|
187
191
|
// Initial poll
|
|
188
192
|
pollWorkflowStatus(recentRun.id);
|
|
193
|
+
|
|
194
|
+
} else if (retryCount < maxRetries) {
|
|
195
|
+
// Retry after 5 seconds
|
|
196
|
+
console.log(`Workflow not found yet, retrying in 5s... (${retryCount + 1}/${maxRetries})`);
|
|
197
|
+
setMessage(`⏳ Waiting for workflow to start... (${retryCount + 1}/${maxRetries})`);
|
|
198
|
+
setTimeout(() => {
|
|
199
|
+
findWorkflowRun(retryCount + 1);
|
|
200
|
+
}, 5000);
|
|
201
|
+
|
|
189
202
|
} else {
|
|
190
|
-
console.log('No recent workflow found');
|
|
203
|
+
console.log('No recent workflow found after retries');
|
|
191
204
|
setDeployState('error');
|
|
192
205
|
setMessage('❌ Could not find workflow run - check if workflow exists');
|
|
206
|
+
monitoringRef.current = false;
|
|
193
207
|
}
|
|
194
208
|
} catch (error) {
|
|
195
209
|
console.error('Error finding workflow:', error);
|
|
196
210
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
197
|
-
|
|
198
|
-
|
|
211
|
+
|
|
212
|
+
if (retryCount < maxRetries) {
|
|
213
|
+
console.log(`Error finding workflow, retrying... (${retryCount + 1}/${maxRetries})`);
|
|
214
|
+
setTimeout(() => {
|
|
215
|
+
findWorkflowRun(retryCount + 1);
|
|
216
|
+
}, 5000);
|
|
217
|
+
} else {
|
|
218
|
+
setDeployState('error');
|
|
219
|
+
setMessage(`❌ Error finding workflow: ${errorMessage}`);
|
|
220
|
+
monitoringRef.current = false;
|
|
221
|
+
}
|
|
199
222
|
}
|
|
200
223
|
};
|
|
201
224
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment Detection Utilities
|
|
3
|
+
* Helper functions to determine if we're in development mode
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Check if running in local development
|
|
8
|
+
* @returns true if in development, false otherwise
|
|
9
|
+
*/
|
|
10
|
+
export const isDevelopment = (): boolean => {
|
|
11
|
+
// Check for localhost
|
|
12
|
+
if (typeof window !== 'undefined') {
|
|
13
|
+
const hostname = window.location.hostname;
|
|
14
|
+
return hostname === 'localhost' || hostname === '127.0.0.1' || hostname.startsWith('192.168.');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Fallback to NODE_ENV if available
|
|
18
|
+
return process.env.NODE_ENV === 'development';
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Check if running in production
|
|
23
|
+
* @returns true if in production, false otherwise
|
|
24
|
+
*/
|
|
25
|
+
export const isProduction = (): boolean => {
|
|
26
|
+
return !isDevelopment();
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Conditionally render component only in development
|
|
31
|
+
* Usage: withDevOnly(MyComponent)
|
|
32
|
+
*/
|
|
33
|
+
export const withDevOnly = <P extends object>(
|
|
34
|
+
Component: React.ComponentType<P>
|
|
35
|
+
): React.FC<P> => {
|
|
36
|
+
return (props: P) => {
|
|
37
|
+
if (!isDevelopment()) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return <Component {...props} />;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Conditionally render component only in production
|
|
46
|
+
* Usage: withProdOnly(MyComponent)
|
|
47
|
+
*/
|
|
48
|
+
export const withProdOnly = <P extends object>(
|
|
49
|
+
Component: React.ComponentType<P>
|
|
50
|
+
): React.FC<P> => {
|
|
51
|
+
return (props: P) => {
|
|
52
|
+
if (!isProduction()) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return <Component {...props} />;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Token Configuration Widget
|
|
3
|
+
* Only show this in local development environment
|
|
4
|
+
* Dependencies: React, @sanity/ui, @sanity/icons, @sanity/studio-secrets
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as React from 'react';
|
|
8
|
+
import { useState } from 'react';
|
|
9
|
+
import { Card, Stack, Text, Button } from '@sanity/ui';
|
|
10
|
+
import { useSecrets, SettingsView } from '@sanity/studio-secrets';
|
|
11
|
+
import { CogIcon } from '@sanity/icons';
|
|
12
|
+
|
|
13
|
+
interface TokenConfigProps {
|
|
14
|
+
title?: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
secretsNamespace?: string;
|
|
17
|
+
secretKey?: string;
|
|
18
|
+
environment?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const GitHubTokenConfig = ({
|
|
22
|
+
title = "🔐 GitHub Token Configuration",
|
|
23
|
+
description = "Configure your GitHub personal access token for deployment automation.",
|
|
24
|
+
secretsNamespace = "deployButton",
|
|
25
|
+
secretKey = "github_token",
|
|
26
|
+
environment = "production"
|
|
27
|
+
}: TokenConfigProps) => {
|
|
28
|
+
const [showSettings, setShowSettings] = useState(false);
|
|
29
|
+
|
|
30
|
+
// Use Sanity secrets to check if token exists
|
|
31
|
+
const { secrets } = useSecrets(secretsNamespace);
|
|
32
|
+
const token = (secrets as Record<string, string | undefined>)?.[secretKey];
|
|
33
|
+
const hasToken = Boolean(token);
|
|
34
|
+
|
|
35
|
+
// Plugin config for secrets
|
|
36
|
+
const pluginConfigKeys = [
|
|
37
|
+
{
|
|
38
|
+
key: secretKey,
|
|
39
|
+
title: `GitHub Token for ${environment}`,
|
|
40
|
+
description: `Personal access token for GitHub Actions deployment`,
|
|
41
|
+
type: 'string' as const,
|
|
42
|
+
inputType: 'password' as const
|
|
43
|
+
}
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
if (showSettings) {
|
|
47
|
+
return (
|
|
48
|
+
<Card padding={4} radius={2} shadow={1}>
|
|
49
|
+
<Stack space={3}>
|
|
50
|
+
<Text size={2} weight="semibold">
|
|
51
|
+
{title}
|
|
52
|
+
</Text>
|
|
53
|
+
<SettingsView
|
|
54
|
+
title={title}
|
|
55
|
+
namespace={secretsNamespace}
|
|
56
|
+
keys={pluginConfigKeys}
|
|
57
|
+
onClose={() => {
|
|
58
|
+
setShowSettings(false);
|
|
59
|
+
}}
|
|
60
|
+
/>
|
|
61
|
+
</Stack>
|
|
62
|
+
</Card>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<Card padding={4} radius={2} shadow={1}>
|
|
68
|
+
<Stack space={3}>
|
|
69
|
+
<Text size={2} weight="semibold">
|
|
70
|
+
{title}
|
|
71
|
+
</Text>
|
|
72
|
+
<Text size={1} muted>
|
|
73
|
+
{description}
|
|
74
|
+
</Text>
|
|
75
|
+
|
|
76
|
+
<Stack space={2}>
|
|
77
|
+
<Text size={1}>
|
|
78
|
+
Status: {hasToken ? '✅ Token configured' : '⚠️ No token configured'}
|
|
79
|
+
</Text>
|
|
80
|
+
|
|
81
|
+
<Button
|
|
82
|
+
tone={hasToken ? 'default' : 'primary'}
|
|
83
|
+
icon={CogIcon}
|
|
84
|
+
onClick={() => setShowSettings(true)}
|
|
85
|
+
text={hasToken ? 'Update Token' : 'Configure Token'}
|
|
86
|
+
/>
|
|
87
|
+
</Stack>
|
|
88
|
+
</Stack>
|
|
89
|
+
</Card>
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export default GitHubTokenConfig;
|
package/src/main.ts
CHANGED
|
@@ -2,5 +2,7 @@ export * from './components/core/SEOImage';
|
|
|
2
2
|
export * from './components/core/SEO';
|
|
3
3
|
// export * from './config/utils';
|
|
4
4
|
export * from './validators/utils';
|
|
5
|
-
export * from './components/
|
|
5
|
+
export * from './components/deploy/EnvUtils';
|
|
6
|
+
export * from './components/deploy/DeployButton';
|
|
7
|
+
export * from './components/deploy/GithubTokenConfig';
|
|
6
8
|
export * from './components/video/VideoSchemas';
|