@limrun/api 0.41.1 → 0.42.1

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.
Files changed (86) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/client.d.mts +6 -2
  3. package/client.d.mts.map +1 -1
  4. package/client.d.ts +6 -2
  5. package/client.d.ts.map +1 -1
  6. package/client.js +3 -0
  7. package/client.js.map +1 -1
  8. package/client.mjs +3 -0
  9. package/client.mjs.map +1 -1
  10. package/exec-client.d.mts +34 -1
  11. package/exec-client.d.mts.map +1 -1
  12. package/exec-client.d.ts +34 -1
  13. package/exec-client.d.ts.map +1 -1
  14. package/exec-client.js +18 -4
  15. package/exec-client.js.map +1 -1
  16. package/exec-client.mjs +18 -4
  17. package/exec-client.mjs.map +1 -1
  18. package/folder-sync-ignore.d.mts +8 -0
  19. package/folder-sync-ignore.d.mts.map +1 -1
  20. package/folder-sync-ignore.d.ts +8 -0
  21. package/folder-sync-ignore.d.ts.map +1 -1
  22. package/folder-sync-ignore.js +12 -4
  23. package/folder-sync-ignore.js.map +1 -1
  24. package/folder-sync-ignore.mjs +12 -4
  25. package/folder-sync-ignore.mjs.map +1 -1
  26. package/index.d.mts +3 -2
  27. package/index.d.mts.map +1 -1
  28. package/index.d.ts +3 -2
  29. package/index.d.ts.map +1 -1
  30. package/index.js.map +1 -1
  31. package/index.mjs.map +1 -1
  32. package/package.json +1 -1
  33. package/resources/daemon-client-shared.d.mts +37 -0
  34. package/resources/daemon-client-shared.d.mts.map +1 -0
  35. package/resources/daemon-client-shared.d.ts +37 -0
  36. package/resources/daemon-client-shared.d.ts.map +1 -0
  37. package/resources/daemon-client-shared.js +51 -0
  38. package/resources/daemon-client-shared.js.map +1 -0
  39. package/resources/daemon-client-shared.mjs +45 -0
  40. package/resources/daemon-client-shared.mjs.map +1 -0
  41. package/resources/gradle-instances-helpers.d.mts +54 -0
  42. package/resources/gradle-instances-helpers.d.mts.map +1 -0
  43. package/resources/gradle-instances-helpers.d.ts +54 -0
  44. package/resources/gradle-instances-helpers.d.ts.map +1 -0
  45. package/resources/gradle-instances-helpers.js +98 -0
  46. package/resources/gradle-instances-helpers.js.map +1 -0
  47. package/resources/gradle-instances-helpers.mjs +94 -0
  48. package/resources/gradle-instances-helpers.mjs.map +1 -0
  49. package/resources/gradle-instances.d.mts +149 -0
  50. package/resources/gradle-instances.d.mts.map +1 -0
  51. package/resources/gradle-instances.d.ts +149 -0
  52. package/resources/gradle-instances.d.ts.map +1 -0
  53. package/resources/gradle-instances.js +41 -0
  54. package/resources/gradle-instances.js.map +1 -0
  55. package/resources/gradle-instances.mjs +37 -0
  56. package/resources/gradle-instances.mjs.map +1 -0
  57. package/resources/index.d.mts +3 -1
  58. package/resources/index.d.mts.map +1 -1
  59. package/resources/index.d.ts +3 -1
  60. package/resources/index.d.ts.map +1 -1
  61. package/resources/index.js +3 -1
  62. package/resources/index.js.map +1 -1
  63. package/resources/index.mjs +1 -0
  64. package/resources/index.mjs.map +1 -1
  65. package/resources/xcode-instances-helpers.d.mts +43 -11
  66. package/resources/xcode-instances-helpers.d.mts.map +1 -1
  67. package/resources/xcode-instances-helpers.d.ts +43 -11
  68. package/resources/xcode-instances-helpers.d.ts.map +1 -1
  69. package/resources/xcode-instances-helpers.js +18 -41
  70. package/resources/xcode-instances-helpers.js.map +1 -1
  71. package/resources/xcode-instances-helpers.mjs +16 -38
  72. package/resources/xcode-instances-helpers.mjs.map +1 -1
  73. package/src/client.ts +31 -0
  74. package/src/exec-client.ts +55 -5
  75. package/src/folder-sync-ignore.ts +22 -6
  76. package/src/index.ts +12 -0
  77. package/src/resources/daemon-client-shared.ts +71 -0
  78. package/src/resources/gradle-instances-helpers.ts +155 -0
  79. package/src/resources/gradle-instances.ts +206 -0
  80. package/src/resources/index.ts +15 -0
  81. package/src/resources/xcode-instances-helpers.ts +81 -56
  82. package/src/version.ts +1 -1
  83. package/version.d.mts +1 -1
  84. package/version.d.ts +1 -1
  85. package/version.js +1 -1
  86. package/version.mjs +1 -1
