@specific.dev/cli 0.1.222 → 0.1.224

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 (95) hide show
  1. package/dist/admin/404/index.html +1 -1
  2. package/dist/admin/404.html +1 -1
  3. package/dist/admin/__next.!KGRlZmF1bHQp.__PAGE__.txt +1 -1
  4. package/dist/admin/__next.!KGRlZmF1bHQp.txt +1 -1
  5. package/dist/admin/__next._full.txt +2 -2
  6. package/dist/admin/__next._head.txt +1 -1
  7. package/dist/admin/__next._index.txt +2 -2
  8. package/dist/admin/__next._tree.txt +2 -2
  9. package/dist/admin/_next/static/chunks/{1fac7a1b12b837ea.css → 24ff82c52f4b2700.css} +3 -3
  10. package/dist/admin/_not-found/__next._full.txt +2 -2
  11. package/dist/admin/_not-found/__next._head.txt +1 -1
  12. package/dist/admin/_not-found/__next._index.txt +2 -2
  13. package/dist/admin/_not-found/__next._not-found.__PAGE__.txt +1 -1
  14. package/dist/admin/_not-found/__next._not-found.txt +1 -1
  15. package/dist/admin/_not-found/__next._tree.txt +2 -2
  16. package/dist/admin/_not-found/index.html +1 -1
  17. package/dist/admin/_not-found/index.txt +2 -2
  18. package/dist/admin/crons/__next.!KGRlZmF1bHQp.crons.__PAGE__.txt +1 -1
  19. package/dist/admin/crons/__next.!KGRlZmF1bHQp.crons.txt +1 -1
  20. package/dist/admin/crons/__next.!KGRlZmF1bHQp.txt +1 -1
  21. package/dist/admin/crons/__next._full.txt +2 -2
  22. package/dist/admin/crons/__next._head.txt +1 -1
  23. package/dist/admin/crons/__next._index.txt +2 -2
  24. package/dist/admin/crons/__next._tree.txt +2 -2
  25. package/dist/admin/crons/index.html +1 -1
  26. package/dist/admin/crons/index.txt +2 -2
  27. package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.__PAGE__.txt +1 -1
  28. package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.txt +1 -1
  29. package/dist/admin/databases/__next.!KGRlZmF1bHQp.txt +1 -1
  30. package/dist/admin/databases/__next._full.txt +2 -2
  31. package/dist/admin/databases/__next._head.txt +1 -1
  32. package/dist/admin/databases/__next._index.txt +2 -2
  33. package/dist/admin/databases/__next._tree.txt +2 -2
  34. package/dist/admin/databases/index.html +1 -1
  35. package/dist/admin/databases/index.txt +2 -2
  36. package/dist/admin/fullscreen/__next._full.txt +2 -2
  37. package/dist/admin/fullscreen/__next._head.txt +1 -1
  38. package/dist/admin/fullscreen/__next._index.txt +2 -2
  39. package/dist/admin/fullscreen/__next._tree.txt +2 -2
  40. package/dist/admin/fullscreen/__next.fullscreen.__PAGE__.txt +1 -1
  41. package/dist/admin/fullscreen/__next.fullscreen.txt +1 -1
  42. package/dist/admin/fullscreen/databases/__next._full.txt +2 -2
  43. package/dist/admin/fullscreen/databases/__next._head.txt +1 -1
  44. package/dist/admin/fullscreen/databases/__next._index.txt +2 -2
  45. package/dist/admin/fullscreen/databases/__next._tree.txt +2 -2
  46. package/dist/admin/fullscreen/databases/__next.fullscreen.databases.__PAGE__.txt +1 -1
  47. package/dist/admin/fullscreen/databases/__next.fullscreen.databases.txt +1 -1
  48. package/dist/admin/fullscreen/databases/__next.fullscreen.txt +1 -1
  49. package/dist/admin/fullscreen/databases/index.html +1 -1
  50. package/dist/admin/fullscreen/databases/index.txt +2 -2
  51. package/dist/admin/fullscreen/index.html +1 -1
  52. package/dist/admin/fullscreen/index.txt +2 -2
  53. package/dist/admin/index.html +1 -1
  54. package/dist/admin/index.txt +2 -2
  55. package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.__PAGE__.txt +1 -1
  56. package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.txt +1 -1
  57. package/dist/admin/mail/__next.!KGRlZmF1bHQp.txt +1 -1
  58. package/dist/admin/mail/__next._full.txt +2 -2
  59. package/dist/admin/mail/__next._head.txt +1 -1
  60. package/dist/admin/mail/__next._index.txt +2 -2
  61. package/dist/admin/mail/__next._tree.txt +2 -2
  62. package/dist/admin/mail/index.html +1 -1
  63. package/dist/admin/mail/index.txt +2 -2
  64. package/dist/admin/services/__next.!KGRlZmF1bHQp.services.__PAGE__.txt +1 -1
  65. package/dist/admin/services/__next.!KGRlZmF1bHQp.services.txt +1 -1
  66. package/dist/admin/services/__next.!KGRlZmF1bHQp.txt +1 -1
  67. package/dist/admin/services/__next._full.txt +2 -2
  68. package/dist/admin/services/__next._head.txt +1 -1
  69. package/dist/admin/services/__next._index.txt +2 -2
  70. package/dist/admin/services/__next._tree.txt +2 -2
  71. package/dist/admin/services/index.html +1 -1
  72. package/dist/admin/services/index.txt +2 -2
  73. package/dist/admin/storage/__next.!KGRlZmF1bHQp.storage.__PAGE__.txt +1 -1
  74. package/dist/admin/storage/__next.!KGRlZmF1bHQp.storage.txt +1 -1
  75. package/dist/admin/storage/__next.!KGRlZmF1bHQp.txt +1 -1
  76. package/dist/admin/storage/__next._full.txt +2 -2
  77. package/dist/admin/storage/__next._head.txt +1 -1
  78. package/dist/admin/storage/__next._index.txt +2 -2
  79. package/dist/admin/storage/__next._tree.txt +2 -2
  80. package/dist/admin/storage/index.html +1 -1
  81. package/dist/admin/storage/index.txt +2 -2
  82. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.txt +1 -1
  83. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.__PAGE__.txt +1 -1
  84. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.txt +1 -1
  85. package/dist/admin/workflows/__next._full.txt +2 -2
  86. package/dist/admin/workflows/__next._head.txt +1 -1
  87. package/dist/admin/workflows/__next._index.txt +2 -2
  88. package/dist/admin/workflows/__next._tree.txt +2 -2
  89. package/dist/admin/workflows/index.html +1 -1
  90. package/dist/admin/workflows/index.txt +2 -2
  91. package/dist/cli.js +349 -187
  92. package/package.json +1 -2
  93. /package/dist/admin/_next/static/{M3IqeXp32gvmPrMB03Tis → iE-b86kGlgLliZY5m6XDj}/_buildManifest.js +0 -0
  94. /package/dist/admin/_next/static/{M3IqeXp32gvmPrMB03Tis → iE-b86kGlgLliZY5m6XDj}/_clientMiddlewareManifest.json +0 -0
  95. /package/dist/admin/_next/static/{M3IqeXp32gvmPrMB03Tis → iE-b86kGlgLliZY5m6XDj}/_ssgManifest.js +0 -0
