@haakco/hostingctl 1.0.315 → 1.0.332

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/main.js CHANGED
@@ -12583,6 +12583,18 @@ var createHostingDatabaseRequestEngineEnum = {
12583
12583
  postgres: "postgres",
12584
12584
  mysql: "mysql"
12585
12585
  };
12586
+ var createHostingDatabaseRequestExtensionsEnum = {
12587
+ pg_stat_statements: "pg_stat_statements",
12588
+ pg_stat_kcache: "pg_stat_kcache",
12589
+ timescaledb: "timescaledb",
12590
+ pgaudit: "pgaudit",
12591
+ vector: "vector",
12592
+ pg_search: "pg_search",
12593
+ citext: "citext",
12594
+ "uuid-ossp": "uuid-ossp",
12595
+ unaccent: "unaccent",
12596
+ pg_trgm: "pg_trgm"
12597
+ };
12586
12598
  var hostingWorkloadEnum = {
12587
12599
  wordpress: "wordpress",
12588
12600
  moodle: "moodle",
@@ -12594,7 +12606,10 @@ var hostingWorkloadEnum = {
12594
12606
  laravel: "laravel",
12595
12607
  "redis-durable": "redis-durable",
12596
12608
  ruby: "ruby",
12597
- infisical: "infisical"
12609
+ infisical: "infisical",
12610
+ "supabase-auth": "supabase-auth",
12611
+ "supabase-rest": "supabase-rest",
12612
+ "supabase-realtime": "supabase-realtime"
12598
12613
  };
12599
12614
  var hostingGeneratedHostnameWordTypeEnum = {
12600
12615
  adjective: "adjective",
@@ -16642,14 +16657,16 @@ function describeCreate$1(name, slug, packageKey) {
16642
16657
  //#region src/commands/environment-databases-text.ts
16643
16658
  var DATABASES_USAGE = [
16644
16659
  "Usage: hostingctl environment databases <environment-uuid> --company <slug>",
16645
- " hostingctl environment database create <environment-uuid> --company <slug> --key <key> --engine <mariadb|postgres|mysql> [--bind <uuid>[,<uuid>…]]",
16660
+ " hostingctl environment database create <environment-uuid> --company <slug> --key <key> --engine <mariadb|postgres|mysql> [--bind <uuid>[,<uuid>…]] [--extensions <name>[,<name>…]]",
16646
16661
  " hostingctl environment database bind <environment-uuid> <database-uuid> --company <slug> (--bind <uuid>[,<uuid>…] | --clear-bindings)",
16647
16662
  " hostingctl environment database review <environment-uuid> <database-uuid> --company <slug>",
16648
16663
  " hostingctl environment database retire <environment-uuid> <database-uuid> --company <slug> --digest <review-digest> --generation <review-generation>",
16649
16664
  "",
16650
16665
  " databases Every environment-owned database, with its engine, its consumers, its configuration key and",
16651
16666
  " any recorded retirement.",
16652
- " create Declare one independent database; --bind grants it to consuming workloads as it is made.",
16667
+ " create Declare one independent database; --bind grants it to consuming workloads as it is made,",
16668
+ " and --extensions selects PostgreSQL extensions (postgres only; the platform preloads what",
16669
+ " each one needs).",
16653
16670
  " bind Replace which applications may consume one database.",
16654
16671
  " review What retiring one identity would do, what it retains, and why it cannot proceed yet.",
16655
16672
  " retire Record a previously reviewed retirement; NEW logins stop after reconciliation.",
@@ -16849,14 +16866,17 @@ async function create$1(context, company, rest) {
16849
16866
  const key = singleFlag(context, "--key");
16850
16867
  const engine = engineFlag(context);
16851
16868
  const bindings = bindFlag(context);
16869
+ const extensions = extensionsFlag(context);
16852
16870
  if (key === null) throw new UsageError(`create needs --key.\n\n${DATABASES_USAGE}`);
16853
16871
  if (engine === null) throw new UsageError(`create needs --engine, one of ${ENGINES.join(", ")}.\n\n${DATABASES_USAGE}`);
16872
+ if (extensions.length > 0 && engine !== "postgres") throw new UsageError(`--extensions applies only to a postgres database (got --engine ${engine}).\n\n${DATABASES_USAGE}`);
16854
16873
  const environment = await environmentAt$1(company, environmentUuid);
16855
16874
  const body = {
16856
16875
  key,
16857
16876
  engine,
16858
16877
  desiredGeneration: environment.desiredGeneration,
16859
- ...bindings.length === 0 ? {} : { bindings }
16878
+ ...bindings.length === 0 ? {} : { bindings },
16879
+ ...extensions.length === 0 ? {} : { extensions }
16860
16880
  };
16861
16881
  return await confirmableWrite(context, describeCreate(environment, body), async () => (await hostingDatabaseStore({
16862
16882
  path: {
@@ -16898,7 +16918,12 @@ async function environmentAt$1(company, environmentUuid) {
16898
16918
  /** What a create would send, so the dry run names the identity, the engine and the initial consumers. */
16899
16919
  function describeCreate(environment, body) {
16900
16920
  const consumers = body.bindings ?? [];
16901
- return [`Create an independent ${body.engine} database "${body.key}" in ${environment.name} (${environment.slug}), generation ${String(body.desiredGeneration)}.`, consumers.length === 0 ? " It is granted to no application yet." : ` Granted to: ${consumers.join(", ")}`].join("\n");
16921
+ const extensions = body.extensions ?? [];
16922
+ return [
16923
+ `Create an independent ${body.engine} database "${body.key}" in ${environment.name} (${environment.slug}), generation ${String(body.desiredGeneration)}.`,
16924
+ consumers.length === 0 ? " It is granted to no application yet." : ` Granted to: ${consumers.join(", ")}`,
16925
+ extensions.length === 0 ? " No optional extensions selected." : ` Extensions: ${extensions.join(", ")} (the platform preloads what each one needs).`
16926
+ ].join("\n");
16902
16927
  }
16903
16928
  /** What a bind would send: the reviewed replacement list, named as the list it becomes. */
16904
16929
  function describeBind(environment, databaseUuid, bindings) {
@@ -16933,6 +16958,18 @@ function engineFlag(context) {
16933
16958
  function bindFlag(context) {
16934
16959
  return (context.flags.get("--bind") ?? "").split(",").map((one) => one.trim()).filter((one) => one !== "");
16935
16960
  }
16961
+ /**
16962
+ * The extensions named by `--extensions`, comma-separated like `--bind`.
16963
+ *
16964
+ * Checked against the generated enum, which the contract derives from the API's own catalogue: an unknown
16965
+ * name is refused here, by name, instead of travelling to the server to be refused there.
16966
+ */
16967
+ function extensionsFlag(context) {
16968
+ const named = (context.flags.get("--extensions") ?? "").split(",").map((one) => one.trim()).filter((one) => one !== "");
16969
+ const known = Object.values(createHostingDatabaseRequestExtensionsEnum);
16970
+ for (const one of named) if (!known.some((candidate) => candidate === one)) throw new UsageError(`--extensions must be among ${known.join(", ")} (got ${one}).\n\n${DATABASES_USAGE}`);
16971
+ return named;
16972
+ }
16936
16973
  /** One required flag's trimmed value, or null when it was absent or empty. */
16937
16974
  function singleFlag(context, flag) {
16938
16975
  const value = context.flags.get(flag)?.trim() ?? "";
@@ -18238,6 +18275,11 @@ var ENVIRONMENT_METADATA = {
18238
18275
  takesValue: true,
18239
18276
  description: "The comma-separated workload uuids that may consume a database."
18240
18277
  },
18278
+ {
18279
+ name: "--extensions",
18280
+ takesValue: true,
18281
+ description: "The comma-separated PostgreSQL extensions to enable; the platform preloads what each one needs."
18282
+ },
18241
18283
  {
18242
18284
  name: "--clear-bindings",
18243
18285
  takesValue: false,
@@ -24686,7 +24728,14 @@ var createHostingAccessUserRequestSchema = object({
24686
24728
  password_confirmation: string().max(1024).nullish()
24687
24729
  }).describe("Creating one environment access user.\n\nThe submitted generation must match the stored one, which the service checks\nagain under its row lock. At least one way to authenticate is required for an\nenabled user, and the service enforces that too: it is the rule that stops a\nrow existing that nobody can log in with.\n\n`username` is validated against the same grammar the database CHECK enforces,\nso a malformed name is a field error rather than a constraint violation.\n\n`readOnly` is optional here and required on update, deliberately: creating a\nlogin without mentioning it produces the read-write credential that existed\nbefore the flag, while changing one must state which access is wanted rather\nthan resetting a read-only login by omission.");
24688
24730
  var createHostingApplicationRequestSchema = object({
24689
- type: _enum(["wordpress", "moodle"]),
24731
+ type: _enum([
24732
+ "wordpress",
24733
+ "moodle",
24734
+ "infisical",
24735
+ "supabase-auth",
24736
+ "supabase-rest",
24737
+ "supabase-realtime"
24738
+ ]),
24690
24739
  name: string().max(100),
24691
24740
  label: string().max(100).nullish(),
24692
24741
  replicas: int().min(1),
@@ -24728,6 +24777,18 @@ var createHostingDatabaseRequestSchema = object({
24728
24777
  "mysql"
24729
24778
  ]),
24730
24779
  desiredGeneration: int().min(0),
24780
+ extensions: array(_enum([
24781
+ "pg_stat_statements",
24782
+ "pg_stat_kcache",
24783
+ "timescaledb",
24784
+ "pgaudit",
24785
+ "vector",
24786
+ "pg_search",
24787
+ "citext",
24788
+ "uuid-ossp",
24789
+ "unaccent",
24790
+ "pg_trgm"
24791
+ ])).max(16).refine((items) => new Set(items).size === items.length, { message: "Array entries must be unique" }).optional().describe("PostgreSQL extensions. Validated against the catalogue that knows what each one needs to be\nloaded, so a name that would be silently ignored is refused instead."),
24731
24792
  bindings: array(uuid()).max(16).refine((items) => new Set(items).size === items.length, { message: "Array entries must be unique" }).optional(),
24732
24793
  applicationUuid: string().optional()
24733
24794
  }).describe("Declaring one environment-owned database identity.\n\n`key` is the stable declaration key a re-declaration resolves the same identity by, so its\ngrammar is the one the database CHECK and the 1.4 configuration contract already enforce.\n`engine` is limited to the SQL engines this product hosts: a Redis cache is a different thing\nwith its own lifecycle and cannot be declared here.\n\n`bindings` is the optional initial grant list, so a declaration and its consumers can be made in\none call. It is optional rather than required: a database declared for nobody yet is a real\nstate, and `[]` says exactly that. Re-declaring an existing key with a changed list is refused by\nthe controller rather than applied, so a repeated submit cannot reset grants by accident.\n\n`applicationUuid` is prohibited rather than optional. This operation creates an independent,\nenvironment-owned identity; an application's own database is reached through the application\njourney, so ownership cannot be handed over by naming it here.\n\n`desiredGeneration` is required: declaring a database changes the environment, so the customer\nstates which version of it they reviewed, and a stale one is a conflict rather than a second\nidentity.");
@@ -24748,7 +24809,8 @@ var createHostingEnvironmentRequestSchema = object({
24748
24809
  resourceProfile: _enum([
24749
24810
  "small_sites",
24750
24811
  "empty_environment",
24751
- "low_end"
24812
+ "low_end",
24813
+ "database_pool"
24752
24814
  ]).optional(),
24753
24815
  name: string().max(100),
24754
24816
  slug: string().min(3).max(63).regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/),
@@ -24799,7 +24861,7 @@ var createHostingEnvironmentRequestSchema = object({
24799
24861
  type: string(),
24800
24862
  name: string().max(100),
24801
24863
  sizeMib: int().min(1)
24802
- })).min(1).max(20).optional(),
24864
+ })).max(20).optional(),
24803
24865
  databases: array(object({
24804
24866
  application: string().max(100),
24805
24867
  server: string().max(100)
@@ -25879,6 +25941,17 @@ var hostingApplicationAddressDataSchema = object({
25879
25941
  publishedUrl: string().nullable(),
25880
25942
  temporaryLoginSupported: boolean()
25881
25943
  });
25944
+ var hostingCuratedComponentOutputDataSchema = object({
25945
+ name: string(),
25946
+ description: string(),
25947
+ secret: boolean()
25948
+ });
25949
+ var hostingApplicationCapabilitiesDataSchema = object({
25950
+ pause: boolean(),
25951
+ backups: boolean(),
25952
+ unsupported: array(string()),
25953
+ outputs: array(hostingCuratedComponentOutputDataSchema)
25954
+ });
25882
25955
  var hostingResourceDataSchema = object({
25883
25956
  cpuRequestMillicores: int(),
25884
25957
  cpuLimitMillicores: int(),
@@ -25956,7 +26029,10 @@ var hostingWorkloadEnumSchema = _enum([
25956
26029
  "laravel",
25957
26030
  "redis-durable",
25958
26031
  "ruby",
25959
- "infisical"
26032
+ "infisical",
26033
+ "supabase-auth",
26034
+ "supabase-rest",
26035
+ "supabase-realtime"
25960
26036
  ]);
25961
26037
  var hostingDatabaseRetirementDataSchema = object({
25962
26038
  status: string(),
@@ -26026,7 +26102,8 @@ object({
26026
26102
  database: union([hostingDatabaseDataSchema, _null()]),
26027
26103
  domains: array(hostingDomainDataSchema),
26028
26104
  administrator: union([hostingAdministratorDataSchema, _null()]),
26029
- canManage: boolean()
26105
+ canManage: boolean(),
26106
+ capabilities: hostingApplicationCapabilitiesDataSchema
26030
26107
  });
26031
26108
  var hostingApplicationImportDetectionDataSchema = object({
26032
26109
  wordpressRoot: string(),
@@ -26320,6 +26397,20 @@ object({
26320
26397
  pending: boolean(),
26321
26398
  routes: array(hostingApplicationRouteDataSchema)
26322
26399
  });
26400
+ var hostingAutoDeployVerdictDataSchema = object({
26401
+ outcome: _enum([
26402
+ "forward",
26403
+ "duplicate",
26404
+ "stale",
26405
+ "no_event_revision",
26406
+ "undecidable"
26407
+ ]).describe("What one update event is, judged against what this application already serves. A delivery is not an instruction. It can be redelivered, and it can arrive after a newer one was already applied, so the platform has to answer \"is this event about a revision we have not applied * yet?\" before anything is built. Each answer has its own name and its own sentence, because \"duplicate\" and \"stale\" are different facts with different next steps: a duplicate says the event names the release that is serving, and a stale event says the event names a revision the platform has already moved past. Nothing is persisted from this enum. It is a decision read at the moment it is asked for, which is why it is a hand-written enum beside the generated catalog enums rather than a type table.\n| |\n|---|\n| `forward` <br/> The event names a revision this application does not serve, and nothing shows the platform moved past it. |\n| `duplicate` <br/> The event names exactly the revision the serving release was built from: the platform already applied it. |\n| `stale` <br/> The event names a revision behind the one this application already serves, so applying it would go backwards. |\n| `no_event_revision` <br/> The delivery carries no commit, so there is nothing to place against the served revision. |\n| `undecidable` <br/> The two revisions could not be ordered against each other. This is not a stale event and it is not a forward one. The repository named no order between the delivered commit and the one this application serves, so the platform cannot prove that applying it would move forward — and it will not apply a revision it cannot place. The delivery is named as undecidable rather than refused under another name, so a reader can tell \"we could not tell\" from \"we know it is behind\". |").describe("What one update event is, judged against what this application already serves. A delivery is not an instruction. It can be redelivered, and it can arrive after a newer one was already applied, so the platform has to answer \"is this event about a revision we have not applied * yet?\" before anything is built. Each answer has its own name and its own sentence, because \"duplicate\" and \"stale\" are different facts with different next steps: a duplicate says the event names the release that is serving, and a stale event says the event names a revision the platform has already moved past. Nothing is persisted from this enum. It is a decision read at the moment it is asked for, which is why it is a hand-written enum beside the generated catalog enums rather than a type table.\n| |\n|---|\n| `forward` <br/> The event names a revision this application does not serve, and nothing shows the platform moved past it. |\n| `duplicate` <br/> The event names exactly the revision the serving release was built from: the platform already applied it. |\n| `stale` <br/> The event names a revision behind the one this application already serves, so applying it would go backwards. |\n| `no_event_revision` <br/> The delivery carries no commit, so there is nothing to place against the served revision. |\n| `undecidable` <br/> The two revisions could not be ordered against each other. This is not a stale event and it is not a forward one. The repository named no order between the delivered commit and the one this application serves, so the platform cannot prove that applying it would move forward — and it will not apply a revision it cannot place. The delivery is named as undecidable rather than refused under another name, so a reader can tell \"we could not tell\" from \"we know it is behind\". |"),
26408
+ eventRevision: string().nullable().describe("The revision the push named, abbreviated, or null when the delivery carried none."),
26409
+ deployedRevision: string().nullable().describe("The revision serving when the decision was drawn, abbreviated, or null when none served."),
26410
+ explanation: string().describe("What the outcome means for this push, in one sentence."),
26411
+ recordedAt: string().describe("When the decision was recorded, as an ISO 8601 timestamp."),
26412
+ refused: boolean().describe("Whether the event was refused rather than built.")
26413
+ });
26323
26414
  var hostingAuxiliaryOwnershipEnumSchema = _enum(["pool", "platform"]).describe("Who pays for an auxiliary Pod. `POOL` is the customer's: it is charged against the purchased package and appears in their allocation. `PLATFORM` is HaakCo's: the file backup runs for the platform's own recoverability, so it is reserved in the namespace quota but is never charged to the customer's purchase. The distinction is arithmetic, not decoration — counting a platform Pod inside the customer's allocation is how a purchase reports as spent when part of it was ours.\n");
26324
26415
  var hostingBackupDestinationCopyDataSchema = object({
26325
26416
  destinationConfigured: boolean().describe("Whether the company has named a destination at all."),
@@ -26373,6 +26464,7 @@ var hostingFailureCauseEnumSchema = _enum([
26373
26464
  "build-timeout",
26374
26465
  "worker-never-reported",
26375
26466
  "image-unavailable",
26467
+ "release-task-failed",
26376
26468
  "validation-refusal",
26377
26469
  "kubernetes-refusal",
26378
26470
  "transient-transport",
@@ -26925,6 +27017,10 @@ var hostingReleaseArtifactSetDataSchema = object({
26925
27017
  compatibility: hostingReleaseArtifactCompatibilityEnumSchema.describe("Whether a release's backend process digest and the artifact that serves its frontend belong together. An application that serves a browser build runs two immutably identified things: the pinned runtime image its backend processes execute, and the application bundle that carries the built frontend. They are not independent — the bundle was produced by a builder for one exact runtime, and running it on another is how a served page and a serving process end up disagreeing about the same release. `Paired` and `NoFrontendArtifact` are both deployable and mean different things, so they are different cases: a bundle release has a frontend artifact that has been checked against its process digest, while a legacy image-only release has none to check. Collapsing them into one \"ok\" would make the platform unable to say which of the two it is holding. `Mismatched` is a refusal with a reason, never a silent success: the release records one process digest and its bundle records another, so nothing here can state which release is being served.\n| |\n|---|\n| `paired` <br/> The bundle was built for exactly the process digest this release serves: the pair belongs together. |\n| `no_frontend_artifact` <br/> This release carries no bundle, so it has no compatibility-bound frontend artifact to pair. |\n| `mismatched` <br/> The bundle records a different runtime than the one this release serves: the pair does not belong together. |"),
26926
27018
  explanation: string().describe("The pairing's own sentence, so the reason a reader sees is the reason the platform decided.")
26927
27019
  });
27020
+ var hostingRetainedBuildLogDataSchema = object({
27021
+ contents: string(),
27022
+ truncated: boolean()
27023
+ });
26928
27024
  var hostingReleaseDataSchema = object({
26929
27025
  uuid: string(),
26930
27026
  sequence: int(),
@@ -26942,7 +27038,8 @@ var hostingReleaseDataSchema = object({
26942
27038
  readyAt: string().nullable(),
26943
27039
  activatedAt: string().nullable(),
26944
27040
  retiredAt: string().nullable(),
26945
- explanation: union([hostingFailureExplanationDataSchema, _null()])
27041
+ explanation: union([hostingFailureExplanationDataSchema, _null()]),
27042
+ taskOutput: union([hostingRetainedBuildLogDataSchema, _null()])
26946
27043
  });
26947
27044
  var hostingRepositorySelectionDataSchema = object({
26948
27045
  uuid: string(),
@@ -26967,6 +27064,7 @@ object({
26967
27064
  releases: array(hostingReleaseDataSchema),
26968
27065
  source: union([hostingRepositorySelectionDataSchema, _null()]),
26969
27066
  autoDeploy: boolean(),
27067
+ autoDeployVerdict: union([hostingAutoDeployVerdictDataSchema, _null()]),
26970
27068
  availableSources: array(hostingRepositorySelectionDataSchema),
26971
27069
  canManage: boolean()
26972
27070
  });
@@ -27058,7 +27156,8 @@ var hostingWorkloadDataSchema = object({
27058
27156
  bounds: hostingWorkloadBoundsDataSchema,
27059
27157
  runtime: union([hostingWorkloadRuntimeDataSchema, _null()]),
27060
27158
  address: union([hostingApplicationAddressDataSchema, _null()]),
27061
- requiresBuild: boolean().describe("Build/deployment controls apply even before this workload has its first release."),
27159
+ capabilities: hostingApplicationCapabilitiesDataSchema.describe("What this workload can and cannot do. Carried here as well as on the application payload because this is the\nshape the applications table renders; the ANSWERS come from one owner either way."),
27160
+ requiresBuild: boolean(),
27062
27161
  valkey: union([hostingValkeySettingsDataSchema, _null()]),
27063
27162
  valkeyEndpoints: array(hostingValkeyEndpointDataSchema).describe("The endpoints that actually exist for this service, each with its own warning. Empty for every type that\nis not a Valkey service.")
27064
27163
  });
@@ -27749,6 +27848,17 @@ object({
27749
27848
  source: union([hostingConfigurationRepositorySourceDataSchema, _null()]),
27750
27849
  sourceProof: string().nullable()
27751
27850
  });
27851
+ object({
27852
+ rows: array(object({
27853
+ claim: string(),
27854
+ provisionedBytes: int().describe("What the purchase allocated, from the platform's own record."),
27855
+ usedBytes: int().nullable().describe("What the volume reports as written, or null when this credential cannot read it."),
27856
+ freeBytes: int().nullable().describe("What the volume still has room for, or null when unreadable."),
27857
+ idle: boolean().describe("Almost nothing written against a large allocation: the state that starved a workload earlier."),
27858
+ unreadable: string().nullable().describe("Why the numbers are absent, when they are.")
27859
+ })),
27860
+ unreadable: string().nullable()
27861
+ });
27752
27862
  var hostingTagDecisionEnumSchema = _enum([
27753
27863
  "adopted",
27754
27864
  "already_recorded",
@@ -30764,6 +30874,11 @@ union([
30764
30874
  object({ message: string().describe("Error overview.") }),
30765
30875
  messageDataSchema
30766
30876
  ]);
30877
+ union([
30878
+ object({ message: string().describe("Error overview.") }),
30879
+ object({ message: string().describe("Error overview.") }),
30880
+ messageDataSchema
30881
+ ]);
30767
30882
  union([
30768
30883
  object({ message: string().describe("Error overview.") }),
30769
30884
  object({ message: string().describe("Error overview.") }),
@@ -36512,7 +36627,7 @@ function writeUpdateState(state, env = process.env) {
36512
36627
  }
36513
36628
  //#endregion
36514
36629
  //#region src/version.ts
36515
- var VERSION = "1.0.315";
36630
+ var VERSION = "1.0.332";
36516
36631
  //#endregion
36517
36632
  //#region src/commands/update.ts
36518
36633
  /**
@@ -36587,7 +36702,7 @@ var update = {
36587
36702
  }, process.env);
36588
36703
  return {
36589
36704
  ...known,
36590
- current: after ?? "1.0.315",
36705
+ current: after ?? "1.0.332",
36591
36706
  latest: after ?? latest,
36592
36707
  upToDate: after !== null && (latest === null || !isNewerVersion(latest, after)),
36593
36708
  action: "run",
@@ -36862,7 +36977,7 @@ async function printUpdateNotice(options) {
36862
36977
  }, env, { waitMs: NOTICE_LOCK_WAIT_MS });
36863
36978
  } catch {}
36864
36979
  if (latest === null) return;
36865
- if (!isNewerVersion(latest, "1.0.315")) return;
36980
+ if (!isNewerVersion(latest, "1.0.332")) return;
36866
36981
  process.stderr.write(`A newer hostingctl is published: ${latest} (this copy is ${VERSION}). Run \`hostingctl update\` to update.\n`);
36867
36982
  }
36868
36983
  /** Whether an environment variable is set to something, which is how CI and the opt-out are read. */