@devdogsuga/backstage 0.1.4 → 0.1.6
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 +90 -23
- package/dist/{catalog-BuNLUu08.js → catalog-Dke7bQVz.js} +131 -8
- package/dist/{cli-BI5Vn9sR.js → cli-BhZ4je2D.js} +9 -7
- package/dist/cli-Do2nWD7W.js +136 -0
- package/dist/{client-B7HhuTaA.js → client-B_MY9kVI.js} +2 -1
- package/dist/client-DwDGjalU.js +2 -0
- package/dist/commands-5Ou8xJw5.js +225 -0
- package/dist/commands-BQ2NIqub.js +436 -0
- package/dist/commands-CLSxKz-l.js +352 -0
- package/dist/commands-DzALVX5g.js +12 -0
- package/dist/{commands-Bk7IbhPD.js → commands-Sx1fDNNn.js} +2 -1
- package/dist/{commands-Dl3UlhBf.js → commands-zPSnOGzH.js} +86 -46
- package/dist/document-DumoZXoO.js +287 -0
- package/dist/dry-run-3IxPmCtW.js +58 -0
- package/dist/launch.js +3 -3
- package/dist/meetings-DfU867eV.js +118 -0
- package/dist/{options-BTjOf5KP.js → options-DCaqm8BI.js} +1 -57
- package/dist/queries-DeEwfe2c.js +170 -0
- package/dist/read-B-aJJewb.js +141 -0
- package/dist/{vault-C8LYJFOQ.js → vault-CsBC-1Mq.js} +1 -287
- package/package.json +7 -7
- package/dist/client-DzXQHd58.js +0 -2
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
//#region ../cli-core/src/env/document.ts
|
|
2
|
+
/**
|
|
3
|
+
* An editable `.env` that survives being edited.
|
|
4
|
+
*
|
|
5
|
+
* The root `.env` is not a data file. It is 150 lines of hard-won commentary
|
|
6
|
+
* about which value breaks the Supabase CLI when empty, which one must stay
|
|
7
|
+
* commented out, and why. A writer that parses to a Map and serializes back
|
|
8
|
+
* destroys all of it on the first save.
|
|
9
|
+
*
|
|
10
|
+
* So this keeps the file as **lines** and edits in place. An untouched line is
|
|
11
|
+
* returned byte-for-byte, including its spacing, quoting style and trailing
|
|
12
|
+
* comment. Only the lines actually being changed are rewritten.
|
|
13
|
+
*
|
|
14
|
+
* Nothing is ever deleted. Removing a key comments it out, so the value stays
|
|
15
|
+
* recoverable from the file itself, and re-adding it later uncomments that line
|
|
16
|
+
* rather than appending a duplicate.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* `KEY=value`, active. Groups: indent, `export ` prefix, key, value.
|
|
20
|
+
*
|
|
21
|
+
* The prefix is CAPTURED rather than skipped so an update can put it back.
|
|
22
|
+
* Dropping it turns `export FOO=` into `FOO=`, which still parses here and
|
|
23
|
+
* stops being exported to child processes. That shows up as a missing variable
|
|
24
|
+
* three tools downstream.
|
|
25
|
+
*/
|
|
26
|
+
const ACTIVE = /^(\s*)((?:export\s+)?)([A-Za-z_][A-Za-z0-9_]*)\s*=(.*)$/;
|
|
27
|
+
/** `# KEY=value`, the commented form this tool writes and reads back. */
|
|
28
|
+
const COMMENTED = /^(\s*)#\s?((?:export\s+)?)([A-Za-z_][A-Za-z0-9_]*)\s*=(.*)$/;
|
|
29
|
+
/**
|
|
30
|
+
* Reads the value half of an assignment.
|
|
31
|
+
*
|
|
32
|
+
* Deliberately NOT a general dotenv parser: it does not expand `$VAR`, because
|
|
33
|
+
* a stored value that means one thing in the file and another in the process
|
|
34
|
+
* cannot be rotated with confidence.
|
|
35
|
+
*/
|
|
36
|
+
function parseValue(rest) {
|
|
37
|
+
const text = rest.trim();
|
|
38
|
+
if (text.startsWith("\"")) {
|
|
39
|
+
const end = findClosing(text, "\"");
|
|
40
|
+
if (end === -1) return unescape(text.slice(1));
|
|
41
|
+
return unescape(text.slice(1, end));
|
|
42
|
+
}
|
|
43
|
+
if (text.startsWith("'")) {
|
|
44
|
+
const end = findClosing(text, "'");
|
|
45
|
+
if (end === -1) return text.slice(1);
|
|
46
|
+
return text.slice(1, end);
|
|
47
|
+
}
|
|
48
|
+
const hash = text.search(/\s#/);
|
|
49
|
+
return (hash === -1 ? text : text.slice(0, hash)).trim();
|
|
50
|
+
}
|
|
51
|
+
function findClosing(text, quote) {
|
|
52
|
+
for (let i = 1; i < text.length; i += 1) {
|
|
53
|
+
if (text[i] === "\\") {
|
|
54
|
+
i += 1;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (text[i] === quote) return i;
|
|
58
|
+
}
|
|
59
|
+
return -1;
|
|
60
|
+
}
|
|
61
|
+
function unescape(text) {
|
|
62
|
+
return text.replace(/\\([nrt\\"'])/g, (_, c) => c === "n" ? "\n" : c === "r" ? "\r" : c === "t" ? " " : c);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Splits everything after `=` into its value and its trailing comment.
|
|
66
|
+
*
|
|
67
|
+
* Quote-aware, because the naive "cut at the first `#`" truncates a generated
|
|
68
|
+
* password into something that still looks like one. A `#` inside quotes is
|
|
69
|
+
* part of the value; outside them it starts a comment only when whitespace
|
|
70
|
+
* precedes it.
|
|
71
|
+
*/
|
|
72
|
+
function splitComment(rest) {
|
|
73
|
+
const text = rest.trimStart();
|
|
74
|
+
let after;
|
|
75
|
+
if (text.startsWith("\"") || text.startsWith("'")) {
|
|
76
|
+
const end = findClosing(text, text[0]);
|
|
77
|
+
after = end === -1 ? text.length : end + 1;
|
|
78
|
+
} else {
|
|
79
|
+
const hash = text.search(/\s#/);
|
|
80
|
+
after = hash === -1 ? text.length : hash;
|
|
81
|
+
}
|
|
82
|
+
const tail = text.slice(after);
|
|
83
|
+
const hash = tail.indexOf("#");
|
|
84
|
+
return { comment: hash === -1 ? "" : tail.slice(hash).trimEnd() };
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Recognises a stamp this tool wrote, so it is replaced rather than duplicated.
|
|
88
|
+
*
|
|
89
|
+
* Deliberately narrow: it matches the exact bracketed shape below and nothing
|
|
90
|
+
* else, because the cost of a false positive is deleting somebody's own note.
|
|
91
|
+
*/
|
|
92
|
+
const STAMP = /\s*#\s*\[[a-z][a-z0-9-]* (?:pushed|pulled) \d{4}-\d{2}-\d{2}\]\s*$/;
|
|
93
|
+
function stampText(stamp) {
|
|
94
|
+
return `# [${stamp.environment} ${stamp.action} ${stamp.date}]`;
|
|
95
|
+
}
|
|
96
|
+
function isStamp(comment) {
|
|
97
|
+
return STAMP.test(comment);
|
|
98
|
+
}
|
|
99
|
+
/** Always double-quoted, so a multi-line value round-trips through one line. */
|
|
100
|
+
function quote(value) {
|
|
101
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t")}"`;
|
|
102
|
+
}
|
|
103
|
+
var EnvDocument = class EnvDocument {
|
|
104
|
+
lines;
|
|
105
|
+
/** Whether this session has already appended, so it separates only once. */
|
|
106
|
+
appended = false;
|
|
107
|
+
constructor(lines) {
|
|
108
|
+
this.lines = lines;
|
|
109
|
+
}
|
|
110
|
+
static parse(text) {
|
|
111
|
+
const lines = text.split("\n").map((raw) => {
|
|
112
|
+
const active = ACTIVE.exec(raw);
|
|
113
|
+
if (active) return {
|
|
114
|
+
raw,
|
|
115
|
+
key: active[3],
|
|
116
|
+
value: parseValue(active[4])
|
|
117
|
+
};
|
|
118
|
+
const commented = COMMENTED.exec(raw);
|
|
119
|
+
if (commented) return {
|
|
120
|
+
raw,
|
|
121
|
+
key: commented[3],
|
|
122
|
+
value: parseValue(commented[4]),
|
|
123
|
+
commented: true
|
|
124
|
+
};
|
|
125
|
+
return { raw };
|
|
126
|
+
});
|
|
127
|
+
return new EnvDocument(lines);
|
|
128
|
+
}
|
|
129
|
+
static empty() {
|
|
130
|
+
return new EnvDocument([]);
|
|
131
|
+
}
|
|
132
|
+
find(key, commented) {
|
|
133
|
+
return this.lines.findIndex((l) => l.key === key && Boolean(l.commented) === commented);
|
|
134
|
+
}
|
|
135
|
+
/** The active value, or undefined when absent or commented out. */
|
|
136
|
+
get(key) {
|
|
137
|
+
const i = this.find(key, false);
|
|
138
|
+
return i === -1 ? void 0 : this.lines[i].value;
|
|
139
|
+
}
|
|
140
|
+
/** Present and active. */
|
|
141
|
+
has(key) {
|
|
142
|
+
return this.find(key, false) !== -1;
|
|
143
|
+
}
|
|
144
|
+
/** Present, but commented out. */
|
|
145
|
+
isCommented(key) {
|
|
146
|
+
return this.find(key, false) === -1 && this.find(key, true) !== -1;
|
|
147
|
+
}
|
|
148
|
+
/** Every active assignment, in file order. */
|
|
149
|
+
entries() {
|
|
150
|
+
return this.lines.filter((l) => l.key !== void 0 && !l.commented).map((l) => [l.key, l.value]);
|
|
151
|
+
}
|
|
152
|
+
keys() {
|
|
153
|
+
return this.entries().map(([k]) => k);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Sets a value, preferring to revive a commented line over appending.
|
|
157
|
+
*
|
|
158
|
+
* Appending when a commented form already exists is how a file ends up with
|
|
159
|
+
* the same key twice: one stale, both plausible, and which one looks
|
|
160
|
+
* authoritative depends on where the reader scrolled to.
|
|
161
|
+
*/
|
|
162
|
+
set(key, value, stamp) {
|
|
163
|
+
const active = this.find(key, false);
|
|
164
|
+
if (active !== -1) {
|
|
165
|
+
const line = this.lines[active];
|
|
166
|
+
const match = ACTIVE.exec(line.raw);
|
|
167
|
+
const existing = splitComment(match[4]).comment;
|
|
168
|
+
if (stamp && existing !== "" && !isStamp(existing)) this.lines.splice(this.first(key), 0, { raw: existing });
|
|
169
|
+
const trailing = stamp ? ` ${stampText(stamp)}` : existing === "" ? "" : ` ${existing}`;
|
|
170
|
+
const at = this.find(key, false);
|
|
171
|
+
const target = this.lines[at];
|
|
172
|
+
target.raw = `${match[1]}${match[2]}${key}=${quote(value)}${trailing}`;
|
|
173
|
+
target.value = value;
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const commented = this.find(key, true);
|
|
177
|
+
if (commented !== -1) {
|
|
178
|
+
const line = this.lines[commented];
|
|
179
|
+
line.raw = `${key}=${quote(value)}${stamp ? ` ${stampText(stamp)}` : ""}`;
|
|
180
|
+
line.value = value;
|
|
181
|
+
line.commented = false;
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (!this.appended && this.lines.length > 0 && this.lines.at(-1).raw !== "") this.lines.push({ raw: "" });
|
|
185
|
+
this.appended = true;
|
|
186
|
+
this.lines.push({
|
|
187
|
+
raw: `${key}=${quote(value)}${stamp ? ` ${stampText(stamp)}` : ""}`,
|
|
188
|
+
key,
|
|
189
|
+
value
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
/** Index of the first line mentioning a key, active or commented. */
|
|
193
|
+
first(key) {
|
|
194
|
+
return this.lines.findIndex((l) => l.key === key);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Moves every line for a key next to that key's first appearance.
|
|
198
|
+
*
|
|
199
|
+
* Files drift: a key gets commented out near the bottom, re-added at the top
|
|
200
|
+
* six weeks later, and now two lines claim the same name a hundred lines
|
|
201
|
+
* apart. Whichever one a reader scrolls to first looks authoritative.
|
|
202
|
+
*
|
|
203
|
+
* The FIRST occurrence keeps its position, so the standalone comment block
|
|
204
|
+
* documenting a key stays attached to it. Everything else moves up to join
|
|
205
|
+
* it, in the order it already had.
|
|
206
|
+
*/
|
|
207
|
+
group() {
|
|
208
|
+
const out = [];
|
|
209
|
+
const taken = /* @__PURE__ */ new Set();
|
|
210
|
+
let moved = false;
|
|
211
|
+
for (let i = 0; i < this.lines.length; i += 1) {
|
|
212
|
+
if (taken.has(i)) continue;
|
|
213
|
+
const line = this.lines[i];
|
|
214
|
+
out.push(line);
|
|
215
|
+
taken.add(i);
|
|
216
|
+
if (line.key === void 0) continue;
|
|
217
|
+
for (let j = i + 1; j < this.lines.length; j += 1) {
|
|
218
|
+
if (taken.has(j) || this.lines[j].key !== line.key) continue;
|
|
219
|
+
if (j !== i + 1 || taken.has(i + 1)) moved = true;
|
|
220
|
+
out.push(this.lines[j]);
|
|
221
|
+
taken.add(j);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
this.lines = out;
|
|
225
|
+
return moved;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Blanks every active value without losing one.
|
|
229
|
+
*
|
|
230
|
+
* Each becomes a commented line holding what it was, plus an empty active
|
|
231
|
+
* line under it. The file still declares every key it needs, which is what
|
|
232
|
+
* makes it a usable checklist, while holding nothing.
|
|
233
|
+
*
|
|
234
|
+
* Already-empty keys are skipped. Commenting out `FOO=""` to write `FOO=""`
|
|
235
|
+
* underneath is churn that makes the next diff harder to read.
|
|
236
|
+
*/
|
|
237
|
+
reset() {
|
|
238
|
+
const out = [];
|
|
239
|
+
const cleared = [];
|
|
240
|
+
for (const line of this.lines) {
|
|
241
|
+
if (line.key === void 0 || line.commented || line.value === "") {
|
|
242
|
+
out.push(line);
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
const exported = ACTIVE.exec(line.raw)?.[2] ?? "";
|
|
246
|
+
line.raw = `# ${line.raw.trimStart()}`;
|
|
247
|
+
line.commented = true;
|
|
248
|
+
out.push(line);
|
|
249
|
+
out.push({
|
|
250
|
+
raw: `${exported}${line.key}=""`,
|
|
251
|
+
key: line.key,
|
|
252
|
+
value: ""
|
|
253
|
+
});
|
|
254
|
+
cleared.push(line.key);
|
|
255
|
+
}
|
|
256
|
+
this.lines = out;
|
|
257
|
+
return cleared;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Comments a key out rather than deleting it.
|
|
261
|
+
*
|
|
262
|
+
* A no-op when it is already commented or absent, so running a sync twice
|
|
263
|
+
* cannot produce `## KEY=`.
|
|
264
|
+
*/
|
|
265
|
+
comment(key) {
|
|
266
|
+
const i = this.find(key, false);
|
|
267
|
+
if (i === -1) return false;
|
|
268
|
+
const line = this.lines[i];
|
|
269
|
+
line.raw = `# ${line.raw.trimStart()}`;
|
|
270
|
+
line.commented = true;
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
/** Restores a commented assignment. Returns its value, or undefined. */
|
|
274
|
+
uncomment(key) {
|
|
275
|
+
const i = this.find(key, true);
|
|
276
|
+
if (i === -1) return void 0;
|
|
277
|
+
const line = this.lines[i];
|
|
278
|
+
line.raw = line.raw.replace(/^(\s*)#\s?/, "$1");
|
|
279
|
+
line.commented = false;
|
|
280
|
+
return line.value;
|
|
281
|
+
}
|
|
282
|
+
toString() {
|
|
283
|
+
return this.lines.map((l) => l.raw).join("\n");
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
//#endregion
|
|
287
|
+
export { EnvDocument as t };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { T as cliName } from "./telemetry-Bjoz29Hl.js";
|
|
2
|
+
//#region ../cli-core/src/dry-run.ts
|
|
3
|
+
const DRY_RUN_FLAG = "--dry-run";
|
|
4
|
+
/** Commands that forward everything after their name to another tool. */
|
|
5
|
+
const FORWARDING = /* @__PURE__ */ new Set([
|
|
6
|
+
"supabase",
|
|
7
|
+
"wrangler",
|
|
8
|
+
"drizzle-kit",
|
|
9
|
+
"psql",
|
|
10
|
+
"bw",
|
|
11
|
+
"run"
|
|
12
|
+
]);
|
|
13
|
+
let active = false;
|
|
14
|
+
function setDryRun(on) {
|
|
15
|
+
active = on;
|
|
16
|
+
}
|
|
17
|
+
function isDryRun() {
|
|
18
|
+
return active;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Decides whether `argv` asks for a dry run and returns the argv to dispatch.
|
|
22
|
+
*
|
|
23
|
+
* For a forwarding command a leading `--dry-run` is consumed (the tool never
|
|
24
|
+
* sees it) and a later one is the tool's. For any other command the argv is
|
|
25
|
+
* returned as it came, because several commands read `--dry-run` themselves.
|
|
26
|
+
*/
|
|
27
|
+
function resolveDryRun(argv) {
|
|
28
|
+
const firstPositional = argv.findIndex((arg) => !arg.startsWith("-"));
|
|
29
|
+
const lead = firstPositional === -1 ? argv.length : firstPositional;
|
|
30
|
+
const command = argv[firstPositional];
|
|
31
|
+
if (command !== void 0 && FORWARDING.has(command)) {
|
|
32
|
+
const rest = argv.filter((arg, i) => i >= lead || arg !== "--dry-run");
|
|
33
|
+
return {
|
|
34
|
+
dryRun: rest.length !== argv.length,
|
|
35
|
+
rest
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
dryRun: argv.includes(DRY_RUN_FLAG),
|
|
40
|
+
rest: [...argv]
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* How a command treats `--dry-run`, from the nearest node on its path that
|
|
45
|
+
* says. `undefined` means it did not say, and the dispatcher stops it.
|
|
46
|
+
*/
|
|
47
|
+
function dryRunKind(catalog, path) {
|
|
48
|
+
for (let end = path.length; end > 0; end -= 1) {
|
|
49
|
+
const kind = catalog.findCommand(path.slice(0, end))?.dryRun;
|
|
50
|
+
if (kind !== void 0) return kind;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** The command line a stopped command would have run. */
|
|
54
|
+
function wouldRunLine(argv) {
|
|
55
|
+
return `Would run: ${cliName()} ${argv.join(" ")}`;
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
export { wouldRunLine as a, setDryRun as i, isDryRun as n, resolveDryRun as r, dryRunKind as t };
|
package/dist/launch.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { C as discoverRepoRoot, E as setCliName, S as RepoNotFoundError, T as cliName, _ as installFailureLog, d as hasYes, g as stripTierFlag, h as stripNoEnvFlag, m as setNoEnv, n as captureDevtoolsError, o as initDevtoolsTelemetry, p as isNonInteractive, s as lastSentryEventId } from "./telemetry-Bjoz29Hl.js";
|
|
2
|
-
import { c as setMenuEnvHook, d as formatCommand, f as helpPath, n as bareGroupStartPath, t as catalog } from "./catalog-
|
|
2
|
+
import { c as setMenuEnvHook, d as formatCommand, f as helpPath, n as bareGroupStartPath, t as catalog } from "./catalog-Dke7bQVz.js";
|
|
3
3
|
import { i as loadEnvSession, r as loadEnvLoad } from "./peers-B0KDkI6a.js";
|
|
4
4
|
import { t as nonEmpty } from "./connection-D3XsOE3S.js";
|
|
5
|
-
import {
|
|
5
|
+
import { i as setDryRun, n as isDryRun, r as resolveDryRun } from "./dry-run-3IxPmCtW.js";
|
|
6
6
|
import { o as unwrap, r as errorMessage } from "./ui-CdKo8mLw.js";
|
|
7
7
|
import { confirm } from "@clack/prompts";
|
|
8
8
|
//#region ../cli-core/src/pipes.ts
|
|
@@ -322,7 +322,7 @@ async function launchWith(argv, options) {
|
|
|
322
322
|
*/
|
|
323
323
|
async function launch(argv) {
|
|
324
324
|
await launchWith(argv, { dispatch: async (args) => {
|
|
325
|
-
const { main } = await import("./cli-
|
|
325
|
+
const { main } = await import("./cli-BhZ4je2D.js");
|
|
326
326
|
await main(args);
|
|
327
327
|
} });
|
|
328
328
|
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { t as UsageError } from "./ui-CdKo8mLw.js";
|
|
2
|
+
import "./cli-Do2nWD7W.js";
|
|
3
|
+
//#region src/production/time.ts
|
|
4
|
+
/**
|
|
5
|
+
* Dates as officers type them. Meetings happen in Athens, so a bare
|
|
6
|
+
* `2026-09-30` means that Eastern day, never UTC's: UTC's date rolls at 20:00
|
|
7
|
+
* Eastern, and the platform's old `to=` filter cut off the evening's meeting
|
|
8
|
+
* on exactly the day it named.
|
|
9
|
+
*/
|
|
10
|
+
const EVENT_TZ = "America/New_York";
|
|
11
|
+
const DAY = /^\d{4}-\d{2}-\d{2}$/;
|
|
12
|
+
function isDay(text) {
|
|
13
|
+
return DAY.test(text);
|
|
14
|
+
}
|
|
15
|
+
/** Minutes `zone` is ahead of UTC at `at` (negative west of Greenwich). */
|
|
16
|
+
function offsetMinutes(at, zone) {
|
|
17
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
18
|
+
timeZone: zone,
|
|
19
|
+
hourCycle: "h23",
|
|
20
|
+
year: "numeric",
|
|
21
|
+
month: "2-digit",
|
|
22
|
+
day: "2-digit",
|
|
23
|
+
hour: "2-digit",
|
|
24
|
+
minute: "2-digit",
|
|
25
|
+
second: "2-digit"
|
|
26
|
+
}).formatToParts(at);
|
|
27
|
+
const get = (type) => Number(parts.find((p) => p.type === type)?.value);
|
|
28
|
+
const asUtc = Date.UTC(get("year"), get("month") - 1, get("day"), get("hour"), get("minute"), get("second"));
|
|
29
|
+
return Math.round((asUtc - at.getTime()) / 6e4);
|
|
30
|
+
}
|
|
31
|
+
/** Midnight at the start of `day` (YYYY-MM-DD) in {@link EVENT_TZ}. */
|
|
32
|
+
function startOfDay(day, zone = EVENT_TZ) {
|
|
33
|
+
const utcMidnight = /* @__PURE__ */ new Date(`${day}T00:00:00Z`);
|
|
34
|
+
const probe = new Date(utcMidnight.getTime() + 216e5);
|
|
35
|
+
return /* @__PURE__ */ new Date(utcMidnight.getTime() - offsetMinutes(probe, zone) * 6e4);
|
|
36
|
+
}
|
|
37
|
+
/** Midnight at the start of the day after `day`. */
|
|
38
|
+
function endOfDay(day, zone = EVENT_TZ) {
|
|
39
|
+
const next = /* @__PURE__ */ new Date(`${day}T00:00:00Z`);
|
|
40
|
+
next.setUTCDate(next.getUTCDate() + 1);
|
|
41
|
+
return startOfDay(next.toISOString().slice(0, 10), zone);
|
|
42
|
+
}
|
|
43
|
+
/** The Eastern calendar date of `at`, as YYYY-MM-DD. */
|
|
44
|
+
function dayOf(at, zone = EVENT_TZ) {
|
|
45
|
+
return new Intl.DateTimeFormat("en-CA", {
|
|
46
|
+
timeZone: zone,
|
|
47
|
+
year: "numeric",
|
|
48
|
+
month: "2-digit",
|
|
49
|
+
day: "2-digit"
|
|
50
|
+
}).format(at);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A `--from`/`--to` value: a day (the whole Eastern day, `to` inclusive) or
|
|
54
|
+
* a full ISO timestamp (exact). Returns the bound to compare with: `from` is
|
|
55
|
+
* inclusive, `to` exclusive.
|
|
56
|
+
*/
|
|
57
|
+
function parseBound(flag, text) {
|
|
58
|
+
if (!text) return void 0;
|
|
59
|
+
if (isDay(text)) return flag === "--from" ? startOfDay(text) : endOfDay(text);
|
|
60
|
+
const at = new Date(text);
|
|
61
|
+
if (/T\d{2}:\d{2}/.test(text) && !Number.isNaN(at.getTime())) return at;
|
|
62
|
+
throw new UsageError(`${flag} takes a day (2026-09-30) or a timestamp (2026-09-30T18:00:00-04:00), not "${text}".`);
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/production/meetings.ts
|
|
66
|
+
/**
|
|
67
|
+
* Naming a meeting on the command line: `--meeting <day|slug|config id|uuid>`.
|
|
68
|
+
*
|
|
69
|
+
* Slugs are the meeting's Eastern date (`2026-09-09`), with `-2` for a second
|
|
70
|
+
* meeting that day, so a day is how officers will usually name one. A day with
|
|
71
|
+
* two meetings is ambiguous and the error lists both slugs rather than picking.
|
|
72
|
+
*/
|
|
73
|
+
/** Live meetings an argument could mean: by id, slug or config id, or on that Eastern day. */
|
|
74
|
+
const MEETING_QUERY = `
|
|
75
|
+
select
|
|
76
|
+
m."id"::text as "id",
|
|
77
|
+
m."slug",
|
|
78
|
+
m."configId",
|
|
79
|
+
coalesce(m."nameOverride", m."kind", 'Meeting') as "title",
|
|
80
|
+
m."startsAt",
|
|
81
|
+
m."endsAt",
|
|
82
|
+
m."cancelledAt",
|
|
83
|
+
m."countsForCredit"
|
|
84
|
+
from "platform"."meetings" m
|
|
85
|
+
where m."deletedAt" is null
|
|
86
|
+
and (
|
|
87
|
+
m."id"::text = $1 or m."slug" = $1 or m."configId" = $1
|
|
88
|
+
or ($2::boolean and (m."startsAt" at time zone '${EVENT_TZ}')::date = $1::date)
|
|
89
|
+
)
|
|
90
|
+
order by m."startsAt"
|
|
91
|
+
`;
|
|
92
|
+
function findMeetingsWith(sql) {
|
|
93
|
+
return async (named) => {
|
|
94
|
+
return (await sql.unsafe(MEETING_QUERY, [named, isDay(named)])).map((r) => ({
|
|
95
|
+
id: String(r.id),
|
|
96
|
+
slug: String(r.slug),
|
|
97
|
+
configId: r.configId ?? null,
|
|
98
|
+
title: String(r.title),
|
|
99
|
+
startsAt: r.startsAt,
|
|
100
|
+
endsAt: r.endsAt,
|
|
101
|
+
cancelledAt: r.cancelledAt ?? null,
|
|
102
|
+
countsForCredit: r.countsForCredit === true
|
|
103
|
+
}));
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/** The one meeting `named` means, or a usage error saying why there isn't one. */
|
|
107
|
+
function pickMeeting(named, found) {
|
|
108
|
+
const exact = found.filter((m) => m.id === named || m.slug === named || m.configId === named);
|
|
109
|
+
const candidates = exact.length > 0 ? exact : found;
|
|
110
|
+
if (candidates.length === 1) return candidates[0];
|
|
111
|
+
if (candidates.length === 0) throw new UsageError(`No meeting matches "${named}". Name it by day (2026-09-09), slug, config id or id.`);
|
|
112
|
+
throw new UsageError(`"${named}" could be ${candidates.map((m) => `${m.slug} (${m.title})`).join(" or ")}. Pass the slug.`);
|
|
113
|
+
}
|
|
114
|
+
function describeMeeting(m) {
|
|
115
|
+
return `${m.title}, ${dayOf(m.startsAt)} (${m.slug})`;
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
export { parseBound as i, findMeetingsWith as n, pickMeeting as r, describeMeeting as t };
|
|
@@ -1,59 +1,3 @@
|
|
|
1
|
-
import { T as cliName } from "./telemetry-Bjoz29Hl.js";
|
|
2
|
-
//#region ../cli-core/src/dry-run.ts
|
|
3
|
-
const DRY_RUN_FLAG = "--dry-run";
|
|
4
|
-
/** Commands that forward everything after their name to another tool. */
|
|
5
|
-
const FORWARDING = /* @__PURE__ */ new Set([
|
|
6
|
-
"supabase",
|
|
7
|
-
"wrangler",
|
|
8
|
-
"drizzle-kit",
|
|
9
|
-
"psql",
|
|
10
|
-
"bw",
|
|
11
|
-
"run"
|
|
12
|
-
]);
|
|
13
|
-
let active = false;
|
|
14
|
-
function setDryRun(on) {
|
|
15
|
-
active = on;
|
|
16
|
-
}
|
|
17
|
-
function isDryRun() {
|
|
18
|
-
return active;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Decides whether `argv` asks for a dry run and returns the argv to dispatch.
|
|
22
|
-
*
|
|
23
|
-
* For a forwarding command a leading `--dry-run` is consumed (the tool never
|
|
24
|
-
* sees it) and a later one is the tool's. For any other command the argv is
|
|
25
|
-
* returned as it came, because several commands read `--dry-run` themselves.
|
|
26
|
-
*/
|
|
27
|
-
function resolveDryRun(argv) {
|
|
28
|
-
const firstPositional = argv.findIndex((arg) => !arg.startsWith("-"));
|
|
29
|
-
const lead = firstPositional === -1 ? argv.length : firstPositional;
|
|
30
|
-
const command = argv[firstPositional];
|
|
31
|
-
if (command !== void 0 && FORWARDING.has(command)) {
|
|
32
|
-
const rest = argv.filter((arg, i) => i >= lead || arg !== "--dry-run");
|
|
33
|
-
return {
|
|
34
|
-
dryRun: rest.length !== argv.length,
|
|
35
|
-
rest
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
return {
|
|
39
|
-
dryRun: argv.includes(DRY_RUN_FLAG),
|
|
40
|
-
rest: [...argv]
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* How a command treats `--dry-run`, from the nearest node on its path that
|
|
45
|
-
* says. `undefined` means it did not say, and the dispatcher stops it.
|
|
46
|
-
*/
|
|
47
|
-
function dryRunKind(catalog, path) {
|
|
48
|
-
for (let end = path.length; end > 0; end -= 1) {
|
|
49
|
-
const kind = catalog.findCommand(path.slice(0, end))?.dryRun;
|
|
50
|
-
if (kind !== void 0) return kind;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
/** The command line a stopped command would have run. */
|
|
54
|
-
function wouldRunLine(argv) {
|
|
55
|
-
return `Would run: ${cliName()} ${argv.join(" ")}`;
|
|
56
|
-
}
|
|
57
1
|
const QR_FLAGS = {
|
|
58
2
|
text: {
|
|
59
3
|
name: "text",
|
|
@@ -180,4 +124,4 @@ function qrParseOptions() {
|
|
|
180
124
|
return Object.fromEntries([...Object.values(QR_FLAGS), ...Object.values(QR_EXTRA_FLAGS)].map((flag) => [flag.name, { type: "string" }]));
|
|
181
125
|
}
|
|
182
126
|
//#endregion
|
|
183
|
-
export {
|
|
127
|
+
export { qrParseOptions as i, QR_FLAGS as n, qrCatalogOptions as r, QR_EXTRA_FLAGS as t };
|