package/dist/cli.js CHANGED
@@ -367566,6 +367566,7 @@ function progressStreamFromBuffer(buffer, onProgress) {
367566
367566
  var FEEDBACK_NOT_ENABLED_CODE = "FEEDBACK_NOT_ENABLED";
367567
367567
  var CLI_DEPLOYMENTS_DISABLED_CODE = "CLI_DEPLOYMENTS_DISABLED";
367568
367568
  var PROJECT_PAUSED_CODE = "PROJECT_PAUSED";
367569
+ var QUOTA_EXCEEDED_CODE = "QUOTA_EXCEEDED";
367569
367570
  var ApiError = class extends Error {
367570
367571
  code;
367571
367572
  httpStatus;
@@ -371172,7 +371173,7 @@ function getClient(info) {
371172
371173
  const cacheKey = `${info.host}:${info.port}:${info.accessKey}`;
371173
371174
  const cached2 = clientCache.get(cacheKey);
371174
371175
  if (cached2) return cached2;
371175
- const client2 = new S3Client({
371176
+ const client = new S3Client({
371176
371177
  endpoint: `http://${info.host}:${info.port}`,
371177
371178
  region: "us-east-1",
371178
371179
  credentials: {
@@ -371181,8 +371182,8 @@ function getClient(info) {
371181
371182
  },
371182
371183
  forcePathStyle: true
371183
371184
  });
371184
- clientCache.set(cacheKey, client2);
371185
- return client2;
371185
+ clientCache.set(cacheKey, client);
371186
+ return client;
371186
371187
  }
371187
371188
  function sendJson(res, status, body) {
371188
371189
  res.setHeader("Content-Type", "application/json");
@@ -371200,14 +371201,14 @@ async function readJsonBody(req) {
371200
371201
  const text = Buffer.concat(chunks).toString("utf8");
371201
371202
  return text.length > 0 ? JSON.parse(text) : {};
371202
371203
  }
371203
- async function handleListObjects(client2, bucket, url, res) {
371204
+ async function handleListObjects(client, bucket, url, res) {
371204
371205
  const prefix = url.searchParams.get("prefix") ?? "";
371205
371206
  const cursor = url.searchParams.get("cursor") ?? void 0;
371206
371207
  const maxKeys = Math.min(
371207
371208
  Math.max(parseInt(url.searchParams.get("maxKeys") ?? "100", 10) || 100, 1),
371208
371209
  1e3
371209
371210
  );
371210
- const response = await client2.send(
371211
+ const response = await client.send(
371211
371212
  new ListObjectsV2Command({
371212
371213
  Bucket: bucket,
371213
371214
  Prefix: prefix,
@@ -371228,14 +371229,14 @@ async function handleListObjects(client2, bucket, url, res) {
371228
371229
  prefix
371229
371230
  });
371230
371231
  }
371231
- async function handleMetrics(client2, bucket, res) {
371232
+ async function handleMetrics(client, bucket, res) {
371232
371233
  let totalObjects = 0;
371233
371234
  let totalSize = 0;
371234
371235
  let continuationToken;
371235
371236
  let pages = 0;
371236
371237
  const MAX_PAGES = 100;
371237
371238
  do {
371238
- const response = await client2.send(
371239
+ const response = await client.send(
371239
371240
  new ListObjectsV2Command({
371240
371241
  Bucket: bucket,
371241
371242
  ContinuationToken: continuationToken
@@ -371255,9 +371256,9 @@ async function handleMetrics(client2, bucket, res) {
371255
371256
  ...pages >= MAX_PAGES ? { truncated: true } : {}
371256
371257
  });
371257
371258
  }
371258
- async function handleGetObject(client2, bucket, key, res) {
371259
+ async function handleGetObject(client, bucket, key, res) {
371259
371260
  try {
371260
- const response = await client2.send(
371261
+ const response = await client.send(
371261
371262
  new HeadObjectCommand({ Bucket: bucket, Key: key })
371262
371263
  );
371263
371264
  sendJson(res, 200, {
@@ -371277,12 +371278,12 @@ async function handleGetObject(client2, bucket, key, res) {
371277
371278
  throw error;
371278
371279
  }
371279
371280
  }
371280
- async function handleDeleteObject(client2, bucket, key, res) {
371281
+ async function handleDeleteObject(client, bucket, key, res) {
371281
371282
  if (key.endsWith("/")) {
371282
371283
  const allKeys = [];
371283
371284
  let continuationToken;
371284
371285
  do {
371285
- const listResponse = await client2.send(
371286
+ const listResponse = await client.send(
371286
371287
  new ListObjectsV2Command({
371287
371288
  Bucket: bucket,
371288
371289
  Prefix: key,
@@ -371297,7 +371298,7 @@ async function handleDeleteObject(client2, bucket, key, res) {
371297
371298
  const batches = [];
371298
371299
  for (let i = 0; i < allKeys.length; i += 1e3) {
371299
371300
  batches.push(
371300
- client2.send(
371301
+ client.send(
371301
371302
  new DeleteObjectsCommand({
371302
371303
  Bucket: bucket,
371303
371304
  Delete: {
@@ -371310,11 +371311,11 @@ async function handleDeleteObject(client2, bucket, key, res) {
371310
371311
  }
371311
371312
  await Promise.all(batches);
371312
371313
  } else {
371313
- await client2.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }));
371314
+ await client.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }));
371314
371315
  }
371315
371316
  sendJson(res, 200, { success: true });
371316
371317
  }
371317
- async function handleCreateFolder(client2, bucket, body, res) {
371318
+ async function handleCreateFolder(client, bucket, body, res) {
371318
371319
  const path172 = body.path;
371319
371320
  if (typeof path172 !== "string") {
371320
371321
  sendError(res, 400, "Missing path");
@@ -371326,12 +371327,12 @@ async function handleCreateFolder(client2, bucket, body, res) {
371326
371327
  sendError(res, 400, keyError);
371327
371328
  return;
371328
371329
  }
371329
- await client2.send(
371330
+ await client.send(
371330
371331
  new PutObjectCommand({ Bucket: bucket, Key: folderKey, Body: "" })
371331
371332
  );
371332
371333
  sendJson(res, 200, { key: folderKey });
371333
371334
  }
371334
- async function handleRename(client2, bucket, body, res) {
371335
+ async function handleRename(client, bucket, body, res) {
371335
371336
  const { sourceKey, destinationKey } = body;
371336
371337
  if (typeof sourceKey !== "string" || typeof destinationKey !== "string") {
371337
371338
  sendError(res, 400, "Missing sourceKey or destinationKey");
@@ -371348,7 +371349,7 @@ async function handleRename(client2, bucket, body, res) {
371348
371349
  const allKeys = [];
371349
371350
  let continuationToken;
371350
371351
  do {
371351
- const listResponse = await client2.send(
371352
+ const listResponse = await client.send(
371352
371353
  new ListObjectsV2Command({
371353
371354
  Bucket: bucket,
371354
371355
  Prefix: sourceKey,
@@ -371366,7 +371367,7 @@ async function handleRename(client2, bucket, body, res) {
371366
371367
  }
371367
371368
  await Promise.all(
371368
371369
  allKeys.map(
371369
- (k) => client2.send(
371370
+ (k) => client.send(
371370
371371
  new CopyObjectCommand({
371371
371372
  Bucket: bucket,
371372
371373
  CopySource: `${bucket}/${encodeURI(k)}`,
@@ -371378,7 +371379,7 @@ async function handleRename(client2, bucket, body, res) {
371378
371379
  const deleteBatches = [];
371379
371380
  for (let i = 0; i < allKeys.length; i += 1e3) {
371380
371381
  deleteBatches.push(
371381
- client2.send(
371382
+ client.send(
371382
371383
  new DeleteObjectsCommand({
371383
371384
  Bucket: bucket,
371384
371385
  Delete: {
@@ -371391,27 +371392,27 @@ async function handleRename(client2, bucket, body, res) {
371391
371392
  }
371392
371393
  await Promise.all(deleteBatches);
371393
371394
  } else {
371394
- await client2.send(
371395
+ await client.send(
371395
371396
  new CopyObjectCommand({
371396
371397
  Bucket: bucket,
371397
371398
  CopySource: `${bucket}/${encodeURI(sourceKey)}`,
371398
371399
  Key: destinationKey
371399
371400
  })
371400
371401
  );
371401
- await client2.send(
371402
+ await client.send(
371402
371403
  new DeleteObjectCommand({ Bucket: bucket, Key: sourceKey })
371403
371404
  );
371404
371405
  }
371405
371406
  sendJson(res, 200, { key: destinationKey });
371406
371407
  }
371407
- async function handleUploadData(client2, bucket, key, req, res) {
371408
+ async function handleUploadData(client, bucket, key, req, res) {
371408
371409
  const chunks = [];
371409
371410
  for await (const chunk of req) {
371410
371411
  chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
371411
371412
  }
371412
371413
  const body = Buffer.concat(chunks);
371413
371414
  const contentType = req.headers["content-type"];
371414
- await client2.send(
371415
+ await client.send(
371415
371416
  new PutObjectCommand({
371416
371417
  Bucket: bucket,
371417
371418
  Key: key,
@@ -371422,9 +371423,9 @@ async function handleUploadData(client2, bucket, key, req, res) {
371422
371423
  res.writeHead(200);
371423
371424
  res.end();
371424
371425
  }
371425
- async function handleDownloadData(client2, bucket, key, res) {
371426
+ async function handleDownloadData(client, bucket, key, res) {
371426
371427
  try {
371427
- const response = await client2.send(
371428
+ const response = await client.send(
371428
371429
  new GetObjectCommand({ Bucket: bucket, Key: key })
371429
371430
  );
371430
371431
  if (response.ContentType) {
@@ -371491,15 +371492,15 @@ async function handleStorageRequest(req, res, pathname, url, getStorageInstance)
371491
371492
  sendError(res, 404, `Storage '${storageName}' not found`);
371492
371493
  return true;
371493
371494
  }
371494
- const client2 = getClient(info);
371495
+ const client = getClient(info);
371495
371496
  const bucket = info.bucket;
371496
371497
  try {
371497
371498
  if (sub === "/objects" && req.method === "GET") {
371498
- await handleListObjects(client2, bucket, url, res);
371499
+ await handleListObjects(client, bucket, url, res);
371499
371500
  return true;
371500
371501
  }
371501
371502
  if (sub === "/metrics" && req.method === "GET") {
371502
- await handleMetrics(client2, bucket, res);
371503
+ await handleMetrics(client, bucket, res);
371503
371504
  return true;
371504
371505
  }
371505
371506
  if (sub === "/object") {
@@ -371514,11 +371515,11 @@ async function handleStorageRequest(req, res, pathname, url, getStorageInstance)
371514
371515
  return true;
371515
371516
  }
371516
371517
  if (req.method === "GET") {
371517
- await handleGetObject(client2, bucket, key, res);
371518
+ await handleGetObject(client, bucket, key, res);
371518
371519
  return true;
371519
371520
  }
371520
371521
  if (req.method === "DELETE") {
371521
- await handleDeleteObject(client2, bucket, key, res);
371522
+ await handleDeleteObject(client, bucket, key, res);
371522
371523
  return true;
371523
371524
  }
371524
371525
  }
@@ -371534,22 +371535,22 @@ async function handleStorageRequest(req, res, pathname, url, getStorageInstance)
371534
371535
  return true;
371535
371536
  }
371536
371537
  if (req.method === "PUT") {
371537
- await handleUploadData(client2, bucket, key, req, res);
371538
+ await handleUploadData(client, bucket, key, req, res);
371538
371539
  return true;
371539
371540
  }
371540
371541
  if (req.method === "GET") {
371541
- await handleDownloadData(client2, bucket, key, res);
371542
+ await handleDownloadData(client, bucket, key, res);
371542
371543
  return true;
371543
371544
  }
371544
371545
  }
371545
371546
  if (sub === "/folder" && req.method === "POST") {
371546
371547
  const body = await readJsonBody(req);
371547
- await handleCreateFolder(client2, bucket, body, res);
371548
+ await handleCreateFolder(client, bucket, body, res);
371548
371549
  return true;
371549
371550
  }
371550
371551
  if (sub === "/rename" && req.method === "POST") {
371551
371552
  const body = await readJsonBody(req);
371552
- await handleRename(client2, bucket, body, res);
371553
+ await handleRename(client, bucket, body, res);
371553
371554
  return true;
371554
371555
  }
371555
371556
  if (sub === "/presigned" && req.method === "POST") {
@@ -374611,7 +374612,6 @@ function getDefaultKey() {
374611
374612
  }
374612
374613
 
374613
374614
  // src/lib/analytics/index.ts
374614
- import { PostHog } from "posthog-node";
374615
374615
  import * as os6 from "os";
374616
374616
  import * as crypto4 from "crypto";
374617
374617
 
@@ -374716,6 +374716,52 @@ function findEnvironmentByNameOrId(environments, flag) {
374716
374716
  return environments.find((e) => e.id === flag) ?? environments.find((e) => e.name === flag);
374717
374717
  }
374718
374718
 
374719
+ // src/lib/deployments/format.ts
374720
+ var DASHBOARD_URL = "https://dashboard.specific.dev";
374721
+ function dashboardBillingUrl(projectId) {
374722
+ return `${DASHBOARD_URL}/projects/${encodeURIComponent(projectId)}/organization-settings/billing`;
374723
+ }
374724
+ function dashboardDeploymentUrl(projectId, environmentId, deploymentId) {
374725
+ return `${DASHBOARD_URL}/projects/${encodeURIComponent(projectId)}/env/${encodeURIComponent(environmentId)}/deployments/${encodeURIComponent(deploymentId)}`;
374726
+ }
374727
+ var PLAN_LIMIT_LABELS = {
374728
+ service: ["service", "services"],
374729
+ cron: ["cron", "crons"],
374730
+ postgres: ["postgres database", "postgres databases"],
374731
+ storage: ["storage bucket", "storage buckets"],
374732
+ redis: ["redis instance", "redis instances"],
374733
+ temporal: ["temporal namespace", "temporal namespaces"],
374734
+ mail: ["mail block", "mail blocks"]
374735
+ };
374736
+ function formatPlanLimitViolations(violations) {
374737
+ return violations.map((v) => {
374738
+ const [singular, plural] = PLAN_LIMIT_LABELS[v.resource] ?? [v.resource, v.resource];
374739
+ return `${v.declared} ${v.declared === 1 ? singular : plural} (limit ${v.limit})`;
374740
+ }).join(", ");
374741
+ }
374742
+ function formatErrorCode(code) {
374743
+ const labels = {
374744
+ build_failed: "Build failed",
374745
+ pre_deploy_failed: "Pre-deploy hook failed",
374746
+ deploy_failed: "Service failed to deploy",
374747
+ provisioning_failed: "Resource provisioning failed",
374748
+ health_check_failed: "Health check failed",
374749
+ post_deploy_failed: "Post-deploy hook failed",
374750
+ volume_reset_failed: "Volume reset failed",
374751
+ internal_error: "Internal error"
374752
+ };
374753
+ return labels[code] || code;
374754
+ }
374755
+
374756
+ // src/lib/project/quota.ts
374757
+ function formatProjectQuotaError(err) {
374758
+ if (!(err instanceof ApiError) || err.code !== QUOTA_EXCEEDED_CODE) return null;
374759
+ if (!err.apiError.includes("maxProjects")) return null;
374760
+ const limit = /limit:\s*(\d+)/.exec(err.apiError)?.[1];
374761
+ if (limit === void 0) return err.apiError;
374762
+ return `Your plan allows ${limit} project${limit === "1" ? "" : "s"}. Upgrade at ${DASHBOARD_URL} or delete a project.`;
374763
+ }
374764
+
374719
374765
  // src/lib/auth/freebuff.ts
374720
374766
  import { createHash as createHash2 } from "node:crypto";
374721
374767
  import { mkdirSync as mkdirSync12, readFileSync as readFileSync9, rmSync as rmSync4, writeFileSync as writeFileSync8 } from "node:fs";
@@ -375283,11 +375329,11 @@ async function getAgentAccessToken(credentials) {
375283
375329
  }
375284
375330
  async function saveCredentialsFromToken(token, deviceCode) {
375285
375331
  const freebuffConversionToken = (deviceCode ? readDeviceAttribution(deviceCode) : void 0) ?? process.env.SPECIFIC_FREEBUFF_BFCID;
375286
- const client2 = new ApiClient({
375332
+ const client = new ApiClient({
375287
375333
  token: token.access_token,
375288
375334
  ...freebuffConversionToken ? { freebuffConversionToken } : {}
375289
375335
  });
375290
- const user = await client2.getMe();
375336
+ const user = await client.getMe();
375291
375337
  const expiresAt = getTokenExpiresAt(token.access_token) ?? Date.now() + 3600 * 1e3;
375292
375338
  writeUserCredentials({
375293
375339
  accessToken: token.access_token,
@@ -375360,7 +375406,8 @@ async function getValidAccessToken() {
375360
375406
 
375361
375407
  // src/lib/analytics/index.ts
375362
375408
  var POSTHOG_HOST = "https://eu.i.posthog.com";
375363
- var client = null;
375409
+ var pendingRequests = /* @__PURE__ */ new Set();
375410
+ var shuttingDown = false;
375364
375411
  var anonymousId = null;
375365
375412
  var identified = false;
375366
375413
  function isEnabled() {
@@ -375381,52 +375428,88 @@ function getProjectId() {
375381
375428
  }
375382
375429
  return void 0;
375383
375430
  }
375384
- function getClient2() {
375385
- if (!isEnabled()) return null;
375386
- if (!client) {
375387
- client = new PostHog("phc_qNQCEUXh6ErdciQRRmeG2xlVvwFjkcW6A5bnOFJ8vXZ", {
375388
- host: POSTHOG_HOST,
375389
- flushAt: 1,
375390
- // Flush immediately for CLI
375391
- flushInterval: 0
375431
+ async function sendEvents(events) {
375432
+ let timer;
375433
+ const controller = new AbortController();
375434
+ try {
375435
+ const body = JSON.stringify({
375436
+ api_key: "phc_qNQCEUXh6ErdciQRRmeG2xlVvwFjkcW6A5bnOFJ8vXZ",
375437
+ batch: events.map((event) => ({
375438
+ ...event,
375439
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
375440
+ uuid: crypto4.randomUUID(),
375441
+ properties: {
375442
+ ...event.properties,
375443
+ $geoip_disable: true,
375444
+ $lib: "specific-cli",
375445
+ $lib_version: "0.1.224"
375446
+ }
375447
+ }))
375392
375448
  });
375449
+ await Promise.race([
375450
+ fetch(`${POSTHOG_HOST}/batch/`, {
375451
+ method: "POST",
375452
+ headers: { "Content-Type": "application/json" },
375453
+ body,
375454
+ signal: controller.signal
375455
+ }).then(async (response) => {
375456
+ await response.body?.cancel();
375457
+ }),
375458
+ new Promise((resolve10) => {
375459
+ timer = setTimeout(() => {
375460
+ controller.abort();
375461
+ resolve10();
375462
+ }, 1e3);
375463
+ timer.unref();
375464
+ })
375465
+ ]);
375466
+ } catch {
375467
+ } finally {
375468
+ clearTimeout(timer);
375469
+ controller.abort();
375393
375470
  }
375394
- return client;
375395
375471
  }
375396
375472
  function trackEvent(event, properties) {
375397
- const ph = getClient2();
375398
- if (!ph) return;
375399
- const userId = getUserId();
375400
- if (userId && !identified) {
375401
- identified = true;
375402
- ph.identify({
375403
- distinctId: userId,
375473
+ try {
375474
+ if (!isEnabled() || shuttingDown) return;
375475
+ const userId = getUserId();
375476
+ const distinctId = userId ?? getAnonymousId();
375477
+ const events = [];
375478
+ if (userId && !identified) {
375479
+ events.push({
375480
+ event: "$identify",
375481
+ distinct_id: userId,
375482
+ properties: { $set: { anonymous_id: getAnonymousId() } }
375483
+ }, {
375484
+ event: "$create_alias",
375485
+ distinct_id: userId,
375486
+ properties: { distinct_id: userId, alias: getAnonymousId() }
375487
+ });
375488
+ identified = true;
375489
+ }
375490
+ events.push({
375491
+ event,
375492
+ distinct_id: distinctId,
375404
375493
  properties: {
375405
- anonymous_id: getAnonymousId()
375494
+ ...properties,
375495
+ cli_version: "0.1.224",
375496
+ platform: process.platform,
375497
+ node_version: process.version,
375498
+ project_id: getProjectId()
375406
375499
  }
375407
375500
  });
375408
- ph.alias({
375409
- distinctId: userId,
375410
- alias: getAnonymousId()
375411
- });
375501
+ const request2 = sendEvents(events);
375502
+ pendingRequests.add(request2);
375503
+ void request2.then(
375504
+ () => pendingRequests.delete(request2),
375505
+ () => pendingRequests.delete(request2)
375506
+ );
375507
+ } catch {
375412
375508
  }
375413
- ph.capture({
375414
- distinctId: userId ?? getAnonymousId(),
375415
- event,
375416
- properties: {
375417
- ...properties,
375418
- cli_version: "0.1.222",
375419
- platform: process.platform,
375420
- node_version: process.version,
375421
- project_id: getProjectId()
375422
- }
375423
- });
375424
375509
  }
375425
375510
  async function shutdown() {
375426
- if (client) {
375427
- await client.shutdown(1e3);
375428
- client = null;
375429
- }
375511
+ shuttingDown = true;
375512
+ await Promise.allSettled([...pendingRequests]);
375430
375513
  }
375431
375514
 
375432
375515
  // src/commands/init.tsx
@@ -377623,9 +377706,9 @@ async function performAgentLogin() {
377623
377706
  userId: ""
377624
377707
  };
377625
377708
  const accessToken = await getAgentAccessToken(base);
377626
- const client2 = new SpecificClient({ accessToken, ...freebuffAttributionOptions() });
377627
- const me = await client2.getMe();
377628
- const info = await client2.getAgentRegistration();
377709
+ const client = new SpecificClient({ accessToken, ...freebuffAttributionOptions() });
377710
+ const me = await client.getMe();
377711
+ const info = await client.getAgentRegistration();
377629
377712
  writeUserCredentials({ ...readAgentCredentials() ?? base, userId: me.id });
377630
377713
  writeLog("auth", `Agent credentials written for user ${me.id}`);
377631
377714
  return { registrationId: registration.id, claimDeadlineAt: new Date(info.claimDeadlineAt) };
@@ -377650,14 +377733,14 @@ function isExpiredRegistrationError(err) {
377650
377733
  return err instanceof AgentRegistrationExpiredError || err instanceof ApiError && err.code === "AGENT_REGISTRATION_EXPIRED";
377651
377734
  }
377652
377735
  async function describeAgentAccount(credentials) {
377653
- const client2 = new SpecificClient({ tokenProvider: getValidAccessToken, ...freebuffAttributionOptions() });
377736
+ const client = new SpecificClient({ tokenProvider: getValidAccessToken, ...freebuffAttributionOptions() });
377654
377737
  try {
377655
- const info = await client2.getAgentRegistration();
377738
+ const info = await client.getAgentRegistration();
377656
377739
  if (info.status === "expired") return { status: "expired" };
377657
377740
  if (info.status === "unclaimed") {
377658
377741
  return { status: "unclaimed", claimDeadlineAt: new Date(info.claimDeadlineAt) };
377659
377742
  }
377660
- const me = await client2.getMe();
377743
+ const me = await client.getMe();
377661
377744
  const current = readAgentCredentials() ?? credentials;
377662
377745
  if (!current.claimed) {
377663
377746
  writeUserCredentials({ ...current, claimVerified: void 0, claimed: true, userId: me.id });
@@ -377698,8 +377781,8 @@ function requireAgentCredentials() {
377698
377781
  async function startClaim(email) {
377699
377782
  const credentials = requireAgentCredentials();
377700
377783
  if (credentials.claimed) {
377701
- const client2 = new SpecificClient({ tokenProvider: getValidAccessToken, ...freebuffAttributionOptions() });
377702
- const me = await client2.getMe();
377784
+ const client = new SpecificClient({ tokenProvider: getValidAccessToken, ...freebuffAttributionOptions() });
377785
+ const me = await client.getMe();
377703
377786
  return { phase: "completed", email: me.email, alreadyClaimed: true };
377704
377787
  }
377705
377788
  if (credentials.claimVerified) {
@@ -377716,8 +377799,8 @@ async function startClaim(email) {
377716
377799
  async function completeClaim(userCode) {
377717
377800
  let credentials = requireAgentCredentials();
377718
377801
  if (credentials.claimed) {
377719
- const client2 = new SpecificClient({ tokenProvider: getValidAccessToken, ...freebuffAttributionOptions() });
377720
- const me = await client2.getMe();
377802
+ const client = new SpecificClient({ tokenProvider: getValidAccessToken, ...freebuffAttributionOptions() });
377803
+ const me = await client.getMe();
377721
377804
  return { phase: "completed", email: me.email, alreadyClaimed: true };
377722
377805
  }
377723
377806
  if (credentials.claimVerified) {
@@ -377732,8 +377815,8 @@ async function completeClaim(userCode) {
377732
377815
  return await handOverToPlatform(credentials);
377733
377816
  }
377734
377817
  async function handOverToPlatform(credentials) {
377735
- const client2 = new SpecificClient({ tokenProvider: getValidAccessToken, ...freebuffAttributionOptions() });
377736
- const result = await client2.claimAgentRegistration();
377818
+ const client = new SpecificClient({ tokenProvider: getValidAccessToken, ...freebuffAttributionOptions() });
377819
+ const result = await client.claimAgentRegistration();
377737
377820
  writeUserCredentials({ ...credentials, claimVerified: void 0, claimed: true, userId: result.user.id });
377738
377821
  writeLog("auth", `Agent registration claimed by ${result.user.email}`);
377739
377822
  return { phase: "completed", email: result.user.email, alreadyClaimed: false };
@@ -377759,25 +377842,10 @@ function describeClaimError(err) {
377759
377842
  return err instanceof Error ? err.message : String(err);
377760
377843
  }
377761
377844
 
377762
- // src/lib/deployments/format.ts
377763
- function formatErrorCode(code) {
377764
- const labels = {
377765
- build_failed: "Build failed",
377766
- pre_deploy_failed: "Pre-deploy hook failed",
377767
- deploy_failed: "Service failed to deploy",
377768
- provisioning_failed: "Resource provisioning failed",
377769
- health_check_failed: "Health check failed",
377770
- post_deploy_failed: "Post-deploy hook failed",
377771
- volume_reset_failed: "Volume reset failed",
377772
- internal_error: "Internal error"
377773
- };
377774
- return labels[code] || code;
377775
- }
377776
-
377777
377845
  // src/lib/deployments/inputs.ts
377778
- async function submitProvidedDeploymentSecrets(client2, deploymentId, secrets) {
377846
+ async function submitProvidedDeploymentSecrets(client, deploymentId, secrets) {
377779
377847
  if (Object.keys(secrets).length === 0) return;
377780
- await client2.submitSecrets(deploymentId, secrets);
377848
+ await client.submitSecrets(deploymentId, secrets);
377781
377849
  }
377782
377850
 
377783
377851
  // src/lib/deployments/target.ts
@@ -377786,8 +377854,8 @@ function validateDeployTargetOptions(options2) {
377786
377854
  throw new Error("--preview cannot be combined with --environment");
377787
377855
  }
377788
377856
  }
377789
- async function resolveExplicitDeployEnvironment(client2, projectId, target) {
377790
- const status = await client2.getProjectStatus(projectId);
377857
+ async function resolveExplicitDeployEnvironment(client, projectId, target) {
377858
+ const status = await client.getProjectStatus(projectId);
377791
377859
  const environments = [
377792
377860
  ...status.environments,
377793
377861
  ...status.previewEnvironments ?? []
@@ -377848,6 +377916,12 @@ function formatCliDeploymentsDisabledError(projectId, environmentId, detail) {
377848
377916
  "Once they have, run this command again."
377849
377917
  ].join("\n");
377850
377918
  }
377919
+ function formatPlanLimitExceededError(projectId, environmentId, deploymentId, action) {
377920
+ return `This deployment exceeds the ${action.planName} plan's limits: ${formatPlanLimitViolations(action.violations)}. Upgrade at ${dashboardBillingUrl(projectId)}, then start it from ${dashboardDeploymentUrl(projectId, environmentId, deploymentId)}, or remove the extra resources from specific.hcl and deploy again.`;
377921
+ }
377922
+ function formatUnknownPendingActionError(projectId, environmentId, deploymentId, type) {
377923
+ return `This deployment is waiting on "${type}", which this CLI version doesn't understand. Update the CLI or continue from ${dashboardDeploymentUrl(projectId, environmentId, deploymentId)}.`;
377924
+ }
377851
377925
  function formatBytes2(bytes) {
377852
377926
  if (bytes < 1024) return `${bytes} B`;
377853
377927
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
@@ -378026,6 +378100,23 @@ function getFailedBuild(pendingActions) {
378026
378100
  if (!pendingActions) return void 0;
378027
378101
  return pendingActions.find((a) => a.type === "build_failed");
378028
378102
  }
378103
+ function getPlanLimitAction(pendingActions) {
378104
+ if (!pendingActions) return void 0;
378105
+ return pendingActions.find(
378106
+ (a) => a.type === "plan_limit_exceeded"
378107
+ );
378108
+ }
378109
+ var KNOWN_PENDING_ACTION_TYPES = /* @__PURE__ */ new Set([
378110
+ "build_in_progress",
378111
+ "build_failed",
378112
+ "missing_secrets",
378113
+ "missing_configs",
378114
+ "plan_limit_exceeded"
378115
+ ]);
378116
+ function getUnknownPendingAction(pendingActions) {
378117
+ if (!pendingActions) return void 0;
378118
+ return pendingActions.find((a) => !KNOWN_PENDING_ACTION_TYPES.has(a.type));
378119
+ }
378029
378120
  function StructuredError({ error }) {
378030
378121
  return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text7, { color: "red", bold: true }, formatErrorCode(error.code), ": ", error.message), error.resource && /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, "Resource: ", error.resource), error.output && /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text7, { bold: true }, "Output:"), /* @__PURE__ */ React7.createElement(Text7, null, error.output)));
378031
378122
  }
@@ -378117,10 +378208,10 @@ function DeployUI({ envFlag, preview, config }) {
378117
378208
  let cancelled = false;
378118
378209
  async function loadProjects() {
378119
378210
  try {
378120
- const client2 = new SpecificClient({ tokenProvider: getValidAccessToken });
378211
+ const client = new SpecificClient({ tokenProvider: getValidAccessToken });
378121
378212
  const [projects2, organizations] = await Promise.all([
378122
- client2.listProjects(),
378123
- client2.listOrganizations()
378213
+ client.listProjects(),
378214
+ client.listOrganizations()
378124
378215
  ]);
378125
378216
  if (cancelled) return;
378126
378217
  const items = buildSelectorItems(projects2, organizations);
@@ -378176,8 +378267,8 @@ function DeployUI({ envFlag, preview, config }) {
378176
378267
  let cancelled = false;
378177
378268
  async function createProject() {
378178
378269
  try {
378179
- const client2 = new SpecificClient({ tokenProvider: getValidAccessToken });
378180
- const project = await client2.createProject(state.newProjectName, state.selectedOrganizationId);
378270
+ const client = new SpecificClient({ tokenProvider: getValidAccessToken });
378271
+ const project = await client.createProject(state.newProjectName, state.selectedOrganizationId);
378181
378272
  if (cancelled) return;
378182
378273
  writeProjectId(project.id);
378183
378274
  setState({
@@ -378189,7 +378280,7 @@ function DeployUI({ envFlag, preview, config }) {
378189
378280
  if (cancelled) return;
378190
378281
  setState({
378191
378282
  phase: "error",
378192
- error: err instanceof Error ? err.message : String(err)
378283
+ error: formatProjectQuotaError(err) ?? (err instanceof Error ? err.message : String(err))
378193
378284
  });
378194
378285
  }
378195
378286
  }
@@ -378203,10 +378294,10 @@ function DeployUI({ envFlag, preview, config }) {
378203
378294
  let cancelled = false;
378204
378295
  async function loadEnvironments() {
378205
378296
  try {
378206
- const client2 = new SpecificClient({ tokenProvider: getValidAccessToken });
378297
+ const client = new SpecificClient({ tokenProvider: getValidAccessToken });
378207
378298
  let environments = state.environments;
378208
378299
  if (!environments) {
378209
- const projects2 = await client2.listProjects();
378300
+ const projects2 = await client.listProjects();
378210
378301
  const project = projects2.find((p) => p.id === state.projectId);
378211
378302
  if (!project) {
378212
378303
  if (cancelled) return;
@@ -378233,7 +378324,7 @@ function DeployUI({ envFlag, preview, config }) {
378233
378324
  if (envFlag) {
378234
378325
  try {
378235
378326
  const match = await resolveExplicitDeployEnvironment(
378236
- client2,
378327
+ client,
378237
378328
  state.projectId,
378238
378329
  envFlag
378239
378330
  );
@@ -378319,19 +378410,19 @@ function DeployUI({ envFlag, preview, config }) {
378319
378410
  let cancelled = false;
378320
378411
  async function createPreview() {
378321
378412
  try {
378322
- const client2 = new SpecificClient({ tokenProvider: getValidAccessToken });
378413
+ const client = new SpecificClient({ tokenProvider: getValidAccessToken });
378323
378414
  const projectDir = process.cwd();
378324
378415
  const previousPreviewId = readPreviewEnvironmentId(projectDir);
378325
378416
  if (previousPreviewId) {
378326
378417
  setState((s) => ({ ...s, previewStatus: "Tearing down previous preview environment" }));
378327
378418
  try {
378328
- await client2.deletePreviewEnvironment(previousPreviewId);
378419
+ await client.deletePreviewEnvironment(previousPreviewId);
378329
378420
  } catch {
378330
378421
  }
378331
378422
  }
378332
378423
  if (cancelled) return;
378333
378424
  setState((s) => ({ ...s, previewStatus: "Creating preview environment" }));
378334
- const previewEnv = await client2.createPreviewEnvironment(state.projectId);
378425
+ const previewEnv = await client.createPreviewEnvironment(state.projectId);
378335
378426
  writePreviewEnvironmentId(previewEnv.id, projectDir);
378336
378427
  if (cancelled) return;
378337
378428
  setState((s) => ({
@@ -378441,15 +378532,15 @@ function DeployUI({ envFlag, preview, config }) {
378441
378532
  (secret) => secretValues[secret]
378442
378533
  );
378443
378534
  if (!allSecretsCollected) return;
378444
- const client2 = clientRef.current;
378445
- if (!client2) return;
378535
+ const client = clientRef.current;
378536
+ if (!client) return;
378446
378537
  (async () => {
378447
378538
  try {
378448
378539
  writeLog(
378449
378540
  "deploy",
378450
378541
  `Submitting secrets: ${Object.keys(secretValues).join(", ")}`
378451
378542
  );
378452
- await client2.submitSecrets(deployment2.id, secretValues);
378543
+ await client.submitSecrets(deployment2.id, secretValues);
378453
378544
  writeLog("deploy", "Secrets submitted successfully");
378454
378545
  setState((s) => ({
378455
378546
  ...s,
@@ -378483,15 +378574,15 @@ function DeployUI({ envFlag, preview, config }) {
378483
378574
  (config2) => configValues[config2]
378484
378575
  );
378485
378576
  if (!allConfigsCollected) return;
378486
- const client2 = clientRef.current;
378487
- if (!client2) return;
378577
+ const client = clientRef.current;
378578
+ if (!client) return;
378488
378579
  (async () => {
378489
378580
  try {
378490
378581
  writeLog(
378491
378582
  "deploy",
378492
378583
  `Submitting configs: ${Object.keys(configValues).join(", ")}`
378493
378584
  );
378494
- await client2.submitConfigs(deployment2.id, configValues);
378585
+ await client.submitConfigs(deployment2.id, configValues);
378495
378586
  writeLog("deploy", "Configs submitted successfully");
378496
378587
  setState((s) => ({
378497
378588
  ...s,
@@ -378518,8 +378609,8 @@ function DeployUI({ envFlag, preview, config }) {
378518
378609
  const projectDir = process.cwd();
378519
378610
  writeLog("deploy", `Starting deployment to "${state.environmentName}"`);
378520
378611
  writeLog("deploy", `Project directory: ${projectDir}`);
378521
- const client2 = new SpecificClient({ tokenProvider: getValidAccessToken });
378522
- clientRef.current = client2;
378612
+ const client = new SpecificClient({ tokenProvider: getValidAccessToken });
378613
+ clientRef.current = client;
378523
378614
  if (cancelled) return;
378524
378615
  let tarball;
378525
378616
  let appPath;
@@ -378548,7 +378639,7 @@ function DeployUI({ envFlag, preview, config }) {
378548
378639
  try {
378549
378640
  writeLog("deploy", `Creating deployment for project ${state.projectId}`);
378550
378641
  const gitInfo = getGitInfo(projectDir);
378551
- deployment2 = await client2.createDeployment(state.projectId, state.environmentId, {
378642
+ deployment2 = await client.createDeployment(state.projectId, state.environmentId, {
378552
378643
  triggeredBy: "cli",
378553
378644
  ...gitInfo && {
378554
378645
  gitCommitSha: gitInfo.commitSha,
@@ -378614,7 +378705,7 @@ function DeployUI({ envFlag, preview, config }) {
378614
378705
  setState((s) => ({ ...s, phase: "uploading", deployment: deployment2 }));
378615
378706
  try {
378616
378707
  writeLog("deploy", `Uploading tarball (${tarball.length} bytes, appPath: ${appPath})`);
378617
- deployment2 = await client2.uploadTarball(deployment2.id, tarball, appPath, {
378708
+ deployment2 = await client.uploadTarball(deployment2.id, tarball, appPath, {
378618
378709
  uploadUrl: deployment2.uploadUrl,
378619
378710
  maxTarballBytes: deployment2.maxTarballBytes,
378620
378711
  onProgress: makeUploadProgressHandler(setState)
@@ -378644,12 +378735,12 @@ function DeployUI({ envFlag, preview, config }) {
378644
378735
  if (state.phase !== "pending" || !state.deployment) return;
378645
378736
  let pollInterval;
378646
378737
  let cancelled = false;
378647
- const client2 = clientRef.current;
378648
- if (!client2) return;
378738
+ const client = clientRef.current;
378739
+ if (!client) return;
378649
378740
  const pollForPendingActions = async () => {
378650
378741
  if (cancelled) return;
378651
378742
  try {
378652
- const status = await client2.getDeployment(state.deployment.id);
378743
+ const status = await client.getDeployment(state.deployment.id);
378653
378744
  if (cancelled) return;
378654
378745
  writeLog(
378655
378746
  "deploy",
@@ -378679,6 +378770,24 @@ function DeployUI({ envFlag, preview, config }) {
378679
378770
  }));
378680
378771
  return;
378681
378772
  }
378773
+ const planLimit = getPlanLimitAction(status.pendingActions);
378774
+ if (planLimit) {
378775
+ if (pollInterval) clearInterval(pollInterval);
378776
+ const message = formatPlanLimitExceededError(
378777
+ state.projectId,
378778
+ state.environmentId,
378779
+ status.id,
378780
+ planLimit
378781
+ );
378782
+ writeLog("deploy:error", message);
378783
+ setState((s) => ({
378784
+ ...s,
378785
+ phase: "error",
378786
+ deployment: status,
378787
+ error: message
378788
+ }));
378789
+ return;
378790
+ }
378682
378791
  const missingSecrets2 = getMissingSecrets(status.pendingActions);
378683
378792
  if (missingSecrets2.length > 0) {
378684
378793
  if (pollInterval) clearInterval(pollInterval);
@@ -378721,6 +378830,24 @@ function DeployUI({ envFlag, preview, config }) {
378721
378830
  }));
378722
378831
  return;
378723
378832
  }
378833
+ const unknownAction = getUnknownPendingAction(status.pendingActions);
378834
+ if (unknownAction) {
378835
+ if (pollInterval) clearInterval(pollInterval);
378836
+ const message = formatUnknownPendingActionError(
378837
+ state.projectId,
378838
+ state.environmentId,
378839
+ status.id,
378840
+ unknownAction.type
378841
+ );
378842
+ writeLog("deploy:error", message);
378843
+ setState((s) => ({
378844
+ ...s,
378845
+ phase: "error",
378846
+ deployment: status,
378847
+ error: message
378848
+ }));
378849
+ return;
378850
+ }
378724
378851
  if (!status.pendingActions || status.pendingActions.length === 0) {
378725
378852
  if (pollInterval) clearInterval(pollInterval);
378726
378853
  pollInterval = void 0;
@@ -378752,12 +378879,12 @@ function DeployUI({ envFlag, preview, config }) {
378752
378879
  useEffect6(() => {
378753
378880
  if (state.phase !== "starting" || !state.deployment) return;
378754
378881
  let cancelled = false;
378755
- const client2 = clientRef.current;
378756
- if (!client2) return;
378882
+ const client = clientRef.current;
378883
+ if (!client) return;
378757
378884
  (async () => {
378758
378885
  try {
378759
378886
  writeLog("deploy", "Calling /start endpoint");
378760
- await client2.startDeployment(state.deployment.id);
378887
+ await client.startDeployment(state.deployment.id);
378761
378888
  writeLog("deploy", "Deployment started successfully");
378762
378889
  if (cancelled) return;
378763
378890
  setState((s) => ({
@@ -378787,12 +378914,12 @@ function DeployUI({ envFlag, preview, config }) {
378787
378914
  if (state.phase !== "queued" && state.phase !== "deploying" || !state.deployment) return;
378788
378915
  let pollInterval;
378789
378916
  let cancelled = false;
378790
- const client2 = clientRef.current;
378791
- if (!client2) return;
378917
+ const client = clientRef.current;
378918
+ if (!client) return;
378792
378919
  const pollForCompletion = async () => {
378793
378920
  if (cancelled) return;
378794
378921
  try {
378795
- const status = await client2.getDeployment(state.deployment.id);
378922
+ const status = await client.getDeployment(state.deployment.id);
378796
378923
  if (cancelled) return;
378797
378924
  writeLog(
378798
378925
  "deploy",
@@ -379039,7 +379166,7 @@ async function runDeployPipeline(options2) {
379039
379166
  console.error(`Error: ${message}`);
379040
379167
  process.exit(1);
379041
379168
  }
379042
- const client2 = new SpecificClient({ tokenProvider: getValidAccessToken });
379169
+ const client = new SpecificClient({ tokenProvider: getValidAccessToken });
379043
379170
  let projectId = options2.projectId;
379044
379171
  let projectIdFromLinkedFile = false;
379045
379172
  if (!projectId) {
@@ -379061,17 +379188,17 @@ async function runDeployPipeline(options2) {
379061
379188
  if (previousPreviewId) {
379062
379189
  console.log("Cleaning up previous preview environment...");
379063
379190
  try {
379064
- await client2.deletePreviewEnvironment(previousPreviewId);
379191
+ await client.deletePreviewEnvironment(previousPreviewId);
379065
379192
  } catch {
379066
379193
  }
379067
379194
  }
379068
379195
  console.log("Creating preview environment...");
379069
- const preview = await client2.createPreviewEnvironment(projectId);
379196
+ const preview = await client.createPreviewEnvironment(projectId);
379070
379197
  writePreviewEnvironmentId(preview.id, projectDir);
379071
379198
  console.log(`Preview environment "${preview.name}" created (expires: ${new Date(preview.expiresAt).toLocaleString()})`);
379072
379199
  previewEnvironmentId = preview.id;
379073
379200
  }
379074
- const projects = await client2.listProjects();
379201
+ const projects = await client.listProjects();
379075
379202
  const project = projects.find((p) => p.id === projectId);
379076
379203
  if (!project) {
379077
379204
  if (projectIdFromLinkedFile) {
@@ -379094,7 +379221,7 @@ async function runDeployPipeline(options2) {
379094
379221
  } else if (options2.environment) {
379095
379222
  try {
379096
379223
  const match = await resolveExplicitDeployEnvironment(
379097
- client2,
379224
+ client,
379098
379225
  projectId,
379099
379226
  options2.environment
379100
379227
  );
@@ -379176,7 +379303,7 @@ async function runDeployPipeline(options2) {
379176
379303
  let deployment;
379177
379304
  try {
379178
379305
  const gitInfo = getGitInfo(projectDir);
379179
- deployment = await client2.createDeployment(projectId, environmentId, {
379306
+ deployment = await client.createDeployment(projectId, environmentId, {
379180
379307
  triggeredBy: "cli",
379181
379308
  ...gitInfo && {
379182
379309
  gitCommitSha: gitInfo.commitSha,
@@ -379221,7 +379348,7 @@ async function runDeployPipeline(options2) {
379221
379348
  console.log("Uploading...");
379222
379349
  try {
379223
379350
  let lastPct = -1;
379224
- deployment = await client2.uploadTarball(deployment.id, tarball, appPath, {
379351
+ deployment = await client.uploadTarball(deployment.id, tarball, appPath, {
379225
379352
  uploadUrl: deployment.uploadUrl,
379226
379353
  maxTarballBytes: deployment.maxTarballBytes,
379227
379354
  onProgress: (bytesSent, totalBytes) => {
@@ -379238,7 +379365,7 @@ async function runDeployPipeline(options2) {
379238
379365
  process.exit(1);
379239
379366
  }
379240
379367
  try {
379241
- await submitProvidedDeploymentSecrets(client2, deployment.id, parsedSecrets);
379368
+ await submitProvidedDeploymentSecrets(client, deployment.id, parsedSecrets);
379242
379369
  } catch (err) {
379243
379370
  console.error(`Error: Failed to submit secrets: ${err instanceof Error ? err.message : String(err)}`);
379244
379371
  process.exit(1);
@@ -379246,7 +379373,7 @@ async function runDeployPipeline(options2) {
379246
379373
  console.log("Waiting for build...");
379247
379374
  while (true) {
379248
379375
  await new Promise((resolve10) => setTimeout(resolve10, 2e3));
379249
- const status = await client2.getDeployment(deployment.id);
379376
+ const status = await client.getDeployment(deployment.id);
379250
379377
  deployment = status;
379251
379378
  if (status.state === "failed") {
379252
379379
  console.error(`Error: Deployment failed: ${status.stateMessage || "Unknown error"}`);
@@ -379262,6 +379389,13 @@ ${failedBuild.output}`);
379262
379389
  }
379263
379390
  process.exit(1);
379264
379391
  }
379392
+ const planLimit = getPlanLimitAction(status.pendingActions);
379393
+ if (planLimit) {
379394
+ console.error(
379395
+ `Error: ${formatPlanLimitExceededError(projectId, environmentId, deployment.id, planLimit)}`
379396
+ );
379397
+ process.exit(1);
379398
+ }
379265
379399
  const missingSecrets = getMissingSecrets(status.pendingActions);
379266
379400
  if (missingSecrets.length > 0) {
379267
379401
  const secretsToSubmit = {};
@@ -379280,7 +379414,7 @@ ${failedBuild.output}`);
379280
379414
  );
379281
379415
  process.exit(1);
379282
379416
  }
379283
- await client2.submitSecrets(deployment.id, secretsToSubmit);
379417
+ await client.submitSecrets(deployment.id, secretsToSubmit);
379284
379418
  continue;
379285
379419
  }
379286
379420
  const missingConfigs = getMissingConfigs(status.pendingActions);
@@ -379301,19 +379435,26 @@ ${failedBuild.output}`);
379301
379435
  );
379302
379436
  process.exit(1);
379303
379437
  }
379304
- await client2.submitConfigs(deployment.id, configsToSubmit);
379438
+ await client.submitConfigs(deployment.id, configsToSubmit);
379305
379439
  continue;
379306
379440
  }
379307
379441
  if (hasBuildsInProgress(status.pendingActions)) {
379308
379442
  continue;
379309
379443
  }
379444
+ const unknownAction = getUnknownPendingAction(status.pendingActions);
379445
+ if (unknownAction) {
379446
+ console.error(
379447
+ `Error: ${formatUnknownPendingActionError(projectId, environmentId, deployment.id, unknownAction.type)}`
379448
+ );
379449
+ process.exit(1);
379450
+ }
379310
379451
  if (!status.pendingActions || status.pendingActions.length === 0) {
379311
379452
  break;
379312
379453
  }
379313
379454
  }
379314
379455
  console.log("Starting deployment...");
379315
379456
  try {
379316
- await client2.startDeployment(deployment.id);
379457
+ await client.startDeployment(deployment.id);
379317
379458
  } catch (err) {
379318
379459
  if (err instanceof ApiError && err.code === CLI_DEPLOYMENTS_DISABLED_CODE) {
379319
379460
  console.error(
@@ -379335,7 +379476,7 @@ ${failedBuild.output}`);
379335
379476
  );
379336
379477
  console.log("Cleaning up preview environment...");
379337
379478
  try {
379338
- await client2.deletePreviewEnvironment(previewEnvironmentId);
379479
+ await client.deletePreviewEnvironment(previewEnvironmentId);
379339
379480
  } catch {
379340
379481
  }
379341
379482
  clearPreviewEnvironmentId(projectDir);
@@ -379347,7 +379488,7 @@ ${failedBuild.output}`);
379347
379488
  console.log("Deploying...");
379348
379489
  while (true) {
379349
379490
  await new Promise((resolve10) => setTimeout(resolve10, 2e3));
379350
- const status = await client2.getDeployment(deployment.id);
379491
+ const status = await client.getDeployment(deployment.id);
379351
379492
  deployment = status;
379352
379493
  if (status.state === "failed") {
379353
379494
  if (status.error) {
@@ -380413,7 +380554,7 @@ function compareVersions(a, b) {
380413
380554
  return 0;
380414
380555
  }
380415
380556
  async function checkForUpdate() {
380416
- const currentVersion = "0.1.222";
380557
+ const currentVersion = "0.1.224";
380417
380558
  const response = await fetch(`${BINARIES_BASE_URL}/latest?t=${Date.now()}`);
380418
380559
  if (!response.ok) {
380419
380560
  throw new Error(`Failed to check for updates: HTTP ${response.status}`);
@@ -380611,10 +380752,10 @@ function updateCommand() {
380611
380752
  // src/commands/project.tsx
380612
380753
  async function projectNewCommand(name, options2) {
380613
380754
  try {
380614
- const client2 = new SpecificClient({ tokenProvider: getValidAccessToken });
380755
+ const client = new SpecificClient({ tokenProvider: getValidAccessToken });
380615
380756
  let orgId = options2.org;
380616
380757
  if (!orgId) {
380617
- const organizations = await client2.listOrganizations();
380758
+ const organizations = await client.listOrganizations();
380618
380759
  if (organizations.length === 0) {
380619
380760
  console.error(
380620
380761
  "You don't belong to any organizations. Please create one first."
@@ -380634,20 +380775,25 @@ async function projectNewCommand(name, options2) {
380634
380775
  process.exit(1);
380635
380776
  }
380636
380777
  }
380637
- const project = await client2.createProject(name, orgId);
380778
+ const project = await client.createProject(name, orgId);
380638
380779
  console.log(`Project created: ${project.id}`);
380639
380780
  } catch (error) {
380781
+ const quotaMessage = formatProjectQuotaError(error);
380782
+ if (quotaMessage) {
380783
+ console.error(quotaMessage);
380784
+ process.exit(1);
380785
+ }
380640
380786
  const message = error instanceof Error ? error.message : "Unknown error occurred";
380641
- console.error(`Failed to create project: ${message}`);
380787
+ console.error(`Error: ${message}`);
380642
380788
  process.exit(1);
380643
380789
  }
380644
380790
  }
380645
380791
  async function projectListCommand() {
380646
380792
  try {
380647
- const client2 = new SpecificClient({ tokenProvider: getValidAccessToken });
380793
+ const client = new SpecificClient({ tokenProvider: getValidAccessToken });
380648
380794
  const [projects, organizations] = await Promise.all([
380649
- client2.listProjects(),
380650
- client2.listOrganizations()
380795
+ client.listProjects(),
380796
+ client.listOrganizations()
380651
380797
  ]);
380652
380798
  if (projects.length === 0) {
380653
380799
  console.log("No projects found.");
@@ -380743,8 +380889,8 @@ function StatusUI({ previews = false }) {
380743
380889
  (async () => {
380744
380890
  try {
380745
380891
  const projectId = readProjectId();
380746
- const client2 = new SpecificClient({ tokenProvider: getValidAccessToken });
380747
- const status2 = await client2.getProjectStatus(projectId);
380892
+ const client = new SpecificClient({ tokenProvider: getValidAccessToken });
380893
+ const status2 = await client.getProjectStatus(projectId);
380748
380894
  setState({ phase: "success", status: status2 });
380749
380895
  } catch (err) {
380750
380896
  setState({
@@ -380791,8 +380937,8 @@ async function statusCommand(options2 = {}) {
380791
380937
  return;
380792
380938
  }
380793
380939
  const projectId = readProjectId();
380794
- const client2 = new SpecificClient({ tokenProvider: getValidAccessToken });
380795
- const status = await client2.getProjectStatus(projectId);
380940
+ const client = new SpecificClient({ tokenProvider: getValidAccessToken });
380941
+ const status = await client.getProjectStatus(projectId);
380796
380942
  process.stdout.write(JSON.stringify(status, null, 2) + "\n");
380797
380943
  } catch (err) {
380798
380944
  const message = err instanceof Error ? err.message : String(err);
@@ -380802,8 +380948,8 @@ async function statusCommand(options2 = {}) {
380802
380948
  }
380803
380949
 
380804
380950
  // src/lib/project/resolve-env.ts
380805
- async function resolveEnvironmentStatus(client2, projectId, envFlag) {
380806
- const status = await client2.getProjectStatus(projectId);
380951
+ async function resolveEnvironmentStatus(client, projectId, envFlag) {
380952
+ const status = await client.getProjectStatus(projectId);
380807
380953
  const environments = status.environments;
380808
380954
  const previewEnvironments = status.previewEnvironments ?? [];
380809
380955
  if (envFlag) {
@@ -380839,8 +380985,8 @@ async function resolveEnvironmentStatus(client2, projectId, envFlag) {
380839
380985
  `Project has multiple environments (${names}). Pass --environment <name|id>.`
380840
380986
  );
380841
380987
  }
380842
- async function resolveEnvironmentId(client2, projectId, envFlag) {
380843
- const match = await resolveEnvironmentStatus(client2, projectId, envFlag);
380988
+ async function resolveEnvironmentId(client, projectId, envFlag) {
380989
+ const match = await resolveEnvironmentStatus(client, projectId, envFlag);
380844
380990
  return match.id;
380845
380991
  }
380846
380992
 
@@ -381034,9 +381180,9 @@ async function queryCommand(sqlArg, options2) {
381034
381180
  const format = resolveOutputFormat2(options2.format);
381035
381181
  const sql = await resolveSql(sqlArg);
381036
381182
  const projectId = readProjectId();
381037
- const client2 = new SpecificClient({ tokenProvider: getValidAccessToken });
381038
- const environmentId = await resolveEnvironmentId(client2, projectId, options2.environment);
381039
- const result = options2.db ? await client2.runDatabaseQuery(environmentId, options2.db, sql) : await client2.runObservabilityQuery(environmentId, sql);
381183
+ const client = new SpecificClient({ tokenProvider: getValidAccessToken });
381184
+ const environmentId = await resolveEnvironmentId(client, projectId, options2.environment);
381185
+ const result = options2.db ? await client.runDatabaseQuery(environmentId, options2.db, sql) : await client.runObservabilityQuery(environmentId, sql);
381040
381186
  if (result.lookback) {
381041
381187
  process.stderr.write(
381042
381188
  `\x1B[2mResults exclude data older than ${result.lookback.cutoff} (${result.lookback.days}-day lookback on your plan).\x1B[0m
@@ -381097,13 +381243,13 @@ async function deploymentShowCommand(idArg, options2) {
381097
381243
  try {
381098
381244
  const format = resolveOutputFormat3(options2.format);
381099
381245
  const includeOutput = options2.output ?? false;
381100
- const client2 = new SpecificClient({ tokenProvider: getValidAccessToken });
381246
+ const client = new SpecificClient({ tokenProvider: getValidAccessToken });
381101
381247
  let deploymentId = idArg;
381102
381248
  if (!deploymentId) {
381103
- const projectId = readProjectId();
381249
+ const projectId2 = readProjectId();
381104
381250
  const env2 = await resolveEnvironmentStatus(
381105
- client2,
381106
- projectId,
381251
+ client,
381252
+ projectId2,
381107
381253
  options2.environment
381108
381254
  );
381109
381255
  const latest = env2.latestDeployment ?? env2.inProgressDeployment ?? env2.activeDeployment;
@@ -381114,13 +381260,18 @@ async function deploymentShowCommand(idArg, options2) {
381114
381260
  }
381115
381261
  deploymentId = latest.id;
381116
381262
  }
381117
- const deployment = await client2.getDeployment(deploymentId);
381263
+ const deployment = await client.getDeployment(deploymentId);
381118
381264
  if (format === "json") {
381119
381265
  const payload = includeOutput ? deployment : stripOutput(deployment);
381120
381266
  process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
381121
381267
  return;
381122
381268
  }
381123
- process.stdout.write(renderDeployment(deployment, includeOutput));
381269
+ let projectId = null;
381270
+ try {
381271
+ projectId = readProjectId();
381272
+ } catch {
381273
+ }
381274
+ process.stdout.write(renderDeployment(deployment, includeOutput, projectId));
381124
381275
  } catch (err) {
381125
381276
  const message = err instanceof Error ? err.message : "Unknown error occurred";
381126
381277
  console.error(`Error: ${message}`);
@@ -381130,12 +381281,12 @@ async function deploymentShowCommand(idArg, options2) {
381130
381281
  async function deploymentRetryCommand(idArg, options2) {
381131
381282
  try {
381132
381283
  const format = resolveOutputFormat3(options2.format);
381133
- const client2 = new SpecificClient({ tokenProvider: getValidAccessToken });
381284
+ const client = new SpecificClient({ tokenProvider: getValidAccessToken });
381134
381285
  let deploymentId = idArg;
381135
381286
  if (!deploymentId) {
381136
381287
  const projectId = readProjectId();
381137
381288
  const env2 = await resolveEnvironmentStatus(
381138
- client2,
381289
+ client,
381139
381290
  projectId,
381140
381291
  options2.environment
381141
381292
  );
@@ -381145,7 +381296,7 @@ async function deploymentRetryCommand(idArg, options2) {
381145
381296
  }
381146
381297
  deploymentId = latest.id;
381147
381298
  }
381148
- const result = await client2.retryDeployment(deploymentId);
381299
+ const result = await client.retryDeployment(deploymentId);
381149
381300
  if (format === "json") {
381150
381301
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
381151
381302
  return;
@@ -381219,7 +381370,7 @@ function formatBytes3(bytes) {
381219
381370
  if (bytes < 1024) return `${bytes} B`;
381220
381371
  return `${(bytes / 1024).toFixed(1)} KB`;
381221
381372
  }
381222
- function renderDeployment(deployment, includeOutput) {
381373
+ function renderDeployment(deployment, includeOutput, projectId) {
381223
381374
  const lines = [];
381224
381375
  lines.push(
381225
381376
  `${ANSI2.bold}Deployment ${deployment.id}${ANSI2.reset} ${ANSI2.dim}(${deployment.environment})${ANSI2.reset}`
@@ -381283,15 +381434,26 @@ function renderDeployment(deployment, includeOutput) {
381283
381434
  }
381284
381435
  }
381285
381436
  const pendingActions = deployment.pendingActions ?? [];
381437
+ const planLimit = pendingActions.find(
381438
+ (a) => a.type === "plan_limit_exceeded"
381439
+ );
381286
381440
  const missingSecrets = pendingActions.flatMap(
381287
381441
  (a) => a.type === "missing_secrets" ? a.secrets : []
381288
381442
  );
381289
381443
  const missingConfigs = pendingActions.flatMap(
381290
381444
  (a) => a.type === "missing_configs" ? a.configs : []
381291
381445
  );
381292
- if (missingSecrets.length > 0 || missingConfigs.length > 0) {
381446
+ if (planLimit || missingSecrets.length > 0 || missingConfigs.length > 0) {
381293
381447
  lines.push("");
381294
381448
  lines.push(colored(ANSI2.yellow, `${ANSI2.bold}Action required${ANSI2.reset}`));
381449
+ if (planLimit) {
381450
+ lines.push(
381451
+ ` This deployment exceeds the ${planLimit.planName} plan's limits: ${formatPlanLimitViolations(planLimit.violations)}.`
381452
+ );
381453
+ lines.push(
381454
+ ` Upgrade at ${projectId ? dashboardBillingUrl(projectId) : DASHBOARD_URL}, or remove the extra resources from specific.hcl and deploy again.`
381455
+ );
381456
+ }
381295
381457
  if (missingSecrets.length > 0) {
381296
381458
  lines.push(` Missing secrets: ${missingSecrets.join(", ")}`);
381297
381459
  }
@@ -381400,13 +381562,13 @@ async function domainAddCommand(service, domain, options2) {
381400
381562
  try {
381401
381563
  const projectId = readProjectId();
381402
381564
  const token = await getValidAccessToken();
381403
- const client2 = new SpecificClient({ accessToken: token });
381565
+ const client = new SpecificClient({ accessToken: token });
381404
381566
  const environmentId = await resolveEnvironmentId(
381405
- client2,
381567
+ client,
381406
381568
  projectId,
381407
381569
  options2.environment
381408
381570
  );
381409
- const result = await client2.createCustomDomain(environmentId, {
381571
+ const result = await client.createCustomDomain(environmentId, {
381410
381572
  domain,
381411
381573
  serviceName: service
381412
381574
  });
@@ -381445,11 +381607,11 @@ async function feedbackCommand(message) {
381445
381607
  } catch {
381446
381608
  }
381447
381609
  try {
381448
- const client2 = new SpecificClient({ tokenProvider: getValidAccessToken });
381449
- await client2.submitFeedback({
381610
+ const client = new SpecificClient({ tokenProvider: getValidAccessToken });
381611
+ await client.submitFeedback({
381450
381612
  message: trimmed,
381451
381613
  projectId,
381452
- cliVersion: "0.1.222"
381614
+ cliVersion: "0.1.224"
381453
381615
  });
381454
381616
  trackEvent("feedback_submitted", { hasProject: !!projectId });
381455
381617
  console.log("Thanks! We will take a look at your feedback shortly.");
@@ -381468,7 +381630,7 @@ async function feedbackCommand(message) {
381468
381630
  var program = new Command();
381469
381631
  var env = "production";
381470
381632
  var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
381471
- program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.222").enablePositionalOptions();
381633
+ program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.224").enablePositionalOptions();
381472
381634
  program.command("init").description("Initialize project for use with a coding agent").option("--agent <name...>", "Agents to configure (cursor, claude, codex, other)").addHelpText("after", `
381473
381635
  Examples:
381474
381636
  $ specific init