vibora 7.5.2 → 7.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.html CHANGED
@@ -5,7 +5,7 @@
5
5
  <link rel="icon" type="image/png" href="/logo.png" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>Vibora - Harness Attention. Ship.</title>
8
- <script type="module" crossorigin src="/assets/index-oEWLASTY.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-mGhyfHBb.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-_oJ5ZjNK.css">
10
10
  </head>
11
11
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibora",
3
- "version": "7.5.2",
3
+ "version": "7.6.0",
4
4
  "description": "Harness Attention. Orchestrate Agents. Ship.",
5
5
  "license": "PolyForm-Shield-1.0.0",
6
6
  "repository": {
package/server/index.js CHANGED
@@ -178317,7 +178317,11 @@ async function checkConfigDirWritable(configDir) {
178317
178317
  return false;
178318
178318
  }
178319
178319
  }
178320
- function getConfigFilename(appId) {
178320
+ function getConfigFilename(appId, appName) {
178321
+ if (appName) {
178322
+ const sanitized = appName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
178323
+ return `vibora-${sanitized}-${appId}.yml`;
178324
+ }
178321
178325
  return `vibora-${appId}.yml`;
178322
178326
  }
178323
178327
  async function checkRouteConflict(configDir, domain, currentAppId) {
@@ -178356,7 +178360,7 @@ async function addRoute(config, appId, domain, upstreamUrl, options) {
178356
178360
  return { success: false, error };
178357
178361
  }
178358
178362
  const routerId = `vibora-${appId}`;
178359
- const filename = getConfigFilename(appId);
178363
+ const filename = getConfigFilename(appId, options?.appName);
178360
178364
  const filepath = join19(config.configDir, filename);
178361
178365
  let tlsConfig;
178362
178366
  let tlsStores;
@@ -178426,22 +178430,33 @@ async function addRoute(config, appId, domain, upstreamUrl, options) {
178426
178430
  return { success: false, error };
178427
178431
  }
178428
178432
  }
178429
- async function removeRoute(config, appId) {
178430
- const filename = getConfigFilename(appId);
178431
- const filepath = join19(config.configDir, filename);
178432
- try {
178433
- await unlink2(filepath);
178434
- log2.deploy.info("Removed Traefik route", { appId, filepath });
178435
- return { success: true };
178436
- } catch (err) {
178437
- if (err.code === "ENOENT") {
178433
+ async function removeRoute(config, appId, appName) {
178434
+ const filesToTry = [
178435
+ appName ? getConfigFilename(appId, appName) : null,
178436
+ getConfigFilename(appId)
178437
+ ].filter(Boolean);
178438
+ let deleted = false;
178439
+ let lastError;
178440
+ for (const filename of filesToTry) {
178441
+ const filepath = join19(config.configDir, filename);
178442
+ try {
178443
+ await unlink2(filepath);
178444
+ log2.deploy.info("Removed Traefik route", { appId, filepath });
178445
+ deleted = true;
178446
+ } catch (err) {
178447
+ if (err.code !== "ENOENT") {
178448
+ lastError = err instanceof Error ? err.message : String(err);
178449
+ }
178450
+ }
178451
+ }
178452
+ if (deleted || !lastError) {
178453
+ if (!deleted) {
178438
178454
  log2.deploy.debug("Traefik route file not found, nothing to remove", { appId });
178439
- return { success: true };
178440
178455
  }
178441
- const error = err instanceof Error ? err.message : String(err);
178442
- log2.deploy.error("Failed to remove Traefik route", { appId, error });
178443
- return { success: false, error };
178456
+ return { success: true };
178444
178457
  }
178458
+ log2.deploy.error("Failed to remove Traefik route", { appId, error: lastError });
178459
+ return { success: false, error: lastError };
178445
178460
  }
178446
178461
 
178447
178462
  // server/services/traefik-docker.ts
@@ -178785,6 +178800,55 @@ function cleanupDeployment(deploymentId) {
178785
178800
  // server/services/deployment.ts
178786
178801
  var cachedTraefikConfig = null;
178787
178802
  var cachedPublicIp = null;
178803
+ var deploymentLogStates = new Map;
178804
+ function getOrCreateLogState(appId) {
178805
+ let state = deploymentLogStates.get(appId);
178806
+ if (!state) {
178807
+ state = {
178808
+ logs: [],
178809
+ subscribers: new Set,
178810
+ isComplete: false
178811
+ };
178812
+ deploymentLogStates.set(appId, state);
178813
+ }
178814
+ return state;
178815
+ }
178816
+ function broadcastProgress(appId, progress) {
178817
+ const state = getOrCreateLogState(appId);
178818
+ state.logs.push(progress);
178819
+ if (progress.stage === "done" || progress.stage === "failed" || progress.stage === "cancelled") {
178820
+ state.isComplete = true;
178821
+ state.finalEvent = progress;
178822
+ }
178823
+ for (const subscriber of state.subscribers) {
178824
+ try {
178825
+ subscriber(progress);
178826
+ } catch {}
178827
+ }
178828
+ }
178829
+ function subscribeToDeploymentLogs(appId, onProgress) {
178830
+ const state = getOrCreateLogState(appId);
178831
+ for (const progress of state.logs) {
178832
+ try {
178833
+ onProgress(progress);
178834
+ } catch {}
178835
+ }
178836
+ state.subscribers.add(onProgress);
178837
+ return {
178838
+ unsubscribe: () => {
178839
+ state.subscribers.delete(onProgress);
178840
+ },
178841
+ isComplete: state.isComplete,
178842
+ finalEvent: state.finalEvent
178843
+ };
178844
+ }
178845
+ function hasActiveDeploymentLogs(appId) {
178846
+ const state = deploymentLogStates.get(appId);
178847
+ return !!state && !state.isComplete;
178848
+ }
178849
+ function clearDeploymentLogs(appId) {
178850
+ deploymentLogStates.delete(appId);
178851
+ }
178788
178852
  async function detectPublicIp() {
178789
178853
  if (cachedPublicIp)
178790
178854
  return cachedPublicIp;
@@ -179000,16 +179064,14 @@ async function deployApp(appId, options = {}, onProgress) {
179000
179064
  const [subdomain, ...domainParts] = service.domain.split(".");
179001
179065
  const rootDomain = domainParts.join(".");
179002
179066
  const upstreamUrl = `http://${projectName}_${service.serviceName}:${service.containerPort}`;
179003
- let routeOptions;
179067
+ const routeOptions = { appName: app15.name };
179004
179068
  if (settings.integrations.cloudflareApiToken && rootDomain) {
179005
179069
  onProgress?.({ stage: "configuring", message: `Generating SSL certificate for ${rootDomain}...` });
179006
179070
  const certResult = await createOriginCACertificate(rootDomain);
179007
179071
  if (certResult.success && certResult.certPath && certResult.keyPath) {
179008
- routeOptions = {
179009
- tlsCert: {
179010
- certFile: `${TRAEFIK_CERTS_MOUNT}/${rootDomain}/cert.pem`,
179011
- keyFile: `${TRAEFIK_CERTS_MOUNT}/${rootDomain}/key.pem`
179012
- }
179072
+ routeOptions.tlsCert = {
179073
+ certFile: `${TRAEFIK_CERTS_MOUNT}/${rootDomain}/cert.pem`,
179074
+ keyFile: `${TRAEFIK_CERTS_MOUNT}/${rootDomain}/key.pem`
179013
179075
  };
179014
179076
  log2.deploy.info("Using Origin CA certificate for TLS", {
179015
179077
  domain: service.domain,
@@ -179246,7 +179308,7 @@ async function stopApp(appId) {
179246
179308
  for (const service of services) {
179247
179309
  if (service.exposed && service.domain) {
179248
179310
  if (traefikConfig) {
179249
- await removeRoute(traefikConfig, appId);
179311
+ await removeRoute(traefikConfig, appId, app15.name);
179250
179312
  }
179251
179313
  const [subdomain, ...domainParts] = service.domain.split(".");
179252
179314
  const rootDomain = domainParts.join(".");
@@ -179261,6 +179323,22 @@ async function stopApp(appId) {
179261
179323
  await db.update(appServices).set({ status: "stopped", containerId: null, updatedAt: new Date().toISOString() }).where(eq(appServices.id, service.id));
179262
179324
  }
179263
179325
  }
179326
+ const tunnel = await db.query.tunnels.findFirst({
179327
+ where: eq(tunnels.appId, appId)
179328
+ });
179329
+ if (tunnel) {
179330
+ try {
179331
+ await deleteTunnel(tunnel.tunnelId);
179332
+ log2.deploy.info("Deleted Cloudflare tunnel", { tunnelId: tunnel.tunnelId, appId });
179333
+ } catch (err) {
179334
+ log2.deploy.warn("Failed to delete Cloudflare tunnel", {
179335
+ tunnelId: tunnel.tunnelId,
179336
+ appId,
179337
+ error: String(err)
179338
+ });
179339
+ }
179340
+ await db.delete(tunnels).where(eq(tunnels.appId, appId));
179341
+ }
179264
179342
  await db.update(apps).set({ status: "stopped", updatedAt: new Date().toISOString() }).where(eq(apps.id, appId));
179265
179343
  log2.deploy.info("App stopped", { appId });
179266
179344
  return { success: true };
@@ -179718,7 +179796,7 @@ app15.delete("/:id", async (c) => {
179718
179796
  }
179719
179797
  if (stopContainers) {
179720
179798
  const projectName = getProjectName(id, repo?.displayName);
179721
- if (existing.status === "running") {
179799
+ if (existing.status === "running" || existing.status === "building") {
179722
179800
  const stopResult = await stopApp(id);
179723
179801
  if (!stopResult.success) {
179724
179802
  log2.deploy.warn("stopApp failed during app deletion, attempting direct stack removal", {
@@ -179745,7 +179823,7 @@ app15.delete("/:id", async (c) => {
179745
179823
  }
179746
179824
  const traefikConfig = await detectTraefik();
179747
179825
  if (traefikConfig) {
179748
- await removeRoute(traefikConfig, id).catch((err) => {
179826
+ await removeRoute(traefikConfig, id, existing.name).catch((err) => {
179749
179827
  log2.deploy.warn("Failed to remove Traefik route during app deletion", {
179750
179828
  appId: id,
179751
179829
  error: String(err)
@@ -179780,6 +179858,7 @@ app15.delete("/:id", async (c) => {
179780
179858
  });
179781
179859
  await db.delete(appServices).where(eq(appServices.appId, id));
179782
179860
  await db.delete(deployments).where(eq(deployments.appId, id));
179861
+ await db.delete(tunnels).where(eq(tunnels.appId, id));
179783
179862
  await db.delete(apps).where(eq(apps.id, id));
179784
179863
  refreshGitWatchers().catch(() => {});
179785
179864
  return c.json({ success: true });
@@ -179873,12 +179952,14 @@ app15.get("/:id/deploy/stream", async (c) => {
179873
179952
  if (!existing) {
179874
179953
  return c.json({ error: "App not found" }, 404);
179875
179954
  }
179955
+ clearDeploymentLogs(id);
179876
179956
  c.header("X-Accel-Buffering", "no");
179877
179957
  return streamSSE(c, async (stream3) => {
179878
179958
  await stream3.write(`: ping
179879
179959
 
179880
179960
  `);
179881
179961
  const result = await deployApp(id, { deployedBy: "manual" }, async (progress) => {
179962
+ broadcastProgress(id, progress);
179882
179963
  try {
179883
179964
  await stream3.writeSSE({
179884
179965
  event: "progress",
@@ -179887,11 +179968,15 @@ app15.get("/:id/deploy/stream", async (c) => {
179887
179968
  } catch {}
179888
179969
  });
179889
179970
  if (result.success) {
179971
+ const finalProgress = { stage: "done", message: "Deployment complete" };
179972
+ broadcastProgress(id, finalProgress);
179890
179973
  await stream3.writeSSE({
179891
179974
  event: "complete",
179892
179975
  data: JSON.stringify({ success: true, deployment: result.deployment })
179893
179976
  });
179894
179977
  } else {
179978
+ const finalProgress = { stage: "failed", message: result.error || "Deployment failed" };
179979
+ broadcastProgress(id, finalProgress);
179895
179980
  await stream3.writeSSE({
179896
179981
  event: "error",
179897
179982
  data: JSON.stringify({ success: false, error: result.error })
@@ -179899,6 +179984,51 @@ app15.get("/:id/deploy/stream", async (c) => {
179899
179984
  }
179900
179985
  });
179901
179986
  });
179987
+ app15.get("/:id/deploy/watch", async (c) => {
179988
+ const id = c.req.param("id");
179989
+ const existing = await db.query.apps.findFirst({
179990
+ where: eq(apps.id, id)
179991
+ });
179992
+ if (!existing) {
179993
+ return c.json({ error: "App not found" }, 404);
179994
+ }
179995
+ if (!hasActiveDeploymentLogs(id) && existing.status !== "building" && existing.status !== "pending") {
179996
+ return c.json({ error: "No deployment in progress" }, 404);
179997
+ }
179998
+ c.header("X-Accel-Buffering", "no");
179999
+ return streamSSE(c, async (stream3) => {
180000
+ await stream3.write(`: ping
180001
+
180002
+ `);
180003
+ const { unsubscribe, isComplete, finalEvent } = subscribeToDeploymentLogs(id, (progress) => {
180004
+ try {
180005
+ stream3.writeSSE({
180006
+ event: "progress",
180007
+ data: JSON.stringify(progress)
180008
+ });
180009
+ } catch {}
180010
+ });
180011
+ if (isComplete && finalEvent) {
180012
+ const event = finalEvent.stage === "done" ? "complete" : "error";
180013
+ const data = finalEvent.stage === "done" ? { success: true } : { success: false, error: finalEvent.message };
180014
+ await stream3.writeSSE({
180015
+ event,
180016
+ data: JSON.stringify(data)
180017
+ });
180018
+ unsubscribe();
180019
+ return;
180020
+ }
180021
+ await new Promise((resolve6) => {
180022
+ const checkComplete = setInterval(() => {
180023
+ if (!hasActiveDeploymentLogs(id)) {
180024
+ clearInterval(checkComplete);
180025
+ unsubscribe();
180026
+ resolve6();
180027
+ }
180028
+ }, 1000);
180029
+ });
180030
+ });
180031
+ });
179902
180032
  app15.post("/:id/cancel-deploy", async (c) => {
179903
180033
  const id = c.req.param("id");
179904
180034
  const existing = await db.query.apps.findFirst({
@@ -179921,6 +180051,11 @@ app15.post("/:id/stop", async (c) => {
179921
180051
  if (!existing) {
179922
180052
  return c.json({ error: "App not found" }, 404);
179923
180053
  }
180054
+ if (existing.status === "building") {
180055
+ log2.deploy.info("Cancelling active deployment before stop", { appId: id });
180056
+ await cancelDeploymentByAppId(id);
180057
+ await new Promise((resolve6) => setTimeout(resolve6, 1000));
180058
+ }
179924
180059
  const result = await stopApp(id);
179925
180060
  if (!result.success) {
179926
180061
  return c.json({ error: result.error }, 500);