ninepm 0.81.0 → 0.85.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
@@ -163,6 +163,8 @@ Delete an app, its managed database, managed container app, and retained image:
163
163
 
164
164
  The app's address goes with it: creating an app again under the same slug gives it a new address, so printed links and QR codes stop working. `9pm delete` prints this warning before it deletes anything.
165
165
 
166
+ For a static or Worker app with no custom domain, `9pm delete` also prints `Its address stops serving the app within about a minute.` It prints nothing about timing for a container app or an app with a custom domain: how long those keep serving after a delete has not been measured, so check the address yourself.
167
+
166
168
  ## Access Modes
167
169
 
168
170
  Apps are public by default.
@@ -267,9 +269,34 @@ another service's certificate check uses `_acme-challenge`) is not ours, so leav
267
269
  that sends no such list it prints no deletion advice at all. When the removal is still finishing
268
270
  upstream (the API answers `202` with `releasing: true`), it says so, names no record to delete, and
269
271
  prints the `9pm domains list` command: once the domain is no longer listed, it has stopped serving
270
- from 9pm. A removal that finishes in the background returns no record list, so the CLI cannot say
271
- which DNS records to delete for it. Such a domain shows as `removing`
272
- in `list` and `status` (`"removing": true` in `--json`), and `wait` on it exits `20`. Since 0.80.0.
272
+ from 9pm. Such a domain shows as `removing` in `list` and `status` (`"removing": true` in `--json`),
273
+ and `wait` on it exits `20`. Since 0.80.0.
274
+
275
+ Since 0.84.0, `9pm domains removed [--owner <organisation>]` lists the DNS records you may delete
276
+ after a removal that finished in the background: a `remove` that answered `releasing`, or a domain of
277
+ a deleted app, finished later by the platform. It is the same list `remove` prints when it finishes
278
+ itself, under the same rule (delete a record only while it still has the value shown), and the
279
+ printed hint after a still-finishing removal or `9pm delete` points at it, with `--owner` and
280
+ `--api-url` kept. A removal is listed for 30 days after it finishes, only to the account that owned
281
+ the domain, and not at all once the hostname has been added again anywhere: the server checks the
282
+ certificate provider for it when you run the command, and a domain it couldn't check is named without
283
+ its records, with the command to ask about it again. The server checks ten domains per page; the
284
+ command reads up to 20 pages and says so if more remain. `--hostname <hostname>` asks about one
285
+ domain only. `--json` prints the API's answer with its pages merged (`next` is null once every page
286
+ was read). Against a server without the route it says it can't list any, prints no record and exits `1`.
287
+
288
+ Since 0.83.0, `9pm domains cleanup <app> <hostname>` lists the setup TXT records 9pm issued for a
289
+ live domain and no longer needs, such as an earlier `_acme-challenge` value or the
290
+ `_cf-custom-hostname` ownership record (`--json` prints the API's answer). Each may be deleted only
291
+ while it still has the value shown; a different value at that name is not ours. The list is checked
292
+ against the certificate provider when you run the command: a value the provider still asks for is
293
+ never in it, a domain that is not live lists nothing, and if the provider cannot be read, or the
294
+ domain changes while it is being checked, the command fails and lists nothing; run it again. Only values the API issued are candidates, never anything read from public
295
+ DNS. `9pm domains status` on a live domain prints the command. `9pm domains remove` lists these values
296
+ with the rest of the domain's records, since once the domain is removed none of them is needed.
297
+ After `9pm domains restart`, the values it says no longer count are worded the same way. Against a
298
+ server without the route, `cleanup` says it can't list any and exits `1`. Values replaced before the
299
+ server kept them cannot be listed.
273
300
 
274
301
  `9pm domains move <hostname> --to <app>` moves a domain to another app of the same account. It keeps
275
302
  its certificate and the DNS records already published, so nothing is validated again. Without a
@@ -317,6 +344,7 @@ and none can be listed for it later.
317
344
  9pm domains restart <app> <hostname> # only after a certificate failed; record values may change
318
345
  9pm domains remove <app> <hostname> --confirm <hostname> [--json]
319
346
  9pm domains move [<app>] <hostname> --to <app> [--json]
347
+ 9pm domains removed [--hostname <hostname>] [--owner <organisation>] [--json] # records to delete after a removal that finished in the background
320
348
  9pm access show <site>
321
349
  9pm access public <site> [--switch-mode]
322
350
  9pm access otp <site> [--switch-mode]
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { Agent } from "undici";
3
+ import { type ProjectQrAddress } from "./qr-code.js";
3
4
  import { type DomainNext, type DomainReason, type DomainRecordStatus, type DomainState, type DomainStateDnsCheck, type DomainStateDnsInstruction, type DomainStateInput } from "./domain-state.js";
4
5
  import type { AccessGrant, AccessGrantType, AccessProviderKey, AppAccessSettings, AppAccessServiceToken, AppAccessToken, AppDatabase, AppRuntime, AppUserFilesUsage, ContainerImageUploadInstruction, ContainerRegion, Deployment, ManagedFileObject, ManagedFileShare, ManagedFileUsage, Project } from "./cli-types.js";
5
6
  export interface DeployOptions {
@@ -112,6 +113,8 @@ export interface DeletedSiteResult {
112
113
  customDomainsReleasing?: unknown;
113
114
  containerApplications?: number;
114
115
  containerImages?: number;
116
+ flyTeardownsQueued?: number;
117
+ workerScripts?: unknown;
115
118
  accessApps: number;
116
119
  serviceTokens: number;
117
120
  databases: number;
@@ -120,7 +123,7 @@ export interface DeletedSiteResult {
120
123
  metadata: boolean;
121
124
  errors?: string[];
122
125
  }
123
- export declare function deletedDomainLines(deleted: Pick<DeletedSiteResult, "customDomainCleanup" | "customDomainsReleasing">): string[];
126
+ export declare function deletedDomainLines(deleted: Pick<DeletedSiteResult, "customDomainCleanup" | "customDomainsReleasing">, options?: DomainFormatOptions): string[];
124
127
  export interface CreatedRuntimeToken {
125
128
  token: string;
126
129
  appAccessToken: AppAccessToken;
@@ -130,7 +133,7 @@ export interface CreatedServiceToken {
130
133
  clientSecret: string;
131
134
  }
132
135
  export type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
133
- export declare const CLI_VERSION = "0.81.0";
136
+ export declare const CLI_VERSION = "0.85.0";
134
137
  type CredentialBackend = "macos-keychain" | "windows-dpapi" | "linux-secret-service" | "file-plaintext" | "none";
135
138
  export interface StoredCredentialEntry {
136
139
  token: string;
@@ -355,6 +358,8 @@ export declare function listSites(options: ApiOptions & {
355
358
  owner?: string;
356
359
  }): Promise<ListSitesResult>;
357
360
  export declare function deleteWarningLines(slug: string): string[];
361
+ export declare const DELETE_SERVING_BOUND_LINE = "Its address stops serving the app within about a minute.";
362
+ export declare function deletedAppStopsServingQuickly(deleted: Pick<DeletedSiteResult, "workerScripts" | "containerApplications" | "flyTeardownsQueued" | "customDomains" | "customDomainsReleasing" | "errors">): boolean;
358
363
  export declare function deleteSite(options: DeleteSiteOptions): Promise<DeletedSiteResult>;
359
364
  export interface DeviceAuthFeedbackOptions {
360
365
  progressStream?: NodeJS.WriteStream;
@@ -528,6 +533,7 @@ type CliCommand = {
528
533
  confirmSlug: string;
529
534
  apiUrl: string;
530
535
  token: string;
536
+ apiUrlFrom?: ApiUrlSource;
531
537
  } | {
532
538
  kind: "apps";
533
539
  apiUrl: string;
@@ -624,6 +630,17 @@ type CliCommand = {
624
630
  };
625
631
  apiUrl: string;
626
632
  token: string;
633
+ } | {
634
+ kind: "qr";
635
+ site: string;
636
+ owner?: string;
637
+ address?: string;
638
+ format: "svg" | "png";
639
+ out?: string;
640
+ terminal: boolean;
641
+ json: boolean;
642
+ apiUrl: string;
643
+ token: string;
627
644
  } | {
628
645
  kind: "domains";
629
646
  owner?: string;
@@ -648,6 +665,11 @@ type CliCommand = {
648
665
  kind: "restart";
649
666
  site: string;
650
667
  hostname: string;
668
+ } | {
669
+ kind: "cleanup";
670
+ site: string;
671
+ hostname: string;
672
+ json: boolean;
651
673
  } | {
652
674
  kind: "remove";
653
675
  site: string;
@@ -659,6 +681,10 @@ type CliCommand = {
659
681
  hostname: string;
660
682
  to: string;
661
683
  json: boolean;
684
+ } | {
685
+ kind: "removed";
686
+ json: boolean;
687
+ hostname?: string;
662
688
  };
663
689
  apiUrl: string;
664
690
  token: string;
@@ -866,7 +892,24 @@ export interface DomainCleanup {
866
892
  }
867
893
  export declare function parseDomainCleanup(value: unknown): DomainCleanup | null;
868
894
  export declare function domainCleanupLines(cleanup: DomainCleanup): string[];
895
+ export declare function getDomainSetupCleanup(options: DomainCommandOptions & {
896
+ hostname: string;
897
+ }): Promise<unknown>;
898
+ export declare function domainSetupCleanupLines(body: unknown, hostname: string): string[];
899
+ export declare function setupCleanupLines(hostname: string, records: DomainCleanupRecord[]): string[];
869
900
  export declare function domainRemovalPendingLines(hostname: string, app: string, options: DomainFormatOptions): string[];
901
+ export declare const FINISHED_REMOVAL_PAGES_PER_COMMAND = 20;
902
+ export declare function getFinishedDomainRemovals(options: ApiOptions & {
903
+ owner?: string;
904
+ hostname?: string;
905
+ }): Promise<unknown>;
906
+ export interface FinishedDomainRemoval {
907
+ hostname: string;
908
+ finishedAt: string | null;
909
+ cleanup: DomainCleanup;
910
+ }
911
+ export declare function parseFinishedDomainRemovals(body: unknown): FinishedDomainRemoval[] | null;
912
+ export declare function finishedDomainRemovalLines(body: unknown, options?: DomainFormatOptions): string[];
870
913
  export interface DomainMove {
871
914
  body: unknown;
872
915
  hostname: string;
@@ -909,6 +952,23 @@ export declare function waitForDomain(options: DomainCommandOptions & {
909
952
  onRetry?: (error: unknown) => void;
910
953
  }): Promise<DomainWaitResult>;
911
954
  export declare function healthWithReason(status: string | null | undefined, reason: string | null | undefined): string;
955
+ export declare const QR_PNG_PIXELS_PER_MODULE = 16;
956
+ /**
957
+ * The minimum width in centimetres, rounded UP to the millimetre: it is a floor, so rounding
958
+ * to nearest would print a 26.5 mm minimum as "2.6 cm", below what the code needs.
959
+ */
960
+ export declare function qrMinimumWidthCmText(minimumWidthMm: number): string;
961
+ export declare const QR_ACCESS_NOTE = "This app is private. Scanning the code doesn't grant access: people still have to sign in and be allowed in.";
962
+ export declare function qrCustomDomainNote(hostname: string): string;
963
+ /**
964
+ * The returned address `--address` names, or an error listing the valid ones. The value is
965
+ * normalized first - a bare hostname gets `https://`, case is ignored, a trailing `/` is
966
+ * allowed - and must then match an address the server offered: no other host, path, query,
967
+ * fragment, credentials, port or scheme. The CLI never encodes a URL the server did not offer.
968
+ */
969
+ export declare function pickQrAddress(addresses: readonly ProjectQrAddress[], requested: string | undefined): ProjectQrAddress;
970
+ /** An 8-bit greyscale PNG of a module matrix (quiet zone included), black on white. */
971
+ export declare function encodeQrPng(modules: readonly (readonly boolean[])[], scale?: number): Buffer;
912
972
  export type ApiUrlSource = "flag" | "env" | "default";
913
973
  export declare function workerPublicFiles(paths: readonly string[], serverEntry: string, selection: string | null): Array<{
914
974
  path: string;