@skyramp/skyramp 1.3.12 → 1.3.14
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/package.json
CHANGED
|
@@ -4,6 +4,18 @@ const path = require('path');
|
|
|
4
4
|
const crypto = require('crypto');
|
|
5
5
|
const { S3Client, GetObjectCommand } = require("@aws-sdk/client-s3");
|
|
6
6
|
|
|
7
|
+
const HEAD_TIMEOUT_MS = 30_000; // 30s for HEAD/ETag check
|
|
8
|
+
const DOWNLOAD_TIMEOUT_MS = 300_000; // 5min for binary download
|
|
9
|
+
const SCRIPT_TIMEOUT_MS = 600_000; // 10min global script timeout
|
|
10
|
+
|
|
11
|
+
// Global script timeout to prevent hanging indefinitely
|
|
12
|
+
const scriptTimer = setTimeout(() => {
|
|
13
|
+
log('error', `Script timed out after ${SCRIPT_TIMEOUT_MS / 1000}s. This is likely a network connectivity issue.`);
|
|
14
|
+
log('error', `Please check your network connection and retry by running: npm install @skyramp/skyramp`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}, SCRIPT_TIMEOUT_MS);
|
|
17
|
+
scriptTimer.unref();
|
|
18
|
+
|
|
7
19
|
function log(level, message) {
|
|
8
20
|
const timestamp = new Date().toISOString();
|
|
9
21
|
console.log(`[${timestamp}] [${level.toUpperCase()}] ${message}`);
|
|
@@ -79,13 +91,22 @@ async function calculateMD5(filePath) {
|
|
|
79
91
|
|
|
80
92
|
async function getETag(url) {
|
|
81
93
|
return new Promise((resolve, reject) => {
|
|
82
|
-
https.get(url, { method: 'HEAD' }, res => {
|
|
94
|
+
const req = https.get(url, { method: 'HEAD', timeout: HEAD_TIMEOUT_MS }, res => {
|
|
83
95
|
if (res.statusCode !== 200) {
|
|
96
|
+
res.resume(); // Drain response to free the socket
|
|
84
97
|
reject(new Error(`HTTP ${res.statusCode}: ${url}`));
|
|
85
98
|
return;
|
|
86
99
|
}
|
|
100
|
+
res.on('error', (err) => {
|
|
101
|
+
reject(new Error(`Response stream error during HEAD request: ${err.message}`));
|
|
102
|
+
});
|
|
87
103
|
resolve(res.headers['etag']?.replace(/"/g, '')); // Remove quotes from ETag
|
|
88
|
-
})
|
|
104
|
+
});
|
|
105
|
+
req.on('timeout', () => {
|
|
106
|
+
req.destroy();
|
|
107
|
+
reject(new Error(`HEAD request timed out after ${HEAD_TIMEOUT_MS / 1000}s: ${url}`));
|
|
108
|
+
});
|
|
109
|
+
req.on('error', (err) => {
|
|
89
110
|
log('error', `Error during HEAD request to ${url}: ${err.message}`);
|
|
90
111
|
reject(err);
|
|
91
112
|
});
|
|
@@ -119,22 +140,61 @@ async function download(url, dest, options = {}) {
|
|
|
119
140
|
});
|
|
120
141
|
} else {
|
|
121
142
|
// Public URL download using https
|
|
122
|
-
|
|
143
|
+
let rejected = false;
|
|
144
|
+
const fail = (err) => {
|
|
145
|
+
if (rejected) return;
|
|
146
|
+
rejected = true;
|
|
147
|
+
// Clean up partial file
|
|
148
|
+
try { fs.unlinkSync(dest); } catch (_) {}
|
|
149
|
+
reject(err);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const req = https.get(url, { timeout: DOWNLOAD_TIMEOUT_MS }, res => {
|
|
123
153
|
if (res.statusCode !== 200) {
|
|
154
|
+
res.resume(); // Drain response to free the socket
|
|
124
155
|
const error = new Error(`HTTP ${res.statusCode}: ${url}`);
|
|
125
156
|
log('error', `Failed to download ${url}: ${error.message}`);
|
|
126
|
-
|
|
157
|
+
fail(error);
|
|
127
158
|
return;
|
|
128
159
|
}
|
|
129
160
|
const file = fs.createWriteStream(dest);
|
|
161
|
+
let downloaded = 0;
|
|
162
|
+
const contentLength = parseInt(res.headers['content-length'], 10);
|
|
163
|
+
let lastLogTime = Date.now();
|
|
164
|
+
|
|
165
|
+
res.on('data', (chunk) => {
|
|
166
|
+
downloaded += chunk.length;
|
|
167
|
+
const now = Date.now();
|
|
168
|
+
if (now - lastLogTime > 10_000) { // Log progress every 10s
|
|
169
|
+
const pct = contentLength ? ` (${Math.round(downloaded / contentLength * 100)}%)` : '';
|
|
170
|
+
log('info', `Downloading ${path.basename(dest)}: ${(downloaded / 1024 / 1024).toFixed(1)} MB${pct}`);
|
|
171
|
+
lastLogTime = now;
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
res.on('error', (err) => {
|
|
176
|
+
file.destroy();
|
|
177
|
+
fail(new Error(`Response stream error: ${err.message}`));
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
file.on('error', (err) => {
|
|
181
|
+
res.destroy();
|
|
182
|
+
fail(new Error(`File write error: ${err.message}`));
|
|
183
|
+
});
|
|
184
|
+
|
|
130
185
|
res.pipe(file);
|
|
131
186
|
file.on('finish', () => {
|
|
132
187
|
log('info', `Successfully downloaded ${url} to ${dest}`);
|
|
133
188
|
file.close(resolve);
|
|
134
189
|
});
|
|
135
|
-
})
|
|
190
|
+
});
|
|
191
|
+
req.on('timeout', () => {
|
|
192
|
+
req.destroy();
|
|
193
|
+
fail(new Error(`Download timed out after ${DOWNLOAD_TIMEOUT_MS / 1000}s: ${url}`));
|
|
194
|
+
});
|
|
195
|
+
req.on('error', (err) => {
|
|
136
196
|
log('error', `Error during download from ${url}: ${err.message}`);
|
|
137
|
-
|
|
197
|
+
fail(err);
|
|
138
198
|
});
|
|
139
199
|
}
|
|
140
200
|
});
|
|
@@ -175,6 +235,23 @@ async function download(url, dest, options = {}) {
|
|
|
175
235
|
}
|
|
176
236
|
|
|
177
237
|
await download(url, file.dest, options);
|
|
238
|
+
|
|
239
|
+
// Verify downloaded file integrity via ETag/MD5
|
|
240
|
+
if (!S3_PRIVATE) {
|
|
241
|
+
try {
|
|
242
|
+
const remoteETag = await getETag(url);
|
|
243
|
+
const localHash = await calculateMD5(file.dest);
|
|
244
|
+
if (remoteETag && remoteETag !== localHash) {
|
|
245
|
+
log('error', `Hash mismatch for ${file.name}: expected ${remoteETag}, got ${localHash}`);
|
|
246
|
+
try { fs.unlinkSync(file.dest); } catch (_) {}
|
|
247
|
+
throw new Error(`Integrity check failed for ${file.name}. Please check your network connection and retry.`);
|
|
248
|
+
}
|
|
249
|
+
} catch (hashErr) {
|
|
250
|
+
if (hashErr.message.startsWith('Integrity check failed')) throw hashErr;
|
|
251
|
+
log('warn', `Could not verify hash for ${file.name}: ${hashErr.message}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
178
255
|
log('info', `✅ Saved ${file.name} to ${file.dest}`);
|
|
179
256
|
} catch (e) {
|
|
180
257
|
log('error', `❌ Failed to process ${file.name}: ${e.message}`);
|
|
@@ -108,8 +108,9 @@ const generateRestTestWrapper = lib.func('generateRestTestWrapper', 'string', [
|
|
|
108
108
|
'bool', // providerMode
|
|
109
109
|
'bool', // consumerMode
|
|
110
110
|
'string', // providerOutput (contract test)
|
|
111
|
-
'string',
|
|
112
|
-
'bool'
|
|
111
|
+
'string', // consumerOutput (contract test)
|
|
112
|
+
'bool', // skipProvisionParents
|
|
113
|
+
'int' // mockPort
|
|
113
114
|
]);
|
|
114
115
|
const generateRestMockWrapper = lib.func('generateRestMockWrapper', 'string', [
|
|
115
116
|
'string', // uri
|
|
@@ -124,17 +125,17 @@ const generateRestMockWrapper = lib.func('generateRestMockWrapper', 'string', [
|
|
|
124
125
|
'string', // k8sNamespace
|
|
125
126
|
'string', // k8sConfig
|
|
126
127
|
'string', // k8sContext
|
|
128
|
+
'string', // requestData
|
|
127
129
|
'string', // responseData
|
|
128
130
|
'string', // responseStatusCode
|
|
129
131
|
'bool', // force
|
|
130
132
|
'bool', // deployDashboard
|
|
131
133
|
'bool', // requestAware
|
|
132
134
|
'string', // formParams
|
|
133
|
-
'string', // pathParams
|
|
134
|
-
'string', // queryParams
|
|
135
135
|
'string', // apiSchema
|
|
136
136
|
'string', // traceFilePath
|
|
137
|
-
'string'
|
|
137
|
+
'string', // entryPoint
|
|
138
|
+
'int' // mockPort
|
|
138
139
|
]);
|
|
139
140
|
const traceCollectWrapper = lib.func('traceCollectWrapper', 'string', ['string', 'string', 'bool', 'string', 'string']);
|
|
140
141
|
const analyzeOpenapiWrapper = lib.func('analyzeOpenapiWrapper', 'string', ['string', 'string']);
|
|
@@ -948,11 +949,12 @@ class SkyrampClient {
|
|
|
948
949
|
parentRequestData || "",
|
|
949
950
|
parentStatusCode || "",
|
|
950
951
|
options.requestAware || false,
|
|
951
|
-
options.providerMode ||
|
|
952
|
+
options.providerMode || false,
|
|
952
953
|
options.consumerMode || false,
|
|
953
954
|
options.providerOutput || "",
|
|
954
955
|
options.consumerOutput || "",
|
|
955
|
-
options.skipProvisionParents ||
|
|
956
|
+
options.skipProvisionParents || false,
|
|
957
|
+
options.mockPort || 0,
|
|
956
958
|
(err, res) => {
|
|
957
959
|
if (err) {
|
|
958
960
|
reject(err);
|
|
@@ -979,17 +981,17 @@ class SkyrampClient {
|
|
|
979
981
|
options.k8sNamespace || "",
|
|
980
982
|
options.k8sConfig || "",
|
|
981
983
|
options.k8sContext || "",
|
|
984
|
+
options.requestData || "",
|
|
982
985
|
options.responseData || "",
|
|
983
986
|
options.responseStatusCode || "",
|
|
984
987
|
options.force || false,
|
|
985
988
|
options.deployDashboard || false,
|
|
986
989
|
options.requestAware || false,
|
|
987
990
|
options.formParams || "",
|
|
988
|
-
options.pathParams || "",
|
|
989
|
-
options.queryParams || "",
|
|
990
991
|
JSON.stringify(options.apiSchema || []),
|
|
991
992
|
options.traceFilePath || "",
|
|
992
993
|
options.entrypoint || "",
|
|
994
|
+
options.mockPort || 0,
|
|
993
995
|
(err, res) => {
|
|
994
996
|
if (err) {
|
|
995
997
|
reject(err);
|
|
@@ -1175,6 +1175,11 @@ class SkyrampPlaywrightPage {
|
|
|
1175
1175
|
return newLocator
|
|
1176
1176
|
}
|
|
1177
1177
|
|
|
1178
|
+
frameLocator(selector) {
|
|
1179
|
+
const originalFrameLocator = this._page.frameLocator(selector);
|
|
1180
|
+
return new SkyrampPlaywrightFrameLocator(this, originalFrameLocator);
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1178
1183
|
locator(selector, options) {
|
|
1179
1184
|
const originalLocator = this._page.locator(selector, options);
|
|
1180
1185
|
return this.newSkyrampPlaywrightLocator(originalLocator, selector, options);
|
|
@@ -1434,5 +1439,6 @@ function expect(obj, testInfo) {
|
|
|
1434
1439
|
|
|
1435
1440
|
module.exports = {
|
|
1436
1441
|
newSkyrampPlaywrightPage,
|
|
1442
|
+
SkyrampPlaywrightPage,
|
|
1437
1443
|
expect,
|
|
1438
1444
|
};
|
package/src/workspace.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export interface Service {
|
|
|
26
26
|
serviceName: string;
|
|
27
27
|
language?: "python" | "typescript" | "javascript" | "java";
|
|
28
28
|
framework?: "playwright" | "pytest" | "robot" | "junit";
|
|
29
|
-
|
|
29
|
+
testDirectory?: string;
|
|
30
30
|
api?: ServiceApi;
|
|
31
31
|
runtimeDetails?: ServiceRuntimeDetails;
|
|
32
32
|
}
|
package/src/workspace.js
CHANGED