stacktape 2.23.0 → 2.24.0-beta.24
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 +283 -0
- package/index.d.ts +5892 -0
- package/index.js +3259 -2
- package/index.js.map +7 -0
- package/package.json +16 -1
- package/sdk.d.ts +26118 -0
- package/sdk.js +16864 -0
- package/sdk.js.map +7 -0
package/bin/stacktape.js
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stacktape CLI launcher
|
|
5
|
+
* Downloads and caches the platform-specific binary on first run
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { spawnSync, execSync } = require('node:child_process');
|
|
9
|
+
const { createWriteStream, existsSync, chmodSync, mkdirSync } = require('node:fs');
|
|
10
|
+
const { get: httpsGet } = require('node:https');
|
|
11
|
+
const { platform, arch, homedir } = require('node:os');
|
|
12
|
+
const { join } = require('node:path');
|
|
13
|
+
|
|
14
|
+
// Get version from package.json
|
|
15
|
+
const PACKAGE_VERSION = require('../package.json').version;
|
|
16
|
+
|
|
17
|
+
const GITHUB_REPO = 'stacktape/stacktape';
|
|
18
|
+
|
|
19
|
+
// Platform detection and mapping
|
|
20
|
+
const PLATFORM_MAP = {
|
|
21
|
+
'win32-x64': { fileName: 'windows.zip', extract: extractZip },
|
|
22
|
+
'linux-x64': { fileName: 'linux.tar.gz', extract: extractTarGz },
|
|
23
|
+
'linux-arm64': { fileName: 'linux-arm.tar.gz', extract: extractTarGz },
|
|
24
|
+
'darwin-x64': { fileName: 'macos.tar.gz', extract: extractTarGz },
|
|
25
|
+
'darwin-arm64': { fileName: 'macos-arm.tar.gz', extract: extractTarGz },
|
|
26
|
+
'linux-x64-musl': { fileName: 'alpine.tar.gz', extract: extractTarGz }
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Detects the current platform
|
|
31
|
+
*/
|
|
32
|
+
function detectPlatform() {
|
|
33
|
+
const currentPlatform = platform();
|
|
34
|
+
const currentArch = arch();
|
|
35
|
+
|
|
36
|
+
// Detect Alpine Linux (uses musl instead of glibc)
|
|
37
|
+
if (currentPlatform === 'linux' && currentArch === 'x64') {
|
|
38
|
+
try {
|
|
39
|
+
const ldd = execSync('ldd --version 2>&1 || true', { encoding: 'utf8' });
|
|
40
|
+
if (ldd.includes('musl')) {
|
|
41
|
+
return 'linux-x64-musl';
|
|
42
|
+
}
|
|
43
|
+
} catch {
|
|
44
|
+
// If ldd fails, assume glibc
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const platformKey = `${currentPlatform}-${currentArch}`;
|
|
49
|
+
|
|
50
|
+
if (!PLATFORM_MAP[platformKey]) {
|
|
51
|
+
console.error(`Error: Unsupported platform ${currentPlatform}-${currentArch}`);
|
|
52
|
+
console.error('Stacktape binaries are available for:');
|
|
53
|
+
Object.keys(PLATFORM_MAP).forEach((key) => {
|
|
54
|
+
console.error(` - ${key}`);
|
|
55
|
+
});
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return platformKey;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Downloads a file from a URL with retry logic
|
|
64
|
+
*/
|
|
65
|
+
async function downloadFile(url, destPath, retries = 3) {
|
|
66
|
+
for (let i = 0; i < retries; i++) {
|
|
67
|
+
try {
|
|
68
|
+
await new Promise((resolve, reject) => {
|
|
69
|
+
const fileStream = createWriteStream(destPath);
|
|
70
|
+
|
|
71
|
+
httpsGet(url, (response) => {
|
|
72
|
+
// Follow redirects
|
|
73
|
+
if (response.statusCode === 301 || response.statusCode === 302) {
|
|
74
|
+
httpsGet(response.headers.location, (redirectResponse) => {
|
|
75
|
+
if (redirectResponse.statusCode !== 200) {
|
|
76
|
+
reject(new Error(`Failed to download: ${redirectResponse.statusCode}`));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const totalBytes = Number.parseInt(redirectResponse.headers['content-length'], 10);
|
|
81
|
+
let downloadedBytes = 0;
|
|
82
|
+
|
|
83
|
+
redirectResponse.on('data', (chunk) => {
|
|
84
|
+
downloadedBytes += chunk.length;
|
|
85
|
+
const percent = totalBytes ? ((downloadedBytes / totalBytes) * 100).toFixed(1) : '?';
|
|
86
|
+
process.stdout.write(`\rDownloading... ${percent}%`);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
redirectResponse.pipe(fileStream);
|
|
90
|
+
fileStream.on('finish', () => {
|
|
91
|
+
fileStream.close();
|
|
92
|
+
process.stdout.write('\n');
|
|
93
|
+
resolve();
|
|
94
|
+
});
|
|
95
|
+
}).on('error', reject);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (response.statusCode !== 200) {
|
|
100
|
+
reject(new Error(`Failed to download: ${response.statusCode}`));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const totalBytes = Number.parseInt(response.headers['content-length'], 10);
|
|
105
|
+
let downloadedBytes = 0;
|
|
106
|
+
|
|
107
|
+
response.on('data', (chunk) => {
|
|
108
|
+
downloadedBytes += chunk.length;
|
|
109
|
+
const percent = totalBytes ? ((downloadedBytes / totalBytes) * 100).toFixed(1) : '?';
|
|
110
|
+
process.stdout.write(`\rDownloading... ${percent}%`);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
response.pipe(fileStream);
|
|
114
|
+
fileStream.on('finish', () => {
|
|
115
|
+
fileStream.close();
|
|
116
|
+
process.stdout.write('\n');
|
|
117
|
+
resolve();
|
|
118
|
+
});
|
|
119
|
+
}).on('error', reject);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
return; // Success
|
|
123
|
+
} catch (error) {
|
|
124
|
+
if (i === retries - 1) {
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
console.info(`\nRetrying download (${i + 1}/${retries})...`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Extracts a tar.gz archive
|
|
134
|
+
*/
|
|
135
|
+
async function extractTarGz(archivePath, destDir) {
|
|
136
|
+
const tar = require('tar');
|
|
137
|
+
await tar.x({
|
|
138
|
+
file: archivePath,
|
|
139
|
+
cwd: destDir
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Extracts a zip archive
|
|
145
|
+
*/
|
|
146
|
+
async function extractZip(archivePath, destDir) {
|
|
147
|
+
const AdmZip = require('adm-zip');
|
|
148
|
+
const zip = new AdmZip(archivePath);
|
|
149
|
+
zip.extractAllTo(destDir, true);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Sets executable permissions on Unix systems
|
|
154
|
+
*/
|
|
155
|
+
function setExecutablePermissions(binDir) {
|
|
156
|
+
if (platform() === 'win32') {
|
|
157
|
+
return; // Windows doesn't need chmod
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const executables = [
|
|
161
|
+
join(binDir, 'stacktape'),
|
|
162
|
+
join(binDir, 'esbuild', 'exec'),
|
|
163
|
+
join(binDir, 'session-manager-plugin', 'smp'),
|
|
164
|
+
join(binDir, 'pack', 'pack'),
|
|
165
|
+
join(binDir, 'nixpacks', 'nixpacks')
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
for (const exe of executables) {
|
|
169
|
+
if (existsSync(exe)) {
|
|
170
|
+
try {
|
|
171
|
+
chmodSync(exe, 0o755);
|
|
172
|
+
} catch {
|
|
173
|
+
// Ignore chmod errors
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Ensures the binary is downloaded and cached
|
|
181
|
+
*/
|
|
182
|
+
async function ensureBinary() {
|
|
183
|
+
const platformKey = detectPlatform();
|
|
184
|
+
const platformInfo = PLATFORM_MAP[platformKey];
|
|
185
|
+
const version = PACKAGE_VERSION;
|
|
186
|
+
|
|
187
|
+
// Cache directory: ~/.stacktape/bin/{version}/
|
|
188
|
+
const cacheDir = join(homedir(), '.stacktape', 'bin', version);
|
|
189
|
+
const binaryName = platform() === 'win32' ? 'stacktape.exe' : 'stacktape';
|
|
190
|
+
const binaryPath = join(cacheDir, binaryName);
|
|
191
|
+
|
|
192
|
+
// Check if binary is already cached
|
|
193
|
+
if (existsSync(binaryPath)) {
|
|
194
|
+
return binaryPath;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
console.info(`Installing Stacktape ${version} for ${platformKey}...`);
|
|
198
|
+
|
|
199
|
+
// Create cache directory
|
|
200
|
+
if (!existsSync(cacheDir)) {
|
|
201
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Download URL
|
|
205
|
+
const downloadUrl = `https://github.com/${GITHUB_REPO}/releases/download/${version}/${platformInfo.fileName}`;
|
|
206
|
+
const archivePath = join(cacheDir, platformInfo.fileName);
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
// Download the archive
|
|
210
|
+
console.info(`Downloading from ${downloadUrl}...`);
|
|
211
|
+
await downloadFile(downloadUrl, archivePath);
|
|
212
|
+
|
|
213
|
+
// Extract the archive
|
|
214
|
+
console.info('Extracting...');
|
|
215
|
+
await platformInfo.extract(archivePath, cacheDir);
|
|
216
|
+
|
|
217
|
+
// Set executable permissions
|
|
218
|
+
setExecutablePermissions(cacheDir);
|
|
219
|
+
|
|
220
|
+
// Remove the archive
|
|
221
|
+
const { unlinkSync } = require('node:fs');
|
|
222
|
+
unlinkSync(archivePath);
|
|
223
|
+
|
|
224
|
+
// Verify the binary exists
|
|
225
|
+
if (!existsSync(binaryPath)) {
|
|
226
|
+
throw new Error(`Binary not found after extraction: ${binaryPath}`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
console.info(`✓ Stacktape ${version} installed successfully`);
|
|
230
|
+
|
|
231
|
+
return binaryPath;
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.error(`
|
|
234
|
+
Error installing Stacktape:
|
|
235
|
+
${error.message}
|
|
236
|
+
|
|
237
|
+
You can also install Stacktape directly using:
|
|
238
|
+
${getManualInstallCommand(platformKey)}`);
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Gets the manual installation command for the platform
|
|
245
|
+
*/
|
|
246
|
+
function getManualInstallCommand(platformKey) {
|
|
247
|
+
const commands = {
|
|
248
|
+
'win32-x64': 'iwr https://installs.stacktape.com/windows.ps1 -useb | iex',
|
|
249
|
+
'linux-x64': 'curl -L https://installs.stacktape.com/linux.sh | sh',
|
|
250
|
+
'linux-arm64': 'curl -L https://installs.stacktape.com/linux-arm.sh | sh',
|
|
251
|
+
'linux-x64-musl': 'curl -L https://installs.stacktape.com/alpine.sh | sh',
|
|
252
|
+
'darwin-x64': 'curl -L https://installs.stacktape.com/macos.sh | sh',
|
|
253
|
+
'darwin-arm64': 'curl -L https://installs.stacktape.com/macos-arm.sh | sh'
|
|
254
|
+
};
|
|
255
|
+
return commands[platformKey] || 'See https://docs.stacktape.com for installation instructions';
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Main execution
|
|
260
|
+
*/
|
|
261
|
+
async function main() {
|
|
262
|
+
try {
|
|
263
|
+
const binaryPath = await ensureBinary();
|
|
264
|
+
const args = process.argv.slice(2);
|
|
265
|
+
|
|
266
|
+
const result = spawnSync(binaryPath, args, {
|
|
267
|
+
stdio: 'inherit',
|
|
268
|
+
env: process.env
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
if (result.error) {
|
|
272
|
+
console.error(`Error executing Stacktape binary: ${result.error.message}`);
|
|
273
|
+
process.exit(1);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
process.exit(result.status || 0);
|
|
277
|
+
} catch (error) {
|
|
278
|
+
console.error('Unexpected error:', error.message);
|
|
279
|
+
process.exit(1);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
main();
|