@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.
@@ -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 };