@limrun/api 0.41.0 → 0.42.0
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 +12 -0
- package/client.d.mts +4 -0
- package/client.d.mts.map +1 -1
- package/client.d.ts +4 -0
- package/client.d.ts.map +1 -1
- package/client.js +3 -0
- package/client.js.map +1 -1
- package/client.mjs +3 -0
- package/client.mjs.map +1 -1
- package/exec-client.d.mts +34 -1
- package/exec-client.d.mts.map +1 -1
- package/exec-client.d.ts +34 -1
- package/exec-client.d.ts.map +1 -1
- package/exec-client.js +18 -4
- package/exec-client.js.map +1 -1
- package/exec-client.mjs +18 -4
- package/exec-client.mjs.map +1 -1
- package/folder-sync-ignore.d.mts +8 -0
- package/folder-sync-ignore.d.mts.map +1 -1
- package/folder-sync-ignore.d.ts +8 -0
- package/folder-sync-ignore.d.ts.map +1 -1
- package/folder-sync-ignore.js +12 -4
- package/folder-sync-ignore.js.map +1 -1
- package/folder-sync-ignore.mjs +12 -4
- package/folder-sync-ignore.mjs.map +1 -1
- package/index.d.mts +2 -1
- package/index.d.mts.map +1 -1
- package/index.d.ts +2 -1
- package/index.d.ts.map +1 -1
- package/index.js.map +1 -1
- package/index.mjs.map +1 -1
- package/ios-client.d.mts +49 -5
- package/ios-client.d.mts.map +1 -1
- package/ios-client.d.ts +49 -5
- package/ios-client.d.ts.map +1 -1
- package/ios-client.js +40 -3
- package/ios-client.js.map +1 -1
- package/ios-client.mjs +40 -3
- package/ios-client.mjs.map +1 -1
- package/package.json +1 -1
- package/resources/daemon-client-shared.d.mts +37 -0
- package/resources/daemon-client-shared.d.mts.map +1 -0
- package/resources/daemon-client-shared.d.ts +37 -0
- package/resources/daemon-client-shared.d.ts.map +1 -0
- package/resources/daemon-client-shared.js +51 -0
- package/resources/daemon-client-shared.js.map +1 -0
- package/resources/daemon-client-shared.mjs +45 -0
- package/resources/daemon-client-shared.mjs.map +1 -0
- package/resources/gradle-instances-helpers.d.mts +54 -0
- package/resources/gradle-instances-helpers.d.mts.map +1 -0
- package/resources/gradle-instances-helpers.d.ts +54 -0
- package/resources/gradle-instances-helpers.d.ts.map +1 -0
- package/resources/gradle-instances-helpers.js +98 -0
- package/resources/gradle-instances-helpers.js.map +1 -0
- package/resources/gradle-instances-helpers.mjs +94 -0
- package/resources/gradle-instances-helpers.mjs.map +1 -0
- package/resources/gradle-instances.d.mts +149 -0
- package/resources/gradle-instances.d.mts.map +1 -0
- package/resources/gradle-instances.d.ts +149 -0
- package/resources/gradle-instances.d.ts.map +1 -0
- package/resources/gradle-instances.js +41 -0
- package/resources/gradle-instances.js.map +1 -0
- package/resources/gradle-instances.mjs +37 -0
- package/resources/gradle-instances.mjs.map +1 -0
- package/resources/index.d.mts +2 -0
- package/resources/index.d.mts.map +1 -1
- package/resources/index.d.ts +2 -0
- package/resources/index.d.ts.map +1 -1
- package/resources/index.js +3 -1
- package/resources/index.js.map +1 -1
- package/resources/index.mjs +1 -0
- package/resources/index.mjs.map +1 -1
- package/resources/xcode-instances-helpers.d.mts +2 -11
- package/resources/xcode-instances-helpers.d.mts.map +1 -1
- package/resources/xcode-instances-helpers.d.ts +2 -11
- package/resources/xcode-instances-helpers.d.ts.map +1 -1
- package/resources/xcode-instances-helpers.js +5 -41
- package/resources/xcode-instances-helpers.js.map +1 -1
- package/resources/xcode-instances-helpers.mjs +3 -38
- package/resources/xcode-instances-helpers.mjs.map +1 -1
- package/src/client.ts +27 -0
- package/src/exec-client.ts +55 -5
- package/src/folder-sync-ignore.ts +22 -6
- package/src/index.ts +10 -0
- package/src/ios-client.ts +99 -8
- package/src/resources/daemon-client-shared.ts +71 -0
- package/src/resources/gradle-instances-helpers.ts +155 -0
- package/src/resources/gradle-instances.ts +206 -0
- package/src/resources/index.ts +13 -0
- package/src/resources/xcode-instances-helpers.ts +13 -59
- package/src/version.ts +1 -1
- package/version.d.mts +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.mjs +1 -1
package/src/index.ts
CHANGED
|
@@ -13,6 +13,10 @@ export { buildSettingKeyPattern, parseBuildSettingEntries, validateBuildSettings
|
|
|
13
13
|
export {
|
|
14
14
|
exec,
|
|
15
15
|
type ExecRequest,
|
|
16
|
+
type XcodeBuildExecRequest,
|
|
17
|
+
type GradleAndroidABI,
|
|
18
|
+
type GradleBuildExecRequest,
|
|
19
|
+
type GradleReactNativeConfig,
|
|
16
20
|
type ExecOptions,
|
|
17
21
|
type ExecResult,
|
|
18
22
|
type ExecChildProcess,
|
|
@@ -46,6 +50,12 @@ export {
|
|
|
46
50
|
type SimulatorDeviceInfo,
|
|
47
51
|
type SimulatorInstallState,
|
|
48
52
|
} from './resources/xcode-instances-helpers';
|
|
53
|
+
export {
|
|
54
|
+
type GradleCreateClientParams,
|
|
55
|
+
type GradleClient,
|
|
56
|
+
type GradleSyncOptions,
|
|
57
|
+
type GradleBuildOptions,
|
|
58
|
+
} from './resources/gradle-instances-helpers';
|
|
49
59
|
export {
|
|
50
60
|
LIMRUN_DIR,
|
|
51
61
|
TRY_IMPORT_LINE,
|
package/src/ios-client.ts
CHANGED
|
@@ -210,6 +210,20 @@ export type HttpProxyOptions = {
|
|
|
210
210
|
localPort: number;
|
|
211
211
|
};
|
|
212
212
|
|
|
213
|
+
/**
|
|
214
|
+
* Targets a file operation at an installed app's container instead of the
|
|
215
|
+
* sandbox staging folder. Mirrors `simctl get_app_container` semantics.
|
|
216
|
+
*/
|
|
217
|
+
export type ContainerTargetOptions = {
|
|
218
|
+
/** Bundle ID of the installed app whose container should be targeted. */
|
|
219
|
+
bundleId: string;
|
|
220
|
+
/**
|
|
221
|
+
* Container to target: `app`, `data`, or a specific App Group identifier.
|
|
222
|
+
* Defaults to `app` server-side, matching `simctl get_app_container`.
|
|
223
|
+
*/
|
|
224
|
+
containerType?: string;
|
|
225
|
+
};
|
|
226
|
+
|
|
213
227
|
/**
|
|
214
228
|
* Result of auto-discovering a StoreKit configuration from the
|
|
215
229
|
* simulator's cached sandbox response.
|
|
@@ -794,12 +808,45 @@ export type InstanceClient = {
|
|
|
794
808
|
simctl: (args: string[], opts?: { disconnectOnExit?: boolean }) => SimctlExecution;
|
|
795
809
|
|
|
796
810
|
/**
|
|
797
|
-
*
|
|
798
|
-
*
|
|
799
|
-
*
|
|
800
|
-
*
|
|
811
|
+
* Push a local file to the simulator. Returns the path of the file on the simulator host
|
|
812
|
+
* that can be used in simctl commands.
|
|
813
|
+
*
|
|
814
|
+
* When `opts.bundleId` is provided, `destination` is treated as a relative path inside
|
|
815
|
+
* that app's container on the simulator (intermediate directories are created
|
|
816
|
+
* as needed) instead of a filename in the sandbox staging folder.
|
|
817
|
+
*
|
|
818
|
+
* @param path The path of the local file to copy to the simulator.
|
|
819
|
+
* @param destination The name of the file in the sandbox of the simulator, or a relative path inside the app container when `opts.bundleId` is set.
|
|
820
|
+
* @param opts Optional app container targeting.
|
|
821
|
+
* @param opts.bundleId Bundle ID of the installed app whose container should receive the file.
|
|
822
|
+
* @param opts.containerType Container to target: `app`, `data`, or a specific App Group identifier. Defaults to `app` server-side, matching `simctl get_app_container`.
|
|
823
|
+
* @returns A promise that resolves to the path of the file on the simulator host.
|
|
801
824
|
*/
|
|
802
|
-
|
|
825
|
+
pushFile: (path: string, destination: string, opts?: ContainerTargetOptions) => Promise<string>;
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* Read a file from the simulator and return its content.
|
|
829
|
+
*
|
|
830
|
+
* When `opts.bundleId` is provided, `name` is treated as a relative path inside
|
|
831
|
+
* that app's container; otherwise it addresses the sandbox staging folder
|
|
832
|
+
* (the same namespace `pushFile` uploads to without a bundle ID).
|
|
833
|
+
*
|
|
834
|
+
* @param name The name of the file, or a relative path inside the app container when `opts.bundleId` is set.
|
|
835
|
+
* @param opts Optional app container targeting (see `pushFile`).
|
|
836
|
+
* @returns A promise that resolves to the file content.
|
|
837
|
+
*/
|
|
838
|
+
pullFile: (name: string, opts?: ContainerTargetOptions) => Promise<Buffer>;
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* Delete a file or folder (recursively) on the simulator.
|
|
842
|
+
*
|
|
843
|
+
* When `opts.bundleId` is provided, `name` is treated as a relative path inside
|
|
844
|
+
* that app's container; otherwise it addresses the sandbox staging folder.
|
|
845
|
+
*
|
|
846
|
+
* @param name The name of the file, or a relative path inside the app container when `opts.bundleId` is set.
|
|
847
|
+
* @param opts Optional app container targeting (see `pushFile`).
|
|
848
|
+
*/
|
|
849
|
+
deleteFile: (name: string, opts?: ContainerTargetOptions) => Promise<void>;
|
|
803
850
|
|
|
804
851
|
/**
|
|
805
852
|
* Run `xcrun` command with the given arguments.
|
|
@@ -1821,7 +1868,9 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
|
|
|
1821
1868
|
xcrun,
|
|
1822
1869
|
startXcrunShim,
|
|
1823
1870
|
xcodebuild,
|
|
1824
|
-
|
|
1871
|
+
pushFile,
|
|
1872
|
+
pullFile,
|
|
1873
|
+
deleteFile,
|
|
1825
1874
|
lsof,
|
|
1826
1875
|
deviceInfo: cachedDeviceInfo,
|
|
1827
1876
|
});
|
|
@@ -2245,9 +2294,24 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
|
|
|
2245
2294
|
return execution;
|
|
2246
2295
|
};
|
|
2247
2296
|
|
|
2248
|
-
const
|
|
2297
|
+
const buildFilesUrl = (name: string, opts?: ContainerTargetOptions): string => {
|
|
2298
|
+
const params = new URLSearchParams({ name });
|
|
2299
|
+
if (opts?.bundleId) {
|
|
2300
|
+
params.set('bundleId', opts.bundleId);
|
|
2301
|
+
if (opts.containerType) {
|
|
2302
|
+
params.set('containerType', opts.containerType);
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
return `${options.apiUrl}/files?${params.toString()}`;
|
|
2306
|
+
};
|
|
2307
|
+
|
|
2308
|
+
const pushFile = async (
|
|
2309
|
+
filePath: string,
|
|
2310
|
+
destination: string,
|
|
2311
|
+
opts?: ContainerTargetOptions,
|
|
2312
|
+
): Promise<string> => {
|
|
2249
2313
|
const fileStream = fs.createReadStream(filePath);
|
|
2250
|
-
const uploadUrl =
|
|
2314
|
+
const uploadUrl = buildFilesUrl(destination, opts);
|
|
2251
2315
|
try {
|
|
2252
2316
|
// Node's fetch (undici) supports streaming request bodies but TS DOM types may not include
|
|
2253
2317
|
// `duplex` and may not accept Node ReadStreams as BodyInit in some configs.
|
|
@@ -2274,6 +2338,33 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
|
|
|
2274
2338
|
}
|
|
2275
2339
|
};
|
|
2276
2340
|
|
|
2341
|
+
const pullFile = async (name: string, opts?: ContainerTargetOptions): Promise<Buffer> => {
|
|
2342
|
+
const response = await nodeProxyTransport.fetch(buildFilesUrl(name, opts), {
|
|
2343
|
+
method: 'GET',
|
|
2344
|
+
headers: {
|
|
2345
|
+
Authorization: `Bearer ${options.token}`,
|
|
2346
|
+
},
|
|
2347
|
+
});
|
|
2348
|
+
if (!response.ok) {
|
|
2349
|
+
const errorBody = await response.text();
|
|
2350
|
+
throw new Error(`Download of '${name}' failed: ${response.status} ${errorBody}`);
|
|
2351
|
+
}
|
|
2352
|
+
return Buffer.from(await response.arrayBuffer());
|
|
2353
|
+
};
|
|
2354
|
+
|
|
2355
|
+
const deleteFile = async (name: string, opts?: ContainerTargetOptions): Promise<void> => {
|
|
2356
|
+
const response = await nodeProxyTransport.fetch(buildFilesUrl(name, opts), {
|
|
2357
|
+
method: 'DELETE',
|
|
2358
|
+
headers: {
|
|
2359
|
+
Authorization: `Bearer ${options.token}`,
|
|
2360
|
+
},
|
|
2361
|
+
});
|
|
2362
|
+
if (!response.ok) {
|
|
2363
|
+
const errorBody = await response.text();
|
|
2364
|
+
throw new Error(`Deletion of '${name}' failed: ${response.status} ${errorBody}`);
|
|
2365
|
+
}
|
|
2366
|
+
};
|
|
2367
|
+
|
|
2277
2368
|
const setStoreKitConfig = async (bundleId: string, storekit: Buffer | Uint8Array): Promise<void> => {
|
|
2278
2369
|
const body = Buffer.isBuffer(storekit) ? storekit : Buffer.from(storekit);
|
|
2279
2370
|
const url = `${options.apiUrl}/payments/storeKitConfigs/${encodeURIComponent(bundleId)}`;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Helpers and platform-neutral types shared by the per-instance build-daemon
|
|
2
|
+
// clients (xcode, gradle).
|
|
3
|
+
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import crypto from 'crypto';
|
|
7
|
+
|
|
8
|
+
export type LogLevel = 'none' | 'error' | 'warn' | 'info' | 'debug';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Derives the client-side folder-sync cache location for a local project.
|
|
12
|
+
* The key format is a compatibility contract: changing it orphans every
|
|
13
|
+
* user's existing basis cache, so both daemon clients must share this one
|
|
14
|
+
* derivation.
|
|
15
|
+
*/
|
|
16
|
+
export function deriveBasisCache(
|
|
17
|
+
localCodePath: string,
|
|
18
|
+
override?: string,
|
|
19
|
+
): { cacheKey: string; basisCacheDir: string } {
|
|
20
|
+
const resolvedPath = path.resolve(localCodePath);
|
|
21
|
+
const folderName = path.basename(resolvedPath);
|
|
22
|
+
const hash = crypto.createHash('sha1').update(resolvedPath).digest('hex').slice(0, 8);
|
|
23
|
+
const cacheKey = `limsync-cache-${folderName}-${hash}`;
|
|
24
|
+
return { cacheKey, basisCacheDir: override ?? path.join(os.tmpdir(), cacheKey) };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type SyncResult = {
|
|
28
|
+
/**
|
|
29
|
+
* Number of bytes transmitted to the server for this sync (full uploads plus
|
|
30
|
+
* delta patches, before transport compression). In watch mode, this reflects
|
|
31
|
+
* the initial sync only.
|
|
32
|
+
*/
|
|
33
|
+
bytesSent?: number;
|
|
34
|
+
/** Present only when watch=true; call to stop watching */
|
|
35
|
+
stopWatching?: () => Promise<void>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type AssetUploadUrls = { signedUploadUrl: string; signedDownloadUrl: string };
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Mints presigned upload/download URLs for a named asset via assets.getOrCreate,
|
|
42
|
+
* wrapping failures with the asset name (and the original error as cause).
|
|
43
|
+
*/
|
|
44
|
+
export function mintAssetUploadUrls(
|
|
45
|
+
assets: { getOrCreate: (body: { name: string; ttl?: string }) => Promise<AssetUploadUrls> },
|
|
46
|
+
name: string,
|
|
47
|
+
ttl?: string,
|
|
48
|
+
): Promise<AssetUploadUrls> {
|
|
49
|
+
return assets.getOrCreate({ name, ...(ttl && { ttl }) }).catch((err) => {
|
|
50
|
+
const message = `Failed to create upload URL for asset '${name}': ${
|
|
51
|
+
err instanceof Error ? err.message : err
|
|
52
|
+
}`;
|
|
53
|
+
// @ts-ignore - not all envs have native support for cause yet
|
|
54
|
+
throw new Error(message, { cause: err });
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const logLevels: LogLevel[] = ['none', 'error', 'warn', 'info', 'debug'];
|
|
59
|
+
|
|
60
|
+
export function createDaemonLogger(prefix: string, logLevel: LogLevel) {
|
|
61
|
+
const threshold = logLevels.indexOf(logLevel);
|
|
62
|
+
const shouldLog = (level: LogLevel) => threshold >= logLevels.indexOf(level);
|
|
63
|
+
return (level: 'debug' | 'info' | 'warn' | 'error', msg: string) => {
|
|
64
|
+
if (!shouldLog(level)) return;
|
|
65
|
+
if (level === 'error' || level === 'warn') {
|
|
66
|
+
console[level](prefix, msg);
|
|
67
|
+
} else {
|
|
68
|
+
console.log(prefix, msg);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { GradleInstances as GeneratedGradleInstances, type GradleInstance } from './gradle-instances';
|
|
2
|
+
import {
|
|
3
|
+
exec,
|
|
4
|
+
type ExecChildProcess,
|
|
5
|
+
type GradleBuildExecRequest,
|
|
6
|
+
type GradleReactNativeConfig,
|
|
7
|
+
} from '../exec-client';
|
|
8
|
+
import { syncFolder as syncFolderImpl, type FolderSyncOptions } from '../folder-sync';
|
|
9
|
+
import { createIgnoreFn } from '../folder-sync-ignore';
|
|
10
|
+
import {
|
|
11
|
+
createDaemonLogger,
|
|
12
|
+
deriveBasisCache,
|
|
13
|
+
mintAssetUploadUrls,
|
|
14
|
+
type LogLevel,
|
|
15
|
+
type SyncResult,
|
|
16
|
+
} from './daemon-client-shared';
|
|
17
|
+
|
|
18
|
+
export type GradleCreateClientParams = { logLevel?: LogLevel } & (
|
|
19
|
+
| { instance: GradleInstance }
|
|
20
|
+
| { apiUrl: string; token: string }
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
export type GradleSyncOptions = {
|
|
24
|
+
/**
|
|
25
|
+
* Directory for the client-side folder-sync cache.
|
|
26
|
+
* Defaults to a temporary directory under the OS temp directory.
|
|
27
|
+
*/
|
|
28
|
+
basisCacheDir?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Optional predicate for ignoring files and directories during sync.
|
|
31
|
+
* Called with the relative path from the sync root (using forward slashes).
|
|
32
|
+
* For directories, the path ends with '/'.
|
|
33
|
+
* Return true to ignore, false to keep.
|
|
34
|
+
*/
|
|
35
|
+
ignore?: (relativePath: string) => boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Optional predicate for force-including paths that would otherwise be
|
|
38
|
+
* dropped. Same calling convention as `ignore`; return true to force-sync.
|
|
39
|
+
*/
|
|
40
|
+
include?: (relativePath: string) => boolean;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type GradleBuildOptions = {
|
|
44
|
+
/** Gradle tasks to run. Omit for the server default (assembleDebug). */
|
|
45
|
+
tasks?: string[];
|
|
46
|
+
/** Relative path to the Gradle root when auto-discovery is ambiguous. */
|
|
47
|
+
projectPath?: string;
|
|
48
|
+
/** Upload the built APK as a named org asset, or to a presigned URL. */
|
|
49
|
+
upload?: { assetName: string; signedUploadUrl?: never } | { signedUploadUrl: string; assetName?: never };
|
|
50
|
+
/** React Native / Expo tuning. */
|
|
51
|
+
reactNative?: GradleReactNativeConfig;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type GradleClient = {
|
|
55
|
+
sync(localCodePath: string, opts?: GradleSyncOptions): Promise<SyncResult>;
|
|
56
|
+
gradlebuild(options?: GradleBuildOptions): ExecChildProcess;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Machine-local or regenerable files that must never reach the build
|
|
60
|
+
// sandbox: local.properties points at the developer's own SDK and would
|
|
61
|
+
// shadow the image's. Only ROOT-level cache/output dirs are defaulted;
|
|
62
|
+
// nested module build/ dirs are the project .gitignore's job (dropping any
|
|
63
|
+
// 'build' segment would silently exclude legitimate packages named build).
|
|
64
|
+
function gradleDefaultIgnore(relativePath: string): boolean {
|
|
65
|
+
const trimmed = relativePath.endsWith('/') ? relativePath.slice(0, -1) : relativePath;
|
|
66
|
+
const segments = trimmed.split('/');
|
|
67
|
+
if (segments[segments.length - 1] === 'local.properties') {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
return segments.length === 1 && (trimmed === '.gradle' || trimmed === '.kotlin' || trimmed === 'build');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export class GradleInstances extends GeneratedGradleInstances {
|
|
74
|
+
async createClient(params: GradleCreateClientParams): Promise<GradleClient> {
|
|
75
|
+
let apiUrl: string;
|
|
76
|
+
let token: string;
|
|
77
|
+
if ('instance' in params) {
|
|
78
|
+
if (!params.instance.status.apiUrl) {
|
|
79
|
+
throw new Error('Instance not ready: apiUrl is not available');
|
|
80
|
+
}
|
|
81
|
+
apiUrl = params.instance.status.apiUrl;
|
|
82
|
+
token = params.instance.status.token;
|
|
83
|
+
} else {
|
|
84
|
+
apiUrl = params.apiUrl;
|
|
85
|
+
token = params.token;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const log = createDaemonLogger('[GradleInstance]', params.logLevel ?? 'info');
|
|
89
|
+
const client = this._client;
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
async sync(localCodePath: string, opts?: GradleSyncOptions): Promise<SyncResult> {
|
|
93
|
+
const { cacheKey, basisCacheDir } = deriveBasisCache(localCodePath, opts?.basisCacheDir);
|
|
94
|
+
const userIgnore = opts?.ignore;
|
|
95
|
+
const codeSyncOpts: FolderSyncOptions = {
|
|
96
|
+
apiUrl,
|
|
97
|
+
token,
|
|
98
|
+
udid: cacheKey,
|
|
99
|
+
install: false,
|
|
100
|
+
ignoreFn: await createIgnoreFn(localCodePath, {
|
|
101
|
+
basisCacheDir,
|
|
102
|
+
log,
|
|
103
|
+
xcodeDefaults: false,
|
|
104
|
+
// Honor nested module .gitignore files (app/.gitignore's /build,
|
|
105
|
+
// etc.); gradleDefaultIgnore only covers root-level build/.gradle/
|
|
106
|
+
// .kotlin, so without this a locally-built project would sync its
|
|
107
|
+
// nested build/ artifact trees.
|
|
108
|
+
nestedGitignore: true,
|
|
109
|
+
additional: userIgnore ? (p) => gradleDefaultIgnore(p) || userIgnore(p) : gradleDefaultIgnore,
|
|
110
|
+
...(opts?.include ? { include: opts.include } : {}),
|
|
111
|
+
}),
|
|
112
|
+
basisCacheDir,
|
|
113
|
+
// Gradle builds are one-shot; no dev-loop watch like xcode.
|
|
114
|
+
watch: false,
|
|
115
|
+
launchMode: 'ForegroundIfRunning',
|
|
116
|
+
log,
|
|
117
|
+
syncSymlinks: true,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const result = await syncFolderImpl(localCodePath, codeSyncOpts);
|
|
121
|
+
const out: SyncResult = {};
|
|
122
|
+
if (result.bytesSent !== undefined) {
|
|
123
|
+
out.bytesSent = result.bytesSent;
|
|
124
|
+
}
|
|
125
|
+
return out;
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
gradlebuild(options?: GradleBuildOptions): ExecChildProcess {
|
|
129
|
+
const request: GradleBuildExecRequest = {
|
|
130
|
+
command: 'gradlebuild',
|
|
131
|
+
...(options?.tasks?.length && { tasks: options.tasks }),
|
|
132
|
+
...(options?.projectPath && { projectPath: options.projectPath }),
|
|
133
|
+
...(options?.reactNative && { reactNative: options.reactNative }),
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
if (options?.upload && 'assetName' in options.upload) {
|
|
137
|
+
const requestPromise = mintAssetUploadUrls(client.assets, options.upload.assetName).then(
|
|
138
|
+
(asset) => {
|
|
139
|
+
request.signedUploadUrl = asset.signedUploadUrl;
|
|
140
|
+
request.additionalMetadata = { signedDownloadUrl: asset.signedDownloadUrl };
|
|
141
|
+
return request;
|
|
142
|
+
},
|
|
143
|
+
);
|
|
144
|
+
return exec(requestPromise, { apiUrl, token, log });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (options?.upload && 'signedUploadUrl' in options.upload) {
|
|
148
|
+
request.signedUploadUrl = options.upload.signedUploadUrl;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return exec(request, { apiUrl, token, log });
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// Hand-written following the Stainless resource pattern; a future generation
|
|
2
|
+
// from the OpenAPI spec reconciles with this file.
|
|
3
|
+
|
|
4
|
+
import { APIResource } from '../core/resource';
|
|
5
|
+
import { APIPromise } from '../core/api-promise';
|
|
6
|
+
import { Items, type ItemsParams, PagePromise } from '../core/pagination';
|
|
7
|
+
import { buildHeaders } from '../internal/headers';
|
|
8
|
+
import { RequestOptions } from '../internal/request-options';
|
|
9
|
+
import { path } from '../internal/utils/path';
|
|
10
|
+
|
|
11
|
+
export class GradleInstances extends APIResource {
|
|
12
|
+
/**
|
|
13
|
+
* Create a gradle instance
|
|
14
|
+
*/
|
|
15
|
+
create(params: GradleInstanceCreateParams, options?: RequestOptions): APIPromise<GradleInstance> {
|
|
16
|
+
const { reuseIfExists, wait, ...body } = params;
|
|
17
|
+
return this._client.post('/v1/gradle_instances', { query: { reuseIfExists, wait }, body, ...options });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* List gradle instances
|
|
22
|
+
*/
|
|
23
|
+
list(
|
|
24
|
+
query: GradleInstanceListParams | null | undefined = {},
|
|
25
|
+
options?: RequestOptions,
|
|
26
|
+
): PagePromise<GradleInstancesItems, GradleInstance> {
|
|
27
|
+
return this._client.getAPIList('/v1/gradle_instances', Items<GradleInstance>, { query, ...options });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Delete gradle instance with given name
|
|
32
|
+
*/
|
|
33
|
+
delete(id: string, options?: RequestOptions): APIPromise<void> {
|
|
34
|
+
return this._client.delete(path`/v1/gradle_instances/${id}`, {
|
|
35
|
+
...options,
|
|
36
|
+
headers: buildHeaders([{ Accept: '*/*' }, options?.headers]),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get gradle instance with given ID
|
|
42
|
+
*/
|
|
43
|
+
get(id: string, options?: RequestOptions): APIPromise<GradleInstance> {
|
|
44
|
+
return this._client.get(path`/v1/gradle_instances/${id}`, options);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type GradleInstancesItems = Items<GradleInstance>;
|
|
49
|
+
|
|
50
|
+
export interface GradleInstance {
|
|
51
|
+
metadata: GradleInstance.Metadata;
|
|
52
|
+
|
|
53
|
+
spec: GradleInstance.Spec;
|
|
54
|
+
|
|
55
|
+
status: GradleInstance.Status;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export namespace GradleInstance {
|
|
59
|
+
export interface Metadata {
|
|
60
|
+
id: string;
|
|
61
|
+
|
|
62
|
+
createdAt: string;
|
|
63
|
+
|
|
64
|
+
organizationId: string;
|
|
65
|
+
|
|
66
|
+
displayName?: string;
|
|
67
|
+
|
|
68
|
+
labels?: { [key: string]: string };
|
|
69
|
+
|
|
70
|
+
terminatedAt?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface Spec {
|
|
74
|
+
inactivityTimeout: string;
|
|
75
|
+
|
|
76
|
+
region: string;
|
|
77
|
+
|
|
78
|
+
hardTimeout?: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface Status {
|
|
82
|
+
token: string;
|
|
83
|
+
|
|
84
|
+
state: 'unknown' | 'creating' | 'assigned' | 'ready' | 'terminated';
|
|
85
|
+
|
|
86
|
+
apiUrl?: string;
|
|
87
|
+
|
|
88
|
+
errorMessage?: string;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Machine-readable reason the instance was terminated. Always present once state
|
|
92
|
+
* is "terminated", never present before that. New values may be added over time,
|
|
93
|
+
* so treat any unrecognized value as "Unknown". Known values:
|
|
94
|
+
*
|
|
95
|
+
* - "UserRequested": terminated by a delete request to the API.
|
|
96
|
+
* - "InactivityTimeout": the timeout given in spec.inactivityTimeout elapsed.
|
|
97
|
+
* - "HardTimeout": the timeout given in spec.hardTimeout elapsed.
|
|
98
|
+
* - "Unknown": terminated for a cause the platform did not attribute, including
|
|
99
|
+
* instances that failed to get ready during creation. See errorMessage for
|
|
100
|
+
* details when available.
|
|
101
|
+
*/
|
|
102
|
+
terminationReason?: string;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface GradleInstanceCreateParams {
|
|
107
|
+
/**
|
|
108
|
+
* Query param: If there is another instance with given labels and region, return
|
|
109
|
+
* that one instead of creating a new instance.
|
|
110
|
+
*/
|
|
111
|
+
reuseIfExists?: boolean;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Query param: Return after the instance is ready to connect.
|
|
115
|
+
*/
|
|
116
|
+
wait?: boolean;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Body param
|
|
120
|
+
*/
|
|
121
|
+
metadata?: GradleInstanceCreateParams.Metadata;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Body param
|
|
125
|
+
*/
|
|
126
|
+
spec?: GradleInstanceCreateParams.Spec;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export namespace GradleInstanceCreateParams {
|
|
130
|
+
export interface Metadata {
|
|
131
|
+
displayName?: string;
|
|
132
|
+
|
|
133
|
+
labels?: { [key: string]: string };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface Spec {
|
|
137
|
+
clues?: Array<Spec.Clue>;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* After how many minutes should the instance be terminated. Example values 1m,
|
|
141
|
+
* 10m, 3h. Default is "0" which means no hard timeout.
|
|
142
|
+
*/
|
|
143
|
+
hardTimeout?: string;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* After how many minutes of inactivity should the instance be terminated. Example
|
|
147
|
+
* values 1m, 10m, 3h. Default is 5m.
|
|
148
|
+
*/
|
|
149
|
+
inactivityTimeout?: string;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Where the instance will be created. If not given, the region is decided based on
|
|
153
|
+
* scheduling clues (client IP) and availability.
|
|
154
|
+
*
|
|
155
|
+
* A region is a preference, not a hard pin: the request always overflows to every
|
|
156
|
+
* other available region, ordered by proximity, when the preferred ones are full.
|
|
157
|
+
*
|
|
158
|
+
* Accepted values:
|
|
159
|
+
*
|
|
160
|
+
* - A specific region name (e.g. "us-west1"). It is tried first, then the
|
|
161
|
+
* remaining regions in order of proximity to it. Scheduling clues (client IP)
|
|
162
|
+
* are ignored when a region is given.
|
|
163
|
+
* - A region group name (e.g. "us", "eu"). Its member regions are tried first in
|
|
164
|
+
* their listed order, then the remaining regions by proximity to the first
|
|
165
|
+
* member.
|
|
166
|
+
* - A pipe-separated, ordered list of regions (e.g. "us-east1|us-west1"). Those
|
|
167
|
+
* are tried first in the given order, then the remaining regions by proximity to
|
|
168
|
+
* the first.
|
|
169
|
+
*/
|
|
170
|
+
region?: string;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export namespace Spec {
|
|
174
|
+
export interface Clue {
|
|
175
|
+
kind: 'ClientIP';
|
|
176
|
+
|
|
177
|
+
clientIp?: string;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export interface GradleInstanceListParams extends ItemsParams {
|
|
183
|
+
/**
|
|
184
|
+
* Labels filter to apply to instances to return. Expects a comma-separated list of
|
|
185
|
+
* key=value pairs (e.g., env=prod,region=us-west).
|
|
186
|
+
*/
|
|
187
|
+
labelSelector?: string;
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* State filter to apply to gradle instances to return. Each comma-separated state
|
|
191
|
+
* will be used as part of an OR clause, e.g. "assigned,ready" will return all
|
|
192
|
+
* instances that are either assigned or ready.
|
|
193
|
+
*
|
|
194
|
+
* Valid states: creating, assigned, ready, terminated, unknown
|
|
195
|
+
*/
|
|
196
|
+
state?: string;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export declare namespace GradleInstances {
|
|
200
|
+
export {
|
|
201
|
+
type GradleInstance as GradleInstance,
|
|
202
|
+
type GradleInstancesItems as GradleInstancesItems,
|
|
203
|
+
type GradleInstanceCreateParams as GradleInstanceCreateParams,
|
|
204
|
+
type GradleInstanceListParams as GradleInstanceListParams,
|
|
205
|
+
};
|
|
206
|
+
}
|
package/src/resources/index.ts
CHANGED
|
@@ -37,6 +37,12 @@ export {
|
|
|
37
37
|
type XcodeInstanceListParams,
|
|
38
38
|
type XcodeInstancesItems,
|
|
39
39
|
} from './xcode-instances';
|
|
40
|
+
export {
|
|
41
|
+
type GradleInstance,
|
|
42
|
+
type GradleInstanceCreateParams,
|
|
43
|
+
type GradleInstanceListParams,
|
|
44
|
+
type GradleInstancesItems,
|
|
45
|
+
} from './gradle-instances';
|
|
40
46
|
|
|
41
47
|
export { Assets, AssetGetOrUploadParams, AssetGetOrUploadResponse } from './assets-helpers';
|
|
42
48
|
export {
|
|
@@ -57,3 +63,10 @@ export {
|
|
|
57
63
|
type SimulatorInstallState,
|
|
58
64
|
type RbeInstallResult,
|
|
59
65
|
} from './xcode-instances-helpers';
|
|
66
|
+
export {
|
|
67
|
+
GradleInstances,
|
|
68
|
+
type GradleCreateClientParams,
|
|
69
|
+
type GradleClient,
|
|
70
|
+
type GradleSyncOptions,
|
|
71
|
+
type GradleBuildOptions,
|
|
72
|
+
} from './gradle-instances-helpers';
|