ninepm 0.85.0 → 0.87.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/README.md CHANGED
@@ -272,6 +272,20 @@ prints the `9pm domains list` command: once the domain is no longer listed, it h
272
272
  from 9pm. Such a domain shows as `removing` in `list` and `status` (`"removing": true` in `--json`),
273
273
  and `wait` on it exits `20`. Since 0.80.0.
274
274
 
275
+ Since 0.86.0, a removal that finished (from `remove` or `9pm delete`) also says that after removal,
276
+ visitors may see a TLS or routing error while the domain's DNS still points at 9pm. It names no
277
+ record and adds no deletion advice of its own: what may be deleted is only the cleanup list printed
278
+ above it, each record only if its value still matches. A removal still finishing gets no such line,
279
+ since it is told to leave its records in place. A server that sends no cleanup list still gets it.
280
+
281
+ Since 0.87.0, that line is followed by the order the staging rehearsal (#2182) demonstrated for a
282
+ move to another host: point DNS at the new host, wait for the DNS TTL to pass, then remove the domain
283
+ from 9pm. While caches update, some visitors may still reach 9pm or briefly see an error from the new
284
+ host, and once DNS points away 9pm may show the domain as failed, which is expected. It links the
285
+ custom-domain guide's "Moving a domain off 9pm" section on the site beside the control plane it
286
+ talked to (staging's guide for `--api-url https://api-staging.9pm.ai`). A move to your own Cloudflare
287
+ DNS zone was not rehearsed, so no order is printed for it; the guide says to contact support first.
288
+
275
289
  Since 0.84.0, `9pm domains removed [--owner <organisation>]` lists the DNS records you may delete
276
290
  after a removal that finished in the background: a `remove` that answered `releasing`, or a domain of
277
291
  a deleted app, finished later by the platform. It is the same list `remove` prints when it finishes
package/dist/index.d.ts CHANGED
@@ -123,7 +123,7 @@ export interface DeletedSiteResult {
123
123
  metadata: boolean;
124
124
  errors?: string[];
125
125
  }
126
- export declare function deletedDomainLines(deleted: Pick<DeletedSiteResult, "customDomainCleanup" | "customDomainsReleasing">, options?: DomainFormatOptions): string[];
126
+ export declare function deletedDomainLines(deleted: Pick<DeletedSiteResult, "customDomainCleanup" | "customDomainsReleasing" | "customDomainNames">, options?: DomainFormatOptions): string[];
127
127
  export interface CreatedRuntimeToken {
128
128
  token: string;
129
129
  appAccessToken: AppAccessToken;
@@ -133,7 +133,7 @@ export interface CreatedServiceToken {
133
133
  clientSecret: string;
134
134
  }
135
135
  export type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
136
- export declare const CLI_VERSION = "0.85.0";
136
+ export declare const CLI_VERSION = "0.87.0";
137
137
  type CredentialBackend = "macos-keychain" | "windows-dpapi" | "linux-secret-service" | "file-plaintext" | "none";
138
138
  export interface StoredCredentialEntry {
139
139
  token: string;
@@ -897,6 +897,8 @@ export declare function getDomainSetupCleanup(options: DomainCommandOptions & {
897
897
  }): Promise<unknown>;
898
898
  export declare function domainSetupCleanupLines(body: unknown, hostname: string): string[];
899
899
  export declare function setupCleanupLines(hostname: string, records: DomainCleanupRecord[]): string[];
900
+ export declare function customDomainMoveOffGuideUrl(options?: DomainFormatOptions): string;
901
+ export declare function domainRemovedConsequenceLines(hostnames: string[], options?: DomainFormatOptions): string[];
900
902
  export declare function domainRemovalPendingLines(hostname: string, app: string, options: DomainFormatOptions): string[];
901
903
  export declare const FINISHED_REMOVAL_PAGES_PER_COMMAND = 20;
902
904
  export declare function getFinishedDomainRemovals(options: ApiOptions & {
package/dist/index.js CHANGED
@@ -73,13 +73,24 @@ export function deletedDomainLines(deleted, options = {}) {
73
73
  for (const hostname of releasing) {
74
74
  lines.push(`${hostname} has stopped serving, but removing it upstream is still finishing; it finishes on its own. ${releaseNoCleanupLine(options)}`);
75
75
  }
76
+ // The consequence is said for the domains whose removal finished; one still releasing is
77
+ // told to leave its records alone, which repointing them would contradict.
78
+ const finished = [];
79
+ const names = Array.isArray(deleted.customDomainNames) ? deleted.customDomainNames : [];
80
+ for (const hostname of [...names, ...cleanups.map((cleanup) => cleanup?.hostname)]) {
81
+ if (typeof hostname !== "string" || hostname === "" || releasing.includes(hostname) || finished.includes(hostname))
82
+ continue;
83
+ finished.push(hostname);
84
+ }
85
+ if (finished.length > 0)
86
+ lines.push(...domainRemovedConsequenceLines(finished, options));
76
87
  }
77
88
  catch {
78
89
  return lines;
79
90
  }
80
91
  return lines;
81
92
  }
82
- export const CLI_VERSION = "0.85.0";
93
+ export const CLI_VERSION = "0.87.0";
83
94
  const PUBLIC_DASHBOARD_URL = "https://9pm.ai/dashboard";
84
95
  const TOKEN_SERVICE = "9pm";
85
96
  const TOKEN_ACCOUNT = "default";
@@ -3875,6 +3886,25 @@ export function setupCleanupLines(hostname, records) {
3875
3886
  "If one of these names now holds a different value, that record is not ours: leave it. Keep the record that points the domain at 9pm.",
3876
3887
  ];
3877
3888
  }
3889
+ // The public guide's section on moving a domain off 9pm, on the site beside the control plane
3890
+ // this command talked to, so a staging run links staging's copy of the guide.
3891
+ export function customDomainMoveOffGuideUrl(options = {}) {
3892
+ const dashboard = options.target ? dashboardUrlForApiUrl(options.target.apiUrl) : PUBLIC_DASHBOARD_URL;
3893
+ return `${dashboard.replace(/\/dashboard$/, "")}/docs/custom-domains#move-off`;
3894
+ }
3895
+ // Said once a removal has finished (#2089): 9pm no longer holds the hostname, so while DNS
3896
+ // still points here a visitor reaches nothing that can answer for it. Which error they see is
3897
+ // not ours to promise. It names no record: what may be deleted is only the cleanup list above
3898
+ // it, which carries its own value condition, so this adds no deletion advice of its own. The
3899
+ // order is the one the staging rehearsal demonstrated for a move to another host (#2182,
3900
+ // scenario 2). A move to the owner's own Cloudflare zone was not rehearsed, so it gets no order.
3901
+ export function domainRemovedConsequenceLines(hostnames, options = {}) {
3902
+ const subject = hostnames.length === 1 ? hostnames[0] : hostnames.join(", ");
3903
+ return [
3904
+ `After removal, visitors may see a TLS or routing error while DNS for ${subject} still points at 9pm.`,
3905
+ `Moving a domain to another host? Point its DNS at the new host, wait for the DNS TTL to pass, then remove the domain from 9pm. While caches update, some visitors may still reach 9pm, or briefly see an error from the new host until it is set up for the name. Once DNS points away, 9pm may show the domain as failed; that is expected, so remove it. Guide: ${customDomainMoveOffGuideUrl(options)}`,
3906
+ ];
3907
+ }
3878
3908
  // The command that lists what a removal finished in the background frees (#2068), aimed at the
3879
3909
  // same account and control plane as the command that printed it. Never a token.
3880
3910
  function domainsRemovedCommand(options, hostname) {
@@ -4466,6 +4496,8 @@ async function runDomainsCommand(command) {
4466
4496
  for (const line of domainCleanupLines(removal.cleanup))
4467
4497
  console.log(line);
4468
4498
  }
4499
+ for (const line of domainRemovedConsequenceLines([removal.hostname], format))
4500
+ console.log(line);
4469
4501
  }
4470
4502
  async function runDomainMove(action, base, format) {
4471
4503
  const hostname = normalizeDomainArg(action.hostname);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ninepm",
3
- "version": "0.85.0",
3
+ "version": "0.87.0",
4
4
  "description": "CLI for deploying built apps to 9pm.ai.",
5
5
  "homepage": "https://9pm.ai",
6
6
  "keywords": [