@@ -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
+ }
@@ -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 {
@@ -56,4 +62,13 @@ export {
56
62
  type SimulatorDeviceInfo,
57
63
  type SimulatorInstallState,
58
64
  type RbeInstallResult,
65
+ type XcodeBuildLog,
66
+ type BazelBuildLog,
59
67
  } from './xcode-instances-helpers';
68
+ export {
69
+ GradleInstances,
70
+ type GradleCreateClientParams,
71
+ type GradleClient,
72
+ type GradleSyncOptions,
73
+ type GradleBuildOptions,
74
+ } from './gradle-instances-helpers';
@@ -1,9 +1,8 @@
1
- import os from 'os';
2
- import path from 'path';
3
- import crypto from 'crypto';
4
-
5
1
  import { XcodeInstances as GeneratedXcodeInstances, type XcodeInstance } from './xcode-instances';
6
2
  import { type IosInstance } from './ios-instances';
3
+ import { APIPromise } from '../core/api-promise';
4
+ import { RequestOptions } from '../internal/request-options';
5
+ import { path } from '../internal/utils/path';
7
6
  import { exec, type ExecChildProcess, type ExecRequest, type TestflightUploadConfig } from '../exec-client';
8
7
 
9
8
  export type { TestflightUploadConfig } from '../exec-client';
@@ -13,6 +12,13 @@ import {
13
12
  type FolderSyncOptions,
14
13
  } from '../folder-sync';
15
14
  import { createIgnoreFn } from '../folder-sync-ignore';
15
+ import {
16
+ createDaemonLogger,
17
+ deriveBasisCache,
18
+ mintAssetUploadUrls,
19
+ type LogLevel,
20
+ type SyncResult,
21
+ } from './daemon-client-shared';
16
22
  import { nodeProxyTransport } from '../internal/proxy-transport';
17
23
  import { directInstanceHttpError, isDirectInstanceHttpError } from '../internal/direct-instance-errors';
18
24
  import { LimrunError } from '../core/error';
@@ -24,7 +30,10 @@ import { createEventSource } from 'eventsource-client';
24
30
 
25
31
  export type { Tunnel } from '../tunnel';
26
32
 
27
- export type LogLevel = 'none' | 'error' | 'warn' | 'info' | 'debug';
33
+ // LogLevel and SyncResult are platform-neutral and now live in the shared
34
+ // daemon-client module; re-exported here so existing deep importers of this
35
+ // file keep working.
36
+ export type { LogLevel, SyncResult } from './daemon-client-shared';
28
37
 
29
38
  export type SyncOptions = {
30
39
  /**
@@ -59,17 +68,6 @@ export type SyncOptions = {
59
68
  onSyncComplete?: FolderSyncOptions['onSyncComplete'];
60
69
  };
61
70
 
62
- export type SyncResult = {
63
- /**
64
- * Number of bytes transmitted to the server for this sync (full uploads plus
65
- * delta patches, before transport compression). In watch mode, this reflects
66
- * the initial sync only.
67
- */
68
- bytesSent?: number;
69
- /** Present only when watch=true; call to stop watching */
70
- stopWatching?: () => Promise<void>;
71
- };
72
-
73
71
  export type XcodeProjectConfig = {
74
72
  workspace?: string;
75
73
  project?: string;
@@ -402,22 +400,6 @@ export type XcodeCreateClientParams =
402
400
  | { instance: XcodeInstance; logLevel?: LogLevel }
403
401
  | { apiUrl: string; token: string; logLevel?: LogLevel };
404
402
 
405
- function createLogger(logLevel: LogLevel) {
406
- const shouldLog = (level: LogLevel) => {
407
- const levels: LogLevel[] = ['none', 'error', 'warn', 'info', 'debug'];
408
- return levels.indexOf(logLevel) >= levels.indexOf(level);
409
- };
410
- return (level: 'debug' | 'info' | 'warn' | 'error', msg: string) => {
411
- if (!shouldLog(level)) return;
412
- const prefix = '[XcodeInstance]';
413
- if (level === 'error' || level === 'warn') {
414
- console[level](prefix, msg);
415
- } else {
416
- console.log(prefix, msg);
417
- }
418
- };
419
- }
420
-
421
403
  function normalizeWorkspaceRelativePath(remotePath: string): string {
422
404
  if (
423
405
  remotePath === '' ||
@@ -512,28 +494,75 @@ async function readJsonResponse<T>(res: Response, operation: string): Promise<T>
512
494
  return JSON.parse(text) as T;
513
495
  }
514
496
 
515
- /**
516
- * Mints presigned upload/download URLs for a named asset via assets.getOrCreate,
517
- * wrapping failures with the asset name (and the original error as cause).
518
- * Shared by the xcodebuild `--upload` path and `uploadLatestRbeBuild`.
519
- */
520
- function mintAssetUploadUrls(
521
- assets: { getOrCreate: (body: { name: string; ttl?: string }) => Promise<AssetUploadUrls> },
522
- name: string,
523
- ttl?: string,
524
- ): Promise<AssetUploadUrls> {
525
- return assets.getOrCreate({ name, ...(ttl && { ttl }) }).catch((err) => {
526
- const message = `Failed to create upload URL for asset '${name}': ${
527
- err instanceof Error ? err.message : err
528
- }`;
529
- // @ts-ignore - not all envs have native support for cause yet
530
- throw new Error(message, { cause: err });
531
- });
497
+ // XcodeBuildLog is one persisted build record from the director's
498
+ // GET /v1/xcode_instances/{id}/build_logs (operationId listXcodeInstanceBuildLogs).
499
+ // Hand-written against api/public/director/openapiv3.yaml; if a Stainless
500
+ // regeneration ever picks that path up, reconcile with the generated surface
501
+ // instead of duplicating it.
502
+ export interface XcodeBuildLog {
503
+ /** Exec ID assigned by limbuild, e.g. build-1776140344112378000. */
504
+ id: string;
505
+
506
+ /** Terminal status reported by limbuild (e.g. SUCCEEDED, FAILED, CANCELLED). */
507
+ status: string;
508
+
509
+ /** Exit code of xcodebuild, if the build reached completion. */
510
+ exitCode?: number;
511
+
512
+ startedAt?: string;
513
+
514
+ finishedAt?: string;
515
+
516
+ /** Time spent running xcodebuild, in milliseconds. */
517
+ buildDurationMs?: number;
518
+
519
+ /** Error message captured by limbuild on failure, if any. */
520
+ error?: string;
521
+
522
+ /** Short-lived presigned URL for fetching the full .jsonl log from object storage. */
523
+ downloadUrl: string;
532
524
  }
533
525
 
534
- type AssetUploadUrls = { signedUploadUrl: string; signedDownloadUrl: string };
526
+ // BazelBuildLog is one persisted Bazel RBE invocation record from the
527
+ // director's GET /v1/xcode_instances/{id}/bazel_build_logs (operationId
528
+ // listBazelInstanceBuildLogs). Same hand-written caveat as XcodeBuildLog.
529
+ export interface BazelBuildLog {
530
+ /** Bazel invocation ID (UUID) from the build event stream. */
531
+ id: string;
532
+
533
+ /** Terminal status (SUCCEEDED, FAILED, CANCELLED, INCOMPLETE). */
534
+ status: string;
535
+
536
+ /** The build target patterns, if captured. */
537
+ pattern?: string[];
538
+
539
+ startedAt?: string;
540
+
541
+ /** Wall-clock duration of the invocation, in milliseconds. */
542
+ durationMs?: number;
543
+
544
+ /** Error message captured on failure, if any. */
545
+ error?: string;
546
+
547
+ /** Short-lived presigned URL for fetching the full .jsonl record from object storage. */
548
+ downloadUrl: string;
549
+ }
535
550
 
536
551
  export class XcodeInstances extends GeneratedXcodeInstances {
552
+ /**
553
+ * List the instance's persisted build logs.
554
+ */
555
+ listBuildLogs(id: string, options?: RequestOptions): APIPromise<XcodeBuildLog[]> {
556
+ return this._client.get(path`/v1/xcode_instances/${id}/build_logs`, options);
557
+ }
558
+
559
+ /**
560
+ * List the instance's persisted Bazel RBE invocation logs.
561
+ */
562
+ listBazelBuildLogs(id: string, options?: RequestOptions): APIPromise<BazelBuildLog[]> {
563
+ return this._client.get(path`/v1/xcode_instances/${id}/bazel_build_logs`, options);
564
+ }
565
+
537
566
  async createClient(params: XcodeCreateClientParams): Promise<XcodeClient> {
538
567
  let apiUrl: string;
539
568
  let token: string;
@@ -548,7 +577,7 @@ export class XcodeInstances extends GeneratedXcodeInstances {
548
577
  token = params.token;
549
578
  }
550
579
 
551
- const log = createLogger(params.logLevel ?? 'info');
580
+ const log = createDaemonLogger('[XcodeInstance]', params.logLevel ?? 'info');
552
581
  const client = this._client;
553
582
  let sandboxInfoPromise: Promise<{ homeDir: string }> | undefined;
554
583
  const getSandboxInfo = () => {
@@ -612,11 +641,7 @@ export class XcodeInstances extends GeneratedXcodeInstances {
612
641
 
613
642
  return {
614
643
  async sync(localCodePath: string, opts?: SyncOptions): Promise<SyncResult> {
615
- const resolvedPath = path.resolve(localCodePath);
616
- const folderName = path.basename(resolvedPath);
617
- const hash = crypto.createHash('sha1').update(resolvedPath).digest('hex').slice(0, 8);
618
- const cacheKey = `limsync-cache-${folderName}-${hash}`;
619
- const basisCacheDir = opts?.basisCacheDir ?? path.join(os.tmpdir(), cacheKey);
644
+ const { cacheKey, basisCacheDir } = deriveBasisCache(localCodePath, opts?.basisCacheDir);
620
645
  const sandboxInfo =
621
646
  opts?.additionalFiles && opts.additionalFiles.length > 0 ? await getSandboxInfo() : undefined;
622
647
  const additionalFiles = opts?.additionalFiles?.map((file) => ({
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.41.1'; // x-release-please-version
1
+ export const VERSION = '0.42.1'; // x-release-please-version
package/version.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.41.1";
1
+ export declare const VERSION = "0.42.1";
2
2
  //# sourceMappingURL=version.d.mts.map
package/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.41.1";
1
+ export declare const VERSION = "0.42.1";
2
2
  //# sourceMappingURL=version.d.ts.map
package/version.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VERSION = void 0;
4
- exports.VERSION = '0.41.1'; // x-release-please-version
4
+ exports.VERSION = '0.42.1'; // x-release-please-version
5
5
  //# sourceMappingURL=version.js.map
package/version.mjs CHANGED
@@ -1,2 +1,2 @@
1
- export const VERSION = '0.41.1'; // x-release-please-version
1
+ export const VERSION = '0.42.1'; // x-release-please-version
2
2
  //# sourceMappingURL=version.mjs.map