@limrun/api 0.33.2 → 0.33.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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.33.3 (2026-06-23)
4
+
5
+ Full Changelog: [v0.33.2...v0.33.3](https://github.com/limrun-inc/typescript-sdk/compare/v0.33.2...v0.33.3)
6
+
3
7
  ## 0.33.2 (2026-06-21)
4
8
 
5
9
  Full Changelog: [v0.33.1...v0.33.2](https://github.com/limrun-inc/typescript-sdk/compare/v0.33.1...v0.33.2)
@@ -0,0 +1,19 @@
1
+ type LogFn = (level: 'debug' | 'info' | 'warn' | 'error', msg: string) => void;
2
+ export type PreparedAppBundle = {
3
+ appPath: string;
4
+ cacheIdentityPath: string;
5
+ isArchive: boolean;
6
+ archivePath?: string;
7
+ };
8
+ export type ArchiveWatcher = {
9
+ close: () => void;
10
+ };
11
+ export declare function extractAppArchiveToStablePath(archivePath: string): Promise<PreparedAppBundle>;
12
+ export declare function prepareAppBundlePath(inputPath: string): Promise<PreparedAppBundle>;
13
+ export declare function watchAppArchive(opts: {
14
+ archivePath: string;
15
+ log?: LogFn;
16
+ onExtracted?: () => void | Promise<void>;
17
+ }): ArchiveWatcher;
18
+ export {};
19
+ //# sourceMappingURL=app-archive.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-archive.d.mts","sourceRoot":"","sources":["src/app-archive.ts"],"names":[],"mappings":"AAOA,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;AAE/E,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB,CAAC;AAkKF,wBAAsB,6BAA6B,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAgDnG;AAED,wBAAsB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAcxF;AA4BD,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,KAAK,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C,GAAG,cAAc,CAoEjB"}
@@ -0,0 +1,19 @@
1
+ type LogFn = (level: 'debug' | 'info' | 'warn' | 'error', msg: string) => void;
2
+ export type PreparedAppBundle = {
3
+ appPath: string;
4
+ cacheIdentityPath: string;
5
+ isArchive: boolean;
6
+ archivePath?: string;
7
+ };
8
+ export type ArchiveWatcher = {
9
+ close: () => void;
10
+ };
11
+ export declare function extractAppArchiveToStablePath(archivePath: string): Promise<PreparedAppBundle>;
12
+ export declare function prepareAppBundlePath(inputPath: string): Promise<PreparedAppBundle>;
13
+ export declare function watchAppArchive(opts: {
14
+ archivePath: string;
15
+ log?: LogFn;
16
+ onExtracted?: () => void | Promise<void>;
17
+ }): ArchiveWatcher;
18
+ export {};
19
+ //# sourceMappingURL=app-archive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-archive.d.ts","sourceRoot":"","sources":["src/app-archive.ts"],"names":[],"mappings":"AAOA,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;AAE/E,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB,CAAC;AAkKF,wBAAsB,6BAA6B,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAgDnG;AAED,wBAAsB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAcxF;AA4BD,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,KAAK,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C,GAAG,cAAc,CAoEjB"}
package/app-archive.js ADDED
@@ -0,0 +1,296 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractAppArchiveToStablePath = extractAppArchiveToStablePath;
4
+ exports.prepareAppBundlePath = prepareAppBundlePath;
5
+ exports.watchAppArchive = watchAppArchive;
6
+ const tslib_1 = require("./internal/tslib.js");
7
+ const crypto_1 = tslib_1.__importDefault(require("crypto"));
8
+ const fs_1 = tslib_1.__importDefault(require("fs"));
9
+ const os_1 = tslib_1.__importDefault(require("os"));
10
+ const path_1 = tslib_1.__importDefault(require("path"));
11
+ const promises_1 = require("stream/promises");
12
+ const yauzl = tslib_1.__importStar(require("yauzl"));
13
+ const noopLogger = () => { };
14
+ function sleep(ms) {
15
+ return new Promise((resolve) => setTimeout(resolve, ms));
16
+ }
17
+ function stableArchiveRoot(archivePath) {
18
+ const resolved = path_1.default.resolve(archivePath);
19
+ const hash = crypto_1.default.createHash('sha1').update(resolved).digest('hex').slice(0, 12);
20
+ const base = path_1.default
21
+ .basename(resolved, path_1.default.extname(resolved))
22
+ .replace(/[^A-Za-z0-9._-]+/g, '-')
23
+ .replace(/^-+|-+$/g, '');
24
+ const safeBase = base === '' ? 'app' : base;
25
+ return path_1.default.join(os_1.default.tmpdir(), `limrun-sync-app-${safeBase}-${hash}`);
26
+ }
27
+ function stableAppPath(archivePath) {
28
+ return path_1.default.join(stableArchiveRoot(archivePath), 'Extracted.app');
29
+ }
30
+ function toZipPath(name) {
31
+ return name.replace(/\\/g, '/');
32
+ }
33
+ function isUnsafeZipPath(name) {
34
+ if (name.includes('\0'))
35
+ return true;
36
+ if (path_1.default.posix.isAbsolute(name))
37
+ return true;
38
+ return name.split('/').some((part) => part === '..');
39
+ }
40
+ function entryMode(entry) {
41
+ return (entry.externalFileAttributes >>> 16) & 0xffff;
42
+ }
43
+ function isSymlinkEntry(entry) {
44
+ return (entryMode(entry) & 0o170000) === 0o120000;
45
+ }
46
+ function isDirectoryEntry(entry) {
47
+ return entry.fileName.endsWith('/');
48
+ }
49
+ function entryPerm(entry, fallback) {
50
+ return entryMode(entry) & 0o7777 || fallback;
51
+ }
52
+ async function openZip(archivePath) {
53
+ try {
54
+ return await yauzl.openPromise(archivePath, {
55
+ autoClose: false,
56
+ lazyEntries: true,
57
+ strictFileNames: true,
58
+ validateEntrySizes: true,
59
+ });
60
+ }
61
+ catch (err) {
62
+ const message = err instanceof Error ? `: ${err.message}` : '';
63
+ throw new Error(`The path is not a valid app bundle directory or ZIP/IPA archive: ${archivePath}${message}`);
64
+ }
65
+ }
66
+ async function readEntries(zip) {
67
+ const entries = [];
68
+ for await (const entry of zip.eachEntry()) {
69
+ entries.push(entry);
70
+ }
71
+ return entries;
72
+ }
73
+ async function ensureDirectoryNotSymlink(dir) {
74
+ const stat = await fs_1.default.promises.lstat(dir).catch((err) => {
75
+ if (err.code === 'ENOENT')
76
+ return null;
77
+ throw err;
78
+ });
79
+ if (stat && (!stat.isDirectory() || stat.isSymbolicLink())) {
80
+ await fs_1.default.promises.rm(dir, { recursive: true, force: true });
81
+ }
82
+ await fs_1.default.promises.mkdir(dir, { recursive: true });
83
+ }
84
+ function discoverPayloadApp(entries, archivePath) {
85
+ const appNames = new Set();
86
+ const appsWithInfoPlist = new Set();
87
+ for (const entry of entries) {
88
+ const name = toZipPath(entry.fileName);
89
+ if (isUnsafeZipPath(name)) {
90
+ throw new Error(`ZIP entry has an unsafe path: ${entry.fileName}`);
91
+ }
92
+ if (isDirectoryEntry(entry)) {
93
+ continue;
94
+ }
95
+ const rest = name.startsWith('Payload/') ? name.slice('Payload/'.length) : '';
96
+ if (rest === '') {
97
+ continue;
98
+ }
99
+ const [appName, ...parts] = rest.split('/');
100
+ if (!appName?.endsWith('.app')) {
101
+ continue;
102
+ }
103
+ appNames.add(appName);
104
+ if (parts.join('/') === 'Info.plist') {
105
+ appsWithInfoPlist.add(appName);
106
+ }
107
+ }
108
+ if (appNames.size === 0) {
109
+ throw new Error(`ZIP/IPA archive contains no Payload/*.app bundle: ${archivePath}`);
110
+ }
111
+ if (appNames.size > 1) {
112
+ throw new Error(`ZIP/IPA archive contains more than one Payload/*.app bundle (${[...appNames].join(', ')}); expected exactly one`);
113
+ }
114
+ const appName = [...appNames][0];
115
+ if (!appsWithInfoPlist.has(appName)) {
116
+ throw new Error(`ZIP/IPA archive app bundle is missing Info.plist: Payload/${appName}/Info.plist`);
117
+ }
118
+ return appName;
119
+ }
120
+ async function extractEntry(zip, entry, target, appRoot) {
121
+ if (isDirectoryEntry(entry)) {
122
+ await fs_1.default.promises.mkdir(target, { recursive: true, mode: entryPerm(entry, 0o755) });
123
+ return;
124
+ }
125
+ await fs_1.default.promises.mkdir(path_1.default.dirname(target), { recursive: true });
126
+ if (isSymlinkEntry(entry)) {
127
+ const stream = await zip.openReadStreamPromise(entry);
128
+ const chunks = [];
129
+ for await (const chunk of stream) {
130
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
131
+ }
132
+ const linkTarget = Buffer.concat(chunks).toString('utf8');
133
+ const resolved = path_1.default.resolve(path_1.default.dirname(target), linkTarget);
134
+ const appRootResolved = path_1.default.resolve(appRoot);
135
+ if (resolved !== appRootResolved && !resolved.startsWith(appRootResolved + path_1.default.sep)) {
136
+ throw new Error(`ZIP/IPA symlink escapes app bundle: ${entry.fileName} -> ${linkTarget}`);
137
+ }
138
+ await fs_1.default.promises.rm(target, { force: true });
139
+ await fs_1.default.promises.symlink(linkTarget, target);
140
+ return;
141
+ }
142
+ const stream = await zip.openReadStreamPromise(entry);
143
+ await (0, promises_1.pipeline)(stream, fs_1.default.createWriteStream(target, { mode: entryPerm(entry, 0o644) }));
144
+ await fs_1.default.promises.chmod(target, entryPerm(entry, 0o644));
145
+ }
146
+ async function extractAppArchiveToStablePath(archivePath) {
147
+ const resolvedArchivePath = path_1.default.resolve(archivePath);
148
+ const appPath = stableAppPath(resolvedArchivePath);
149
+ const stableRoot = path_1.default.dirname(appPath);
150
+ await ensureDirectoryNotSymlink(stableRoot);
151
+ const zip = await openZip(resolvedArchivePath);
152
+ try {
153
+ const entries = await readEntries(zip);
154
+ const appName = discoverPayloadApp(entries, resolvedArchivePath);
155
+ const payloadPrefix = `Payload/${appName}/`;
156
+ await fs_1.default.promises.rm(appPath, { recursive: true, force: true });
157
+ await ensureDirectoryNotSymlink(appPath);
158
+ for (const entry of entries) {
159
+ const name = toZipPath(entry.fileName);
160
+ if (name !== `Payload/${appName}` && !name.startsWith(payloadPrefix)) {
161
+ continue;
162
+ }
163
+ const rel = name === `Payload/${appName}` ? '' : name.slice(payloadPrefix.length);
164
+ if (rel === '') {
165
+ await fs_1.default.promises.mkdir(appPath, { recursive: true });
166
+ continue;
167
+ }
168
+ if (isUnsafeZipPath(rel)) {
169
+ throw new Error(`ZIP entry has an unsafe app-relative path: ${entry.fileName}`);
170
+ }
171
+ const target = path_1.default.join(appPath, rel.split('/').join(path_1.default.sep));
172
+ const stagingAppResolved = path_1.default.resolve(appPath);
173
+ const targetResolved = path_1.default.resolve(target);
174
+ if (targetResolved !== stagingAppResolved &&
175
+ !targetResolved.startsWith(stagingAppResolved + path_1.default.sep)) {
176
+ throw new Error(`ZIP entry escapes app bundle: ${entry.fileName}`);
177
+ }
178
+ await extractEntry(zip, entry, target, appPath);
179
+ }
180
+ return {
181
+ appPath,
182
+ cacheIdentityPath: resolvedArchivePath,
183
+ isArchive: true,
184
+ archivePath: resolvedArchivePath,
185
+ };
186
+ }
187
+ finally {
188
+ zip.close();
189
+ }
190
+ }
191
+ async function prepareAppBundlePath(inputPath) {
192
+ const resolvedPath = path_1.default.resolve(inputPath);
193
+ const stat = await fs_1.default.promises.stat(resolvedPath);
194
+ if (stat.isDirectory()) {
195
+ return {
196
+ appPath: resolvedPath,
197
+ cacheIdentityPath: resolvedPath,
198
+ isArchive: false,
199
+ };
200
+ }
201
+ if (!stat.isFile()) {
202
+ throw new Error(`The path is neither an app bundle directory nor a ZIP/IPA archive: ${inputPath}`);
203
+ }
204
+ return await extractAppArchiveToStablePath(resolvedPath);
205
+ }
206
+ async function waitForStableArchiveFile(archivePath, shouldStop) {
207
+ const deadline = Date.now() + 10000;
208
+ let previous;
209
+ while (!shouldStop() && Date.now() < deadline) {
210
+ const stat = await fs_1.default.promises.stat(archivePath).catch((err) => {
211
+ if (err.code === 'ENOENT')
212
+ return null;
213
+ throw err;
214
+ });
215
+ if (stat?.isFile()) {
216
+ const current = { size: stat.size, mtimeMs: stat.mtimeMs };
217
+ if (previous && previous.size === current.size && previous.mtimeMs === current.mtimeMs) {
218
+ return true;
219
+ }
220
+ previous = current;
221
+ }
222
+ else {
223
+ previous = undefined;
224
+ }
225
+ await sleep(100);
226
+ }
227
+ return false;
228
+ }
229
+ function watchAppArchive(opts) {
230
+ const archivePath = path_1.default.resolve(opts.archivePath);
231
+ const log = opts.log ?? noopLogger;
232
+ const parentDir = path_1.default.dirname(archivePath);
233
+ const archiveBase = path_1.default.basename(archivePath);
234
+ let timer;
235
+ let inFlight = false;
236
+ let queued = false;
237
+ let closed = false;
238
+ const run = async () => {
239
+ if (closed)
240
+ return;
241
+ if (inFlight) {
242
+ queued = true;
243
+ return;
244
+ }
245
+ inFlight = true;
246
+ try {
247
+ const ready = await waitForStableArchiveFile(archivePath, () => closed);
248
+ if (!ready) {
249
+ if (!closed) {
250
+ log('warn', `ZIP/IPA archive did not become readable after change: ${archivePath}`);
251
+ }
252
+ return;
253
+ }
254
+ await extractAppArchiveToStablePath(archivePath);
255
+ log('debug', `re-extracted ZIP/IPA archive: ${archivePath}`);
256
+ await opts.onExtracted?.();
257
+ }
258
+ catch (err) {
259
+ log('error', `failed to re-extract ZIP/IPA archive ${archivePath}: ${err instanceof Error ? err.message : err}`);
260
+ }
261
+ finally {
262
+ inFlight = false;
263
+ if (queued) {
264
+ queued = false;
265
+ void run();
266
+ }
267
+ }
268
+ };
269
+ const schedule = () => {
270
+ if (closed)
271
+ return;
272
+ if (timer)
273
+ clearTimeout(timer);
274
+ timer = setTimeout(() => {
275
+ timer = undefined;
276
+ void run();
277
+ }, 500);
278
+ };
279
+ const watcher = fs_1.default.watch(parentDir, (_eventType, filename) => {
280
+ if (filename?.toString() === archiveBase) {
281
+ schedule();
282
+ }
283
+ });
284
+ log('debug', `watchAppArchive: ${archivePath}`);
285
+ return {
286
+ close: () => {
287
+ closed = true;
288
+ if (timer) {
289
+ clearTimeout(timer);
290
+ timer = undefined;
291
+ }
292
+ watcher.close();
293
+ },
294
+ };
295
+ }
296
+ //# sourceMappingURL=app-archive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-archive.js","sourceRoot":"","sources":["src/app-archive.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,290 @@
1
+ import crypto from 'crypto';
2
+ import fs from 'fs';
3
+ import os from 'os';
4
+ import path from 'path';
5
+ import { pipeline } from 'stream/promises';
6
+ import * as yauzl from 'yauzl';
7
+ const noopLogger = () => { };
8
+ function sleep(ms) {
9
+ return new Promise((resolve) => setTimeout(resolve, ms));
10
+ }
11
+ function stableArchiveRoot(archivePath) {
12
+ const resolved = path.resolve(archivePath);
13
+ const hash = crypto.createHash('sha1').update(resolved).digest('hex').slice(0, 12);
14
+ const base = path
15
+ .basename(resolved, path.extname(resolved))
16
+ .replace(/[^A-Za-z0-9._-]+/g, '-')
17
+ .replace(/^-+|-+$/g, '');
18
+ const safeBase = base === '' ? 'app' : base;
19
+ return path.join(os.tmpdir(), `limrun-sync-app-${safeBase}-${hash}`);
20
+ }
21
+ function stableAppPath(archivePath) {
22
+ return path.join(stableArchiveRoot(archivePath), 'Extracted.app');
23
+ }
24
+ function toZipPath(name) {
25
+ return name.replace(/\\/g, '/');
26
+ }
27
+ function isUnsafeZipPath(name) {
28
+ if (name.includes('\0'))
29
+ return true;
30
+ if (path.posix.isAbsolute(name))
31
+ return true;
32
+ return name.split('/').some((part) => part === '..');
33
+ }
34
+ function entryMode(entry) {
35
+ return (entry.externalFileAttributes >>> 16) & 0xffff;
36
+ }
37
+ function isSymlinkEntry(entry) {
38
+ return (entryMode(entry) & 0o170000) === 0o120000;
39
+ }
40
+ function isDirectoryEntry(entry) {
41
+ return entry.fileName.endsWith('/');
42
+ }
43
+ function entryPerm(entry, fallback) {
44
+ return entryMode(entry) & 0o7777 || fallback;
45
+ }
46
+ async function openZip(archivePath) {
47
+ try {
48
+ return await yauzl.openPromise(archivePath, {
49
+ autoClose: false,
50
+ lazyEntries: true,
51
+ strictFileNames: true,
52
+ validateEntrySizes: true,
53
+ });
54
+ }
55
+ catch (err) {
56
+ const message = err instanceof Error ? `: ${err.message}` : '';
57
+ throw new Error(`The path is not a valid app bundle directory or ZIP/IPA archive: ${archivePath}${message}`);
58
+ }
59
+ }
60
+ async function readEntries(zip) {
61
+ const entries = [];
62
+ for await (const entry of zip.eachEntry()) {
63
+ entries.push(entry);
64
+ }
65
+ return entries;
66
+ }
67
+ async function ensureDirectoryNotSymlink(dir) {
68
+ const stat = await fs.promises.lstat(dir).catch((err) => {
69
+ if (err.code === 'ENOENT')
70
+ return null;
71
+ throw err;
72
+ });
73
+ if (stat && (!stat.isDirectory() || stat.isSymbolicLink())) {
74
+ await fs.promises.rm(dir, { recursive: true, force: true });
75
+ }
76
+ await fs.promises.mkdir(dir, { recursive: true });
77
+ }
78
+ function discoverPayloadApp(entries, archivePath) {
79
+ const appNames = new Set();
80
+ const appsWithInfoPlist = new Set();
81
+ for (const entry of entries) {
82
+ const name = toZipPath(entry.fileName);
83
+ if (isUnsafeZipPath(name)) {
84
+ throw new Error(`ZIP entry has an unsafe path: ${entry.fileName}`);
85
+ }
86
+ if (isDirectoryEntry(entry)) {
87
+ continue;
88
+ }
89
+ const rest = name.startsWith('Payload/') ? name.slice('Payload/'.length) : '';
90
+ if (rest === '') {
91
+ continue;
92
+ }
93
+ const [appName, ...parts] = rest.split('/');
94
+ if (!appName?.endsWith('.app')) {
95
+ continue;
96
+ }
97
+ appNames.add(appName);
98
+ if (parts.join('/') === 'Info.plist') {
99
+ appsWithInfoPlist.add(appName);
100
+ }
101
+ }
102
+ if (appNames.size === 0) {
103
+ throw new Error(`ZIP/IPA archive contains no Payload/*.app bundle: ${archivePath}`);
104
+ }
105
+ if (appNames.size > 1) {
106
+ throw new Error(`ZIP/IPA archive contains more than one Payload/*.app bundle (${[...appNames].join(', ')}); expected exactly one`);
107
+ }
108
+ const appName = [...appNames][0];
109
+ if (!appsWithInfoPlist.has(appName)) {
110
+ throw new Error(`ZIP/IPA archive app bundle is missing Info.plist: Payload/${appName}/Info.plist`);
111
+ }
112
+ return appName;
113
+ }
114
+ async function extractEntry(zip, entry, target, appRoot) {
115
+ if (isDirectoryEntry(entry)) {
116
+ await fs.promises.mkdir(target, { recursive: true, mode: entryPerm(entry, 0o755) });
117
+ return;
118
+ }
119
+ await fs.promises.mkdir(path.dirname(target), { recursive: true });
120
+ if (isSymlinkEntry(entry)) {
121
+ const stream = await zip.openReadStreamPromise(entry);
122
+ const chunks = [];
123
+ for await (const chunk of stream) {
124
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
125
+ }
126
+ const linkTarget = Buffer.concat(chunks).toString('utf8');
127
+ const resolved = path.resolve(path.dirname(target), linkTarget);
128
+ const appRootResolved = path.resolve(appRoot);
129
+ if (resolved !== appRootResolved && !resolved.startsWith(appRootResolved + path.sep)) {
130
+ throw new Error(`ZIP/IPA symlink escapes app bundle: ${entry.fileName} -> ${linkTarget}`);
131
+ }
132
+ await fs.promises.rm(target, { force: true });
133
+ await fs.promises.symlink(linkTarget, target);
134
+ return;
135
+ }
136
+ const stream = await zip.openReadStreamPromise(entry);
137
+ await pipeline(stream, fs.createWriteStream(target, { mode: entryPerm(entry, 0o644) }));
138
+ await fs.promises.chmod(target, entryPerm(entry, 0o644));
139
+ }
140
+ export async function extractAppArchiveToStablePath(archivePath) {
141
+ const resolvedArchivePath = path.resolve(archivePath);
142
+ const appPath = stableAppPath(resolvedArchivePath);
143
+ const stableRoot = path.dirname(appPath);
144
+ await ensureDirectoryNotSymlink(stableRoot);
145
+ const zip = await openZip(resolvedArchivePath);
146
+ try {
147
+ const entries = await readEntries(zip);
148
+ const appName = discoverPayloadApp(entries, resolvedArchivePath);
149
+ const payloadPrefix = `Payload/${appName}/`;
150
+ await fs.promises.rm(appPath, { recursive: true, force: true });
151
+ await ensureDirectoryNotSymlink(appPath);
152
+ for (const entry of entries) {
153
+ const name = toZipPath(entry.fileName);
154
+ if (name !== `Payload/${appName}` && !name.startsWith(payloadPrefix)) {
155
+ continue;
156
+ }
157
+ const rel = name === `Payload/${appName}` ? '' : name.slice(payloadPrefix.length);
158
+ if (rel === '') {
159
+ await fs.promises.mkdir(appPath, { recursive: true });
160
+ continue;
161
+ }
162
+ if (isUnsafeZipPath(rel)) {
163
+ throw new Error(`ZIP entry has an unsafe app-relative path: ${entry.fileName}`);
164
+ }
165
+ const target = path.join(appPath, rel.split('/').join(path.sep));
166
+ const stagingAppResolved = path.resolve(appPath);
167
+ const targetResolved = path.resolve(target);
168
+ if (targetResolved !== stagingAppResolved &&
169
+ !targetResolved.startsWith(stagingAppResolved + path.sep)) {
170
+ throw new Error(`ZIP entry escapes app bundle: ${entry.fileName}`);
171
+ }
172
+ await extractEntry(zip, entry, target, appPath);
173
+ }
174
+ return {
175
+ appPath,
176
+ cacheIdentityPath: resolvedArchivePath,
177
+ isArchive: true,
178
+ archivePath: resolvedArchivePath,
179
+ };
180
+ }
181
+ finally {
182
+ zip.close();
183
+ }
184
+ }
185
+ export async function prepareAppBundlePath(inputPath) {
186
+ const resolvedPath = path.resolve(inputPath);
187
+ const stat = await fs.promises.stat(resolvedPath);
188
+ if (stat.isDirectory()) {
189
+ return {
190
+ appPath: resolvedPath,
191
+ cacheIdentityPath: resolvedPath,
192
+ isArchive: false,
193
+ };
194
+ }
195
+ if (!stat.isFile()) {
196
+ throw new Error(`The path is neither an app bundle directory nor a ZIP/IPA archive: ${inputPath}`);
197
+ }
198
+ return await extractAppArchiveToStablePath(resolvedPath);
199
+ }
200
+ async function waitForStableArchiveFile(archivePath, shouldStop) {
201
+ const deadline = Date.now() + 10000;
202
+ let previous;
203
+ while (!shouldStop() && Date.now() < deadline) {
204
+ const stat = await fs.promises.stat(archivePath).catch((err) => {
205
+ if (err.code === 'ENOENT')
206
+ return null;
207
+ throw err;
208
+ });
209
+ if (stat?.isFile()) {
210
+ const current = { size: stat.size, mtimeMs: stat.mtimeMs };
211
+ if (previous && previous.size === current.size && previous.mtimeMs === current.mtimeMs) {
212
+ return true;
213
+ }
214
+ previous = current;
215
+ }
216
+ else {
217
+ previous = undefined;
218
+ }
219
+ await sleep(100);
220
+ }
221
+ return false;
222
+ }
223
+ export function watchAppArchive(opts) {
224
+ const archivePath = path.resolve(opts.archivePath);
225
+ const log = opts.log ?? noopLogger;
226
+ const parentDir = path.dirname(archivePath);
227
+ const archiveBase = path.basename(archivePath);
228
+ let timer;
229
+ let inFlight = false;
230
+ let queued = false;
231
+ let closed = false;
232
+ const run = async () => {
233
+ if (closed)
234
+ return;
235
+ if (inFlight) {
236
+ queued = true;
237
+ return;
238
+ }
239
+ inFlight = true;
240
+ try {
241
+ const ready = await waitForStableArchiveFile(archivePath, () => closed);
242
+ if (!ready) {
243
+ if (!closed) {
244
+ log('warn', `ZIP/IPA archive did not become readable after change: ${archivePath}`);
245
+ }
246
+ return;
247
+ }
248
+ await extractAppArchiveToStablePath(archivePath);
249
+ log('debug', `re-extracted ZIP/IPA archive: ${archivePath}`);
250
+ await opts.onExtracted?.();
251
+ }
252
+ catch (err) {
253
+ log('error', `failed to re-extract ZIP/IPA archive ${archivePath}: ${err instanceof Error ? err.message : err}`);
254
+ }
255
+ finally {
256
+ inFlight = false;
257
+ if (queued) {
258
+ queued = false;
259
+ void run();
260
+ }
261
+ }
262
+ };
263
+ const schedule = () => {
264
+ if (closed)
265
+ return;
266
+ if (timer)
267
+ clearTimeout(timer);
268
+ timer = setTimeout(() => {
269
+ timer = undefined;
270
+ void run();
271
+ }, 500);
272
+ };
273
+ const watcher = fs.watch(parentDir, (_eventType, filename) => {
274
+ if (filename?.toString() === archiveBase) {
275
+ schedule();
276
+ }
277
+ });
278
+ log('debug', `watchAppArchive: ${archivePath}`);
279
+ return {
280
+ close: () => {
281
+ closed = true;
282
+ if (timer) {
283
+ clearTimeout(timer);
284
+ timer = undefined;
285
+ }
286
+ watcher.close();
287
+ },
288
+ };
289
+ }
290
+ //# sourceMappingURL=app-archive.mjs.map