sproutboat 0.52.1 → 0.53.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/src/tail.ts CHANGED
@@ -22,6 +22,12 @@ export type TailArgs = {
22
22
  samplingRate: string | undefined;
23
23
  search: string | undefined;
24
24
  outcome: string[];
25
+ /** #268: follow a standalone binary's SB_LOG=json records in this file ("-" for stdin) instead of the control plane. */
26
+ file: string | undefined;
27
+ /** With --file: how many of the last matching records to print before following. */
28
+ lines: number;
29
+ /** With --file: false prints the snapshot and exits. */
30
+ follow: boolean;
25
31
  };
26
32
 
27
33
  export type TailLog = { level: string; message: JsonValue[]; timestamp: number };
@@ -47,9 +53,19 @@ export type TailFrame =
47
53
  | { type: "closed"; reason: string };
48
54
 
49
55
  export const TAIL_USAGE =
50
- "tail [project-dir] [--format pretty|json] [--status ok|error|<code>|5xx] [--method GET,POST] [--sampling-rate 0.1] [--search <text>] [--outcome <name>] | tail --recent | tail --sprout";
56
+ "tail [project-dir] [--format pretty|json] [--status ok|error|<code>|5xx] [--method GET,POST] [--sampling-rate 0.1] [--search <text>] [--outcome <name>] | tail --recent | tail --sprout | tail --file <log|-> [--lines <n>] [--no-follow] [--format pretty|json] [--status <s>] [--method <m>] [--search <text>]";
51
57
 
52
- const VALUE_FLAGS = ["--format", "--status", "--method", "--sampling-rate", "--search", "--outcome"] as const;
58
+ const VALUE_FLAGS = [
59
+ "--format",
60
+ "--status",
61
+ "--method",
62
+ "--sampling-rate",
63
+ "--search",
64
+ "--outcome",
65
+ "--file",
66
+ "--lines",
67
+ ] as const;
68
+ const MAX_LINES = 100_000;
53
69
 
54
70
  /** Parse `tail` arguments; `error` is a usage error message. */
55
71
  export function parseTailArgs(args: string[]): { args: TailArgs } | { error: string } {
@@ -63,7 +79,11 @@ export function parseTailArgs(args: string[]): { args: TailArgs } | { error: str
63
79
  samplingRate: undefined,
64
80
  search: undefined,
65
81
  outcome: [],
82
+ file: undefined,
83
+ lines: 10,
84
+ follow: true,
66
85
  };
86
+ let linesGiven = false;
67
87
  const split = (value: string) =>
68
88
  value
69
89
  .split(",")
@@ -73,6 +93,7 @@ export function parseTailArgs(args: string[]): { args: TailArgs } | { error: str
73
93
  const arg = args[index];
74
94
  if (arg === "--recent") parsed.recent = true;
75
95
  else if (arg === "--sprout") parsed.sprout = true;
96
+ else if (arg === "--no-follow") parsed.follow = false;
76
97
  else if (arg.startsWith("--")) {
77
98
  const eq = arg.indexOf("=");
78
99
  const flag = eq < 0 ? arg : arg.slice(0, eq);
@@ -87,12 +108,28 @@ export function parseTailArgs(args: string[]): { args: TailArgs } | { error: str
87
108
  else if (flag === "--method") parsed.method.push(...split(value).map((method) => method.toUpperCase()));
88
109
  else if (flag === "--outcome") parsed.outcome.push(...split(value));
89
110
  else if (flag === "--sampling-rate") parsed.samplingRate = value;
90
- else parsed.search = value;
111
+ else if (flag === "--file") parsed.file = value;
112
+ else if (flag === "--lines") {
113
+ const lines = Number(value);
114
+ if (value.trim() === "" || !Number.isInteger(lines) || lines < 0 || lines > MAX_LINES)
115
+ return { error: `tail: --lines must be a whole number from 0 to ${MAX_LINES} (got "${value}")` };
116
+ parsed.lines = lines;
117
+ linesGiven = true;
118
+ } else parsed.search = value;
91
119
  } else if (parsed.dir === undefined) parsed.dir = arg;
92
120
  else return { error: `tail: unexpected argument "${arg}"` };
93
121
  }
94
122
  const filtered =
95
123
  parsed.status.length || parsed.method.length || parsed.outcome.length || parsed.samplingRate || parsed.search;
124
+ if (parsed.file === undefined) {
125
+ if (linesGiven) return { error: "tail: --lines needs --file" };
126
+ if (!parsed.follow) return { error: "tail: --no-follow needs --file" };
127
+ } else {
128
+ if (parsed.recent || parsed.sprout) return { error: "tail: --file is an alternative to --recent and --sprout" };
129
+ if (parsed.dir !== undefined) return { error: "tail: --file reads a log, not a project" };
130
+ if (parsed.samplingRate !== undefined || parsed.outcome.length)
131
+ return { error: "tail: --sampling-rate and --outcome are not available with --file" };
132
+ }
96
133
  if (parsed.recent && parsed.sprout) return { error: "tail: --recent and --sprout are alternatives" };
97
134
  if ((parsed.recent || parsed.sprout) && filtered)
98
135
  return { error: "tail: filters apply to the live stream, not --recent or --sprout" };