haven-cypress-integration 1.6.1 โ†’ 2.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/index.js CHANGED
@@ -1,231 +1,103 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
- const { execSync } = require('child_process');
4
-
5
- class HavenCypressIntegration {
6
- constructor() {
7
- this.templatesDir = path.join(__dirname, 'templates');
8
- }
9
-
10
- /**
11
- * Build Docker image with Haven integration
12
- * @param {string} tag - Docker image tag
13
- * @param {Object} options - Build options
14
- */
15
- buildImage(tag = 'haven-cypress-tests:latest', options = {}) {
16
- console.log('๐Ÿณ Building Haven-integrated Docker image...');
17
-
18
- // Validate product name if pushing
19
- if (options.push && !options.product) {
20
- throw new Error('โŒ --product is required when using --push');
21
- }
22
-
23
- // Create temporary build directory
24
- const buildDir = path.join(process.cwd(), '.haven-build');
25
- if (fs.existsSync(buildDir)) {
26
- fs.rmSync(buildDir, { recursive: true });
27
- }
28
- fs.mkdirSync(buildDir, { recursive: true });
29
-
30
- try {
31
- // Copy user's project files
32
- console.log('๐Ÿ“ Copying project files...');
33
- this.copyProjectFiles(buildDir);
34
-
35
- // Copy Haven integration files
36
- console.log('๐Ÿ”ง Adding Haven integration files...');
37
- this.copyHavenFiles(buildDir);
38
-
39
- // Build Docker image
40
- console.log('๐Ÿ—๏ธ Building Docker image...');
41
- execSync(`docker build -t ${tag} .`, {
42
- cwd: buildDir,
43
- stdio: 'inherit'
44
- });
45
-
46
- console.log(`โœ… Docker image built successfully: ${tag}`);
47
-
48
- // Push if requested
49
- if (options.push) {
50
- this.pushToECR(tag, options.product);
51
- }
52
-
53
- } finally {
54
- // Cleanup build directory
55
- if (fs.existsSync(buildDir)) {
56
- fs.rmSync(buildDir, { recursive: true });
57
- }
58
- }
59
- }
60
-
61
- /**
62
- * Copy user's project files to build directory
63
- */
64
- copyProjectFiles(buildDir) {
65
- const filesToCopy = [
66
- 'package.json',
67
- 'package-lock.json',
68
- 'cypress.config.js',
69
- 'cypress'
70
- ];
71
-
72
- filesToCopy.forEach(file => {
73
- const src = path.join(process.cwd(), file);
74
- const dest = path.join(buildDir, file);
75
-
76
- if (fs.existsSync(src)) {
77
- if (fs.statSync(src).isDirectory()) {
78
- this.copyDir(src, dest);
79
- } else {
80
- fs.copyFileSync(src, dest);
81
- }
82
- console.log(` โœ… Copied ${file}`);
83
- } else {
84
- console.log(` โš ๏ธ Skipped ${file} (not found)`);
85
- }
86
- });
87
- }
88
-
89
- /**
90
- * Copy Haven integration files to build directory
91
- */
92
- copyHavenFiles(buildDir) {
93
- const havenFiles = [
94
- 'Dockerfile',
95
- 'run-filtered.sh',
96
- 'syncCypressResults.js'
97
- ];
98
-
99
- havenFiles.forEach(file => {
100
- const src = path.join(this.templatesDir, file);
101
- const dest = path.join(buildDir, file);
102
-
103
- if (fs.existsSync(src)) {
104
- fs.copyFileSync(src, dest);
105
- console.log(` โœ… Added ${file}`);
106
- }
107
- });
108
-
109
- // Make run-filtered.sh executable
110
- execSync('chmod +x run-filtered.sh', { cwd: buildDir });
111
- }
112
-
113
- /**
114
- * Copy directory recursively
115
- */
116
- copyDir(src, dest) {
117
- if (!fs.existsSync(dest)) {
118
- fs.mkdirSync(dest, { recursive: true });
119
- }
3
+ const { spawnSync } = require('child_process');
120
4
 
121
- const entries = fs.readdirSync(src, { withFileTypes: true });
5
+ function run(cmd, args, opts = {}) {
6
+ const res = spawnSync(cmd, args, { stdio: 'inherit', shell: false, ...opts });
7
+ if (res.status !== 0) throw new Error(`${cmd} ${args.join(' ')} failed`);
8
+ return res;
9
+ }
122
10
 
123
- for (const entry of entries) {
124
- const srcPath = path.join(src, entry.name);
125
- const destPath = path.join(dest, entry.name);
11
+ module.exports = {
12
+ buildImage(tag = 'haven-cypress-tests:latest', options = {}) {
13
+ const buildType = options.type || 'thin';
14
+ console.log(`Building Haven-integrated Cypress image (${buildType} mode)...`);
126
15
 
127
- if (entry.isDirectory()) {
128
- this.copyDir(srcPath, destPath);
129
- } else {
130
- fs.copyFileSync(srcPath, destPath);
131
- }
132
- }
133
- }
134
-
135
- /**
136
- * Push image to ECR with product organization
137
- * @param {string} localTag - Local Docker image tag
138
- * @param {string} product - Product name for organization
139
- */
140
- pushToECR(localTag, product) {
141
- console.log(`๐Ÿ“ค Pushing image to ECR for product: ${product}`);
142
-
143
- try {
144
- // Get AWS account ID and region
145
- console.log('๐Ÿ” Getting AWS account information...');
146
- const accountId = execSync('aws sts get-caller-identity --query Account --output text', { encoding: 'utf8' }).trim();
147
- const region = execSync('aws configure get region', { encoding: 'utf8' }).trim() || 'us-east-1';
148
-
149
- console.log(`๐Ÿ“‹ Account: ${accountId}, Region: ${region}`);
150
-
151
- // ECR repository and image details
152
- const ecrRepo = 'haven-test-images';
153
- const ecrUri = `${accountId}.dkr.ecr.${region}.amazonaws.com/${ecrRepo}`;
154
-
155
- // Extract version from local tag or use package.json version
156
- const localVersion = localTag.split(':')[1] || 'latest';
157
-
158
- // Generate ECR tag with product and version
159
- let ecrTag;
160
- if (localVersion === 'latest') {
161
- // Try to read version from package.json first
162
- let packageVersion = null;
163
- try {
164
- const packageJsonPath = path.join(process.cwd(), 'package.json');
165
- if (fs.existsSync(packageJsonPath)) {
166
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
167
- packageVersion = packageJson.version;
168
- }
169
- } catch (error) {
170
- console.warn('โš ๏ธ Could not read package.json version:', error.message);
16
+ // Validate product name if pushing
17
+ if (options.push && !options.product) {
18
+ throw new Error('--product is required when using --push');
171
19
  }
172
20
 
173
- if (packageVersion) {
174
- // Use package.json version
175
- const cleanVersion = packageVersion.startsWith('v') ? packageVersion.slice(1) : packageVersion;
176
- ecrTag = `${product}-${cleanVersion}`;
21
+ const buildDir = path.join(process.cwd(), '.haven-cypress-build');
22
+ if (fs.existsSync(buildDir)) fs.rmSync(buildDir, { recursive: true });
23
+ fs.mkdirSync(buildDir, { recursive: true });
24
+
25
+ // Copy templates (always needed)
26
+ const tplDir = path.join(__dirname, 'templates');
27
+ run('bash', ['-lc', `cp -R ${JSON.stringify(path.join(tplDir, 'Dockerfile'))} ${JSON.stringify(buildDir)}`]);
28
+ run('bash', ['-lc', `cp -R ${JSON.stringify(path.join(tplDir, 'run-filtered.sh'))} ${JSON.stringify(path.join(buildDir, 'run-filtered.sh'))}`]);
29
+ run('bash', ['-lc', `cp -R ${JSON.stringify(path.join(tplDir, 'syncCypressResults.js'))} ${JSON.stringify(path.join(buildDir, 'syncCypressResults.js'))}`]);
30
+
31
+ // For full image: copy project files
32
+ if (buildType === 'full') {
33
+ console.log('Full image: copying project files...');
34
+ const include = ['package.json', 'package-lock.json', 'cypress.config.js', 'cypress.config.ts', 'cypress', 'src', 'node_modules'];
35
+ for (const item of include) {
36
+ if (fs.existsSync(path.join(process.cwd(), item))) {
37
+ run('bash', ['-lc', `cp -R ${JSON.stringify(item)} ${JSON.stringify(buildDir)}`]);
38
+ }
39
+ }
177
40
  } else {
178
- // Fallback to build number format
179
- const buildNumber = process.env.BUILD_NUMBER || Date.now().toString().slice(-6);
180
- ecrTag = `${product}-1.0.${buildNumber}`;
41
+ console.log('Thin image: skipping project files (will be cloned at runtime)...');
181
42
  }
182
- } else {
183
- // For custom versions, preserve semantic versioning without 'v' prefix
184
- const cleanVersion = localVersion.startsWith('v') ? localVersion.slice(1) : localVersion;
185
- ecrTag = `${product}-${cleanVersion}`;
186
- }
187
43
 
188
- const fullEcrUri = `${ecrUri}:${ecrTag}`;
44
+ // Build with target stage
45
+ const target = buildType === 'thin' ? 'thin-image' : 'full-image';
46
+ console.log(`Building Docker image with target: ${target}`);
47
+ run('bash', ['-lc', `podman build --platform linux/amd64 --target ${target} -t ${tag} ${JSON.stringify(buildDir)}`]);
189
48
 
190
- console.log(`๐Ÿท๏ธ ECR tag: ${ecrTag}`);
191
-
192
- // Login to ECR
193
- console.log('๐Ÿ” Logging in to ECR...');
194
- const loginCmd = `aws ecr get-login-password --region ${region} | docker login --username AWS --password-stdin ${ecrUri}`;
195
- execSync(loginCmd, { stdio: 'inherit' });
49
+ if (options.push) {
50
+ this.pushToECR(tag, options.product, buildType);
51
+ } else {
52
+ console.log(`${buildType.charAt(0).toUpperCase() + buildType.slice(1)} image build complete (not pushed)`);
53
+ }
54
+ },
196
55
 
197
- // Tag image for ECR
198
- console.log(`๐Ÿท๏ธ Tagging image: ${localTag} -> ${fullEcrUri}`);
199
- execSync(`docker tag ${localTag} ${fullEcrUri}`, { stdio: 'inherit' });
56
+ pushToECR(localTag, product = 'unknown', buildType = 'full') {
57
+ console.log(`Pushing ${buildType} image to ECR for product: ${product}`);
200
58
 
201
- // Push to ECR
202
- console.log(`๐Ÿ“ค Pushing to ECR: ${fullEcrUri}`);
203
- execSync(`docker push ${fullEcrUri}`, { stdio: 'inherit' });
59
+ // Account + region
60
+ const accountId = spawnSync('bash', ['-lc', 'aws sts get-caller-identity --query Account --output text'], { encoding: 'utf-8' }).stdout.trim();
61
+ const region = spawnSync('bash', ['-lc', 'aws configure get region || echo us-east-1'], { encoding: 'utf-8' }).stdout.trim();
204
62
 
205
- console.log(`โœ… Image pushed successfully!`);
206
- console.log(`๐Ÿ“ ECR URI: ${fullEcrUri}`);
207
- console.log(`๐Ÿ—‚๏ธ Product: ${product}`);
208
- console.log(`๐Ÿท๏ธ Version: ${ecrTag}`);
63
+ const ecrRepo = 'haven-test-images';
64
+ const ecrRegistry = `${accountId}.dkr.ecr.${region}.amazonaws.com`;
65
+ const ecrUri = `${ecrRegistry}/${ecrRepo}`;
209
66
 
210
- } catch (error) {
211
- console.error(`โŒ Failed to push to ECR: ${error.message}`);
212
- throw error;
67
+ // Version from consumer package.json
68
+ let version = '1.0.0';
69
+ try {
70
+ const pkg = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf-8'));
71
+ version = (pkg.version || '1.0.0').replace(/^v/, '');
72
+ } catch { }
73
+
74
+ // Include build type in tag: product-thin-version (e.g., BE-thin-1.0.0)
75
+ const ecrTag = buildType === 'full' ? `${product}-${version}` : `${product}-${buildType}-${version}`;
76
+
77
+ // Ensure ECR repository exists
78
+ run('bash', ['-lc', `aws ecr describe-repositories --repository-names ${ecrRepo} --region ${region} >/dev/null 2>&1 || aws ecr create-repository --repository-name ${ecrRepo} --region ${region}`]);
79
+
80
+ // Clear stale credentials before login to prevent "token expired" errors
81
+ console.log('Clearing stale ECR credentials...');
82
+ spawnSync('bash', ['-lc', `podman logout ${ecrRegistry} 2>/dev/null || true`], { stdio: 'inherit' });
83
+
84
+ // Fresh login to ECR
85
+ console.log(`Logging in to ECR: ${ecrRegistry}`);
86
+ run('bash', ['-lc', `aws ecr get-login-password --region ${region} | podman login --username AWS --password-stdin ${ecrRegistry}`]);
87
+
88
+ run('bash', ['-lc', `podman tag ${localTag} ${ecrUri}:${ecrTag}`]);
89
+
90
+ // Use --force-compression to avoid 403 errors when podman tries to reuse
91
+ // blobs from other registries (e.g., when switching AWS accounts)
92
+ console.log(`Pushing image: ${ecrUri}:${ecrTag}`);
93
+ run('bash', ['-lc', `podman push --force-compression ${ecrUri}:${ecrTag}`]);
94
+ console.log(`${buildType.charAt(0).toUpperCase() + buildType.slice(1)} image pushed: ${ecrUri}:${ecrTag}`);
95
+ },
96
+
97
+ runTests(automationIds = '', customTags = '') {
98
+ console.log('Running Cypress tests via run-filtered.sh inside container');
99
+ console.log(`Automation IDs: ${automationIds || 'None'}`);
100
+ console.log(`Custom Tags: ${customTags || 'None'}`);
101
+ return { success: true };
213
102
  }
214
- }
215
-
216
- /**
217
- * Run tests with Haven integration
218
- * This is called by Haven when the container runs
219
- */
220
- runTests(automationIds = '', customTags = '') {
221
- console.log('๐Ÿงช Running Haven-integrated Cypress tests...');
222
- console.log(`๐Ÿ” Automation IDs: ${automationIds || 'None'}`);
223
- console.log(`๐Ÿ” Custom Tags: ${customTags || 'None'}`);
224
-
225
- // This will be handled by run-filtered.sh when container runs
226
- // The library just provides the interface
227
- return { success: true, message: 'Tests will run via run-filtered.sh' };
228
- }
229
- }
230
-
231
- module.exports = HavenCypressIntegration;
103
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "haven-cypress-integration",
3
- "version": "1.6.1",
3
+ "version": "2.0.0",
4
4
  "description": "Seamless Cypress integration with HAVEN test case management",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -18,9 +18,10 @@
18
18
  "files": [
19
19
  "index.js",
20
20
  "bin/",
21
- "templates/"
21
+ "templates/",
22
+ "README.md"
22
23
  ],
23
24
  "engines": {
24
- "node": ">=14.0.0"
25
+ "node": ">=16.0.0"
25
26
  }
26
- }
27
+ }
@@ -1,29 +1,39 @@
1
- FROM cypress/included:14.3.1
1
+ # =============================================================================
2
+ # Haven Cypress Test Image - Supports Full and Thin modes
3
+ # =============================================================================
2
4
 
3
- WORKDIR /app
4
-
5
- # Copy project files
6
- COPY . .
5
+ # -----------------------------------------------------------------------------
6
+ # Base Stage
7
+ # -----------------------------------------------------------------------------
8
+ FROM cypress/included:14.3.1 AS base
7
9
 
8
- # โœ… Install zip and AWS CLI v2 via official method
10
+ # Install git, zip, AWS CLI
9
11
  RUN apt-get update && \
10
- apt-get install -y \
11
- zip \
12
- unzip \
13
- curl && \
12
+ apt-get install -y zip unzip curl git && \
14
13
  curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \
15
- unzip awscliv2.zip && \
16
- ./aws/install && \
17
- rm -rf aws awscliv2.zip
14
+ unzip awscliv2.zip && ./aws/install && rm -rf aws awscliv2.zip && \
15
+ rm -rf /var/lib/apt/lists/*
18
16
 
19
- # โœ… Install Node deps (including dev dependencies for mochawesome)
20
- RUN npm ci --include=dev
21
- RUN npm install --no-save aws-sdk mochawesome mochawesome-merge mochawesome-report-generator
17
+ # Haven scripts go in /haven (not /app)
18
+ WORKDIR /haven
19
+ COPY run-filtered.sh /haven/run-filtered.sh
20
+ COPY syncCypressResults.js /haven/syncCypressResults.js
21
+ RUN chmod +x /haven/run-filtered.sh
22
22
 
23
- # โœ… Ensure script is executable
24
- COPY run-filtered.sh /app/run-filtered.sh
25
- RUN chmod +x /app/run-filtered.sh
23
+ # App code goes in /app
24
+ WORKDIR /app
25
+
26
+ # -----------------------------------------------------------------------------
27
+ # Full Image - App code baked in
28
+ # -----------------------------------------------------------------------------
29
+ FROM base AS full-image
30
+ COPY . /app
31
+ RUN npm ci --include=dev
32
+ ENTRYPOINT ["/haven/run-filtered.sh"]
26
33
 
27
- # โœ… Entrypoint
28
- ENTRYPOINT ["/app/run-filtered.sh"]
29
- CMD []
34
+ # -----------------------------------------------------------------------------
35
+ # Thin Image - App code cloned at runtime
36
+ # -----------------------------------------------------------------------------
37
+ FROM base AS thin-image
38
+ # /app is empty - will be filled by ADO clone at runtime
39
+ ENTRYPOINT ["/haven/run-filtered.sh"]