openship 0.6.8 → 0.6.9

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.
@@ -1627,7 +1627,7 @@ function operatorEnvPassthrough(managed, prev) {
1627
1627
  }
1628
1628
  var PRESERVED_ENV_HEADER = "# Preserved from your existing .env \u2014 set by you, not by `openship up` (#485).";
1629
1629
  function resolveImageVersion(opts) {
1630
- return opts.version?.trim() || process.env.OPENSHIP_VERSION?.trim() || (true ? "0.6.8" : "latest");
1630
+ return opts.version?.trim() || process.env.OPENSHIP_VERSION?.trim() || (true ? "0.6.9" : "latest");
1631
1631
  }
1632
1632
  function managedEnvLines(opts, host, cfg, prev) {
1633
1633
  const lines = [
@@ -28,7 +28,7 @@ import {
28
28
  resolvePorts,
29
29
  sourceBuildDir,
30
30
  verifyHostChannel
31
- } from "./chunk-5I3COAFH.js";
31
+ } from "./chunk-634DDFT5.js";
32
32
  import {
33
33
  bootstrapAdmin,
34
34
  importMigratedSites,
@@ -1564,7 +1564,7 @@ async function runForeground(opts, source) {
1564
1564
  const uiSpinner = ora("Preparing the dashboard\u2026").start();
1565
1565
  try {
1566
1566
  const bundle = await ensureDashboard({
1567
- tag: source ? "local" : opts.uiVersion || `v${"0.6.8"}`,
1567
+ tag: source ? "local" : opts.uiVersion || `v${"0.6.9"}`,
1568
1568
  onProgress: (received, total) => {
1569
1569
  if (total) {
1570
1570
  uiSpinner.text = `Downloading dashboard\u2026 ${Math.round(received / total * 100)}%`;
@@ -5,7 +5,7 @@ const require = __ospCreateRequire(import.meta.url);
5
5
  import {
6
6
  checkHostChannel,
7
7
  verifyHostChannel
8
- } from "./chunk-5I3COAFH.js";
8
+ } from "./chunk-634DDFT5.js";
9
9
  import "./chunk-5UXHT6ED.js";
10
10
  import "./chunk-QWHBRY2K.js";
11
11
  import "./chunk-ELGRMZOW.js";
package/dist/index.js CHANGED
@@ -26,7 +26,7 @@ import {
26
26
  stop,
27
27
  upCommand,
28
28
  writeCliInstall
29
- } from "./chunk-RCMERVBL.js";
29
+ } from "./chunk-FCGJN5Q5.js";
30
30
  import {
31
31
  composeDown,
32
32
  composeInternalToken,
@@ -60,7 +60,7 @@ import {
60
60
  sourceBuildDir,
61
61
  storedApiPort,
62
62
  storedDashboardPort
63
- } from "./chunk-5I3COAFH.js";
63
+ } from "./chunk-634DDFT5.js";
64
64
  import {
65
65
  bootstrapAdmin,
66
66
  detectPublicIp,
@@ -313,7 +313,7 @@ function getDashboardUrl(name) {
313
313
  }
314
314
 
315
315
  // src/lib/api-client.ts
316
- var USER_AGENT = `openship-cli/${true ? "0.6.8" : "dev"}`;
316
+ var USER_AGENT = `openship-cli/${true ? "0.6.9" : "dev"}`;
317
317
  function getApiUrl2() {
318
318
  return `${getApiUrl()}/api`;
319
319
  }
@@ -1109,7 +1109,7 @@ function dockerNameRunning(nameFilter) {
1109
1109
  return r.stdout.trim().length > 0;
1110
1110
  }
1111
1111
  async function hostControlCheck(api) {
1112
- const { checkHostChannel } = await import("./host-channel-preflight-OMCZMMKR.js");
1112
+ const { checkHostChannel } = await import("./host-channel-preflight-OI2TRHSG.js");
1113
1113
  return hostControlRow(await checkHostChannel().catch(() => null), api);
1114
1114
  }
1115
1115
  var NEVER_PROVISIONED_DETAIL = `never provisioned \u2014 run \`${HOST_CHANNEL_PROVISION_COMMAND}\``;
@@ -1514,7 +1514,7 @@ async function interactiveDoctor() {
1514
1514
  continue;
1515
1515
  }
1516
1516
  if (action2 === "host-control") {
1517
- const { verifyHostChannel } = await import("./host-channel-preflight-OMCZMMKR.js");
1517
+ const { verifyHostChannel } = await import("./host-channel-preflight-OI2TRHSG.js");
1518
1518
  const report3 = await verifyHostChannel().catch(() => null);
1519
1519
  if (report3?.status === "fixed") log2.success("Host control is reachable now.");
1520
1520
  else if (report3?.status === "ok") log2.success("Host control was already reachable.");
@@ -4576,7 +4576,7 @@ function printMonitoringNextSteps() {
4576
4576
  info(" openship edge analytics -p <project> per-domain analytics");
4577
4577
  }
4578
4578
  async function runMonitoringInstall(opts) {
4579
- const up = await import("./up-HQS7IR7H.js");
4579
+ const up = await import("./up-RC5ZQIAD.js");
4580
4580
  let adminName = opts.adminName;
4581
4581
  let adminEmail = opts.adminEmail;
4582
4582
  let adminPassword = opts.adminPassword;
@@ -6733,7 +6733,7 @@ function applyOnlyArgv(selfArgs, json) {
6733
6733
  // src/commands/update.ts
6734
6734
  async function verifyHostChannelAfterUpdate() {
6735
6735
  if (isJsonMode()) return;
6736
- const { verifyHostChannel } = await import("./host-channel-preflight-OMCZMMKR.js");
6736
+ const { verifyHostChannel } = await import("./host-channel-preflight-OI2TRHSG.js");
6737
6737
  await verifyHostChannel().catch(() => {
6738
6738
  });
6739
6739
  }
@@ -6810,7 +6810,7 @@ async function runSourceUpdate(source, opts) {
6810
6810
  }
6811
6811
  var REINSTALL_HINT = "curl -fsSL https://get.openship.io | sh";
6812
6812
  async function runTarballUpdate(install, opts) {
6813
- const current = "0.6.8";
6813
+ const current = "0.6.9";
6814
6814
  let latest;
6815
6815
  try {
6816
6816
  latest = (await resolveLatestTag()).replace(/^v/, "");
@@ -6894,7 +6894,7 @@ var updateCommand = new Command26("update").description("Update the Openship CLI
6894
6894
  if (source) return runSourceUpdate(source, opts);
6895
6895
  const cliInstall = readCliInstall();
6896
6896
  if (cliInstall?.method === "tarball") return runTarballUpdate(cliInstall, opts);
6897
- const current = "0.6.8";
6897
+ const current = "0.6.9";
6898
6898
  let latest;
6899
6899
  try {
6900
6900
  latest = (await resolveLatestTag()).replace(/^v/, "");
@@ -7502,7 +7502,7 @@ async function runWizard() {
7502
7502
  }
7503
7503
  }
7504
7504
  }
7505
- const uiTag = `v${"0.6.8"}`;
7505
+ const uiTag = `v${"0.6.9"}`;
7506
7506
  if (method === "bare") {
7507
7507
  const dl = spinner4();
7508
7508
  dl.start("Pulling the Openship dist from GitHub");
@@ -7562,7 +7562,7 @@ async function runWizard() {
7562
7562
  const dashboardPort = String(ports.dashboard);
7563
7563
  const moved = portMoveNotice(ports, composeTrustedOriginUrls());
7564
7564
  if (moved.length) log5.info(moved.join("\n"));
7565
- if (!pinnedImagesReady({ version: "0.6.8" })) {
7565
+ if (!pinnedImagesReady({ version: "0.6.9" })) {
7566
7566
  cancel4("The pinned Openship image tag isn't in the registry yet \u2014 nothing on this box was changed.");
7567
7567
  process.exit(1);
7568
7568
  }
@@ -7586,7 +7586,7 @@ async function runWizard() {
7586
7586
  dashboardPort,
7587
7587
  publicUrl,
7588
7588
  trustProxy: behindProxy,
7589
- version: "0.6.8",
7589
+ version: "0.6.9",
7590
7590
  noHostControl: !allowHostControl
7591
7591
  });
7592
7592
  if (!fetched.ok) {
@@ -7615,7 +7615,7 @@ async function runWizard() {
7615
7615
  dashboardPort,
7616
7616
  publicUrl,
7617
7617
  trustProxy: behindProxy,
7618
- version: "0.6.8",
7618
+ version: "0.6.9",
7619
7619
  noHostControl: !allowHostControl
7620
7620
  });
7621
7621
  if (!up.ok) {
@@ -7716,7 +7716,7 @@ async function runWizard() {
7716
7716
  if (result.liveUrl) liveUrl2 = result.liveUrl;
7717
7717
  else if (!result.domainRegistered) liveUrl2 = `http://localhost:${started.dashPort}`;
7718
7718
  for (const w of result.warnings) log5.warn(w);
7719
- const { verifyHostChannel } = await import("./host-channel-preflight-OMCZMMKR.js");
7719
+ const { verifyHostChannel } = await import("./host-channel-preflight-OI2TRHSG.js");
7720
7720
  await verifyHostChannel().catch(() => {
7721
7721
  });
7722
7722
  finishSetup({
@@ -7995,7 +7995,7 @@ function attachCompletion(program2) {
7995
7995
 
7996
7996
  // src/index.ts
7997
7997
  var program = new Command28();
7998
- program.name("openship").description("Openship CLI \u2014 install, run, and manage Openship from your terminal").version("0.6.8").option("--json", "Machine-readable JSON output (stdout data only)").hook("preAction", (thisCommand) => {
7998
+ program.name("openship").description("Openship CLI \u2014 install, run, and manage Openship from your terminal").version("0.6.9").option("--json", "Machine-readable JSON output (stdout data only)").hook("preAction", (thisCommand) => {
7999
7999
  if (thisCommand.opts().json) setJsonMode(true);
8000
8000
  }).action(async () => {
8001
8001
  const installed = serviceStatus().installed || readInstallMethod() === "compose";
@@ -123865,6 +123865,27 @@ function toComposeSpec(s4) {
123865
123865
  advanced
123866
123866
  };
123867
123867
  }
123868
+ function isComposeProvenanceUpgrade(baseInput, nextInput) {
123869
+ const base = toComposeSpec(baseInput);
123870
+ const next = toComposeSpec(nextInput);
123871
+ const baseAdvanced = { ...base.advanced ?? {} };
123872
+ const nextAdvanced = { ...next.advanced ?? {} };
123873
+ const markerKeys = ["environmentTemplateKeys", "buildArgTemplateKeys"];
123874
+ const addsMarker = markerKeys.some((key) => !Object.hasOwn(baseAdvanced, key) && Object.hasOwn(nextAdvanced, key));
123875
+ if (!addsMarker)
123876
+ return false;
123877
+ for (const key of markerKeys) {
123878
+ delete baseAdvanced[key];
123879
+ delete nextAdvanced[key];
123880
+ }
123881
+ const envKeys = (value) => Object.keys(value).sort();
123882
+ const comparable = (spec, advanced) => ({
123883
+ ...spec,
123884
+ environment: envKeys(spec.environment ?? {}),
123885
+ advanced
123886
+ });
123887
+ return JSON.stringify(canonicalize(comparable(base, baseAdvanced))) === JSON.stringify(canonicalize(comparable(next, nextAdvanced)));
123888
+ }
123868
123889
  function composeWritePatch(parsed, stored, composeAuthoritative = false) {
123869
123890
  const hasBuildArgMarker = Object.hasOwn(parsed.advanced ?? {}, "buildArgTemplateKeys");
123870
123891
  const suppliedBuildArgCount = Object.keys(parsed.buildArgs ?? {}).length;
@@ -124216,6 +124237,10 @@ function createServiceRepo(db3) {
124216
124237
  });
124217
124238
  continue;
124218
124239
  }
124240
+ if (isComposeProvenanceUpgrade(base, theirs)) {
124241
+ await this.update(ex.id, { importedSpec: theirs, driftSpec: null });
124242
+ continue;
124243
+ }
124219
124244
  if (composeSpecsEqual(theirs, base)) {
124220
124245
  if (!Object.hasOwn(base, "buildArgs")) {
124221
124246
  await this.update(ex.id, { importedSpec: theirs, driftSpec: null });
@@ -214575,7 +214600,7 @@ var package_default;
214575
214600
  var init_package = __esm(() => {
214576
214601
  package_default = {
214577
214602
  name: "@repo/api",
214578
- version: "0.6.8",
214603
+ version: "0.6.9",
214579
214604
  license: "Apache-2.0",
214580
214605
  private: true,
214581
214606
  type: "module",
@@ -275257,6 +275282,9 @@ async function allocateAndReservePinnedHostPort(input) {
275257
275282
  avoid,
275258
275283
  reuseOccupiedPreferred: reusable ? ownsReusablePinnedHostPort(input.claims, reusable) && !avoid.has(reusable.port) : false
275259
275284
  });
275285
+ if (input.lockPreferred && preferred !== undefined && allocation.port !== preferred) {
275286
+ throw new Error(`Refusing to change the locked host port for ${input.lockPreferred.ownerLabel} from ` + `${preferred} to ${allocation.port} during a same-server redeploy. ` + `The existing workload and routes were left unchanged.`);
275287
+ }
275260
275288
  const claim2 = await reserveTargetPinnedHostPort(input.target, {
275261
275289
  ...input.owner,
275262
275290
  containerPort: previousClaim ? previousClaim.containerPort : input.owner.containerPort,
@@ -282279,6 +282307,9 @@ var init_deployment_class2 = __esm(() => {
282279
282307
  });
282280
282308
 
282281
282309
  // ../api/src/modules/services/live-state.ts
282310
+ function liveMatchTiersForDeployment(meta6) {
282311
+ return meta6?.adopt === true ? undefined : OWNED_CONTAINER_TIERS;
282312
+ }
282282
282313
  function canonicalServiceContainerName(slug, serviceName) {
282283
282314
  if (!slug || !serviceName)
282284
282315
  return null;
@@ -282401,8 +282432,9 @@ function describeLiveState(services, live, matches) {
282401
282432
  return `${svc.name} → ${name2 ? displayName(name2) : m4.containerId.slice(0, 12)} (${m4.status}, by ${m4.matchedBy})${dupes}`;
282402
282433
  });
282403
282434
  }
282404
- var UNMATCHED, displayName = (c2) => c2.names[0] ?? c2.id.slice(0, 12);
282435
+ var OWNED_CONTAINER_TIERS, UNMATCHED, displayName = (c2) => c2.names[0] ?? c2.id.slice(0, 12);
282405
282436
  var init_live_state = __esm(() => {
282437
+ OWNED_CONTAINER_TIERS = ["label", "name", "trackedId"];
282406
282438
  UNMATCHED = {
282407
282439
  containerId: null,
282408
282440
  status: "stopped",
@@ -298886,6 +298918,16 @@ function upstreamHostPortFor(args2) {
298886
298918
  return pinned.get(port) ?? (sameService && port === primaryPort && resultHostPort ? resultHostPort : undefined);
298887
298919
  }
298888
298920
 
298921
+ // ../api/src/modules/deployments/compose/foreign-compose-collision.ts
298922
+ function findForeignComposeCollisions(input) {
298923
+ const services = new Set(input.serviceNames);
298924
+ const tracked = new Set(input.trackedContainerIds ?? []);
298925
+ return input.containers.filter((container) => !tracked.has(container.id) && container.composeProject === input.slug && !!container.composeService && services.has(container.composeService)).map((container) => ({
298926
+ serviceName: container.composeService,
298927
+ containerName: container.names[0] ?? container.id.slice(0, 12)
298928
+ }));
298929
+ }
298930
+
298889
298931
  // ../api/src/modules/deployments/compose/deploy.service.ts
298890
298932
  function composeDeployMadeNoChanges(result) {
298891
298933
  return result.status === "ready" && result.summary.total > 0 && result.summary.deployed === 0 && result.summary.failed === 0 && !result.summary.mutated;
@@ -299301,8 +299343,27 @@ async function deployComposeServicesUnlocked(project2, dep, runtime, logger4, op
299301
299343
  `, "info");
299302
299344
  }
299303
299345
  const previousServiceDeps = project2.activeDeploymentId ? await repos.service.listByDeployment(project2.activeDeploymentId) : [];
299346
+ const activeDeployment = project2.activeDeploymentId ? await repos.deployment.findById(project2.activeDeploymentId) : null;
299347
+ const activeServerId = (activeDeployment?.meta ?? {}).serverId ?? null;
299348
+ const targetServerId = opts?.serverId ?? null;
299349
+ const lockCarriedHostPorts = activeDeployment !== null && activeServerId === targetServerId;
299304
299350
  const previousByServiceId = new Map(previousServiceDeps.map((row) => [row.serviceId, row]));
299305
299351
  const enabledServiceIds = new Set(enabled.map((svc) => svc.id));
299352
+ const activeMeta = activeDeployment?.meta ?? {};
299353
+ if (activeMeta.adopt !== true && runtime.supports("hostContainerQuery") && runtime.listAllContainers) {
299354
+ const collisions = findForeignComposeCollisions({
299355
+ slug: project2.slug,
299356
+ serviceNames: enabled.map((service2) => service2.name),
299357
+ containers: await runtime.listAllContainers(),
299358
+ trackedContainerIds: previousServiceDeps.map((row) => row.containerId).filter((id2) => !!id2)
299359
+ });
299360
+ if (collisions.length > 0) {
299361
+ throw new DeployError(`Refusing to create a parallel stack: Docker Compose already runs ${collisions.map((collision) => `${collision.serviceName} (${collision.containerName})`).join(", ")} under project name "${project2.slug}". Import/adopt that stack, remove it explicitly, or choose a different project slug before deploying.`, "FOREIGN_COMPOSE_STACK", {
299362
+ projectSlug: project2.slug,
299363
+ collisions
299364
+ });
299365
+ }
299366
+ }
299306
299367
  let keepSetPromise = null;
299307
299368
  const retentionKeepSet = () => {
299308
299369
  keepSetPromise ??= computeKeepSet(project2).catch(() => new Set);
@@ -299909,13 +299970,14 @@ async function deployComposeServicesUnlocked(project2, dep, runtime, logger4, op
299909
299970
  cachedPreferred,
299910
299971
  allowLegacyContainerPort: containerPort === primaryRoutedPort,
299911
299972
  additionalAvoid: allocatedHostPorts,
299973
+ lockPreferred: lockCarriedHostPorts ? { ownerLabel: svc.name } : undefined,
299912
299974
  allocate: (allocationOptions) => allocateHostPort(opts.executor, allocationOptions)
299913
299975
  });
299914
299976
  serviceHostPortAllocations.push(allocation);
299915
299977
  const carried2 = allocation.preferred;
299916
299978
  const hostPort = allocation.port;
299917
299979
  if (carried2 && hostPort !== carried2) {
299918
- logger4.log(`Host port ${carried2} for ${svc.name} is taken on this server — using ${hostPort}. (Expected when a project moves to a different host.)
299980
+ logger4.log(`Project moved hosts: ${svc.name} uses ${hostPort} instead of ${carried2}.
299919
299981
  `);
299920
299982
  }
299921
299983
  if (!allocation.scanned) {
@@ -302445,7 +302507,8 @@ async function getActiveServiceContainers(ctx2, projectId) {
302445
302507
  live: containers,
302446
302508
  projectId,
302447
302509
  slug: project2.slug,
302448
- trackedIds
302510
+ trackedIds,
302511
+ tiers: liveMatchTiersForDeployment(dep.meta)
302449
302512
  });
302450
302513
  return services.filter((svc) => svc.enabled !== false || matches.get(svc.id)?.containerId).map((svc) => {
302451
302514
  const m4 = matches.get(svc.id);
@@ -315757,7 +315820,11 @@ async function connect3(sub) {
315757
315820
  sub.connecting = false;
315758
315821
  }
315759
315822
  }
315760
- function onEvent(sub, _event) {
315823
+ function onEvent(sub, event) {
315824
+ if (!isTrackedHealthContainer(sub.key, event.containerId))
315825
+ return;
315826
+ if (!["die", "oom", "restart", "start", "unhealthy", "healthy", "destroy"].includes(event.action))
315827
+ return;
315761
315828
  if (Date.now() - sub.lastRunAt >= MIN_RUN_GAP_MS)
315762
315829
  sub.followUpsLeft = MAX_FOLLOW_UPS;
315763
315830
  schedule(sub);
@@ -315891,6 +315958,12 @@ var init_container_events = __esm(async () => {
315891
315958
  });
315892
315959
 
315893
315960
  // ../api/src/modules/monitoring/health-watch.ts
315961
+ function listWorkloadHealthSnapshots(organizationId) {
315962
+ return [...HEALTH_SNAPSHOTS.values()].filter((row) => row.organizationId === organizationId);
315963
+ }
315964
+ function isTrackedHealthContainer(groupKey, containerId) {
315965
+ return TRACKED_CONTAINERS.get(groupKey)?.has(containerId) ?? false;
315966
+ }
315894
315967
  function memoryKey(projectId, serviceKey) {
315895
315968
  return `${projectId}:${serviceKey}`;
315896
315969
  }
@@ -316072,6 +316145,10 @@ async function sweepOnce(opts) {
316072
316145
  summary.stale++;
316073
316146
  MEMORY.delete(key);
316074
316147
  }
316148
+ for (const [key, row] of HEALTH_SNAPSHOTS) {
316149
+ if (!visited2.has(key) && settled(row.projectId))
316150
+ HEALTH_SNAPSHOTS.delete(key);
316151
+ }
316075
316152
  for (const key of MEMORY.keys()) {
316076
316153
  if (visited2.has(key))
316077
316154
  continue;
@@ -316187,6 +316264,7 @@ async function readServerGroup(ctx2, first, target) {
316187
316264
  });
316188
316265
  }
316189
316266
  }
316267
+ TRACKED_CONTAINERS.set(watchGroupKey(serverId, organizationId), new Set(pending.map((entry) => entry.workload.containerId)));
316190
316268
  pending.sort((a2, b3) => inspectRank(a2.container) - inspectRank(b3.container));
316191
316269
  await mapWithLimit(pending, INSPECT_CONCURRENCY, async ({ candidate, workload, container }) => {
316192
316270
  if (handle2.abandoned)
@@ -316257,6 +316335,8 @@ async function resolveWorkloads(candidate, services, live) {
316257
316335
  });
316258
316336
  const workloads = [];
316259
316337
  for (const service2 of services) {
316338
+ if (service2.advanced?.monitoringEnabled === false)
316339
+ continue;
316260
316340
  const hint = hints.get(service2.id);
316261
316341
  if (!hint)
316262
316342
  continue;
@@ -316315,6 +316395,19 @@ async function evaluate(args2) {
316315
316395
  expectRunning: workload.expectRunning,
316316
316396
  createdTicks: sameContainer ? previous?.createdTicks ?? 0 : 0
316317
316397
  }) : missingContainerVerdict(workload);
316398
+ HEALTH_SNAPSHOTS.set(key, {
316399
+ organizationId: candidate.project.organizationId,
316400
+ projectId: candidate.project.id,
316401
+ projectName: candidate.project.name,
316402
+ projectSlug: candidate.project.slug,
316403
+ serviceId: workload.serviceId,
316404
+ serviceKey: workload.serviceKey,
316405
+ serviceName: workload.serviceName,
316406
+ serverId: candidate.serverId,
316407
+ containerId: workload.containerId,
316408
+ state: verdict.clear === "unknown" ? "unknown" : verdict.kind ?? "healthy",
316409
+ observedAt: new Date(nowMs).toISOString()
316410
+ });
316318
316411
  if (verdict.clear === "unknown") {
316319
316412
  summary.indeterminate++;
316320
316413
  const carried = previous && sameContainer ? previous : {
@@ -316418,7 +316511,7 @@ async function pruneResolvedIncidents() {
316418
316511
  const cutoff = new Date(Date.now() - INCIDENT_RETENTION_DAYS * 24 * 60 * 60 * 1000);
316419
316512
  return { deleted: await repos.serviceIncident.deleteResolvedOlderThan(cutoff) };
316420
316513
  }
316421
- var AGREE_TICKS = 2, MIN_CONFIRM_GAP_MS = 4000, RESTART_WINDOW_MS = 45000, RESTART_STALE_MS, DEPLOY_GRACE_MS, SERVER_CONCURRENCY = 4, GROUP_DEADLINE_MS = 90000, GROUP_DEADLINE_REASON, INSPECT_CONCURRENCY = 8, MAX_INSPECTS_PER_SERVER = 120, LOG_TAIL_LINES = 12, FLAP_WINDOW_MS, RECOVERY_HOLD_MS, SINGLE_APP_KEY = "__app__", MEMORY, LOCAL_SERVER_KEY = "__local__", sweepChain, KNOWN_STATES, INCIDENT_RETENTION_DAYS = 30;
316514
+ var AGREE_TICKS = 2, MIN_CONFIRM_GAP_MS = 4000, RESTART_WINDOW_MS = 45000, RESTART_STALE_MS, DEPLOY_GRACE_MS, SERVER_CONCURRENCY = 4, GROUP_DEADLINE_MS = 90000, GROUP_DEADLINE_REASON, INSPECT_CONCURRENCY = 8, MAX_INSPECTS_PER_SERVER = 120, LOG_TAIL_LINES = 12, FLAP_WINDOW_MS, RECOVERY_HOLD_MS, SINGLE_APP_KEY = "__app__", MEMORY, HEALTH_SNAPSHOTS, TRACKED_CONTAINERS, LOCAL_SERVER_KEY = "__local__", sweepChain, KNOWN_STATES, INCIDENT_RETENTION_DAYS = 30;
316422
316515
  var init_health_watch = __esm(async () => {
316423
316516
  init_src2();
316424
316517
  init_src();
@@ -316437,6 +316530,8 @@ var init_health_watch = __esm(async () => {
316437
316530
  FLAP_WINDOW_MS = 10 * 60000;
316438
316531
  RECOVERY_HOLD_MS = 2 * 60000;
316439
316532
  MEMORY = new Map;
316533
+ HEALTH_SNAPSHOTS = new Map;
316534
+ TRACKED_CONTAINERS = new Map;
316440
316535
  sweepChain = Promise.resolve();
316441
316536
  KNOWN_STATES = new Set([
316442
316537
  "created",
@@ -344050,6 +344145,11 @@ async function loadNames(organizationId) {
344050
344145
  }
344051
344146
 
344052
344147
  // ../api/src/modules/issues/issues.controller.ts
344148
+ init_env();
344149
+ await __promiseAll([
344150
+ init_health_watch(),
344151
+ init_src3()
344152
+ ]);
344053
344153
  async function listIssues(c2) {
344054
344154
  const ctx2 = getRequestContext(c2);
344055
344155
  const status = c2.req.query("status") === "resolved" ? "resolved" : "open";
@@ -344061,30 +344161,72 @@ async function issuesSummary(c2) {
344061
344161
  const { counts } = await listOrganizationIssues(ctx2);
344062
344162
  return c2.json({ data: counts });
344063
344163
  }
344164
+ async function healthSnapshot(c2) {
344165
+ const ctx2 = getRequestContext(c2);
344166
+ const [rows, job2, servers2] = await Promise.all([
344167
+ Promise.resolve(listWorkloadHealthSnapshots(ctx2.organizationId)),
344168
+ repos.job.findByKey("services:health-watch"),
344169
+ repos.server.listByOrganization(ctx2.organizationId)
344170
+ ]);
344171
+ const serverNames = new Map(servers2.map((server) => [server.id, server.name ?? server.sshHost]));
344172
+ return c2.json({
344173
+ data: rows.map((row) => ({
344174
+ ...row,
344175
+ serverName: row.serverId ? serverNames.get(row.serverId) ?? row.serverId : "This server"
344176
+ })),
344177
+ watching: job2?.enabled ?? false,
344178
+ watcher: {
344179
+ key: "services:health-watch",
344180
+ schedule: job2?.cronExpression ?? null,
344181
+ eventsEnabled: !env.OPENSHIP_DISABLE_CONTAINER_EVENTS
344182
+ }
344183
+ });
344184
+ }
344064
344185
  var RESCAN_JOBS = [
344065
344186
  "services:health-watch",
344066
344187
  "infra:scan",
344067
344188
  "domains:verify-pending",
344068
344189
  "updates:scan"
344069
344190
  ];
344191
+ var activeRescan = null;
344192
+ function rescanStatus(c2) {
344193
+ return c2.json({ data: activeRescan });
344194
+ }
344070
344195
  async function rescanIssues(c2) {
344196
+ if (activeRescan?.status === "running")
344197
+ return c2.json({ data: activeRescan }, 202);
344071
344198
  const available = RESCAN_JOBS.filter((key) => {
344072
344199
  const def = SYSTEM_JOB_BY_KEY.get(key);
344073
344200
  return !def?.available || def.available();
344074
344201
  });
344075
344202
  const skipped = RESCAN_JOBS.filter((key) => !available.includes(key));
344076
- const settled = await Promise.allSettled(available.map((key) => runJobNow(key)));
344077
- const ran = [];
344078
- const failed = [];
344079
- settled.forEach((result, i3) => {
344080
- const key = available[i3];
344081
- if (result.status === "fulfilled")
344082
- ran.push(key);
344083
- else
344084
- failed.push({ key, error: result.reason?.message ?? "failed" });
344203
+ activeRescan = {
344204
+ id: crypto.randomUUID(),
344205
+ status: "running",
344206
+ startedAt: new Date().toISOString(),
344207
+ stages: RESCAN_JOBS.map((key) => ({
344208
+ key,
344209
+ status: skipped.includes(key) ? "skipped" : "pending"
344210
+ }))
344211
+ };
344212
+ const session5 = activeRescan;
344213
+ Promise.all(available.map(async (key) => {
344214
+ const stage = session5.stages.find((item) => item.key === key);
344215
+ stage.status = "running";
344216
+ try {
344217
+ const result = await runJobNow(key);
344218
+ stage.status = "completed";
344219
+ stage.summary = result.summary ?? {};
344220
+ } catch (err2) {
344221
+ stage.status = "failed";
344222
+ stage.error = err2?.message ?? "failed";
344223
+ }
344224
+ })).finally(() => {
344225
+ session5.status = "completed";
344226
+ session5.finishedAt = new Date().toISOString();
344085
344227
  });
344086
- c2.set("auditAfter", { ran, skipped, failed: failed.map((f3) => f3.key) });
344087
- return c2.json({ data: { ran, skipped, failed } });
344228
+ c2.set("auditAfter", { scanSessionId: session5.id, stages: available, skipped });
344229
+ return c2.json({ data: session5 }, 202);
344088
344230
  }
344089
344231
 
344090
344232
  // ../api/src/modules/issues/issues.routes.ts
@@ -344104,6 +344246,13 @@ r11.get("/summary", {
344104
344246
  description: "Counts only from the same feed as GET /issues: {outage, actionRequired, advisory, total}. Use for a quick health verdict; read GET /issues for the rows and their fixes."
344105
344247
  }
344106
344248
  }, issuesSummary);
344249
+ r11.get("/health", {
344250
+ tag: "project:list",
344251
+ localOnly: true,
344252
+ mcp: {
344253
+ description: "Latest health-watch snapshot for every expected workload in the organization. Cached only: this read performs no Docker polling. Includes healthy, unhealthy, crash-looping, down and unknown states plus watcher enabled status."
344254
+ }
344255
+ }, healthSnapshot);
344107
344256
  r11.post("/rescan", {
344108
344257
  tag: "job:write",
344109
344258
  collection: true,
@@ -344112,6 +344261,7 @@ r11.post("/rescan", {
344112
344261
  description: "Run the checkers behind GET /issues immediately (health watch, managed-container scan, pending domain verification, update scan) instead of waiting for their schedules, then re-read GET /issues. Returns which jobs ran, which were skipped as not applicable to this platform, and which failed. Adds no new probing — these are the same scheduled jobs, recorded in the jobs history as manual runs. Self-hosted only."
344113
344262
  }
344114
344263
  }, rescanIssues);
344264
+ r11.get("/rescan/status", { tag: "job:read", localOnly: true }, rescanStatus);
344115
344265
  var issuesRoutes = r11.hono;
344116
344266
 
344117
344267
  // ../api/src/modules/jobs/job.routes.ts
@@ -344646,6 +344796,7 @@ var HealthcheckSchema = Type.Object({
344646
344796
  }, { additionalProperties: false });
344647
344797
  var AdvancedSchema = Type.Object({
344648
344798
  healthcheck: Type.Optional(Type.Union([HealthcheckSchema, Type.Null()])),
344799
+ monitoringEnabled: Type.Optional(Type.Union([Type.Boolean(), Type.Null()])),
344649
344800
  readiness: Type.Optional(Type.Union([
344650
344801
  Type.Object({
344651
344802
  enabled: Type.Optional(Type.Boolean()),
@@ -7,8 +7,8 @@ import {
7
7
  runHeadlessProvision,
8
8
  startService,
9
9
  upCommand
10
- } from "./chunk-RCMERVBL.js";
11
- import "./chunk-5I3COAFH.js";
10
+ } from "./chunk-FCGJN5Q5.js";
11
+ import "./chunk-634DDFT5.js";
12
12
  import "./chunk-7YBWXEM6.js";
13
13
  import "./chunk-5UXHT6ED.js";
14
14
  import "./chunk-QWHBRY2K.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openship",
3
- "version": "0.6.8",
3
+ "version": "0.6.9",
4
4
  "description": "Openship CLI - deploy, manage, and open your Openship platform from the terminal",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {