@quiltdata/benchling-webhook 0.4.13
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/AGENTS.md +226 -0
- package/CHANGELOG.md +91 -0
- package/LICENSE +201 -0
- package/README.benchling.md +77 -0
- package/README.md +53 -0
- package/bin/benchling-webhook.ts +172 -0
- package/bin/check-logs.js +231 -0
- package/bin/cli-auth.sh +74 -0
- package/bin/get-env.js +564 -0
- package/bin/publish-manual.js +211 -0
- package/bin/release-notes.sh +82 -0
- package/bin/release.js +118 -0
- package/bin/send-event.js +203 -0
- package/bin/sync-version.js +72 -0
- package/bin/test-invalid-signature.js +125 -0
- package/bin/version.js +178 -0
- package/cdk.context.json +58 -0
- package/cdk.json +85 -0
- package/doc/NPM_OIDC_SETUP.md +95 -0
- package/doc/PARAMETERS.md +203 -0
- package/doc/RELEASE.md +297 -0
- package/doc/RELEASE_NOTES.md +64 -0
- package/env.template +67 -0
- package/jest.config.js +14 -0
- package/lib/README.md +50 -0
- package/lib/index.ts +31 -0
- package/lib/oauth-tester.json +35 -0
- package/package.json +79 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Manual NPM publish script using access token
|
|
5
|
+
*
|
|
6
|
+
* This script allows manual publishing to npmjs.org using an NPM access token.
|
|
7
|
+
* It's useful for:
|
|
8
|
+
* - Local testing of the publish process
|
|
9
|
+
* - Manual releases when CI/CD is unavailable
|
|
10
|
+
* - Emergency hotfix releases
|
|
11
|
+
*
|
|
12
|
+
* Prerequisites:
|
|
13
|
+
* 1. You must have an NPM access token with publish permissions
|
|
14
|
+
* 2. Set the token as environment variable: NPM_TOKEN=your_token_here
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* NPM_TOKEN=your_token npm run publish:manual
|
|
18
|
+
* NPM_TOKEN=your_token npm run publish:manual -- --dry-run
|
|
19
|
+
* NPM_TOKEN=your_token npm run publish:manual -- --tag beta
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const fs = require('fs');
|
|
23
|
+
const path = require('path');
|
|
24
|
+
const { execSync } = require('child_process');
|
|
25
|
+
|
|
26
|
+
const NPMRC_PATH = path.join(__dirname, '..', '.npmrc');
|
|
27
|
+
const NPMRC_BACKUP_PATH = path.join(__dirname, '..', '.npmrc.backup');
|
|
28
|
+
|
|
29
|
+
function validateToken() {
|
|
30
|
+
const token = process.env.NPM_TOKEN;
|
|
31
|
+
|
|
32
|
+
if (!token) {
|
|
33
|
+
console.error('❌ Error: NPM_TOKEN environment variable is not set');
|
|
34
|
+
console.error('');
|
|
35
|
+
console.error('Usage:');
|
|
36
|
+
console.error(' NPM_TOKEN=your_token_here npm run publish:manual');
|
|
37
|
+
console.error('');
|
|
38
|
+
console.error('To get an NPM access token:');
|
|
39
|
+
console.error(' 1. Go to https://www.npmjs.com/settings/[your-username]/tokens');
|
|
40
|
+
console.error(' 2. Click "Generate New Token"');
|
|
41
|
+
console.error(' 3. Select "Automation" type for CI/CD or "Publish" for manual use');
|
|
42
|
+
console.error(' 4. Copy the token and use it with this script');
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return token;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function validateGitState() {
|
|
50
|
+
// Check for uncommitted changes
|
|
51
|
+
try {
|
|
52
|
+
execSync('git diff-index --quiet HEAD --', { stdio: 'ignore' });
|
|
53
|
+
} catch (e) {
|
|
54
|
+
console.error('⚠️ Warning: You have uncommitted changes');
|
|
55
|
+
console.error(' It is recommended to commit changes before publishing');
|
|
56
|
+
console.error('');
|
|
57
|
+
|
|
58
|
+
const readline = require('readline').createInterface({
|
|
59
|
+
input: process.stdin,
|
|
60
|
+
output: process.stdout
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return new Promise((resolve) => {
|
|
64
|
+
readline.question('Continue anyway? (y/N): ', (answer) => {
|
|
65
|
+
readline.close();
|
|
66
|
+
if (answer.toLowerCase() !== 'y') {
|
|
67
|
+
console.log('Aborted');
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
resolve();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function createNpmrc(token) {
|
|
77
|
+
// Backup existing .npmrc if it exists
|
|
78
|
+
if (fs.existsSync(NPMRC_PATH)) {
|
|
79
|
+
console.log('📋 Backing up existing .npmrc');
|
|
80
|
+
fs.copyFileSync(NPMRC_PATH, NPMRC_BACKUP_PATH);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Create .npmrc with token
|
|
84
|
+
const npmrcContent = `//registry.npmjs.org/:_authToken=${token}\nregistry=https://registry.npmjs.org/\n`;
|
|
85
|
+
fs.writeFileSync(NPMRC_PATH, npmrcContent, { mode: 0o600 });
|
|
86
|
+
console.log('✅ Created .npmrc with authentication token');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function restoreNpmrc() {
|
|
90
|
+
// Remove the temporary .npmrc
|
|
91
|
+
if (fs.existsSync(NPMRC_PATH)) {
|
|
92
|
+
fs.unlinkSync(NPMRC_PATH);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Restore backup if it exists
|
|
96
|
+
if (fs.existsSync(NPMRC_BACKUP_PATH)) {
|
|
97
|
+
console.log('📋 Restoring original .npmrc');
|
|
98
|
+
fs.renameSync(NPMRC_BACKUP_PATH, NPMRC_PATH);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function publishPackage(isDryRun, tag) {
|
|
103
|
+
const packagePath = path.join(__dirname, '..', 'package.json');
|
|
104
|
+
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
105
|
+
|
|
106
|
+
console.log('');
|
|
107
|
+
console.log('📦 Publishing package: ' + pkg.name);
|
|
108
|
+
console.log('📌 Version: ' + pkg.version);
|
|
109
|
+
if (tag) {
|
|
110
|
+
console.log('🏷️ Tag: ' + tag);
|
|
111
|
+
}
|
|
112
|
+
console.log('');
|
|
113
|
+
|
|
114
|
+
let publishCmd = 'npm publish --access public';
|
|
115
|
+
|
|
116
|
+
if (isDryRun) {
|
|
117
|
+
publishCmd += ' --dry-run';
|
|
118
|
+
console.log('🔍 Running in dry-run mode (no actual publish)');
|
|
119
|
+
console.log('');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (tag) {
|
|
123
|
+
publishCmd += ` --tag ${tag}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
execSync(publishCmd, { stdio: 'inherit', cwd: path.join(__dirname, '..') });
|
|
128
|
+
|
|
129
|
+
if (isDryRun) {
|
|
130
|
+
console.log('');
|
|
131
|
+
console.log('✅ Dry run completed successfully');
|
|
132
|
+
console.log(' Remove --dry-run to publish for real');
|
|
133
|
+
} else {
|
|
134
|
+
console.log('');
|
|
135
|
+
console.log('✅ Package published successfully!');
|
|
136
|
+
console.log(` View at: https://www.npmjs.com/package/${pkg.name}/v/${pkg.version}`);
|
|
137
|
+
}
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error('');
|
|
140
|
+
console.error('❌ Failed to publish package');
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function main() {
|
|
146
|
+
const args = process.argv.slice(2);
|
|
147
|
+
|
|
148
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
149
|
+
const packagePath = path.join(__dirname, '..', 'package.json');
|
|
150
|
+
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
151
|
+
|
|
152
|
+
console.log('📦 Manual NPM Publish');
|
|
153
|
+
console.log('');
|
|
154
|
+
console.log('Current package:', pkg.name);
|
|
155
|
+
console.log('Current version:', pkg.version);
|
|
156
|
+
console.log('');
|
|
157
|
+
console.log('Usage:');
|
|
158
|
+
console.log(' NPM_TOKEN=token npm run publish:manual [options]');
|
|
159
|
+
console.log('');
|
|
160
|
+
console.log('Options:');
|
|
161
|
+
console.log(' --dry-run Test the publish process without actually publishing');
|
|
162
|
+
console.log(' --tag TAG Publish with a specific dist-tag (e.g., beta, next, latest)');
|
|
163
|
+
console.log(' --help, -h Show this help message');
|
|
164
|
+
console.log('');
|
|
165
|
+
console.log('Examples:');
|
|
166
|
+
console.log(' NPM_TOKEN=npm_xxx npm run publish:manual');
|
|
167
|
+
console.log(' NPM_TOKEN=npm_xxx npm run publish:manual -- --dry-run');
|
|
168
|
+
console.log(' NPM_TOKEN=npm_xxx npm run publish:manual -- --tag beta');
|
|
169
|
+
console.log('');
|
|
170
|
+
console.log('Getting an NPM token:');
|
|
171
|
+
console.log(' 1. Visit: https://www.npmjs.com/settings/[your-username]/tokens');
|
|
172
|
+
console.log(' 2. Click "Generate New Token"');
|
|
173
|
+
console.log(' 3. Choose "Automation" (for CI/CD) or "Publish" (for manual use)');
|
|
174
|
+
console.log(' 4. Copy the token (it starts with "npm_")');
|
|
175
|
+
process.exit(0);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const isDryRun = args.includes('--dry-run');
|
|
179
|
+
const tagIndex = args.indexOf('--tag');
|
|
180
|
+
const tag = tagIndex !== -1 && args[tagIndex + 1] ? args[tagIndex + 1] : null;
|
|
181
|
+
|
|
182
|
+
console.log('🚀 Manual NPM Publish Script');
|
|
183
|
+
console.log('═'.repeat(50));
|
|
184
|
+
|
|
185
|
+
// Validate token
|
|
186
|
+
const token = validateToken();
|
|
187
|
+
|
|
188
|
+
// Validate git state
|
|
189
|
+
await validateGitState();
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
// Create .npmrc with token
|
|
193
|
+
createNpmrc(token);
|
|
194
|
+
|
|
195
|
+
// Publish package
|
|
196
|
+
publishPackage(isDryRun, tag);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
console.error('');
|
|
199
|
+
console.error('Publishing failed');
|
|
200
|
+
process.exit(1);
|
|
201
|
+
} finally {
|
|
202
|
+
// Always restore the original .npmrc
|
|
203
|
+
restoreNpmrc();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
main().catch((error) => {
|
|
208
|
+
console.error('Unexpected error:', error);
|
|
209
|
+
restoreNpmrc();
|
|
210
|
+
process.exit(1);
|
|
211
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Generate release notes for GitHub releases
|
|
3
|
+
# Usage: ./bin/release-notes.sh VERSION IMAGE_URI [IS_PRERELEASE]
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
VERSION="${1}"
|
|
8
|
+
IMAGE_URI="${2}"
|
|
9
|
+
IS_PRERELEASE="${3:-false}"
|
|
10
|
+
|
|
11
|
+
if [ -z "$VERSION" ] || [ -z "$IMAGE_URI" ]; then
|
|
12
|
+
echo "Usage: $0 VERSION IMAGE_URI [IS_PRERELEASE]"
|
|
13
|
+
echo "Example: $0 0.4.12 123456.dkr.ecr.us-west-2.amazonaws.com/quiltdata/benchling:0.4.12 false"
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
# Extract changelog notes if available
|
|
18
|
+
CHANGELOG_NOTES=""
|
|
19
|
+
if [ -f CHANGELOG.md ]; then
|
|
20
|
+
CHANGELOG_NOTES=$(sed -n "/## \[$VERSION\]/,/## \[/p" CHANGELOG.md | sed '$d' | sed '1d')
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# Generate release notes
|
|
24
|
+
cat << EOFNOTES
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
# 1. Configure
|
|
28
|
+
cp env.template .env
|
|
29
|
+
# Edit .env with AWS account, Benchling credentials, S3/SQS settings
|
|
30
|
+
|
|
31
|
+
# 2. Install app-manifest.yaml as a Benchling app
|
|
32
|
+
|
|
33
|
+
# 3. Deploy
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
set -a; source .env; set +a
|
|
37
|
+
npx cdk bootstrap aws://\$CDK_DEFAULT_ACCOUNT/\$CDK_DEFAULT_REGION
|
|
38
|
+
npm run check
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
# 4. Set Benchling webhook URL in the app overview page
|
|
42
|
+
|
|
43
|
+
# 5. Insert a canvas into a notebook entry and click "Create"
|
|
44
|
+
|
|
45
|
+
# 6. Set `experiment_id` in a package's metadata to link it to a notebook'
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
\`\`\`
|
|
49
|
+
|
|
50
|
+
## Docker Image
|
|
51
|
+
|
|
52
|
+
For custom deployments, use the following Docker image:
|
|
53
|
+
|
|
54
|
+
\`\`\`
|
|
55
|
+
${IMAGE_URI}
|
|
56
|
+
\`\`\`
|
|
57
|
+
|
|
58
|
+
Pull and run:
|
|
59
|
+
\`\`\`bash
|
|
60
|
+
docker pull ${IMAGE_URI}
|
|
61
|
+
\`\`\`
|
|
62
|
+
|
|
63
|
+
EOFNOTES
|
|
64
|
+
|
|
65
|
+
# Add changelog notes if available
|
|
66
|
+
if [ -n "$CHANGELOG_NOTES" ]; then
|
|
67
|
+
echo ""
|
|
68
|
+
echo "## Changes"
|
|
69
|
+
echo ""
|
|
70
|
+
echo "$CHANGELOG_NOTES"
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# Add resources
|
|
74
|
+
cat << EOFRESOURCES
|
|
75
|
+
|
|
76
|
+
## Resources
|
|
77
|
+
|
|
78
|
+
- [Installation Guide](https://github.com/quiltdata/benchling-webhook#installation)
|
|
79
|
+
- [Configuration Guide](https://github.com/quiltdata/benchling-webhook#configuration)
|
|
80
|
+
- [Development Guide](https://github.com/quiltdata/benchling-webhook/tree/main/docker)
|
|
81
|
+
- [Release Process](https://github.com/quiltdata/benchling-webhook/blob/main/doc/RELEASE.md)
|
|
82
|
+
EOFRESOURCES
|
package/bin/release.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Release management script - creates and pushes git tags
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node bin/release.js # Create production release from current version
|
|
8
|
+
* node bin/release.js dev # Create dev release with timestamp from current version
|
|
9
|
+
* node bin/release.js --no-push # Create tag but don't push
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const { execSync } = require('child_process');
|
|
15
|
+
|
|
16
|
+
const packagePath = path.join(__dirname, '..', 'package.json');
|
|
17
|
+
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
18
|
+
|
|
19
|
+
function createGitTag(version, isDev, noPush) {
|
|
20
|
+
let tagName = `v${version}`;
|
|
21
|
+
|
|
22
|
+
// For dev releases, append timestamp to make unique
|
|
23
|
+
if (isDev) {
|
|
24
|
+
const timestamp = new Date().toISOString().replace(/[-:]/g, '').replace(/\.\d+Z$/, 'Z');
|
|
25
|
+
tagName = `v${version}-${timestamp}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const tagType = isDev ? 'pre-release (dev)' : 'release';
|
|
29
|
+
|
|
30
|
+
// Check if tag already exists
|
|
31
|
+
try {
|
|
32
|
+
execSync(`git rev-parse ${tagName}`, { stdio: 'ignore' });
|
|
33
|
+
console.error(`❌ Tag ${tagName} already exists`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
} catch (e) {
|
|
36
|
+
// Tag doesn't exist, continue
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Create tag
|
|
40
|
+
const message = isDev
|
|
41
|
+
? `Development ${tagType} ${tagName}\n\nThis is a pre-release for testing purposes.`
|
|
42
|
+
: `Release ${tagName}`;
|
|
43
|
+
|
|
44
|
+
execSync(`git tag -a ${tagName} -m "${message}"`, { stdio: 'inherit' });
|
|
45
|
+
console.log(`✅ Created git tag ${tagName}`);
|
|
46
|
+
|
|
47
|
+
// Push tag unless --no-push is specified
|
|
48
|
+
if (!noPush) {
|
|
49
|
+
console.log('');
|
|
50
|
+
console.log(`Pushing tag ${tagName} to origin...`);
|
|
51
|
+
try {
|
|
52
|
+
execSync(`git push origin ${tagName}`, { stdio: 'inherit' });
|
|
53
|
+
console.log(`✅ Pushed tag ${tagName} to origin`);
|
|
54
|
+
console.log('');
|
|
55
|
+
console.log('CI/CD pipeline will now:');
|
|
56
|
+
console.log(' - Run all tests');
|
|
57
|
+
console.log(' - Build and push Docker image to ECR');
|
|
58
|
+
console.log(' - Create GitHub release');
|
|
59
|
+
if (!isDev) {
|
|
60
|
+
console.log(' - Publish to NPM (production releases only)');
|
|
61
|
+
}
|
|
62
|
+
console.log(' - Publish to GitHub Packages');
|
|
63
|
+
console.log('');
|
|
64
|
+
console.log('Monitor progress at: https://github.com/quiltdata/benchling-webhook/actions');
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error(`❌ Failed to push tag ${tagName}`);
|
|
67
|
+
console.error('You can manually push with: git push origin ' + tagName);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
console.log('');
|
|
72
|
+
console.log('Tag created but not pushed (--no-push specified)');
|
|
73
|
+
console.log(`To push later: git push origin ${tagName}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function main() {
|
|
78
|
+
const args = process.argv.slice(2);
|
|
79
|
+
|
|
80
|
+
// Check for uncommitted changes
|
|
81
|
+
try {
|
|
82
|
+
execSync('git diff-index --quiet HEAD --', { stdio: 'ignore' });
|
|
83
|
+
} catch (e) {
|
|
84
|
+
console.error('❌ You have uncommitted changes');
|
|
85
|
+
console.error(' Commit or stash your changes before creating a release');
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const isDev = args.includes('dev');
|
|
90
|
+
const noPush = args.includes('--no-push');
|
|
91
|
+
const version = pkg.version;
|
|
92
|
+
|
|
93
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
94
|
+
console.log('Current version:', version);
|
|
95
|
+
console.log('');
|
|
96
|
+
console.log('Usage: node bin/release.js [dev] [--no-push]');
|
|
97
|
+
console.log('');
|
|
98
|
+
console.log('Commands:');
|
|
99
|
+
console.log(' (no args) - Create production release tag and push');
|
|
100
|
+
console.log(' dev - Create dev release tag with timestamp and push');
|
|
101
|
+
console.log('');
|
|
102
|
+
console.log('Options:');
|
|
103
|
+
console.log(' --no-push - Create tag but do not push to origin');
|
|
104
|
+
console.log('');
|
|
105
|
+
console.log('Examples:');
|
|
106
|
+
console.log(' node bin/release.js # Create v0.4.12 and push');
|
|
107
|
+
console.log(' node bin/release.js dev # Create v0.4.12-20251027T123456Z and push');
|
|
108
|
+
console.log(' node bin/release.js --no-push # Create tag but don\'t push');
|
|
109
|
+
process.exit(0);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log(`Creating ${isDev ? 'dev' : 'production'} release from version: ${version}`);
|
|
113
|
+
console.log('');
|
|
114
|
+
|
|
115
|
+
createGitTag(version, isDev, noPush);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
main();
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Send a test event to the deployed Benchling webhook endpoint
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
require("dotenv/config");
|
|
7
|
+
const { execSync } = require("child_process");
|
|
8
|
+
const fs = require("fs");
|
|
9
|
+
const path = require("path");
|
|
10
|
+
|
|
11
|
+
const STACK_NAME = "BenchlingWebhookStack";
|
|
12
|
+
|
|
13
|
+
// Validate required environment variables
|
|
14
|
+
if (!process.env.CDK_DEFAULT_REGION) {
|
|
15
|
+
console.error("Error: CDK_DEFAULT_REGION is not set in .env file");
|
|
16
|
+
console.error("Please set CDK_DEFAULT_REGION in your .env file");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const AWS_REGION = process.env.CDK_DEFAULT_REGION;
|
|
21
|
+
|
|
22
|
+
function getStackOutputs() {
|
|
23
|
+
try {
|
|
24
|
+
const output = execSync(
|
|
25
|
+
`aws cloudformation describe-stacks --stack-name ${STACK_NAME} --region ${AWS_REGION} --query 'Stacks[0].Outputs' --output json`,
|
|
26
|
+
{ encoding: "utf-8" },
|
|
27
|
+
);
|
|
28
|
+
return JSON.parse(output);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error(`Error: Could not get stack outputs for ${STACK_NAME}`);
|
|
31
|
+
console.error("Make sure the stack is deployed and AWS credentials are configured.");
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getWebhookEndpoint(outputs) {
|
|
37
|
+
const endpoint = outputs.find((o) => o.OutputKey === "WebhookEndpoint");
|
|
38
|
+
if (!endpoint) {
|
|
39
|
+
console.error("Error: Could not find WebhookEndpoint in stack outputs");
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
return endpoint.OutputValue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function listTestEvents() {
|
|
46
|
+
const eventsDir = path.join(__dirname, "..", "test-events");
|
|
47
|
+
const files = fs.readdirSync(eventsDir).filter(f => f.endsWith(".json"));
|
|
48
|
+
|
|
49
|
+
console.log("Available test events:");
|
|
50
|
+
files.forEach(file => {
|
|
51
|
+
console.log(` - ${file.replace(".json", "")}`);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function loadTestEvent(eventName) {
|
|
56
|
+
const eventsDir = path.join(__dirname, "..", "test-events");
|
|
57
|
+
|
|
58
|
+
// Add .json extension if not present
|
|
59
|
+
if (!eventName.endsWith(".json")) {
|
|
60
|
+
eventName = `${eventName}.json`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const eventPath = path.join(eventsDir, eventName);
|
|
64
|
+
|
|
65
|
+
if (!fs.existsSync(eventPath)) {
|
|
66
|
+
console.error(`Error: Test event file not found: ${eventPath}`);
|
|
67
|
+
console.error("");
|
|
68
|
+
listTestEvents();
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return fs.readFileSync(eventPath, "utf-8");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function sendEvent(endpoint, eventData, eventName, dryRun = false) {
|
|
76
|
+
// Determine the endpoint path based on event type
|
|
77
|
+
const event = JSON.parse(eventData);
|
|
78
|
+
let path = "/";
|
|
79
|
+
|
|
80
|
+
if (event.message?.type) {
|
|
81
|
+
const type = event.message.type;
|
|
82
|
+
if (type.includes("canvas")) {
|
|
83
|
+
path = "/canvas";
|
|
84
|
+
} else if (type.includes("entry")) {
|
|
85
|
+
path = "/entry";
|
|
86
|
+
} else if (type.includes("app")) {
|
|
87
|
+
path = "/app";
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const url = endpoint.replace(/\/$/, "") + path;
|
|
92
|
+
|
|
93
|
+
console.log("=".repeat(80));
|
|
94
|
+
console.log("Sending Test Event");
|
|
95
|
+
console.log("=".repeat(80));
|
|
96
|
+
console.log(`Event: ${eventName}`);
|
|
97
|
+
console.log(`Type: ${event.message?.type || "unknown"}`);
|
|
98
|
+
console.log(`Endpoint: ${url}`);
|
|
99
|
+
console.log("=".repeat(80));
|
|
100
|
+
console.log("");
|
|
101
|
+
|
|
102
|
+
if (dryRun) {
|
|
103
|
+
console.log("DRY RUN - Would send the following payload:");
|
|
104
|
+
console.log(JSON.stringify(JSON.parse(eventData), null, 2));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Create a temporary file for the event data
|
|
109
|
+
const tmpFile = `/tmp/benchling-event-${Date.now()}.json`;
|
|
110
|
+
fs.writeFileSync(tmpFile, eventData);
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
console.log("Sending request...");
|
|
114
|
+
const command = `curl -X POST "${url}" \\
|
|
115
|
+
-H "Content-Type: application/json" \\
|
|
116
|
+
-H "webhook-id: msg_test_${Date.now()}" \\
|
|
117
|
+
-H "webhook-timestamp: ${Math.floor(Date.now() / 1000)}" \\
|
|
118
|
+
-H "webhook-signature: v1,test_signature" \\
|
|
119
|
+
--data @${tmpFile} \\
|
|
120
|
+
-w "\\n\\nHTTP Status: %{http_code}\\n" \\
|
|
121
|
+
-v`;
|
|
122
|
+
|
|
123
|
+
console.log("");
|
|
124
|
+
execSync(command, { stdio: "inherit", shell: "/bin/bash" });
|
|
125
|
+
console.log("");
|
|
126
|
+
console.log("=".repeat(80));
|
|
127
|
+
console.log("Event sent successfully!");
|
|
128
|
+
console.log("Check logs with: npm run logs");
|
|
129
|
+
console.log("=".repeat(80));
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error("\nError sending event:", error.message);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
} finally {
|
|
134
|
+
// Clean up temp file
|
|
135
|
+
if (fs.existsSync(tmpFile)) {
|
|
136
|
+
fs.unlinkSync(tmpFile);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function printHelp() {
|
|
142
|
+
console.log("Usage: npm run event [event-name] [options]");
|
|
143
|
+
console.log("");
|
|
144
|
+
console.log("Arguments:");
|
|
145
|
+
console.log(" event-name Name of test event file (without .json extension)");
|
|
146
|
+
console.log(" Defaults to 'canvas-created' if not specified");
|
|
147
|
+
console.log("");
|
|
148
|
+
console.log("Options:");
|
|
149
|
+
console.log(" --list, -l List available test events");
|
|
150
|
+
console.log(" --dry-run, -d Show what would be sent without actually sending");
|
|
151
|
+
console.log(" --help, -h Show this help message");
|
|
152
|
+
console.log("");
|
|
153
|
+
console.log("Examples:");
|
|
154
|
+
console.log(" npm run event # Send canvas-created.json (default)");
|
|
155
|
+
console.log(" npm run event canvas-created # Send canvas-created.json event");
|
|
156
|
+
console.log(" npm run event entry-updated # Send entry-updated.json event");
|
|
157
|
+
console.log(" npm run event -- --list # List all available events");
|
|
158
|
+
console.log(" npm run event canvas-created -- --dry-run # Preview without sending");
|
|
159
|
+
console.log("");
|
|
160
|
+
console.log("After sending an event, check the logs:");
|
|
161
|
+
console.log(" npm run logs");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function main() {
|
|
165
|
+
const args = process.argv.slice(2);
|
|
166
|
+
|
|
167
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
168
|
+
printHelp();
|
|
169
|
+
process.exit(0);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (args.includes("--list") || args.includes("-l")) {
|
|
173
|
+
listTestEvents();
|
|
174
|
+
process.exit(0);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const dryRun = args.includes("--dry-run") || args.includes("-d");
|
|
178
|
+
const eventName = args.find(arg => !arg.startsWith("-")) || "canvas-created";
|
|
179
|
+
|
|
180
|
+
// If using default event, notify the user
|
|
181
|
+
if (!args.find(arg => !arg.startsWith("-"))) {
|
|
182
|
+
console.log("No event specified, using default: canvas-created\n");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Get webhook endpoint from stack outputs
|
|
186
|
+
const outputs = getStackOutputs();
|
|
187
|
+
const endpoint = getWebhookEndpoint(outputs);
|
|
188
|
+
|
|
189
|
+
// Load test event
|
|
190
|
+
const eventData = loadTestEvent(eventName);
|
|
191
|
+
|
|
192
|
+
// Send event
|
|
193
|
+
sendEvent(endpoint, eventData, eventName, dryRun);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (require.main === module) {
|
|
197
|
+
try {
|
|
198
|
+
main();
|
|
199
|
+
} catch (error) {
|
|
200
|
+
console.error("Error:", error.message);
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Synchronize version between package.json, docker/pyproject.toml, and docker/app-manifest.yaml
|
|
4
|
+
* The source of truth is docker/pyproject.toml
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const PYPROJECT_PATH = path.join(__dirname, '..', 'docker', 'pyproject.toml');
|
|
11
|
+
const PACKAGE_JSON_PATH = path.join(__dirname, '..', 'package.json');
|
|
12
|
+
const APP_MANIFEST_PATH = path.join(__dirname, '..', 'docker', 'app-manifest.yaml');
|
|
13
|
+
|
|
14
|
+
function extractVersionFromPyproject(content) {
|
|
15
|
+
const match = content.match(/^version\s*=\s*"([^"]+)"/m);
|
|
16
|
+
if (!match) {
|
|
17
|
+
throw new Error('Could not find version in pyproject.toml');
|
|
18
|
+
}
|
|
19
|
+
return match[1];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function updateAppManifest(version) {
|
|
23
|
+
const content = fs.readFileSync(APP_MANIFEST_PATH, 'utf-8');
|
|
24
|
+
const updatedContent = content.replace(
|
|
25
|
+
/^version:\s*.+$/m,
|
|
26
|
+
`version: ${version}`
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
if (content !== updatedContent) {
|
|
30
|
+
fs.writeFileSync(APP_MANIFEST_PATH, updatedContent);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function main() {
|
|
37
|
+
// Read pyproject.toml
|
|
38
|
+
const pyprojectContent = fs.readFileSync(PYPROJECT_PATH, 'utf-8');
|
|
39
|
+
const version = extractVersionFromPyproject(pyprojectContent);
|
|
40
|
+
|
|
41
|
+
console.log(`Version from docker/pyproject.toml: ${version}`);
|
|
42
|
+
|
|
43
|
+
// Update package.json
|
|
44
|
+
const packageJson = JSON.parse(fs.readFileSync(PACKAGE_JSON_PATH, 'utf-8'));
|
|
45
|
+
const oldVersion = packageJson.version;
|
|
46
|
+
|
|
47
|
+
if (oldVersion !== version) {
|
|
48
|
+
packageJson.version = version;
|
|
49
|
+
fs.writeFileSync(PACKAGE_JSON_PATH, JSON.stringify(packageJson, null, 2) + '\n');
|
|
50
|
+
console.log(`✓ Updated package.json version: ${oldVersion} → ${version}`);
|
|
51
|
+
} else {
|
|
52
|
+
console.log(`✓ package.json version already matches: ${version}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Update app-manifest.yaml
|
|
56
|
+
if (updateAppManifest(version)) {
|
|
57
|
+
console.log(`✓ Updated app-manifest.yaml version to: ${version}`);
|
|
58
|
+
} else {
|
|
59
|
+
console.log(`✓ app-manifest.yaml version already matches: ${version}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (require.main === module) {
|
|
64
|
+
try {
|
|
65
|
+
main();
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error('Error:', error.message);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { extractVersionFromPyproject };
|