@amodalai/amodal 0.3.90 → 0.3.92

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 (62) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/src/commands/audit.d.ts +1 -3
  3. package/dist/src/commands/audit.d.ts.map +1 -1
  4. package/dist/src/commands/audit.js +4 -53
  5. package/dist/src/commands/audit.js.map +1 -1
  6. package/dist/src/commands/build-manifest-types.js +1 -1
  7. package/dist/src/commands/build-tools.d.ts +3 -10
  8. package/dist/src/commands/build-tools.d.ts.map +1 -1
  9. package/dist/src/commands/build-tools.js +5 -118
  10. package/dist/src/commands/build-tools.js.map +1 -1
  11. package/dist/src/commands/build.js +1 -1
  12. package/dist/src/commands/build.js.map +1 -1
  13. package/dist/src/commands/deploy.d.ts +1 -1
  14. package/dist/src/commands/deploy.d.ts.map +1 -1
  15. package/dist/src/commands/deploy.js +3 -61
  16. package/dist/src/commands/deploy.js.map +1 -1
  17. package/dist/src/commands/deployments.d.ts.map +1 -1
  18. package/dist/src/commands/deployments.js +3 -36
  19. package/dist/src/commands/deployments.js.map +1 -1
  20. package/dist/src/commands/dev.d.ts.map +1 -1
  21. package/dist/src/commands/dev.js +7 -10
  22. package/dist/src/commands/dev.js.map +1 -1
  23. package/dist/src/commands/experiment.d.ts +1 -3
  24. package/dist/src/commands/experiment.d.ts.map +1 -1
  25. package/dist/src/commands/experiment.js +4 -102
  26. package/dist/src/commands/experiment.js.map +1 -1
  27. package/dist/src/commands/promote.d.ts.map +1 -1
  28. package/dist/src/commands/promote.js +3 -21
  29. package/dist/src/commands/promote.js.map +1 -1
  30. package/dist/src/commands/rollback.d.ts.map +1 -1
  31. package/dist/src/commands/rollback.js +3 -24
  32. package/dist/src/commands/rollback.js.map +1 -1
  33. package/dist/src/commands/secrets.d.ts.map +1 -1
  34. package/dist/src/commands/secrets.js +2 -102
  35. package/dist/src/commands/secrets.js.map +1 -1
  36. package/dist/src/commands/status.d.ts.map +1 -1
  37. package/dist/src/commands/status.js +3 -49
  38. package/dist/src/commands/status.js.map +1 -1
  39. package/dist/tsconfig.tsbuildinfo +1 -1
  40. package/package.json +7 -8
  41. package/src/commands/audit.ts +4 -71
  42. package/src/commands/build-manifest-types.ts +1 -1
  43. package/src/commands/build-tools.ts +5 -142
  44. package/src/commands/build.ts +1 -1
  45. package/src/commands/deploy.test.ts +2 -13
  46. package/src/commands/deploy.ts +5 -67
  47. package/src/commands/deployments.ts +3 -39
  48. package/src/commands/dev.ts +7 -10
  49. package/src/commands/experiment.ts +4 -110
  50. package/src/commands/promote.ts +3 -21
  51. package/src/commands/rollback.ts +3 -24
  52. package/src/commands/secrets.test.ts +12 -134
  53. package/src/commands/secrets.ts +2 -116
  54. package/src/commands/status.ts +3 -51
  55. package/dist/src/shared/platform-client.d.ts +0 -110
  56. package/dist/src/shared/platform-client.d.ts.map +0 -1
  57. package/dist/src/shared/platform-client.js +0 -263
  58. package/dist/src/shared/platform-client.js.map +0 -1
  59. package/src/commands/audit.test.ts +0 -92
  60. package/src/commands/experiment.test.ts +0 -125
  61. package/src/shared/platform-client.test.ts +0 -70
  62. package/src/shared/platform-client.ts +0 -343
