stacktape 3.5.7 → 3.5.8
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/bin/stacktape.js +206 -29
- package/package.json +1 -1
package/bin/stacktape.js
CHANGED
|
@@ -14,7 +14,11 @@ const {
|
|
|
14
14
|
accessSync,
|
|
15
15
|
constants,
|
|
16
16
|
unlinkSync,
|
|
17
|
-
readFileSync
|
|
17
|
+
readFileSync,
|
|
18
|
+
writeFileSync,
|
|
19
|
+
readdirSync,
|
|
20
|
+
rmSync,
|
|
21
|
+
statSync
|
|
18
22
|
} = require('node:fs');
|
|
19
23
|
const { get: httpsGet } = require('node:https');
|
|
20
24
|
const { platform, arch, homedir } = require('node:os');
|
|
@@ -33,6 +37,18 @@ const PLATFORM_MAP = {
|
|
|
33
37
|
'linux-x64-musl': { fileName: 'alpine.tar.gz', extract: extractTarGz }
|
|
34
38
|
};
|
|
35
39
|
|
|
40
|
+
const REQUIRED_HELPER_LAMBDA_PREFIXES = [
|
|
41
|
+
'stacktapeServiceLambda',
|
|
42
|
+
'cdnOriginRequestLambda',
|
|
43
|
+
'cdnOriginResponseLambda',
|
|
44
|
+
'batchJobTriggerLambda'
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
const INSTALL_MARKER_FILE_NAME = '.stacktape-install.json';
|
|
48
|
+
const INSTALL_LOCK_DIR_SUFFIX = '.stacktape-install.lock';
|
|
49
|
+
const LOCK_WAIT_TIMEOUT_MS = 120000;
|
|
50
|
+
const STALE_LOCK_TIMEOUT_MS = 300000;
|
|
51
|
+
|
|
36
52
|
// ANSI color codes
|
|
37
53
|
const colors = {
|
|
38
54
|
reset: '\x1B[0m',
|
|
@@ -200,55 +216,207 @@ async function ensureBinary() {
|
|
|
200
216
|
|
|
201
217
|
const binaryName = platform() === 'win32' ? 'stacktape.exe' : 'stacktape';
|
|
202
218
|
|
|
219
|
+
const localCacheDir = join(__dirname, '..', 'bin');
|
|
220
|
+
|
|
203
221
|
let cacheDir;
|
|
204
222
|
try {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
cacheDir = localDir;
|
|
223
|
+
mkdirSync(localCacheDir, { recursive: true });
|
|
224
|
+
accessSync(localCacheDir, constants.W_OK);
|
|
225
|
+
cacheDir = localCacheDir;
|
|
209
226
|
} catch {
|
|
210
227
|
cacheDir = join(homedir(), '.stacktape', 'bin', version);
|
|
211
228
|
}
|
|
212
229
|
|
|
213
230
|
const binaryPath = join(cacheDir, binaryName);
|
|
231
|
+
const preserveLauncherScript = cacheDir === localCacheDir;
|
|
214
232
|
|
|
215
|
-
|
|
216
|
-
return binaryPath;
|
|
217
|
-
}
|
|
233
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
218
234
|
|
|
219
|
-
|
|
235
|
+
const lockDirPath = `${cacheDir}${INSTALL_LOCK_DIR_SUFFIX}`;
|
|
220
236
|
|
|
221
|
-
|
|
237
|
+
const isHelperLambdasCacheComplete = () => {
|
|
238
|
+
const helperLambdasDir = join(cacheDir, 'helper-lambdas');
|
|
239
|
+
if (!existsSync(helperLambdasDir)) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
222
242
|
|
|
223
|
-
|
|
224
|
-
|
|
243
|
+
try {
|
|
244
|
+
const files = readdirSync(helperLambdasDir);
|
|
245
|
+
return REQUIRED_HELPER_LAMBDA_PREFIXES.every((prefix) =>
|
|
246
|
+
files.some((fileName) => fileName.startsWith(`${prefix}-`) && fileName.endsWith('.zip'))
|
|
247
|
+
);
|
|
248
|
+
} catch {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
};
|
|
225
252
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
253
|
+
const isCacheValid = () => {
|
|
254
|
+
if (!existsSync(binaryPath)) {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
229
257
|
|
|
230
|
-
|
|
231
|
-
|
|
258
|
+
if (!isHelperLambdasCacheComplete()) {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
232
261
|
|
|
233
|
-
|
|
262
|
+
const markerPath = join(cacheDir, INSTALL_MARKER_FILE_NAME);
|
|
263
|
+
if (!existsSync(markerPath)) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
234
266
|
|
|
235
|
-
|
|
267
|
+
try {
|
|
268
|
+
const parsedMarker = JSON.parse(readFileSync(markerPath, 'utf8'));
|
|
269
|
+
return parsedMarker.version === version && parsedMarker.platformKey === platformKey;
|
|
270
|
+
} catch {
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
};
|
|
236
274
|
|
|
237
|
-
|
|
238
|
-
|
|
275
|
+
const writeInstallMarker = () => {
|
|
276
|
+
const markerPath = join(cacheDir, INSTALL_MARKER_FILE_NAME);
|
|
277
|
+
writeFileSync(
|
|
278
|
+
markerPath,
|
|
279
|
+
JSON.stringify(
|
|
280
|
+
{
|
|
281
|
+
version,
|
|
282
|
+
platformKey,
|
|
283
|
+
helperLambdas: REQUIRED_HELPER_LAMBDA_PREFIXES,
|
|
284
|
+
installedAt: new Date().toISOString()
|
|
285
|
+
},
|
|
286
|
+
null,
|
|
287
|
+
2
|
|
288
|
+
)
|
|
289
|
+
);
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const cleanupExtractedCache = () => {
|
|
293
|
+
if (!existsSync(cacheDir)) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
try {
|
|
298
|
+
const entries = readdirSync(cacheDir);
|
|
299
|
+
for (const entry of entries) {
|
|
300
|
+
if (preserveLauncherScript && entry === 'stacktape.js') {
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
rmSync(join(cacheDir, entry), { recursive: true, force: true });
|
|
304
|
+
}
|
|
305
|
+
} catch {
|
|
306
|
+
// Ignore cleanup errors and try reinstall anyway
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const acquireInstallLock = async () => {
|
|
311
|
+
const start = Date.now();
|
|
312
|
+
while (true) {
|
|
313
|
+
try {
|
|
314
|
+
mkdirSync(lockDirPath);
|
|
315
|
+
return;
|
|
316
|
+
} catch (error) {
|
|
317
|
+
if (error.code !== 'EEXIST') {
|
|
318
|
+
throw error;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
try {
|
|
322
|
+
const lockStats = statSync(lockDirPath);
|
|
323
|
+
const lockAge = Date.now() - lockStats.mtimeMs;
|
|
324
|
+
if (lockAge > STALE_LOCK_TIMEOUT_MS) {
|
|
325
|
+
rmSync(lockDirPath, { recursive: true, force: true });
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
} catch {
|
|
329
|
+
// Lock directory disappeared between checks
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (Date.now() - start > LOCK_WAIT_TIMEOUT_MS) {
|
|
333
|
+
throw new Error('Timed out waiting for Stacktape binary installation lock');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
await sleep(200);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const releaseInstallLock = () => {
|
|
342
|
+
try {
|
|
343
|
+
rmSync(lockDirPath, { recursive: true, force: true });
|
|
344
|
+
} catch {
|
|
345
|
+
// Ignore release lock errors
|
|
239
346
|
}
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const installBinary = async () => {
|
|
350
|
+
console.info(`${colors.dim}Installing Stacktape ${version} for ${platformKey}...${colors.reset}`);
|
|
351
|
+
|
|
352
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
353
|
+
|
|
354
|
+
const downloadUrl = `https://github.com/${GITHUB_REPO}/releases/download/${version}/${platformInfo.fileName}`;
|
|
355
|
+
const archivePath = join(cacheDir, `.download-${Date.now()}-${platformInfo.fileName}`);
|
|
356
|
+
|
|
357
|
+
try {
|
|
358
|
+
console.info(`${colors.dim}Downloading from GitHub releases...${colors.reset}`);
|
|
359
|
+
await downloadFile(downloadUrl, archivePath);
|
|
360
|
+
|
|
361
|
+
console.info(`${colors.dim}Extracting...${colors.reset}`);
|
|
362
|
+
await platformInfo.extract(archivePath, cacheDir);
|
|
240
363
|
|
|
241
|
-
|
|
364
|
+
setExecutablePermissions(cacheDir);
|
|
242
365
|
|
|
366
|
+
unlinkSync(archivePath);
|
|
367
|
+
|
|
368
|
+
if (!existsSync(binaryPath)) {
|
|
369
|
+
throw new Error(`Binary not found after extraction: ${binaryPath}`);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (!isHelperLambdasCacheComplete()) {
|
|
373
|
+
throw new Error('Incomplete installation: helper lambdas were not extracted correctly');
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
writeInstallMarker();
|
|
377
|
+
printLogo();
|
|
378
|
+
|
|
379
|
+
return binaryPath;
|
|
380
|
+
} catch (error) {
|
|
381
|
+
try {
|
|
382
|
+
if (existsSync(archivePath)) {
|
|
383
|
+
unlinkSync(archivePath);
|
|
384
|
+
}
|
|
385
|
+
} catch {
|
|
386
|
+
// Ignore archive cleanup errors
|
|
387
|
+
}
|
|
388
|
+
throw error;
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
if (isCacheValid()) {
|
|
243
393
|
return binaryPath;
|
|
244
|
-
}
|
|
245
|
-
console.error(`
|
|
246
|
-
${colors.red}Error installing Stacktape:${colors.reset}
|
|
247
|
-
${error.message}
|
|
394
|
+
}
|
|
248
395
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
396
|
+
await acquireInstallLock();
|
|
397
|
+
|
|
398
|
+
try {
|
|
399
|
+
if (isCacheValid()) {
|
|
400
|
+
return binaryPath;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
let lastError;
|
|
404
|
+
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
405
|
+
try {
|
|
406
|
+
cleanupExtractedCache();
|
|
407
|
+
return await installBinary();
|
|
408
|
+
} catch (error) {
|
|
409
|
+
lastError = error;
|
|
410
|
+
cleanupExtractedCache();
|
|
411
|
+
if (attempt < 2) {
|
|
412
|
+
console.info(`\n${colors.dim}Retrying installation (${attempt + 1}/2)...${colors.reset}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
throw lastError;
|
|
418
|
+
} finally {
|
|
419
|
+
releaseInstallLock();
|
|
252
420
|
}
|
|
253
421
|
}
|
|
254
422
|
|
|
@@ -269,6 +437,7 @@ function getGlobalBinaryPathIfVersionMatches() {
|
|
|
269
437
|
const globalDir = join(homedir(), '.stacktape', 'bin');
|
|
270
438
|
const globalBinaryPath = join(globalDir, binaryName);
|
|
271
439
|
const releaseDataPath = join(globalDir, 'release-data.json');
|
|
440
|
+
const globalHelperLambdasDir = join(globalDir, 'helper-lambdas');
|
|
272
441
|
|
|
273
442
|
if (!existsSync(globalBinaryPath) || !existsSync(releaseDataPath)) {
|
|
274
443
|
return null;
|
|
@@ -279,6 +448,14 @@ function getGlobalBinaryPathIfVersionMatches() {
|
|
|
279
448
|
if (version !== PACKAGE_VERSION) {
|
|
280
449
|
return null;
|
|
281
450
|
}
|
|
451
|
+
|
|
452
|
+
const files = readdirSync(globalHelperLambdasDir);
|
|
453
|
+
const hasAllHelperLambdas = REQUIRED_HELPER_LAMBDA_PREFIXES.every((prefix) =>
|
|
454
|
+
files.some((fileName) => fileName.startsWith(`${prefix}-`) && fileName.endsWith('.zip'))
|
|
455
|
+
);
|
|
456
|
+
if (!hasAllHelperLambdas) {
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
282
459
|
} catch {
|
|
283
460
|
return null;
|
|
284
461
|
}
|