@driggsby/deploy 0.2.4 → 0.3.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
@@ -25,7 +25,10 @@ npx @driggsby/deploy
25
25
  ```
26
26
 
27
27
  This reads `driggsby.json` in the current directory, deploys the `serve`
28
- folder, and makes it live. A first deploy creates the app and saves its
28
+ folder, and makes it live. It prints two addresses: the app's page in
29
+ Driggsby first, where it runs with the person's data, then the app's own
30
+ `driggsby.dev` address, which shows no Driggsby data when opened
31
+ directly. A first deploy creates the app and saves its
29
32
  assigned address back into `driggsby.json` — keep (commit) that updated
30
33
  file, because it is the only record of the app's address; a checkout that
31
34
  discards it makes the next deploy create a second app at a new address.
@@ -1,3 +1,4 @@
1
1
  export declare const PUBLIC_BASE_URL = "https://app.driggsby.com";
2
2
  export declare function isLoopbackBaseUrl(baseUrl: string): boolean;
3
3
  export declare function resolveBaseUrl(env: NodeJS.ProcessEnv): string;
4
+ export declare function consoleUrlOnOrigin(consoleUrl: string | null, baseUrl: string): string | null;
package/dist/base-url.js CHANGED
@@ -44,3 +44,26 @@ export function resolveBaseUrl(env) {
44
44
  }
45
45
  return trimmed;
46
46
  }
47
+ // The Driggsby page address the server hands back after a deploy is printed
48
+ // first and unquoted, as this tool's own word on where to go, so it is
49
+ // accepted only on the origin this run is signed in to: an answer naming any
50
+ // other host (a lookalike included) yields null, and the caller prints the
51
+ // app's own address alone. Anything that does not parse as a URL, or that
52
+ // carries userinfo (which reads as a different host), is no address either.
53
+ // What comes back is the parsed href, so control bytes in a path arrive
54
+ // percent-encoded rather than raw.
55
+ export function consoleUrlOnOrigin(consoleUrl, baseUrl) {
56
+ if (consoleUrl === null) {
57
+ return null;
58
+ }
59
+ try {
60
+ const parsed = new URL(consoleUrl);
61
+ if (parsed.username !== "" || parsed.password !== "") {
62
+ return null;
63
+ }
64
+ return parsed.origin === new URL(baseUrl).origin ? parsed.href : null;
65
+ }
66
+ catch {
67
+ return null;
68
+ }
69
+ }
package/dist/client.d.ts CHANGED
@@ -20,6 +20,7 @@ export interface FinalizedVersion {
20
20
  versionNumber: number;
21
21
  live: boolean;
22
22
  url: string | null;
23
+ consoleUrl: string | null;
23
24
  }
24
25
  export interface BlobsMissing {
25
26
  kind: "blobs-missing";
package/dist/client.js CHANGED
@@ -265,9 +265,16 @@ function parseFinalized(parsed) {
265
265
  appSlug: stringField(parsed, "app_slug"),
266
266
  versionNumber: integerField(parsed, "version_number"),
267
267
  live: parsed.live === true,
268
- url: typeof parsed.url === "string" ? parsed.url : null,
268
+ url: optionalUrlField(parsed, "url"),
269
+ consoleUrl: optionalUrlField(parsed, "console_url"),
269
270
  };
270
271
  }
272
+ // A blank address is no address: it would otherwise take the printed slot
273
+ // meant for a real one.
274
+ function optionalUrlField(record, field) {
275
+ const value = record[field];
276
+ return typeof value === "string" && value.trim() !== "" ? value : null;
277
+ }
271
278
  function stringField(record, field) {
272
279
  const value = record[field];
273
280
  if (typeof value !== "string") {
package/dist/deploy.d.ts CHANGED
@@ -11,6 +11,7 @@ export interface DeployOutcome {
11
11
  versionNumber: number;
12
12
  live: boolean;
13
13
  url: string | null;
14
+ consoleUrl: string | null;
14
15
  fileCount: number;
15
16
  uploadedBlobCount: number;
16
17
  uploadedBytes: number;
package/dist/deploy.js CHANGED
@@ -85,6 +85,7 @@ export async function deployCollectedFiles(api, slug, collected, options) {
85
85
  versionNumber: outcome.versionNumber,
86
86
  live: outcome.live,
87
87
  url: outcome.url,
88
+ consoleUrl: outcome.consoleUrl,
88
89
  fileCount: collected.files.length,
89
90
  uploadedBlobCount: uploadedShas.size,
90
91
  uploadedBytes,
@@ -8,6 +8,7 @@ import { resolveBaseUrl } from "./base-url.js";
8
8
  import { deployProjectFiles } from "./deploy.js";
9
9
  import { DeployApiError, DeployError } from "./errors.js";
10
10
  import { collectDeployFiles, formatBytes } from "./manifest.js";
11
+ import { hasLiveAddress, liveAddresses, liveAddressLines } from "./live-addresses.js";
11
12
  import { readProjectConfig } from "./project-config.js";
12
13
  import { capForTerminal, quotedForTerminal, wrapProse } from "./terminal-text.js";
13
14
  // Server-supplied error text is bounded like every other string this bin did
@@ -74,8 +75,11 @@ export async function runDeployEntrypoint(io) {
74
75
  // sentence. The bounds never truncate legitimate values: a slug tops
75
76
  // out at 63 characters and the app URL at 84.
76
77
  io.out(`Deployed ${quotedForTerminal(outcome.appSlug, 80)} (v${outcome.versionNumber}, ${uploadedNote}).\n`);
77
- if (outcome.url !== null) {
78
- io.out(`Live at:\n\n ${capForTerminal(outcome.url, 200)}\n`);
78
+ // The Driggsby page for the app first, where it runs with the person's
79
+ // data, then the app's own address: the same lines the driggsby CLI prints.
80
+ const addresses = liveAddresses(outcome.url, outcome.consoleUrl, baseUrl);
81
+ if (hasLiveAddress(addresses)) {
82
+ io.out(`Live at:\n${liveAddressLines(addresses)}`);
79
83
  }
80
84
  return 0;
81
85
  }
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  export { DeployApiError, DeployError } from "./errors.ts";
2
- export { PUBLIC_BASE_URL, isLoopbackBaseUrl, resolveBaseUrl } from "./base-url.ts";
2
+ export { PUBLIC_BASE_URL, consoleUrlOnOrigin, isLoopbackBaseUrl, resolveBaseUrl } from "./base-url.ts";
3
3
  export { MAX_FILE_BYTES, MAX_FILE_COUNT, MAX_PATH_LENGTH, MAX_TOTAL_BYTES, slugProblem, } from "./limits.ts";
4
4
  export { type ProjectConfig, readProjectConfig, writeAssignedSlug } from "./project-config.ts";
5
5
  export { type CollectedDeploy, type DeployFile, collectDeployFiles, formatBytes, } from "./manifest.ts";
6
6
  export { type BlobsMissing, type CreatedApp, type CreatedVersion, type DeployApi, type FinalizedVersion, type ManifestFile, type VersionList, type VersionSummary, createApp, createVersion, finalizeVersion, listVersions, setLiveVersion, uploadBlob, } from "./client.ts";
7
7
  export { type DeployOptions, type DeployOutcome, type ProjectDeployOptions, type ProjectDeployResult, deployCollectedFiles, deployProjectFiles, } from "./deploy.ts";
8
+ export { type LiveAddresses, MAX_LIVE_ADDRESS_CHARS, hasLiveAddress, liveAddresses, liveAddressLines, } from "./live-addresses.ts";
8
9
  export { capForTerminal, quotedForTerminal, sanitizeForTerminal, wrapProse, } from "./terminal-text.ts";
package/dist/index.js CHANGED
@@ -5,12 +5,15 @@ export { DeployApiError, DeployError } from "./errors.js";
5
5
  // The base-URL policy: which origin the token travels to, and what
6
6
  // DRIGGSBY_BASE_URL may override it with. One implementation for both this
7
7
  // package's bin and the driggsby CLI.
8
- export { PUBLIC_BASE_URL, isLoopbackBaseUrl, resolveBaseUrl } from "./base-url.js";
8
+ export { PUBLIC_BASE_URL, consoleUrlOnOrigin, isLoopbackBaseUrl, resolveBaseUrl } from "./base-url.js";
9
9
  export { MAX_FILE_BYTES, MAX_FILE_COUNT, MAX_PATH_LENGTH, MAX_TOTAL_BYTES, slugProblem, } from "./limits.js";
10
10
  export { readProjectConfig, writeAssignedSlug } from "./project-config.js";
11
11
  export { collectDeployFiles, formatBytes, } from "./manifest.js";
12
12
  export { createApp, createVersion, finalizeVersion, listVersions, setLiveVersion, uploadBlob, } from "./client.js";
13
13
  export { deployCollectedFiles, deployProjectFiles, } from "./deploy.js";
14
+ // The addresses a deploy or rollback prints, shared by this package's bin
15
+ // and the driggsby CLI so both say the same thing.
16
+ export { MAX_LIVE_ADDRESS_CHARS, hasLiveAddress, liveAddresses, liveAddressLines, } from "./live-addresses.js";
14
17
  // Terminal-safety helpers for anything that echoes untrusted text (file
15
18
  // names, server strings) into CLI output. See terminal-text.ts for what the
16
19
  // sanitizer strips and the deliberate boundary of that defense.
@@ -0,0 +1,8 @@
1
+ export declare const MAX_LIVE_ADDRESS_CHARS = 200;
2
+ export interface LiveAddresses {
3
+ url: string | null;
4
+ consoleUrl: string | null;
5
+ }
6
+ export declare function liveAddresses(url: string | null, consoleUrl: string | null, baseUrl: string): LiveAddresses;
7
+ export declare function hasLiveAddress(addresses: LiveAddresses): boolean;
8
+ export declare function liveAddressLines(addresses: LiveAddresses): string;
@@ -0,0 +1,45 @@
1
+ // The addresses printed under a "Live ... at:" line, by the driggsby CLI
2
+ // and by this package's own bin alike. The Driggsby page for the app comes
3
+ // first: that is where the person sees it running with their own data. The
4
+ // app's own origin follows, named for what it is, because the app gets no
5
+ // Driggsby data when opened there. Both are server-supplied strings,
6
+ // sanitized and length-bounded before printing. A server that predates
7
+ // the Driggsby page hands back only the app's own address, which then
8
+ // prints alone, as it always did.
9
+ import { consoleUrlOnOrigin } from "./base-url.js";
10
+ import { capForTerminal, wrapProse } from "./terminal-text.js";
11
+ // Longer than any address Driggsby issues (a slug tops out at 63
12
+ // characters), short enough that a hostile answer cannot flood the terminal.
13
+ export const MAX_LIVE_ADDRESS_CHARS = 200;
14
+ // The Driggsby page is accepted only on the origin this run is signed in
15
+ // to (see consoleUrlOnOrigin). Both values are sanitized here, so what is
16
+ // decided on is exactly what prints; a value that sanitizes to nothing is
17
+ // no address.
18
+ export function liveAddresses(url, consoleUrl, baseUrl) {
19
+ return { url: printable(url), consoleUrl: printable(consoleUrlOnOrigin(consoleUrl, baseUrl)) };
20
+ }
21
+ export function hasLiveAddress(addresses) {
22
+ return addresses.consoleUrl !== null || addresses.url !== null;
23
+ }
24
+ // Empty when there is nothing to print, so "at:" is promised only when an
25
+ // address follows.
26
+ export function liveAddressLines(addresses) {
27
+ const primary = addresses.consoleUrl ?? addresses.url;
28
+ if (primary === null) {
29
+ return "";
30
+ }
31
+ let text = `\n ${primary}\n`;
32
+ if (addresses.consoleUrl !== null && addresses.url !== null) {
33
+ text +=
34
+ `\n${wrapProse("The app's own address, which shows no Driggsby data when opened directly:")}\n\n` +
35
+ ` ${addresses.url}\n`;
36
+ }
37
+ return text;
38
+ }
39
+ function printable(value) {
40
+ if (value === null) {
41
+ return null;
42
+ }
43
+ const clean = capForTerminal(value, MAX_LIVE_ADDRESS_CHARS).trim();
44
+ return clean === "" ? null : clean;
45
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@driggsby/deploy",
3
- "version": "0.2.4",
3
+ "version": "0.3.0",
4
4
  "description": "Deploy static Driggsby apps: the deploy protocol client used by the driggsby CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {