freestyle-sandboxes 0.1.30 → 0.1.32

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 (4) hide show
  1. package/cli.mjs +253 -23
  2. package/index.cjs +61 -12
  3. package/index.mjs +61 -12
  4. package/package.json +1 -1
package/cli.mjs CHANGED
@@ -64,6 +64,26 @@ ${line}
64
64
  }
65
65
  fs.writeFileSync(envPath, next, { encoding: "utf-8" });
66
66
  }
67
+ function removeRefreshTokenFromDotenv() {
68
+ const envPath = path.join(process.cwd(), ".env");
69
+ if (!fs.existsSync(envPath)) {
70
+ return false;
71
+ }
72
+ const existing = fs.readFileSync(envPath, "utf-8");
73
+ const lines = existing.split(/\r?\n/);
74
+ const nextLines = lines.filter(
75
+ (line) => !line.startsWith(`${STACK_REFRESH_TOKEN_ENV_KEY}=`)
76
+ );
77
+ if (nextLines.length === lines.length) {
78
+ return false;
79
+ }
80
+ const next = nextLines.filter((line) => line.length > 0).join("\n");
81
+ fs.writeFileSync(envPath, next.length > 0 ? `${next}
82
+ ` : "", {
83
+ encoding: "utf-8"
84
+ });
85
+ return true;
86
+ }
67
87
  function walkUpDirectories(startDir) {
68
88
  const result = [];
69
89
  let current = path.resolve(startDir);
@@ -148,6 +168,9 @@ function discoverStackConfigFromWorkspace() {
148
168
  }
149
169
  return discovered;
150
170
  }
171
+ function resolveAuthFilePath() {
172
+ return process.env.FREESTYLE_STACK_AUTH_FILE ?? path.join(os.homedir(), ".freestyle", "stack-auth.json");
173
+ }
151
174
  function resolveStackConfig() {
152
175
  const discovered = discoverStackConfigFromWorkspace();
153
176
  const projectId = process.env.FREESTYLE_STACK_PROJECT_ID ?? process.env.NEXT_PUBLIC_STACK_PROJECT_ID ?? process.env.VITE_STACK_PROJECT_ID ?? discovered.projectId ?? DEFAULT_STACK_PROJECT_ID;
@@ -156,8 +179,8 @@ function resolveStackConfig() {
156
179
  return null;
157
180
  }
158
181
  const stackApiUrl = (process.env.FREESTYLE_STACK_API_URL ?? DEFAULT_STACK_API_URL).replace(/\/+$/, "");
159
- const appUrl = (process.env.FREESTYLE_STACK_APP_URL ?? DEFAULT_STACK_APP_URL).replace(/\/+$/, "");
160
- const authFilePath = process.env.FREESTYLE_STACK_AUTH_FILE ?? path.join(os.homedir(), ".freestyle", "stack-auth.json");
182
+ const appUrl = (process.env.FREESTYLE_STACK_APP_URL ?? process.env.FREESTYLE_DASHBOARD_URL ?? DEFAULT_STACK_APP_URL).replace(/\/+$/, "");
183
+ const authFilePath = process.env.FREESTYLE_STACK_AUTH_FILE ?? resolveAuthFilePath();
161
184
  return {
162
185
  stackApiUrl,
163
186
  appUrl,
@@ -185,21 +208,23 @@ function loadStoredAuth(config) {
185
208
  }
186
209
  return {
187
210
  refreshToken: auth.refreshToken,
188
- updatedAt: typeof auth.updatedAt === "number" ? auth.updatedAt : Date.now()
211
+ updatedAt: typeof auth.updatedAt === "number" ? auth.updatedAt : Date.now(),
212
+ defaultTeamId: typeof auth.defaultTeamId === "string" ? auth.defaultTeamId : void 0
189
213
  };
190
214
  } catch {
191
215
  return null;
192
216
  }
193
217
  }
194
- function persistAuth(config, refreshToken) {
218
+ function persistAuth(config, auth) {
195
219
  const dirPath = path.dirname(config.authFilePath);
196
220
  fs.mkdirSync(dirPath, { recursive: true });
197
221
  fs.writeFileSync(
198
222
  config.authFilePath,
199
223
  JSON.stringify(
200
224
  {
201
- refreshToken,
202
- updatedAt: Date.now()
225
+ refreshToken: auth.refreshToken,
226
+ updatedAt: auth.updatedAt,
227
+ defaultTeamId: auth.defaultTeamId
203
228
  },
204
229
  null,
205
230
  2
@@ -215,6 +240,23 @@ function clearStoredAuth(config) {
215
240
  } catch {
216
241
  }
217
242
  }
243
+ function logoutCliAuth(options) {
244
+ const authFilePath = resolveAuthFilePath();
245
+ let clearedStored = false;
246
+ try {
247
+ if (fs.existsSync(authFilePath)) {
248
+ fs.unlinkSync(authFilePath);
249
+ clearedStored = true;
250
+ }
251
+ } catch {
252
+ }
253
+ let clearedDotenv = false;
254
+ if (options?.removeFromDotenv) {
255
+ clearedDotenv = removeRefreshTokenFromDotenv();
256
+ }
257
+ delete process.env[STACK_REFRESH_TOKEN_ENV_KEY];
258
+ return { clearedStored, clearedDotenv };
259
+ }
218
260
  function tryOpenBrowser(url) {
219
261
  try {
220
262
  if (process.platform === "darwin") {
@@ -346,7 +388,11 @@ async function getStackAccessTokenForCli(options) {
346
388
  let refreshToken = refreshTokenFromEnv ?? stored?.refreshToken;
347
389
  if (!refreshToken) {
348
390
  refreshToken = await startCliLogin(config);
349
- persistAuth(config, refreshToken);
391
+ const auth = {
392
+ refreshToken,
393
+ updatedAt: Date.now()
394
+ };
395
+ persistAuth(config, auth);
350
396
  persistRefreshTokenToDotenv(refreshToken, options);
351
397
  }
352
398
  let refreshed = await refreshStackAccessToken(config, refreshToken);
@@ -355,7 +401,12 @@ async function getStackAccessTokenForCli(options) {
355
401
  clearStoredAuth(config);
356
402
  }
357
403
  refreshToken = await startCliLogin(config);
358
- persistAuth(config, refreshToken);
404
+ const auth = {
405
+ refreshToken,
406
+ updatedAt: Date.now(),
407
+ defaultTeamId: stored?.defaultTeamId
408
+ };
409
+ persistAuth(config, auth);
359
410
  persistRefreshTokenToDotenv(refreshToken, options);
360
411
  refreshed = await refreshStackAccessToken(config, refreshToken);
361
412
  }
@@ -363,26 +414,153 @@ async function getStackAccessTokenForCli(options) {
363
414
  throw new Error("Failed to authenticate.");
364
415
  }
365
416
  if (refreshed.refreshToken && refreshed.refreshToken !== refreshToken) {
366
- persistAuth(config, refreshed.refreshToken);
417
+ const auth = {
418
+ refreshToken: refreshed.refreshToken,
419
+ updatedAt: Date.now(),
420
+ defaultTeamId: stored?.defaultTeamId
421
+ };
422
+ persistAuth(config, auth);
367
423
  persistRefreshTokenToDotenv(refreshed.refreshToken, options);
368
424
  }
369
425
  return refreshed.accessToken;
370
426
  }
427
+ function getDashboardApiUrl() {
428
+ return process.env.FREESTYLE_DASHBOARD_URL || "https://dash.freestyle.sh";
429
+ }
430
+ async function callDashboardApi(endpoint, accessToken, body) {
431
+ const response = await fetch(`${getDashboardApiUrl()}${endpoint}`, {
432
+ method: "POST",
433
+ headers: {
434
+ "Content-Type": "application/json"
435
+ },
436
+ body: JSON.stringify({
437
+ data: {
438
+ accessToken,
439
+ ...body
440
+ }
441
+ })
442
+ });
443
+ if (!response.ok) {
444
+ throw new Error(
445
+ `Dashboard API call failed: ${response.status} ${response.statusText}`
446
+ );
447
+ }
448
+ return response.json();
449
+ }
450
+ async function getTeamsForCli() {
451
+ const config = resolveStackConfig();
452
+ if (!config) {
453
+ throw new Error(
454
+ "Stack Auth is not configured. Please check your environment variables."
455
+ );
456
+ }
457
+ const stored = loadStoredAuth(config);
458
+ if (!stored?.refreshToken) {
459
+ throw new Error(
460
+ "No authentication found. Please run 'freestyle login' first."
461
+ );
462
+ }
463
+ const tokenResponse = await refreshStackAccessToken(
464
+ config,
465
+ stored.refreshToken
466
+ );
467
+ if (!tokenResponse) {
468
+ throw new Error("Failed to refresh access token.");
469
+ }
470
+ const teams = await callDashboardApi("/api/cli/teams", tokenResponse.accessToken);
471
+ return teams;
472
+ }
473
+ async function setDefaultTeam(teamId) {
474
+ const config = resolveStackConfig();
475
+ if (!config) {
476
+ throw new Error(
477
+ "Stack Auth is not configured. Please check your environment variables."
478
+ );
479
+ }
480
+ const stored = loadStoredAuth(config);
481
+ if (!stored?.refreshToken) {
482
+ throw new Error(
483
+ "No authentication found. Please run 'freestyle login' first."
484
+ );
485
+ }
486
+ const auth = {
487
+ refreshToken: stored.refreshToken,
488
+ updatedAt: Date.now(),
489
+ defaultTeamId: teamId
490
+ };
491
+ persistAuth(config, auth);
492
+ }
493
+ function getDefaultTeamId() {
494
+ const config = resolveStackConfig();
495
+ if (!config) {
496
+ return void 0;
497
+ }
498
+ const stored = loadStoredAuth(config);
499
+ return stored?.defaultTeamId;
500
+ }
371
501
 
372
- async function getFreestyleClient() {
373
- const apiKey = process.env.FREESTYLE_API_KEY;
374
- const baseUrl = process.env.FREESTYLE_API_URL;
375
- if (apiKey) {
376
- return new Freestyle({ apiKey, baseUrl });
502
+ function createProxyFetch(accessToken, teamId) {
503
+ const dashboardApiUrl = process.env.FREESTYLE_DASHBOARD_URL || "https://dash.freestyle.sh";
504
+ return async (url, init) => {
505
+ const urlObj = typeof url === "string" ? new URL(url) : url instanceof URL ? url : new URL(url.url);
506
+ const path2 = urlObj.pathname + urlObj.search;
507
+ const proxyResponse = await fetch(`${dashboardApiUrl}/api/proxy/request`, {
508
+ method: "POST",
509
+ headers: {
510
+ "Content-Type": "application/json"
511
+ },
512
+ body: JSON.stringify({
513
+ data: {
514
+ accessToken,
515
+ teamId,
516
+ path: path2.startsWith("/") ? path2.substring(1) : path2,
517
+ method: init?.method || "GET",
518
+ headers: init?.headers ? Object.fromEntries(new Headers(init.headers).entries()) : {},
519
+ body: init?.body ? init.body.toString() : void 0
520
+ }
521
+ })
522
+ });
523
+ if (!proxyResponse.ok) {
524
+ const errorText = await proxyResponse.text();
525
+ return new Response(errorText, {
526
+ status: proxyResponse.status,
527
+ statusText: proxyResponse.statusText
528
+ });
529
+ }
530
+ const data = await proxyResponse.json();
531
+ return new Response(JSON.stringify(data), {
532
+ status: 200,
533
+ headers: { "Content-Type": "application/json" }
534
+ });
535
+ };
536
+ }
537
+ async function getFreestyleClient(teamId) {
538
+ const directApiKey = process.env.FREESTYLE_API_KEY;
539
+ if (directApiKey) {
540
+ const baseUrl2 = process.env.FREESTYLE_API_URL;
541
+ return new Freestyle({ apiKey: directApiKey, baseUrl: baseUrl2 });
377
542
  }
378
543
  const accessToken = await getStackAccessTokenForCli();
379
- if (accessToken) {
380
- return new Freestyle({ accessToken, baseUrl });
544
+ if (!accessToken) {
545
+ console.error(
546
+ "Error: No API key found. Please run 'freestyle login' or set FREESTYLE_API_KEY in your .env file."
547
+ );
548
+ process.exit(1);
381
549
  }
382
- console.error(
383
- "Error: FREESTYLE_API_KEY is required. Set it in your current folder's .env file."
384
- );
385
- process.exit(1);
550
+ const resolvedTeamId = getDefaultTeamId();
551
+ if (!resolvedTeamId) {
552
+ console.error(
553
+ "Error: No team selected. Please run 'freestyle login' to set a default team."
554
+ );
555
+ process.exit(1);
556
+ }
557
+ const baseUrl = process.env.FREESTYLE_API_URL || "https://api.freestyle.sh";
558
+ return new Freestyle({
559
+ apiKey: "placeholder",
560
+ // Need something to pass validation
561
+ baseUrl,
562
+ fetch: createProxyFetch(accessToken, resolvedTeamId)
563
+ });
386
564
  }
387
565
  function handleError(error) {
388
566
  if (error.response) {
@@ -405,13 +583,13 @@ function formatTable(headers, rows) {
405
583
  const maxRowWidth = Math.max(...rows.map((r) => (r[i] || "").length));
406
584
  return Math.max(h.length, maxRowWidth);
407
585
  });
408
- const headerRow = headers.map((h, i) => h.padEnd(colWidths[i])).join(" ");
586
+ const headerRow = headers.map((h, i) => h.padEnd(colWidths[i] || 0)).join(" ");
409
587
  const separator = colWidths.map((w) => "-".repeat(w)).join(" ");
410
588
  console.log(headerRow);
411
589
  console.log(separator);
412
590
  rows.forEach((row) => {
413
591
  console.log(
414
- row.map((cell, i) => (cell || "").padEnd(colWidths[i])).join(" ")
592
+ row.map((cell, i) => (cell || "").padEnd(colWidths[i] || 0)).join(" ")
415
593
  );
416
594
  });
417
595
  }
@@ -1675,6 +1853,58 @@ const loginCommand = {
1675
1853
  } else {
1676
1854
  console.log("\u2713 Saved refresh token to global CLI auth store");
1677
1855
  }
1856
+ console.log("Fetching teams...");
1857
+ const teams = await getTeamsForCli();
1858
+ if (teams.length === 0) {
1859
+ console.log(
1860
+ "\u26A0\uFE0F No teams found. You may need to create a team in the dashboard."
1861
+ );
1862
+ return;
1863
+ }
1864
+ const defaultTeam = teams[0];
1865
+ console.log(`Setting up default team: ${defaultTeam.displayName}`);
1866
+ await setDefaultTeam(defaultTeam.id);
1867
+ console.log(
1868
+ `\u2713 Default team configured: ${defaultTeam.displayName} (${defaultTeam.id})`
1869
+ );
1870
+ console.log("You can now use the CLI to manage resources.");
1871
+ if (teams.length > 1) {
1872
+ console.log(
1873
+ `
1874
+ \u2139\uFE0F You have ${teams.length} teams. Use 'freestyle team switch' to change teams.`
1875
+ );
1876
+ }
1877
+ } catch (error) {
1878
+ handleError(error);
1879
+ }
1880
+ }
1881
+ };
1882
+
1883
+ const logoutCommand = {
1884
+ command: "logout",
1885
+ describe: "Sign out the CLI",
1886
+ builder: (yargs) => {
1887
+ return yargs.option("dotenv", {
1888
+ type: "boolean",
1889
+ description: "Remove the refresh token from the current folder's .env",
1890
+ default: false
1891
+ });
1892
+ },
1893
+ handler: async (argv) => {
1894
+ loadEnv();
1895
+ const args = argv;
1896
+ try {
1897
+ const result = logoutCliAuth({ removeFromDotenv: args.dotenv });
1898
+ if (!result.clearedStored && !result.clearedDotenv) {
1899
+ console.log("No stored credentials found.");
1900
+ return;
1901
+ }
1902
+ if (result.clearedStored) {
1903
+ console.log("\u2713 Cleared CLI credentials");
1904
+ }
1905
+ if (result.clearedDotenv) {
1906
+ console.log("\u2713 Removed refresh token from .env");
1907
+ }
1678
1908
  } catch (error) {
1679
1909
  handleError(error);
1680
1910
  }
@@ -1682,4 +1912,4 @@ const loginCommand = {
1682
1912
  };
1683
1913
 
1684
1914
  dotenv.config({ quiet: true });
1685
- yargs(hideBin(process.argv)).scriptName("freestyle").usage("$0 <command> [options]").command(vmCommand).command(gitCommand).command(domainsCommand).command(cronCommand).command(loginCommand).command(deployCommand).command(runCommand).demandCommand(1, "You need to specify a command").help().alias("help", "h").version().alias("version", "v").strict().parse();
1915
+ yargs(hideBin(process.argv)).scriptName("freestyle").usage("$0 <command> [options]").command(vmCommand).command(gitCommand).command(domainsCommand).command(cronCommand).command(loginCommand).command(logoutCommand).command(deployCommand).command(runCommand).demandCommand(1, "You need to specify a command").help().alias("help", "h").version().alias("version", "v").strict().parse();
package/index.cjs CHANGED
@@ -3772,7 +3772,7 @@ class ApiClient {
3772
3772
  if (this.apiKey) {
3773
3773
  headers["Authorization"] = `Bearer ${this.apiKey}`;
3774
3774
  } else if (this.accessToken) {
3775
- headers["X-Freestyle-Identity-Access-Token"] = this.accessToken;
3775
+ headers["Authorization"] = `Bearer ${this.accessToken}`;
3776
3776
  }
3777
3777
  return headers;
3778
3778
  }
@@ -3820,33 +3820,82 @@ class ApiClient {
3820
3820
  return this.fetchFn(url, finalOptions);
3821
3821
  }
3822
3822
  getRaw(path, options) {
3823
- const url = this.buildUrl(path, options?.params, options?.query);
3823
+ const url = this.buildUrl(
3824
+ path,
3825
+ options?.params,
3826
+ options?.query
3827
+ );
3824
3828
  return this.requestRaw("GET", url, void 0, options?.headers);
3825
3829
  }
3826
3830
  get(path, ...args) {
3827
3831
  const options = args[0];
3828
- const url = this.buildUrl(path, options?.params, options?.query);
3829
- return this.request("GET", url, options?.body, options?.headers);
3832
+ const url = this.buildUrl(
3833
+ path,
3834
+ options?.params,
3835
+ options?.query
3836
+ );
3837
+ return this.request(
3838
+ "GET",
3839
+ url,
3840
+ options?.body,
3841
+ options?.headers
3842
+ );
3830
3843
  }
3831
3844
  post(path, ...args) {
3832
3845
  const options = args[0];
3833
- const url = this.buildUrl(path, options?.params, options?.query);
3834
- return this.request("POST", url, options?.body, options?.headers);
3846
+ const url = this.buildUrl(
3847
+ path,
3848
+ options?.params,
3849
+ options?.query
3850
+ );
3851
+ return this.request(
3852
+ "POST",
3853
+ url,
3854
+ options?.body,
3855
+ options?.headers
3856
+ );
3835
3857
  }
3836
3858
  put(path, ...args) {
3837
3859
  const options = args[0];
3838
- const url = this.buildUrl(path, options?.params, options?.query);
3839
- return this.request("PUT", url, options?.body, options?.headers);
3860
+ const url = this.buildUrl(
3861
+ path,
3862
+ options?.params,
3863
+ options?.query
3864
+ );
3865
+ return this.request(
3866
+ "PUT",
3867
+ url,
3868
+ options?.body,
3869
+ options?.headers
3870
+ );
3840
3871
  }
3841
3872
  delete(path, ...args) {
3842
3873
  const options = args[0];
3843
- const url = this.buildUrl(path, options?.params, options?.query);
3844
- return this.request("DELETE", url, options?.body, options?.headers);
3874
+ const url = this.buildUrl(
3875
+ path,
3876
+ options?.params,
3877
+ options?.query
3878
+ );
3879
+ return this.request(
3880
+ "DELETE",
3881
+ url,
3882
+ options?.body,
3883
+ options?.headers
3884
+ );
3845
3885
  }
3846
3886
  patch(path, ...args) {
3847
3887
  const options = args[0];
3848
- const url = this.buildUrl(path, options?.params, options?.query);
3849
- return this.request("PATCH", url, options?.body, options?.headers);
3888
+ const url = this.buildUrl(
3889
+ path,
3890
+ options?.params,
3891
+ options?.query
3892
+ );
3893
+ return this.request(
3894
+ "PATCH",
3895
+ url,
3896
+ options?.body,
3897
+ options?.headers
3898
+ );
3850
3899
  }
3851
3900
  }
3852
3901
 
package/index.mjs CHANGED
@@ -3770,7 +3770,7 @@ class ApiClient {
3770
3770
  if (this.apiKey) {
3771
3771
  headers["Authorization"] = `Bearer ${this.apiKey}`;
3772
3772
  } else if (this.accessToken) {
3773
- headers["X-Freestyle-Identity-Access-Token"] = this.accessToken;
3773
+ headers["Authorization"] = `Bearer ${this.accessToken}`;
3774
3774
  }
3775
3775
  return headers;
3776
3776
  }
@@ -3818,33 +3818,82 @@ class ApiClient {
3818
3818
  return this.fetchFn(url, finalOptions);
3819
3819
  }
3820
3820
  getRaw(path, options) {
3821
- const url = this.buildUrl(path, options?.params, options?.query);
3821
+ const url = this.buildUrl(
3822
+ path,
3823
+ options?.params,
3824
+ options?.query
3825
+ );
3822
3826
  return this.requestRaw("GET", url, void 0, options?.headers);
3823
3827
  }
3824
3828
  get(path, ...args) {
3825
3829
  const options = args[0];
3826
- const url = this.buildUrl(path, options?.params, options?.query);
3827
- return this.request("GET", url, options?.body, options?.headers);
3830
+ const url = this.buildUrl(
3831
+ path,
3832
+ options?.params,
3833
+ options?.query
3834
+ );
3835
+ return this.request(
3836
+ "GET",
3837
+ url,
3838
+ options?.body,
3839
+ options?.headers
3840
+ );
3828
3841
  }
3829
3842
  post(path, ...args) {
3830
3843
  const options = args[0];
3831
- const url = this.buildUrl(path, options?.params, options?.query);
3832
- return this.request("POST", url, options?.body, options?.headers);
3844
+ const url = this.buildUrl(
3845
+ path,
3846
+ options?.params,
3847
+ options?.query
3848
+ );
3849
+ return this.request(
3850
+ "POST",
3851
+ url,
3852
+ options?.body,
3853
+ options?.headers
3854
+ );
3833
3855
  }
3834
3856
  put(path, ...args) {
3835
3857
  const options = args[0];
3836
- const url = this.buildUrl(path, options?.params, options?.query);
3837
- return this.request("PUT", url, options?.body, options?.headers);
3858
+ const url = this.buildUrl(
3859
+ path,
3860
+ options?.params,
3861
+ options?.query
3862
+ );
3863
+ return this.request(
3864
+ "PUT",
3865
+ url,
3866
+ options?.body,
3867
+ options?.headers
3868
+ );
3838
3869
  }
3839
3870
  delete(path, ...args) {
3840
3871
  const options = args[0];
3841
- const url = this.buildUrl(path, options?.params, options?.query);
3842
- return this.request("DELETE", url, options?.body, options?.headers);
3872
+ const url = this.buildUrl(
3873
+ path,
3874
+ options?.params,
3875
+ options?.query
3876
+ );
3877
+ return this.request(
3878
+ "DELETE",
3879
+ url,
3880
+ options?.body,
3881
+ options?.headers
3882
+ );
3843
3883
  }
3844
3884
  patch(path, ...args) {
3845
3885
  const options = args[0];
3846
- const url = this.buildUrl(path, options?.params, options?.query);
3847
- return this.request("PATCH", url, options?.body, options?.headers);
3886
+ const url = this.buildUrl(
3887
+ path,
3888
+ options?.params,
3889
+ options?.query
3890
+ );
3891
+ return this.request(
3892
+ "PATCH",
3893
+ url,
3894
+ options?.body,
3895
+ options?.headers
3896
+ );
3848
3897
  }
3849
3898
  }
3850
3899
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "freestyle-sandboxes",
3
- "version": "0.1.30",
3
+ "version": "0.1.32",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "require": {