@@ -1,343 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2025 Amodal Labs, Inc.
4
- * SPDX-License-Identifier: MIT
5
- */
6
-
7
- import {readProjectLink} from '../commands/link.js';
8
- import {readRcFile} from '../commands/login.js';
9
-
10
- /**
11
- * Metadata returned for a deployment by the platform API.
12
- */
13
- export interface DeploymentMeta {
14
- id: string;
15
- environment: string;
16
- isActive: boolean;
17
- createdAt: string;
18
- createdBy: string;
19
- source: string;
20
- commitSha?: string;
21
- branch?: string;
22
- message?: string;
23
- }
24
-
25
- /**
26
- * Resolve platform URL and API key from multiple sources:
27
- * 1. Explicit options (flags)
28
- * 2. .amodal/project.json (platformUrl from `amodal link`)
29
- * 3. ~/.amodalrc (auth token from `amodal login`)
30
- * 4. Env vars (fallback)
31
- */
32
- export async function resolvePlatformConfig(options?: {
33
- url?: string;
34
- apiKey?: string;
35
- }): Promise<{url: string; apiKey: string}> {
36
- let url = options?.url;
37
- let apiKey = options?.apiKey;
38
-
39
- // Try project link for URL
40
- if (!url) {
41
- const link = await readProjectLink();
42
- if (link?.platformUrl) {
43
- url = link.platformUrl;
44
- }
45
- }
46
-
47
- // Try rc file for auth token
48
- if (!apiKey) {
49
- const rc = await readRcFile();
50
- if (rc.platform?.token) {
51
- apiKey = rc.platform.token;
52
- // Also use the URL from rc if still missing
53
- if (!url && rc.platform.url) {
54
- url = rc.platform.url;
55
- }
56
- }
57
- }
58
-
59
- // Env vars as fallback
60
- if (!url) url = process.env['PLATFORM_API_URL'];
61
- if (!apiKey) apiKey = process.env['PLATFORM_API_KEY'];
62
-
63
- if (!url) throw new Error('Platform URL not found. Run `amodal login` + `amodal link`, or set PLATFORM_API_URL.');
64
- if (!apiKey) throw new Error('Platform auth not found. Run `amodal login`, or set PLATFORM_API_KEY.');
65
-
66
- return {url: url.replace(/\/$/, ''), apiKey};
67
- }
68
-
69
- /**
70
- * Platform API client for snapshot deployments.
71
- *
72
- * Resolves credentials from: explicit options → project link → rc file → env vars.
73
- * Use `PlatformClient.create()` for async auto-discovery, or `new PlatformClient()` for sync usage.
74
- */
75
- export class PlatformClient {
76
- private readonly baseUrl: string;
77
- private readonly apiKey: string;
78
-
79
- constructor(options?: {url?: string; apiKey?: string}) {
80
- const url = options?.url ?? process.env['PLATFORM_API_URL'];
81
- const apiKey = options?.apiKey ?? process.env['PLATFORM_API_KEY'];
82
- if (!url) throw new Error('Platform URL not found. Run `amodal login` + `amodal link`, or set PLATFORM_API_URL.');
83
- if (!apiKey) throw new Error('Platform auth not found. Run `amodal login`, or set PLATFORM_API_KEY.');
84
- this.baseUrl = url.replace(/\/$/, '');
85
- this.apiKey = apiKey;
86
- }
87
-
88
- /**
89
- * Create a PlatformClient with auto-discovery of credentials.
90
- * Resolves from: explicit options → project link → rc file → env vars.
91
- */
92
- static async create(options?: {url?: string; apiKey?: string}): Promise<PlatformClient> {
93
- const config = await resolvePlatformConfig(options);
94
- return new PlatformClient(config);
95
- }
96
-
97
- private headers(): Record<string, string> {
98
- return {
99
- 'Authorization': `Bearer ${this.apiKey}`,
100
- 'Content-Type': 'application/json',
101
- };
102
- }
103
-
104
- private async request<T>(method: string, path: string, body?: unknown): Promise<T> {
105
- const url = `${this.baseUrl}${path}`;
106
- let resp = await fetch(url, {
107
- method,
108
- headers: this.headers(),
109
- ...(body ? {body: JSON.stringify(body)} : {}),
110
- });
111
-
112
- // Auto-refresh on 401
113
- if (resp.status === 401) {
114
- const refreshed = await this.tryRefreshToken();
115
- if (refreshed) {
116
- resp = await fetch(url, {
117
- method,
118
- headers: this.headers(),
119
- ...(body ? {body: JSON.stringify(body)} : {}),
120
- });
121
- }
122
- }
123
-
124
- if (!resp.ok) {
125
- let detail = '';
126
- try {
127
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
128
- const errBody = await resp.json() as {error?: string};
129
- detail = errBody.error ? `: ${errBody.error}` : '';
130
- } catch {
131
- // ignore parse errors
132
- }
133
- throw new Error(`Platform API ${method} ${path} failed (${resp.status})${detail}`);
134
- }
135
-
136
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
137
- return resp.json() as Promise<T>;
138
- }
139
-
140
- /**
141
- * Try to refresh the token using the stored refresh token.
142
- * Updates both the in-memory key and the rc file on success.
143
- */
144
- private async tryRefreshToken(): Promise<boolean> {
145
- try {
146
- const {readRcFile} = await import('../commands/login.js');
147
- const rc = await readRcFile();
148
- if (!rc.platform?.refreshToken) return false;
149
-
150
- const res = await fetch(`${this.baseUrl}/api/auth/refresh`, {
151
- method: 'POST',
152
- headers: {'Content-Type': 'application/json'},
153
- body: JSON.stringify({refresh_token: rc.platform.refreshToken}),
154
- });
155
- if (!res.ok) return false;
156
-
157
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
158
- const data = (await res.json()) as {access_token?: string; refresh_token?: string};
159
- if (!data.access_token) return false;
160
-
161
- // Update in-memory
162
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- private field update
163
- (this as unknown as {apiKey: string}).apiKey = data.access_token;
164
-
165
- // Persist to rc file
166
- rc.platform.token = data.access_token;
167
- if (data.refresh_token) rc.platform.refreshToken = data.refresh_token;
168
- const {writeFile} = await import('node:fs/promises');
169
- const {homedir} = await import('node:os');
170
- const path = await import('node:path');
171
- const rcPath = path.join(homedir(), '.amodalrc');
172
- await writeFile(rcPath, JSON.stringify(rc, null, 2) + '\n', {mode: 0o600});
173
-
174
- process.stderr.write('[auth] Token refreshed automatically.\n');
175
- return true;
176
- } catch {
177
- return false;
178
- }
179
- }
180
-
181
- /**
182
- * Trigger a runtime-app build on the build server.
183
- * Sends the repo tarball to the build server which builds the SPA and uploads to R2.
184
- */
185
- async triggerBuild(
186
- buildServerUrl: string,
187
- appId: string,
188
- deployId: string,
189
- repoTarball: import('node:fs').ReadStream,
190
- ): Promise<void> {
191
- const url = `${buildServerUrl}/build`;
192
-
193
- const formData = new FormData();
194
- formData.append('appId', appId);
195
- formData.append('deployId', deployId);
196
-
197
- // Convert ReadStream to Blob for FormData
198
- const chunks: Uint8Array[] = [];
199
- for await (const chunk of repoTarball) {
200
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- ReadStream chunks are Buffer/Uint8Array
201
- chunks.push(chunk as Uint8Array);
202
- }
203
- const blob = new Blob(chunks, {type: 'application/gzip'});
204
- formData.append('repo', blob, 'repo.tar.gz');
205
-
206
- const resp = await fetch(url, {
207
- method: 'POST',
208
- headers: {Authorization: `Bearer ${this.apiKey}`},
209
- body: formData,
210
- });
211
-
212
- if (!resp.ok) {
213
- let detail = '';
214
- try {
215
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
216
- const errBody = await resp.json() as {error?: string; message?: string};
217
- detail = errBody.message ?? errBody.error ?? '';
218
- } catch { /* ignore */ }
219
- throw new Error(`Build server failed (${resp.status}): ${detail}`);
220
- }
221
- }
222
-
223
- /**
224
- * Trigger a remote build:
225
- * 1. Get scoped R2 temp credentials from the platform API
226
- * 2. Upload the tarball directly to R2 with those creds
227
- * 3. Tell the platform API to trigger a Fly Machine build
228
- *
229
- * Returns a buildId for polling.
230
- */
231
- async triggerRemoteBuild(
232
- appId: string,
233
- environment: string,
234
- tarballPath: string,
235
- message?: string,
236
- ): Promise<{ buildId: string }> {
237
- // Step 1: Mint scoped R2 temp credentials for the upload
238
-
239
- const uploadInfo = await this.request<{
240
- buildId: string;
241
- tarballKey: string;
242
- bucket: string;
243
- endpoint: string;
244
- accessKeyId: string;
245
- secretAccessKey: string;
246
- sessionToken: string;
247
- }>(
248
- 'POST',
249
- '/api/deploys/build?action=upload-url',
250
- {appId},
251
- );
252
-
253
- // Step 2: Upload tarball directly to R2 with the scoped temp creds
254
- const {readFileSync} = await import('node:fs');
255
- const tarball = readFileSync(tarballPath);
256
-
257
- const {S3Client, PutObjectCommand} = await import('@aws-sdk/client-s3');
258
- const s3 = new S3Client({
259
- region: 'auto',
260
- endpoint: uploadInfo.endpoint,
261
- credentials: {
262
- accessKeyId: uploadInfo.accessKeyId,
263
- secretAccessKey: uploadInfo.secretAccessKey,
264
- sessionToken: uploadInfo.sessionToken,
265
- },
266
- });
267
-
268
- await s3.send(
269
- new PutObjectCommand({
270
- Bucket: uploadInfo.bucket,
271
- Key: uploadInfo.tarballKey,
272
- Body: tarball,
273
- ContentType: 'application/gzip',
274
- }),
275
- );
276
-
277
- // Step 3: Trigger the build
278
-
279
- const result = await this.request<{buildId: string}>(
280
- 'POST',
281
- '/api/deploys/build?action=trigger',
282
- {appId, tarballKey: uploadInfo.tarballKey, environment, message},
283
- );
284
-
285
- return result;
286
- }
287
-
288
- /**
289
- * Poll build status.
290
- */
291
- async getBuildStatus(buildId: string): Promise<{
292
- status: 'building' | 'complete' | 'error';
293
- deployId?: string;
294
- environment?: string;
295
- error?: string;
296
- }> {
297
- return this.request('GET', `/api/builds/${encodeURIComponent(buildId)}/status`);
298
- }
299
-
300
- /**
301
- * List deployments for the authenticated app.
302
- */
303
- async listDeployments(options: {
304
- environment?: string;
305
- limit?: number;
306
- } = {}): Promise<DeploymentMeta[]> {
307
- const params = new URLSearchParams();
308
- if (options.environment) params.set('environment', options.environment);
309
- if (options.limit) params.set('limit', String(options.limit));
310
- const qs = params.toString();
311
- return this.request<DeploymentMeta[]>('GET', `/api/deployments${qs ? `?${qs}` : ''}`);
312
- }
313
-
314
- /**
315
- * Rollback to a previous deployment.
316
- */
317
- async rollback(options: {
318
- deployId?: string;
319
- environment?: string;
320
- } = {}): Promise<DeploymentMeta> {
321
- return this.request<DeploymentMeta>('POST', '/api/deployments/rollback', {
322
- deployId: options.deployId,
323
- environment: options.environment ?? 'production',
324
- });
325
- }
326
-
327
- /**
328
- * Promote a deployment from one environment to another.
329
- */
330
- async promote(fromEnv: string, toEnv: string = 'production'): Promise<DeploymentMeta> {
331
- return this.request<DeploymentMeta>('POST', '/api/deployments/promote', {
332
- fromEnvironment: fromEnv,
333
- toEnvironment: toEnv,
334
- });
335
- }
336
-
337
- /**
338
- * Get status of a specific deployment.
339
- */
340
- async getStatus(deployId: string): Promise<DeploymentMeta> {
341
- return this.request<DeploymentMeta>('GET', `/api/deployments/${deployId}`);
342
- }
343
- }