@dagu-org/dagu 1.17.4 → 1.22.10

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/lib/download.js DELETED
@@ -1,404 +0,0 @@
1
- const https = require('https');
2
- const fs = require('fs');
3
- const path = require('path');
4
- const crypto = require('crypto');
5
- const zlib = require('zlib');
6
- const tar = require('tar');
7
- const { getCachedBinary, cacheBinary, cleanOldCache } = require('./cache');
8
-
9
- // Get package version
10
- const PACKAGE_VERSION = require('../package.json').version;
11
- const GITHUB_RELEASES_URL = 'https://github.com/dagu-org/dagu/releases/download';
12
-
13
- /**
14
- * Map Node.js platform/arch to goreleaser asset names
15
- */
16
- function getAssetName(version) {
17
- const platform = process.platform;
18
- const arch = process.arch;
19
-
20
- // Platform name mapping (matches goreleaser output - lowercase)
21
- const osMap = {
22
- 'darwin': 'darwin',
23
- 'linux': 'linux',
24
- 'win32': 'windows',
25
- 'freebsd': 'freebsd',
26
- 'openbsd': 'openbsd'
27
- };
28
-
29
- // Architecture name mapping (matches goreleaser output)
30
- const archMap = {
31
- 'x64': 'amd64',
32
- 'ia32': '386',
33
- 'arm64': 'arm64',
34
- 'ppc64': 'ppc64le',
35
- 's390x': 's390x'
36
- };
37
-
38
- let osName = osMap[platform] || platform;
39
- let archName = archMap[arch] || arch;
40
-
41
- // Special handling for ARM
42
- if (arch === 'arm' && platform === 'linux') {
43
- const { getArmVariant } = require('./platform');
44
- const variant = getArmVariant();
45
- archName = `armv${variant}`;
46
- }
47
-
48
- // All assets are .tar.gz now (goreleaser changed this)
49
- const ext = '.tar.gz';
50
- return `dagu_${version}_${osName}_${archName}${ext}`;
51
- }
52
-
53
- /**
54
- * Make HTTP request and return buffer (Sentry-style)
55
- */
56
- function makeRequest(url) {
57
- return new Promise((resolve, reject) => {
58
- https
59
- .get(url, (response) => {
60
- if (response.statusCode >= 200 && response.statusCode < 300) {
61
- const chunks = [];
62
- response.on('data', (chunk) => chunks.push(chunk));
63
- response.on('end', () => {
64
- resolve(Buffer.concat(chunks));
65
- });
66
- } else if (
67
- response.statusCode >= 300 &&
68
- response.statusCode < 400 &&
69
- response.headers.location
70
- ) {
71
- // Follow redirects
72
- makeRequest(response.headers.location).then(resolve, reject);
73
- } else {
74
- reject(
75
- new Error(
76
- `Server responded with status code ${response.statusCode} when downloading the package!`
77
- )
78
- );
79
- }
80
- })
81
- .on('error', (error) => {
82
- reject(error);
83
- });
84
- });
85
- }
86
-
87
- /**
88
- * Download file with progress reporting
89
- */
90
- async function downloadFile(url, destination, options = {}) {
91
- const { onProgress, maxRetries = 3 } = options;
92
- let lastError;
93
-
94
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
95
- try {
96
- return await downloadFileAttempt(url, destination, { onProgress, attempt });
97
- } catch (error) {
98
- lastError = error;
99
- if (attempt < maxRetries) {
100
- console.log(`Download failed (attempt ${attempt}/${maxRetries}), retrying...`);
101
- await new Promise(resolve => setTimeout(resolve, 1000 * attempt)); // Exponential backoff
102
- }
103
- }
104
- }
105
-
106
- throw lastError;
107
- }
108
-
109
- /**
110
- * Single download attempt
111
- */
112
- function downloadFileAttempt(url, destination, options = {}) {
113
- const { onProgress, attempt = 1 } = options;
114
-
115
- return new Promise((resolve, reject) => {
116
- const tempFile = `${destination}.download.${process.pid}.tmp`;
117
-
118
- https.get(url, (response) => {
119
- // Handle redirects
120
- if (response.statusCode === 301 || response.statusCode === 302) {
121
- const redirectUrl = response.headers.location;
122
- if (!redirectUrl) {
123
- reject(new Error('Redirect location not provided'));
124
- return;
125
- }
126
- downloadFileAttempt(redirectUrl, destination, options)
127
- .then(resolve)
128
- .catch(reject);
129
- return;
130
- }
131
-
132
- if (response.statusCode !== 200) {
133
- reject(new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`));
134
- return;
135
- }
136
-
137
- const totalSize = parseInt(response.headers['content-length'], 10);
138
- let downloadedSize = 0;
139
-
140
- const fileStream = fs.createWriteStream(tempFile);
141
-
142
- response.on('data', (chunk) => {
143
- downloadedSize += chunk.length;
144
- if (onProgress && totalSize) {
145
- const percentage = Math.round((downloadedSize / totalSize) * 100);
146
- onProgress(percentage, downloadedSize, totalSize);
147
- }
148
- });
149
-
150
- response.pipe(fileStream);
151
-
152
- fileStream.on('finish', () => {
153
- fileStream.close(() => {
154
- // Move temp file to final destination
155
- fs.renameSync(tempFile, destination);
156
- resolve();
157
- });
158
- });
159
-
160
- fileStream.on('error', (err) => {
161
- fs.unlinkSync(tempFile);
162
- reject(err);
163
- });
164
- }).on('error', (err) => {
165
- if (fs.existsSync(tempFile)) {
166
- fs.unlinkSync(tempFile);
167
- }
168
- reject(err);
169
- });
170
- });
171
- }
172
-
173
- /**
174
- * Extract archive based on file extension
175
- */
176
- async function extractArchive(archivePath, outputDir) {
177
- const ext = path.extname(archivePath).toLowerCase();
178
-
179
- if (ext === '.gz' || archivePath.endsWith('.tar.gz')) {
180
- // Extract tar.gz
181
- await tar.extract({
182
- file: archivePath,
183
- cwd: outputDir,
184
- filter: (path) => path === 'dagu' || path === 'dagu.exe'
185
- });
186
- } else if (ext === '.zip') {
187
- // For Windows, we need a zip extractor
188
- // Using built-in Windows extraction via PowerShell
189
- const { execSync } = require('child_process');
190
- const command = `powershell -command "Expand-Archive -Path '${archivePath}' -DestinationPath '${outputDir}' -Force"`;
191
- execSync(command);
192
- } else {
193
- throw new Error(`Unsupported archive format: ${ext}`);
194
- }
195
- }
196
-
197
- /**
198
- * Download and verify checksums
199
- */
200
- async function downloadChecksums(version) {
201
- const checksumsUrl = `${GITHUB_RELEASES_URL}/v${version}/checksums.txt`;
202
- const tempFile = path.join(require('os').tmpdir(), `dagu-checksums-${process.pid}.txt`);
203
-
204
- try {
205
- await downloadFile(checksumsUrl, tempFile);
206
- const content = fs.readFileSync(tempFile, 'utf8');
207
-
208
- // Parse checksums file
209
- const checksums = {};
210
- content.split('\n').forEach(line => {
211
- const match = line.match(/^([a-f0-9]{64})\s+(.+)$/);
212
- if (match) {
213
- checksums[match[2]] = match[1];
214
- }
215
- });
216
-
217
- return checksums;
218
- } finally {
219
- if (fs.existsSync(tempFile)) {
220
- fs.unlinkSync(tempFile);
221
- }
222
- }
223
- }
224
-
225
- /**
226
- * Verify file checksum
227
- */
228
- function verifyChecksum(filePath, expectedChecksum) {
229
- return new Promise((resolve, reject) => {
230
- const hash = crypto.createHash('sha256');
231
- const stream = fs.createReadStream(filePath);
232
-
233
- stream.on('data', (data) => hash.update(data));
234
- stream.on('end', () => {
235
- const actualChecksum = hash.digest('hex');
236
- if (actualChecksum === expectedChecksum) {
237
- resolve(true);
238
- } else {
239
- reject(new Error(`Checksum mismatch: expected ${expectedChecksum}, got ${actualChecksum}`));
240
- }
241
- });
242
- stream.on('error', reject);
243
- });
244
- }
245
-
246
- /**
247
- * Extract file from npm tarball (aligned with Sentry approach)
248
- */
249
- function extractFileFromTarball(tarballBuffer, filepath) {
250
- // Tar archives are organized in 512 byte blocks.
251
- // Blocks can either be header blocks or data blocks.
252
- // Header blocks contain file names of the archive in the first 100 bytes, terminated by a null byte.
253
- // The size of a file is contained in bytes 124-135 of a header block and in octal format.
254
- // The following blocks will be data blocks containing the file.
255
- let offset = 0;
256
- while (offset < tarballBuffer.length) {
257
- const header = tarballBuffer.subarray(offset, offset + 512);
258
- offset += 512;
259
-
260
- const fileName = header.toString('utf-8', 0, 100).replace(/\0.*/g, '');
261
- const fileSize = parseInt(header.toString('utf-8', 124, 136).replace(/\0.*/g, ''), 8);
262
-
263
- if (fileName === filepath) {
264
- return tarballBuffer.subarray(offset, offset + fileSize);
265
- }
266
-
267
- // Clamp offset to the upper multiple of 512
268
- offset = (offset + fileSize + 511) & ~511;
269
- }
270
-
271
- throw new Error(`File ${filepath} not found in tarball`);
272
- }
273
-
274
- /**
275
- * Download binary from npm registry (Sentry-style)
276
- */
277
- async function downloadBinaryFromNpm(version) {
278
- const { getPlatformPackage } = require('./platform');
279
- const platformPackage = getPlatformPackage();
280
-
281
- if (!platformPackage) {
282
- throw new Error('Platform not supported!');
283
- }
284
-
285
- const packageName = platformPackage.replace('@dagu-org/', '');
286
- const binaryName = process.platform === 'win32' ? 'dagu.exe' : 'dagu';
287
-
288
- console.log(`Downloading ${platformPackage} from npm registry...`);
289
-
290
- // Download the tarball of the right binary distribution package
291
- const tarballUrl = `https://registry.npmjs.org/${platformPackage}/-/${packageName}-${version}.tgz`;
292
- const tarballDownloadBuffer = await makeRequest(tarballUrl);
293
- const tarballBuffer = zlib.unzipSync(tarballDownloadBuffer);
294
-
295
- // Extract binary from package
296
- const binaryData = extractFileFromTarball(tarballBuffer, `package/bin/${binaryName}`);
297
-
298
- return binaryData;
299
- }
300
-
301
- /**
302
- * Main download function
303
- */
304
- async function downloadBinary(destination, options = {}) {
305
- const version = options.version || PACKAGE_VERSION;
306
- const { method = 'auto', useCache = true } = options;
307
- const platformKey = `${process.platform}-${process.arch}`;
308
-
309
- console.log(`Installing Dagu v${version} for ${platformKey}...`);
310
-
311
- // Check cache first
312
- if (useCache) {
313
- const cachedBinary = getCachedBinary(version, platformKey);
314
- if (cachedBinary) {
315
- console.log('✓ Using cached binary');
316
- fs.copyFileSync(cachedBinary, destination);
317
- if (process.platform !== 'win32') {
318
- fs.chmodSync(destination, 0o755);
319
- }
320
- // Clean old cache entries
321
- cleanOldCache();
322
- return;
323
- }
324
- }
325
-
326
- try {
327
- let binaryData;
328
-
329
- if (method === 'npm' || method === 'auto') {
330
- // Try npm registry first (following Sentry's approach)
331
- try {
332
- binaryData = await downloadBinaryFromNpm(version);
333
- console.log('✓ Downloaded from npm registry');
334
- } catch (npmError) {
335
- if (method === 'npm') {
336
- throw npmError;
337
- }
338
- console.log('npm registry download failed, trying GitHub releases...');
339
- }
340
- }
341
-
342
- if (!binaryData && (method === 'github' || method === 'auto')) {
343
- // Fallback to GitHub releases
344
- const assetName = getAssetName(version);
345
- const downloadUrl = `${GITHUB_RELEASES_URL}/v${version}/${assetName}`;
346
-
347
- const tempFile = path.join(require('os').tmpdir(), `dagu-${process.pid}-${Date.now()}.tmp`);
348
-
349
- try {
350
- await downloadFile(downloadUrl, tempFile, {
351
- onProgress: (percentage, downloaded, total) => {
352
- const mb = (size) => (size / 1024 / 1024).toFixed(2);
353
- process.stdout.write(`\rProgress: ${percentage}% (${mb(downloaded)}MB / ${mb(total)}MB)`);
354
- }
355
- });
356
- console.log('\n✓ Downloaded from GitHub releases');
357
-
358
- // Extract from archive
359
- const binaryName = process.platform === 'win32' ? 'dagu.exe' : 'dagu';
360
-
361
- // All files are .tar.gz now
362
- const archiveData = fs.readFileSync(tempFile);
363
- const tarData = zlib.gunzipSync(archiveData);
364
- binaryData = extractFileFromTarball(tarData, binaryName);
365
- } finally {
366
- if (fs.existsSync(tempFile)) {
367
- fs.unlinkSync(tempFile);
368
- }
369
- }
370
- }
371
-
372
- if (!binaryData) {
373
- throw new Error('Failed to download binary from any source');
374
- }
375
-
376
- // Write binary to destination
377
- const dir = path.dirname(destination);
378
- if (!fs.existsSync(dir)) {
379
- fs.mkdirSync(dir, { recursive: true });
380
- }
381
-
382
- fs.writeFileSync(destination, binaryData, { mode: 0o755 });
383
- console.log('✓ Binary installed successfully');
384
-
385
- // Cache the binary for future use
386
- if (useCache) {
387
- try {
388
- cacheBinary(destination, version, platformKey);
389
- console.log('✓ Binary cached for future installations');
390
- } catch (e) {
391
- // Caching failed, but installation succeeded
392
- }
393
- }
394
-
395
- } catch (error) {
396
- throw new Error(`Failed to download binary: ${error.message}`);
397
- }
398
- }
399
-
400
- module.exports = {
401
- downloadBinary,
402
- downloadBinaryFromNpm,
403
- getAssetName
404
- };
package/lib/validate.js DELETED
@@ -1,65 +0,0 @@
1
- const { spawn } = require('child_process');
2
- const fs = require('fs');
3
-
4
- /**
5
- * Validate that the binary is executable and returns expected output
6
- * @param {string} binaryPath Path to the binary
7
- * @returns {Promise<boolean>} True if valid, false otherwise
8
- */
9
- async function validateBinary(binaryPath) {
10
- // Check if file exists
11
- if (!fs.existsSync(binaryPath)) {
12
- return false;
13
- }
14
-
15
- // Check if file is executable (on Unix-like systems)
16
- if (process.platform !== 'win32') {
17
- try {
18
- fs.accessSync(binaryPath, fs.constants.X_OK);
19
- } catch (e) {
20
- console.error('Binary is not executable');
21
- return false;
22
- }
23
- }
24
-
25
- // Try to run the binary with --version flag
26
- return new Promise((resolve) => {
27
- const proc = spawn(binaryPath, ['--version'], {
28
- timeout: 5000, // 5 second timeout
29
- windowsHide: true
30
- });
31
-
32
- let stdout = '';
33
- let stderr = '';
34
-
35
- proc.stdout.on('data', (data) => {
36
- stdout += data.toString();
37
- });
38
-
39
- proc.stderr.on('data', (data) => {
40
- stderr += data.toString();
41
- });
42
-
43
- proc.on('error', (error) => {
44
- console.error('Failed to execute binary:', error.message);
45
- resolve(false);
46
- });
47
-
48
- proc.on('close', (code) => {
49
- // Check if the binary executed successfully and returned version info
50
- if (code === 0 && stdout.toLowerCase().includes('dagu')) {
51
- resolve(true);
52
- } else {
53
- console.error(`Binary validation failed: exit code ${code}`);
54
- if (stderr) {
55
- console.error('stderr:', stderr);
56
- }
57
- resolve(false);
58
- }
59
- });
60
- });
61
- }
62
-
63
- module.exports = {
64
- validateBinary
65
- };