@bitmagic/cli 0.1.46-dev.3 → 0.1.46-dev.4
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 +46 -1
- package/dist/auth/session.js +1 -1
- package/dist/auth/session.js.map +1 -1
- package/dist/auth/subscribe-flow.d.ts +3 -2
- package/dist/auth/subscribe-flow.js.map +1 -1
- package/dist/bin.js +6 -2
- package/dist/bin.js.map +1 -1
- package/dist/cli.d.ts +26 -3
- package/dist/cli.js +76 -7
- package/dist/cli.js.map +1 -1
- package/dist/commands/dev.js +9 -0
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/forge.js +1 -1
- package/dist/commands/forge.js.map +1 -1
- package/dist/commands/generate.js +9 -1
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/login.js +5 -7
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/reset-account.d.ts +22 -9
- package/dist/commands/reset-account.js +58 -42
- package/dist/commands/reset-account.js.map +1 -1
- package/dist/commands/subscribe.d.ts +12 -0
- package/dist/commands/subscribe.js +12 -11
- package/dist/commands/subscribe.js.map +1 -1
- package/dist/commands/upgrade.d.ts +2 -1
- package/dist/commands/upgrade.js +12 -5
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/config/environments.d.ts +14 -0
- package/dist/config/environments.js +22 -0
- package/dist/config/environments.js.map +1 -1
- package/dist/errors.d.ts +10 -1
- package/dist/errors.js +11 -1
- package/dist/errors.js.map +1 -1
- package/dist/forge/run-pipeline.js +1 -1
- package/dist/forge/run-pipeline.js.map +1 -1
- package/dist/forge/stream.js +10 -5
- package/dist/forge/stream.js.map +1 -1
- package/dist/forge/upload-proxy.js +2 -0
- package/dist/forge/upload-proxy.js.map +1 -1
- package/dist/generate/stream.js +10 -5
- package/dist/generate/stream.js.map +1 -1
- package/dist/http/client.d.ts +6 -2
- package/dist/http/client.js +22 -12
- package/dist/http/client.js.map +1 -1
- package/dist/http/spark-refusal.d.ts +11 -0
- package/dist/http/spark-refusal.js +13 -0
- package/dist/http/spark-refusal.js.map +1 -1
- package/dist/publish/client.js +3 -1
- package/dist/publish/client.js.map +1 -1
- package/dist/scaffold/agents-md.d.ts +2 -1
- package/dist/scaffold/agents-md.js +7 -2
- package/dist/scaffold/agents-md.js.map +1 -1
- package/dist/scaffold/engine-download.js +5 -18
- package/dist/scaffold/engine-download.js.map +1 -1
- package/dist/scaffold/project-files.d.ts +3 -2
- package/dist/scaffold/project-files.js +46 -2
- package/dist/scaffold/project-files.js.map +1 -1
- package/dist/scaffold/project.d.ts +30 -0
- package/dist/scaffold/project.js +55 -29
- package/dist/scaffold/project.js.map +1 -1
- package/dist/scaffold/upgrade-project.js +19 -33
- package/dist/scaffold/upgrade-project.js.map +1 -1
- package/dist/telemetry/command-context.d.ts +73 -0
- package/dist/telemetry/command-context.js +114 -0
- package/dist/telemetry/command-context.js.map +1 -0
- package/dist/telemetry/report.d.ts +43 -0
- package/dist/telemetry/report.js +123 -0
- package/dist/telemetry/report.js.map +1 -0
- package/dist/telemetry/request-headers.d.ts +14 -0
- package/dist/telemetry/request-headers.js +48 -0
- package/dist/telemetry/request-headers.js.map +1 -0
- package/dist/update/check.d.ts +14 -0
- package/dist/update/check.js +17 -1
- package/dist/update/check.js.map +1 -1
- package/dist/update/notice.d.ts +40 -0
- package/dist/update/notice.js +53 -0
- package/dist/update/notice.js.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type Environment } from '../config/environments.js';
|
|
2
|
+
/**
|
|
3
|
+
* What is driving the CLI.
|
|
4
|
+
*
|
|
5
|
+
* - `claude-code` — a Claude Code session (its `CLAUDECODE` env var, verified).
|
|
6
|
+
* - `json` — some agent, unidentified: `--json` is the flag agents pass.
|
|
7
|
+
* - `tty` — a person at a terminal.
|
|
8
|
+
* - `pipe` — a script or CI: no `--json`, no terminal.
|
|
9
|
+
*
|
|
10
|
+
* Add an agent here only once its marker is verified against the real thing; a
|
|
11
|
+
* guessed env var never fires and just misleads whoever reads the table.
|
|
12
|
+
*/
|
|
13
|
+
export type CliDriver = 'claude-code' | 'json' | 'tty' | 'pipe';
|
|
14
|
+
export declare function detectDriver(input: {
|
|
15
|
+
json: boolean;
|
|
16
|
+
env: Record<string, string | undefined>;
|
|
17
|
+
isTTY: boolean;
|
|
18
|
+
}): CliDriver;
|
|
19
|
+
/**
|
|
20
|
+
* `DO_NOT_TRACK` set to anything but `0`/`false`/empty. The convention says
|
|
21
|
+
* `1`; being generous about the spelling is the polite reading of a signal
|
|
22
|
+
* that only ever means "please don't".
|
|
23
|
+
*
|
|
24
|
+
* Lives here rather than in report.ts because it is consulted BEFORE anything
|
|
25
|
+
* is collected — `beginCommand` skips the whole snapshot when it is set — and
|
|
26
|
+
* report.ts already imports this module, so the other direction would close a
|
|
27
|
+
* cycle. Re-exported from report.ts, which is where callers expect it.
|
|
28
|
+
*/
|
|
29
|
+
export declare function isTelemetryDisabled(env: Record<string, string | undefined>): boolean;
|
|
30
|
+
export interface TokenSnapshot {
|
|
31
|
+
accessToken: string;
|
|
32
|
+
/** Epoch milliseconds, as stored. */
|
|
33
|
+
expiresAt: number;
|
|
34
|
+
}
|
|
35
|
+
export interface CommandContext {
|
|
36
|
+
/** As typed, so a report reads the way the creator invoked it: `publish`, `generate skybox`. */
|
|
37
|
+
name: string;
|
|
38
|
+
json: boolean;
|
|
39
|
+
driver: CliDriver;
|
|
40
|
+
/**
|
|
41
|
+
* Where this command is talking to, resolved ONCE here through the same
|
|
42
|
+
* precedence every command uses (`--env` > BITMAGIC_ENV > the project's
|
|
43
|
+
* bitmagic.json > the stored default). `report.ts` reads it rather than
|
|
44
|
+
* resolving again: a second resolution is a second chance to disagree, and
|
|
45
|
+
* one that skipped the project tier would send a project's beacons to a
|
|
46
|
+
* different environment than the work they describe.
|
|
47
|
+
*
|
|
48
|
+
* Null only when resolution threw — an unknown `--env`, or a project whose
|
|
49
|
+
* `environment` field is not a valid name. The command itself fails with
|
|
50
|
+
* that message moments later; there is simply nowhere to report it to.
|
|
51
|
+
*/
|
|
52
|
+
environment: Environment | null;
|
|
53
|
+
/** Epoch milliseconds when the command began. */
|
|
54
|
+
startedAt: number;
|
|
55
|
+
/** See the module header: the stored access token at start, for the one command that removes it. */
|
|
56
|
+
tokenSnapshot: TokenSnapshot | null;
|
|
57
|
+
}
|
|
58
|
+
export interface BeginCommandInput {
|
|
59
|
+
name: string;
|
|
60
|
+
json: boolean;
|
|
61
|
+
explicitEnv?: string;
|
|
62
|
+
env?: Record<string, string | undefined>;
|
|
63
|
+
isTTY?: boolean;
|
|
64
|
+
now?: () => number;
|
|
65
|
+
baseDir?: string;
|
|
66
|
+
/** Where to look for the project's own environment pin; defaults to process.cwd(). */
|
|
67
|
+
cwd?: string;
|
|
68
|
+
}
|
|
69
|
+
export declare function beginCommand(input: BeginCommandInput): CommandContext;
|
|
70
|
+
/** The running command, or null before `beginCommand` — e.g. under a test that calls a module directly. */
|
|
71
|
+
export declare function currentCommand(): CommandContext | null;
|
|
72
|
+
/** Test seam: forget the running command, so one test's context cannot leak into the next. */
|
|
73
|
+
export declare function resetCommandContextForTests(): void;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which command this process is running — the one piece of state the CLI's usage
|
|
3
|
+
* reporting is built on.
|
|
4
|
+
*
|
|
5
|
+
* One process runs one command, so this is process-scoped: cli.ts's wrapper calls
|
|
6
|
+
* {@link beginCommand} once before the command's `run`, and two readers use it —
|
|
7
|
+
* `request-headers.ts`, which names the command on every api-server call, and
|
|
8
|
+
* `report.ts`, which sends the outcome when the command ends. It is the CLI's
|
|
9
|
+
* equivalent of a request context, and it lives here rather than being threaded
|
|
10
|
+
* through every command signature because there are five separate fetch sites and
|
|
11
|
+
* eighteen commands, and a parameter every one of them must remember to pass is
|
|
12
|
+
* how one of them ends up unlabelled.
|
|
13
|
+
*
|
|
14
|
+
* Nothing here reaches the network. The token snapshot is a file read, and it
|
|
15
|
+
* exists for exactly one command: `logout` removes the stored credentials, so by
|
|
16
|
+
* the time its outcome is reported there is nothing to authenticate the report
|
|
17
|
+
* with — except what was there when it started.
|
|
18
|
+
*/
|
|
19
|
+
import { readCredentials } from '../config/credentials.js';
|
|
20
|
+
import { resolveEnvironmentForCommand } from '../config/environments.js';
|
|
21
|
+
const AGENT_ENV_MARKERS = [['CLAUDECODE', 'claude-code']];
|
|
22
|
+
export function detectDriver(input) {
|
|
23
|
+
for (const [marker, driver] of AGENT_ENV_MARKERS) {
|
|
24
|
+
if (input.env[marker])
|
|
25
|
+
return driver;
|
|
26
|
+
}
|
|
27
|
+
if (input.json)
|
|
28
|
+
return 'json';
|
|
29
|
+
return input.isTTY ? 'tty' : 'pipe';
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* `DO_NOT_TRACK` set to anything but `0`/`false`/empty. The convention says
|
|
33
|
+
* `1`; being generous about the spelling is the polite reading of a signal
|
|
34
|
+
* that only ever means "please don't".
|
|
35
|
+
*
|
|
36
|
+
* Lives here rather than in report.ts because it is consulted BEFORE anything
|
|
37
|
+
* is collected — `beginCommand` skips the whole snapshot when it is set — and
|
|
38
|
+
* report.ts already imports this module, so the other direction would close a
|
|
39
|
+
* cycle. Re-exported from report.ts, which is where callers expect it.
|
|
40
|
+
*/
|
|
41
|
+
export function isTelemetryDisabled(env) {
|
|
42
|
+
const value = env.DO_NOT_TRACK?.trim().toLowerCase();
|
|
43
|
+
return value !== undefined && value !== '' && value !== '0' && value !== 'false';
|
|
44
|
+
}
|
|
45
|
+
let current = null;
|
|
46
|
+
/**
|
|
47
|
+
* The environment this command targets, or null if it cannot be resolved.
|
|
48
|
+
*
|
|
49
|
+
* `resolveEnvironmentForCommand` is the same entry point the commands use, so
|
|
50
|
+
* a project pinned to an environment reports there too. It throws for an
|
|
51
|
+
* unknown name typed right now (a bad `--env`, a bad `bitmagic.json`), which
|
|
52
|
+
* the command surfaces properly — here it only means "no environment to report
|
|
53
|
+
* to", never a failed command.
|
|
54
|
+
*/
|
|
55
|
+
function snapshotEnvironment(input, env) {
|
|
56
|
+
try {
|
|
57
|
+
return resolveEnvironmentForCommand({
|
|
58
|
+
explicit: input.explicitEnv,
|
|
59
|
+
cwd: input.cwd,
|
|
60
|
+
baseDir: input.baseDir,
|
|
61
|
+
env,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The stored access token for `environment`, or null.
|
|
70
|
+
*
|
|
71
|
+
* Every failure collapses to null on purpose: no environment, an unreadable
|
|
72
|
+
* credentials file, nothing stored — the command itself will report whichever
|
|
73
|
+
* of those matters, in its own words. This is a snapshot for a report, and a
|
|
74
|
+
* report is never worth failing a command over.
|
|
75
|
+
*/
|
|
76
|
+
function snapshotToken(environment, baseDir) {
|
|
77
|
+
if (environment === null)
|
|
78
|
+
return null;
|
|
79
|
+
try {
|
|
80
|
+
const stored = readCredentials(environment.name, baseDir);
|
|
81
|
+
return stored ? { accessToken: stored.accessToken, expiresAt: stored.expiresAt } : null;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export function beginCommand(input) {
|
|
88
|
+
const env = input.env ?? process.env;
|
|
89
|
+
// Nothing is collected for a creator who opted out. Both snapshots below are
|
|
90
|
+
// filesystem work — a walk up the tree for the project's bitmagic.json, then
|
|
91
|
+
// credentials.json — done on every one of the eighteen commands, including
|
|
92
|
+
// the offline ones and the `reload` hook that runs after every agent turn.
|
|
93
|
+
// None of it has a purpose if no report will be sent.
|
|
94
|
+
const collect = !isTelemetryDisabled(env);
|
|
95
|
+
const environment = collect ? snapshotEnvironment(input, env) : null;
|
|
96
|
+
current = {
|
|
97
|
+
name: input.name,
|
|
98
|
+
json: input.json,
|
|
99
|
+
driver: detectDriver({ json: input.json, env, isTTY: input.isTTY ?? process.stdout.isTTY === true }),
|
|
100
|
+
environment,
|
|
101
|
+
startedAt: (input.now ?? Date.now)(),
|
|
102
|
+
tokenSnapshot: collect ? snapshotToken(environment, input.baseDir) : null,
|
|
103
|
+
};
|
|
104
|
+
return current;
|
|
105
|
+
}
|
|
106
|
+
/** The running command, or null before `beginCommand` — e.g. under a test that calls a module directly. */
|
|
107
|
+
export function currentCommand() {
|
|
108
|
+
return current;
|
|
109
|
+
}
|
|
110
|
+
/** Test seam: forget the running command, so one test's context cannot leak into the next. */
|
|
111
|
+
export function resetCommandContextForTests() {
|
|
112
|
+
current = null;
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=command-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-context.js","sourceRoot":"","sources":["../../src/telemetry/command-context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { isTelemetryDisabled } from './command-context.js';
|
|
2
|
+
export { isTelemetryDisabled };
|
|
3
|
+
/**
|
|
4
|
+
* Cap on each request the report makes (the token refresh, when one is due, and
|
|
5
|
+
* the POST itself). A pending fetch keeps Node alive, so an unbounded one would
|
|
6
|
+
* hold a finished command open for as long as the network sulks.
|
|
7
|
+
*/
|
|
8
|
+
export declare const REPORT_TIMEOUT_MS = 1500;
|
|
9
|
+
export declare const CLI_EVENTS_PATH = "/api/cli/v1/events";
|
|
10
|
+
export interface CommandReport {
|
|
11
|
+
/** `finished` for every command's end; `started` for a long-running command once it is up. */
|
|
12
|
+
stage: 'started' | 'finished';
|
|
13
|
+
ok?: boolean;
|
|
14
|
+
durationMs?: number;
|
|
15
|
+
exitCode?: number;
|
|
16
|
+
/** A category — `CliError.code`, `cli_error`, `crash:TypeError` — never a message. */
|
|
17
|
+
failure?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ReportDeps {
|
|
20
|
+
fetch: typeof globalThis.fetch;
|
|
21
|
+
env: Record<string, string | undefined>;
|
|
22
|
+
now?: () => number;
|
|
23
|
+
/** Where credentials live; tests point this at a temp dir. */
|
|
24
|
+
baseDir?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The failure category for an error that is not a CliError — a genuine bug, not
|
|
28
|
+
* a refusal we wrote a message for. `crash:TypeError`, `crash:RangeError`: the
|
|
29
|
+
* error's class, which says what kind of bug without saying anything about the
|
|
30
|
+
* creator's machine. Anything odd in the name is folded to `_` so the value
|
|
31
|
+
* always fits the shape api-server accepts.
|
|
32
|
+
*/
|
|
33
|
+
export declare function crashCategory(error: unknown): string;
|
|
34
|
+
/**
|
|
35
|
+
* Report the running command's `report` to api-server. Resolves when the report
|
|
36
|
+
* has been sent, refused, or given up on — always, and never by throwing.
|
|
37
|
+
*
|
|
38
|
+
* Callers `await` it (bounded, see above) rather than fire and forget: on the
|
|
39
|
+
* failure path cli.ts calls `process.exit` next, which would otherwise cut the
|
|
40
|
+
* request off mid-flight, and on the success path the pending fetch would keep
|
|
41
|
+
* the process alive for the same time either way.
|
|
42
|
+
*/
|
|
43
|
+
export declare function reportCommandEvent(report: CommandReport, deps?: ReportDeps): Promise<void>;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The command beacon: one `POST /api/cli/v1/events` per command, saying how it went.
|
|
3
|
+
*
|
|
4
|
+
* Why it exists: api-server can see the commands that call it — but `dev`,
|
|
5
|
+
* `build`, `check`, `verify` and `reload` are offline by design, and even a
|
|
6
|
+
* `publish` that fails on a stale verify never reaches the server. This is how
|
|
7
|
+
* every command becomes visible, and how the ones the server does see get an
|
|
8
|
+
* end-to-end outcome and duration to go with the server's own event. The
|
|
9
|
+
* server turns it into `cli_command` / `cli_command_started` (see
|
|
10
|
+
* `api-server/src/cli/cli-analytics.ts`); nothing goes to Google from here.
|
|
11
|
+
*
|
|
12
|
+
* Contract, in order of importance:
|
|
13
|
+
*
|
|
14
|
+
* 1. It never fails or delays a command. Every failure path — not logged in,
|
|
15
|
+
* no network, a slow server, a bad `--env` — returns silently. It is
|
|
16
|
+
* bounded to {@link REPORT_TIMEOUT_MS} per request so a finished command is
|
|
17
|
+
* never held open by a sulking network (the same reasoning as
|
|
18
|
+
* update/check.ts). It never prints.
|
|
19
|
+
* 2. It sends categories, never messages: the command name, ok/failed, the
|
|
20
|
+
* exit code, a `CliError.code`, and a duration. A message can name a path
|
|
21
|
+
* or a game; none of that belongs in an analytics event.
|
|
22
|
+
* 3. It is authenticated with the creator's own token, so the server can key
|
|
23
|
+
* the event to the account and look up their real Pro state. No token, no
|
|
24
|
+
* report — an anonymous beacon would be a spammable endpoint for nothing.
|
|
25
|
+
* 4. `DO_NOT_TRACK=1` (the cross-tool convention, consoledonottrack.com)
|
|
26
|
+
* turns it off. It does not touch the ordinary api-server calls a command
|
|
27
|
+
* makes — those are the command's work, and the server logs them anyway.
|
|
28
|
+
*/
|
|
29
|
+
import { resolveAuth0ClientId } from '../config/environments.js';
|
|
30
|
+
import { getAccessToken } from '../auth/session.js';
|
|
31
|
+
import { apiServerHeaders } from './request-headers.js';
|
|
32
|
+
import { currentCommand, isTelemetryDisabled } from './command-context.js';
|
|
33
|
+
// Re-exported because this is where the opt-out is enforced for a report that is already
|
|
34
|
+
// under way; `beginCommand` consults the same predicate to skip collecting anything at all.
|
|
35
|
+
export { isTelemetryDisabled };
|
|
36
|
+
/**
|
|
37
|
+
* Cap on each request the report makes (the token refresh, when one is due, and
|
|
38
|
+
* the POST itself). A pending fetch keeps Node alive, so an unbounded one would
|
|
39
|
+
* hold a finished command open for as long as the network sulks.
|
|
40
|
+
*/
|
|
41
|
+
export const REPORT_TIMEOUT_MS = 1500;
|
|
42
|
+
export const CLI_EVENTS_PATH = '/api/cli/v1/events';
|
|
43
|
+
/**
|
|
44
|
+
* The failure category for an error that is not a CliError — a genuine bug, not
|
|
45
|
+
* a refusal we wrote a message for. `crash:TypeError`, `crash:RangeError`: the
|
|
46
|
+
* error's class, which says what kind of bug without saying anything about the
|
|
47
|
+
* creator's machine. Anything odd in the name is folded to `_` so the value
|
|
48
|
+
* always fits the shape api-server accepts.
|
|
49
|
+
*/
|
|
50
|
+
export function crashCategory(error) {
|
|
51
|
+
const name = error instanceof Error && error.name ? error.name : 'unknown';
|
|
52
|
+
return `crash:${name.replace(/[^A-Za-z0-9_.-]/g, '_').slice(0, 40)}`;
|
|
53
|
+
}
|
|
54
|
+
/** The given fetch, with every call bounded by REPORT_TIMEOUT_MS. */
|
|
55
|
+
function boundedFetch(fetchImpl) {
|
|
56
|
+
return (input, init) => fetchImpl(input, { ...init, signal: AbortSignal.timeout(REPORT_TIMEOUT_MS) });
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The token to authenticate the report with, or null for "no report".
|
|
60
|
+
*
|
|
61
|
+
* The stored token first — refreshed if it has expired, exactly as any command
|
|
62
|
+
* would, so a report never wastes a request on a 401 — and failing that the
|
|
63
|
+
* snapshot taken when the command began, which is what `logout` leaves behind.
|
|
64
|
+
*/
|
|
65
|
+
async function resolveReportToken(environment, command, deps) {
|
|
66
|
+
try {
|
|
67
|
+
const clientId = resolveAuth0ClientId(environment, deps.env);
|
|
68
|
+
return await getAccessToken(environment, clientId, {
|
|
69
|
+
fetch: boundedFetch(deps.fetch),
|
|
70
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
71
|
+
now: deps.now,
|
|
72
|
+
}, deps.baseDir);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
const snapshot = command.tokenSnapshot;
|
|
76
|
+
const now = (deps.now ?? Date.now)();
|
|
77
|
+
return snapshot !== null && snapshot.expiresAt > now ? snapshot.accessToken : null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function defaultDeps() {
|
|
81
|
+
return { fetch: globalThis.fetch, env: process.env };
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Report the running command's `report` to api-server. Resolves when the report
|
|
85
|
+
* has been sent, refused, or given up on — always, and never by throwing.
|
|
86
|
+
*
|
|
87
|
+
* Callers `await` it (bounded, see above) rather than fire and forget: on the
|
|
88
|
+
* failure path cli.ts calls `process.exit` next, which would otherwise cut the
|
|
89
|
+
* request off mid-flight, and on the success path the pending fetch would keep
|
|
90
|
+
* the process alive for the same time either way.
|
|
91
|
+
*/
|
|
92
|
+
export async function reportCommandEvent(report, deps = defaultDeps()) {
|
|
93
|
+
try {
|
|
94
|
+
if (isTelemetryDisabled(deps.env))
|
|
95
|
+
return;
|
|
96
|
+
const command = currentCommand();
|
|
97
|
+
if (command === null)
|
|
98
|
+
return;
|
|
99
|
+
// Resolved once, at command start — see CommandContext.environment for why
|
|
100
|
+
// this must not resolve again. Null means it could not be resolved at all,
|
|
101
|
+
// so there is nowhere to send this.
|
|
102
|
+
const environment = command.environment;
|
|
103
|
+
if (environment === null)
|
|
104
|
+
return;
|
|
105
|
+
const token = await resolveReportToken(environment, command, deps);
|
|
106
|
+
if (token === null)
|
|
107
|
+
return;
|
|
108
|
+
await boundedFetch(deps.fetch)(`${environment.apiUrl}${CLI_EVENTS_PATH}`, {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
headers: {
|
|
111
|
+
...apiServerHeaders(),
|
|
112
|
+
Authorization: `Bearer ${token}`,
|
|
113
|
+
'Content-Type': 'application/json',
|
|
114
|
+
},
|
|
115
|
+
body: JSON.stringify({ command: command.name, ...report }),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// Best-effort by design — see the module header. Whatever went wrong here,
|
|
120
|
+
// the command's own outcome has already been decided and printed.
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/telemetry/report.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const CLI_COMMAND_HEADER = "X-Bitmagic-Cli-Command";
|
|
2
|
+
export declare const CLI_DRIVER_HEADER = "X-Bitmagic-Cli-Driver";
|
|
3
|
+
/** `bitmagic-cli/0.1.46 (darwin-arm64; node/v22.12.0)` — the shape api-server's context reader parses. */
|
|
4
|
+
export declare function userAgent(): string;
|
|
5
|
+
/**
|
|
6
|
+
* Spread these into the `headers` of every fetch aimed at api-server. Callers
|
|
7
|
+
* add their own `Authorization`, `Accept` and `Content-Type` after — nothing
|
|
8
|
+
* here collides with those.
|
|
9
|
+
*
|
|
10
|
+
* The command headers are present only once cli.ts has begun a command; a
|
|
11
|
+
* module exercised directly (a test, a future library caller) still identifies
|
|
12
|
+
* the CLI, just not the command.
|
|
13
|
+
*/
|
|
14
|
+
export declare function apiServerHeaders(): Record<string, string>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The headers every api-server call carries, so the server's analytics know who
|
|
3
|
+
* asked and why.
|
|
4
|
+
*
|
|
5
|
+
* The CLI never sends analytics to Google itself — the GA4 Measurement Protocol
|
|
6
|
+
* needs an API secret, and a public npm package cannot keep one. api-server
|
|
7
|
+
* emits every event instead (`api-server/src/cli/cli-analytics.ts` reads these
|
|
8
|
+
* headers back as `cli_version`, `cli_platform`, `cli_node`, `cli_command` and
|
|
9
|
+
* `cli_driver`). That is what lets a `cli_asset_generated` event tell `generate
|
|
10
|
+
* image` from `cover`, which share a route, and a `publish` from a `forge`,
|
|
11
|
+
* which share the signed-URL route.
|
|
12
|
+
*
|
|
13
|
+
* Only for api-server. GCS signed URLs and the npm registry get nothing from
|
|
14
|
+
* here — a signed PUT can refuse headers it did not sign for, and neither has
|
|
15
|
+
* any use for them.
|
|
16
|
+
*/
|
|
17
|
+
import { currentCliVersion } from '../update/cli-version.js';
|
|
18
|
+
import { currentCommand } from './command-context.js';
|
|
19
|
+
export const CLI_COMMAND_HEADER = 'X-Bitmagic-Cli-Command';
|
|
20
|
+
export const CLI_DRIVER_HEADER = 'X-Bitmagic-Cli-Driver';
|
|
21
|
+
let cachedUserAgent = null;
|
|
22
|
+
/** `bitmagic-cli/0.1.46 (darwin-arm64; node/v22.12.0)` — the shape api-server's context reader parses. */
|
|
23
|
+
export function userAgent() {
|
|
24
|
+
if (cachedUserAgent === null) {
|
|
25
|
+
const version = currentCliVersion() || 'unknown';
|
|
26
|
+
cachedUserAgent = `bitmagic-cli/${version} (${process.platform}-${process.arch}; node/${process.version})`;
|
|
27
|
+
}
|
|
28
|
+
return cachedUserAgent;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Spread these into the `headers` of every fetch aimed at api-server. Callers
|
|
32
|
+
* add their own `Authorization`, `Accept` and `Content-Type` after — nothing
|
|
33
|
+
* here collides with those.
|
|
34
|
+
*
|
|
35
|
+
* The command headers are present only once cli.ts has begun a command; a
|
|
36
|
+
* module exercised directly (a test, a future library caller) still identifies
|
|
37
|
+
* the CLI, just not the command.
|
|
38
|
+
*/
|
|
39
|
+
export function apiServerHeaders() {
|
|
40
|
+
const headers = { 'User-Agent': userAgent() };
|
|
41
|
+
const command = currentCommand();
|
|
42
|
+
if (command) {
|
|
43
|
+
headers[CLI_COMMAND_HEADER] = command.name;
|
|
44
|
+
headers[CLI_DRIVER_HEADER] = command.driver;
|
|
45
|
+
}
|
|
46
|
+
return headers;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=request-headers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-headers.js","sourceRoot":"","sources":["../../src/telemetry/request-headers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,MAAM,CAAC,MAAM,kBAAkB,GAAG,wBAAwB,CAAC;AAC3D,MAAM,CAAC,MAAM,iBAAiB,GAAG,uBAAuB,CAAC;AAEzD,IAAI,eAAe,GAAkB,IAAI,CAAC;AAE1C,0GAA0G;AAC1G,MAAM,UAAU,SAAS;IACvB,IAAI,eAAe,KAAK,IAAI,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAG,iBAAiB,EAAE,IAAI,SAAS,CAAC;QACjD,eAAe,GAAG,gBAAgB,OAAO,KAAK,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,IAAI,UAAU,OAAO,CAAC,OAAO,GAAG,CAAC;IAC7G,CAAC;IACD,OAAO,eAAe,CAAC;AACzB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB;IAC9B,MAAM,OAAO,GAA2B,EAAE,YAAY,EAAE,SAAS,EAAE,EAAE,CAAC;IACtE,MAAM,OAAO,GAAG,cAAc,EAAE,CAAC;IACjC,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,CAAC,kBAAkB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;QAC3C,OAAO,CAAC,iBAAiB,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAC9C,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
package/dist/update/check.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { EnvironmentName } from '../config/environments.js';
|
|
1
2
|
import { type UpdateCheckCache, type UpdateTag } from './notice.js';
|
|
2
3
|
export declare function updateCachePath(baseDir?: string): string;
|
|
3
4
|
/**
|
|
@@ -24,6 +25,19 @@ export declare function fetchTagVersion(tag: UpdateTag, fetchImpl?: typeof globa
|
|
|
24
25
|
* every command wait on npm to be told something it could just as well be told tomorrow.
|
|
25
26
|
*/
|
|
26
27
|
export declare function pendingUpdateNotice(currentVersion: string, baseDir?: string): string | null;
|
|
28
|
+
/**
|
|
29
|
+
* The single line bin.ts prints before a command runs — at most one install command, ever.
|
|
30
|
+
*
|
|
31
|
+
* The wrong-line notice wins over the newer-version one, and suppresses it. Both at once would
|
|
32
|
+
* print two different `npm i -g` commands in the same breath ("update to 0.1.45 on your line" next
|
|
33
|
+
* to "be on the other line"), which is the ambiguity this exists to end: a creator standing in a
|
|
34
|
+
* project should never have to choose which one they meant.
|
|
35
|
+
*
|
|
36
|
+
* `projectEnvironment` is the project's committed pin, or undefined outside a project and for one
|
|
37
|
+
* that has none. Undefined leaves the old behaviour exactly as it was: nothing here invents a line
|
|
38
|
+
* for a directory that never claimed one.
|
|
39
|
+
*/
|
|
40
|
+
export declare function startupNotice(currentVersion: string, projectEnvironment: EnvironmentName | undefined, baseDir?: string): string | null;
|
|
27
41
|
/**
|
|
28
42
|
* Refresh the cache if it is stale. Fire-and-forget: callers must not await this.
|
|
29
43
|
*
|
package/dist/update/check.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { defaultBaseDir } from '../config/credentials.js';
|
|
4
|
-
import { cliUpdateNotice, isCacheStale, updateTagFor, } from './notice.js';
|
|
4
|
+
import { cliLineMismatchNotice, cliUpdateNotice, isCacheStale, updateTagFor, } from './notice.js';
|
|
5
5
|
/** Public registry metadata, one document per dist-tag. No auth, no scope needed. */
|
|
6
6
|
const NPM_PACKAGE_URL = 'https://registry.npmjs.org/@bitmagic/cli';
|
|
7
7
|
/**
|
|
@@ -90,6 +90,22 @@ export function pendingUpdateNotice(currentVersion, baseDir) {
|
|
|
90
90
|
return null;
|
|
91
91
|
return cliUpdateNotice(currentVersion, cache.latest);
|
|
92
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* The single line bin.ts prints before a command runs — at most one install command, ever.
|
|
95
|
+
*
|
|
96
|
+
* The wrong-line notice wins over the newer-version one, and suppresses it. Both at once would
|
|
97
|
+
* print two different `npm i -g` commands in the same breath ("update to 0.1.45 on your line" next
|
|
98
|
+
* to "be on the other line"), which is the ambiguity this exists to end: a creator standing in a
|
|
99
|
+
* project should never have to choose which one they meant.
|
|
100
|
+
*
|
|
101
|
+
* `projectEnvironment` is the project's committed pin, or undefined outside a project and for one
|
|
102
|
+
* that has none. Undefined leaves the old behaviour exactly as it was: nothing here invents a line
|
|
103
|
+
* for a directory that never claimed one.
|
|
104
|
+
*/
|
|
105
|
+
export function startupNotice(currentVersion, projectEnvironment, baseDir) {
|
|
106
|
+
const mismatch = projectEnvironment === undefined ? null : cliLineMismatchNotice(currentVersion, projectEnvironment);
|
|
107
|
+
return mismatch ?? pendingUpdateNotice(currentVersion, baseDir);
|
|
108
|
+
}
|
|
93
109
|
/**
|
|
94
110
|
* Refresh the cache if it is stale. Fire-and-forget: callers must not await this.
|
|
95
111
|
*
|
package/dist/update/check.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/update/check.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/update/check.ts"],"names":[],"mappings":"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"}
|
package/dist/update/notice.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { EnvironmentName } from '../config/environments.js';
|
|
1
2
|
/** Which published line a build came from: a prerelease was installed from `@bitmagic/cli@dev`. */
|
|
2
3
|
export type UpdateTag = 'latest' | 'dev';
|
|
3
4
|
/**
|
|
@@ -11,6 +12,32 @@ export type UpdateTag = 'latest' | 'dev';
|
|
|
11
12
|
export declare function updateTagFor(version: string): UpdateTag;
|
|
12
13
|
/** How to install the line a build is already on, so a nudge never moves someone between lines. */
|
|
13
14
|
export declare function installCommandFor(tag: UpdateTag): string;
|
|
15
|
+
/**
|
|
16
|
+
* What to call a line in a sentence.
|
|
17
|
+
*
|
|
18
|
+
* The dev line says "prerelease" out loud because the only place this is used asks someone to
|
|
19
|
+
* install it, and portal/src/cli-install.ts is right that putting an untested build on the machine
|
|
20
|
+
* of someone who never opted into testing is the worse failure. Being asked to cross lines is
|
|
21
|
+
* legitimate — the project's environment is what wants it — but nobody should cross without knowing
|
|
22
|
+
* which side they are landing on.
|
|
23
|
+
*/
|
|
24
|
+
export declare function describeUpdateTag(tag: UpdateTag): string;
|
|
25
|
+
/**
|
|
26
|
+
* Which line a PROJECT's CLI should come from — the one answer to "dev or prod?" for anyone
|
|
27
|
+
* standing in a project directory, and the reason nobody should ever be asked.
|
|
28
|
+
*
|
|
29
|
+
* The project decides because the project is where the mismatch bites: new `/api/cli/v1` endpoints
|
|
30
|
+
* reach the dev api-server before production, so a prerelease run against `prod` calls endpoints
|
|
31
|
+
* that are not there yet, and a release run against `dev` is behind the engine that environment
|
|
32
|
+
* serves. `local` sits with `dev` — a localhost api-server is built from the same `main` a
|
|
33
|
+
* prerelease is cut from, so it is ahead of the release by construction.
|
|
34
|
+
*
|
|
35
|
+
* Note this is NOT `updateTagFor` applied to something else: that reads a version string and
|
|
36
|
+
* answers "which line is this build ON", which is a fact about the install. This answers "which
|
|
37
|
+
* line SHOULD it be on here", which is a fact about the project. The two disagreeing is exactly
|
|
38
|
+
* what `cliLineMismatchNotice` reports.
|
|
39
|
+
*/
|
|
40
|
+
export declare function updateTagForEnvironment(environment: EnvironmentName): UpdateTag;
|
|
14
41
|
/**
|
|
15
42
|
* How stale a cached npm answer may be before it is refreshed.
|
|
16
43
|
*
|
|
@@ -46,3 +73,16 @@ export declare function isCacheStale(cache: UpdateCheckCache | null, now: number
|
|
|
46
73
|
* 0.1.46-dev.3 to "update" to the older 0.1.45 release.
|
|
47
74
|
*/
|
|
48
75
|
export declare function cliUpdateNotice(currentVersion: string, latestVersion: string | null | undefined): string | null;
|
|
76
|
+
/**
|
|
77
|
+
* The line to say when the CLI running inside a project came from the other one.
|
|
78
|
+
*
|
|
79
|
+
* This is the one place the two lines are deliberately crossed, and it needs no version comparison
|
|
80
|
+
* to do it: whatever the numbers say, a project pinned to `dev` wants the dev line and a project
|
|
81
|
+
* pinned to `prod` wants the release. Version-driven nudges structurally cannot report this — a
|
|
82
|
+
* prerelease sitting in a `prod` project is *ahead* of the release tag, so `cliUpdateNotice` is
|
|
83
|
+
* correctly silent about a CLI that is on the wrong line entirely.
|
|
84
|
+
*
|
|
85
|
+
* `projectEnvironment` must be a project's committed pin, never a resolved environment: a one-off
|
|
86
|
+
* `--env` is a statement about this command, not about which CLI belongs in this directory.
|
|
87
|
+
*/
|
|
88
|
+
export declare function cliLineMismatchNotice(currentVersion: string, projectEnvironment: EnvironmentName): string | null;
|
package/dist/update/notice.js
CHANGED
|
@@ -14,6 +14,36 @@ export function updateTagFor(version) {
|
|
|
14
14
|
export function installCommandFor(tag) {
|
|
15
15
|
return tag === 'dev' ? 'npm i -g @bitmagic/cli@dev' : 'npm i -g @bitmagic/cli';
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* What to call a line in a sentence.
|
|
19
|
+
*
|
|
20
|
+
* The dev line says "prerelease" out loud because the only place this is used asks someone to
|
|
21
|
+
* install it, and portal/src/cli-install.ts is right that putting an untested build on the machine
|
|
22
|
+
* of someone who never opted into testing is the worse failure. Being asked to cross lines is
|
|
23
|
+
* legitimate — the project's environment is what wants it — but nobody should cross without knowing
|
|
24
|
+
* which side they are landing on.
|
|
25
|
+
*/
|
|
26
|
+
export function describeUpdateTag(tag) {
|
|
27
|
+
return tag === 'dev' ? 'dev (prerelease)' : 'release';
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Which line a PROJECT's CLI should come from — the one answer to "dev or prod?" for anyone
|
|
31
|
+
* standing in a project directory, and the reason nobody should ever be asked.
|
|
32
|
+
*
|
|
33
|
+
* The project decides because the project is where the mismatch bites: new `/api/cli/v1` endpoints
|
|
34
|
+
* reach the dev api-server before production, so a prerelease run against `prod` calls endpoints
|
|
35
|
+
* that are not there yet, and a release run against `dev` is behind the engine that environment
|
|
36
|
+
* serves. `local` sits with `dev` — a localhost api-server is built from the same `main` a
|
|
37
|
+
* prerelease is cut from, so it is ahead of the release by construction.
|
|
38
|
+
*
|
|
39
|
+
* Note this is NOT `updateTagFor` applied to something else: that reads a version string and
|
|
40
|
+
* answers "which line is this build ON", which is a fact about the install. This answers "which
|
|
41
|
+
* line SHOULD it be on here", which is a fact about the project. The two disagreeing is exactly
|
|
42
|
+
* what `cliLineMismatchNotice` reports.
|
|
43
|
+
*/
|
|
44
|
+
export function updateTagForEnvironment(environment) {
|
|
45
|
+
return environment === 'prod' ? 'latest' : 'dev';
|
|
46
|
+
}
|
|
17
47
|
/**
|
|
18
48
|
* How stale a cached npm answer may be before it is refreshed.
|
|
19
49
|
*
|
|
@@ -52,4 +82,27 @@ export function cliUpdateNotice(currentVersion, latestVersion) {
|
|
|
52
82
|
const install = installCommandFor(updateTagFor(currentVersion));
|
|
53
83
|
return `A newer bitmagic CLI is available: ${latestVersion} (you have ${currentVersion}). Run \`${install}\` to update.`;
|
|
54
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* The line to say when the CLI running inside a project came from the other one.
|
|
87
|
+
*
|
|
88
|
+
* This is the one place the two lines are deliberately crossed, and it needs no version comparison
|
|
89
|
+
* to do it: whatever the numbers say, a project pinned to `dev` wants the dev line and a project
|
|
90
|
+
* pinned to `prod` wants the release. Version-driven nudges structurally cannot report this — a
|
|
91
|
+
* prerelease sitting in a `prod` project is *ahead* of the release tag, so `cliUpdateNotice` is
|
|
92
|
+
* correctly silent about a CLI that is on the wrong line entirely.
|
|
93
|
+
*
|
|
94
|
+
* `projectEnvironment` must be a project's committed pin, never a resolved environment: a one-off
|
|
95
|
+
* `--env` is a statement about this command, not about which CLI belongs in this directory.
|
|
96
|
+
*/
|
|
97
|
+
export function cliLineMismatchNotice(currentVersion, projectEnvironment) {
|
|
98
|
+
if (typeof currentVersion !== 'string' || currentVersion === '')
|
|
99
|
+
return null;
|
|
100
|
+
const wanted = updateTagForEnvironment(projectEnvironment);
|
|
101
|
+
const installed = updateTagFor(currentVersion);
|
|
102
|
+
if (installed === wanted)
|
|
103
|
+
return null;
|
|
104
|
+
return (`This project is on the ${projectEnvironment} environment, so its CLI comes from the ` +
|
|
105
|
+
`${describeUpdateTag(wanted)} line — but you are running a ${describeUpdateTag(installed)} ` +
|
|
106
|
+
`build (${currentVersion}). Run \`${installCommandFor(wanted)}\`.`);
|
|
107
|
+
}
|
|
55
108
|
//# sourceMappingURL=notice.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notice.js","sourceRoot":"","sources":["../../src/update/notice.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"notice.js","sourceRoot":"","sources":["../../src/update/notice.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAKrE;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,OAAO,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC;AAClD,CAAC;AAED,mGAAmG;AACnG,MAAM,UAAU,iBAAiB,CAAC,GAAc;IAC9C,OAAO,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,4BAA4B,CAAC,CAAC,CAAC,wBAAwB,CAAC;AACjF,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAc;IAC9C,OAAO,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,SAAS,CAAC;AACxD,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,uBAAuB,CAAC,WAA4B;IAClE,OAAO,WAAW,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC;AACnD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAkB5D,MAAM,UAAU,YAAY,CAAC,KAA8B,EAAE,GAAW;IACtE,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAChC,+FAA+F;IAC/F,oBAAoB;IACpB,IAAI,KAAK,CAAC,SAAS,GAAG,GAAG;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,GAAG,GAAG,KAAK,CAAC,SAAS,IAAI,wBAAwB,CAAC;AAC3D,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,cAAsB,EAAE,aAAwC;IAC9F,IAAI,OAAO,aAAa,KAAK,QAAQ,IAAI,aAAa,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAC3E,IAAI,OAAO,cAAc,KAAK,QAAQ,IAAI,cAAc,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAC7E,IAAI,oBAAoB,CAAC,cAAc,EAAE,aAAa,CAAC,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC9E,8FAA8F;IAC9F,MAAM,OAAO,GAAG,iBAAiB,CAAC,YAAY,CAAC,cAAc,CAAC,CAAC,CAAC;IAChE,OAAO,sCAAsC,aAAa,cAAc,cAAc,YAAY,OAAO,eAAe,CAAC;AAC3H,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,qBAAqB,CACnC,cAAsB,EACtB,kBAAmC;IAEnC,IAAI,OAAO,cAAc,KAAK,QAAQ,IAAI,cAAc,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAC7E,MAAM,MAAM,GAAG,uBAAuB,CAAC,kBAAkB,CAAC,CAAC;IAC3D,MAAM,SAAS,GAAG,YAAY,CAAC,cAAc,CAAC,CAAC;IAC/C,IAAI,SAAS,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IACtC,OAAO,CACL,0BAA0B,kBAAkB,0CAA0C;QACtF,GAAG,iBAAiB,CAAC,MAAM,CAAC,iCAAiC,iBAAiB,CAAC,SAAS,CAAC,GAAG;QAC5F,UAAU,cAAc,YAAY,iBAAiB,CAAC,MAAM,CAAC,KAAK,CACnE,CAAC;AACJ,CAAC"}
|