@jr2/cli 0.1.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/LICENSE +21 -0
- package/README.md +18 -0
- package/bin/jr2.js +38 -0
- package/manifests/operator.yaml +6669 -0
- package/package.json +56 -0
- package/src/build.ts +1551 -0
- package/src/cli.ts +110 -0
- package/src/client.ts +219 -0
- package/src/commands/down.ts +104 -0
- package/src/commands/gc.ts +73 -0
- package/src/commands/init.ts +238 -0
- package/src/commands/kit.ts +141 -0
- package/src/commands/logs.ts +50 -0
- package/src/commands/run.ts +64 -0
- package/src/commands/runs.ts +19 -0
- package/src/commands/send.ts +83 -0
- package/src/commands/status.ts +90 -0
- package/src/commands/up.ts +1402 -0
- package/src/deploy.ts +592 -0
- package/src/env.ts +68 -0
- package/src/index.ts +11 -0
- package/src/instance.ts +105 -0
- package/src/kube.ts +809 -0
- package/src/nodes.ts +74 -0
- package/src/output.ts +211 -0
- package/src/repo-sweep.ts +134 -0
- package/src/run-id.ts +85 -0
- package/src/sse.ts +41 -0
- package/src/sweep.ts +232 -0
- package/src/typecheck.ts +75 -0
package/src/instance.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Instance addressing (ADR-0009/0019). Two concerns the HTTP client doesn't have: WHICH instance
|
|
2
|
+
// folder we are in, and WHERE its deployed orchestrator is.
|
|
3
|
+
//
|
|
4
|
+
// - `resolveRoot` walks up from cwd to the dir holding `jr2.config.ts` — the root marker (mirrors
|
|
5
|
+
// flue's `flue.config.ts`).
|
|
6
|
+
// - `resolveTarget` finds the orchestrator. `--url` / `JR2_URL` (+ `JR2_TOKEN`) short-circuits
|
|
7
|
+
// everything — the ingress-exposed/remote-caller escape hatch, no folder walk. Otherwise the
|
|
8
|
+
// DEPLOYMENT is addressed by the current kube context + the instance's namespace (`-n` >
|
|
9
|
+
// `config.name` > folder name): the Instance token is read from the in-cluster `jr2-instance`
|
|
10
|
+
// Secret and the `jr2-orchestrator` Service is port-forwarded for the life of the command —
|
|
11
|
+
// kube RBAC is the real gate, and no local state file can go stale (ADR-0019).
|
|
12
|
+
// - Every resolution prints its target on stderr, so ambient-context drift stays visible.
|
|
13
|
+
|
|
14
|
+
import { existsSync } from "node:fs";
|
|
15
|
+
import { basename, dirname, join } from "node:path";
|
|
16
|
+
import { loadConfig } from "@jr2/orchestrator";
|
|
17
|
+
import { INSTANCE_SECRET, kubectlKube, ORCHESTRATOR_PORT, ORCHESTRATOR_SERVICE } from "./kube.ts";
|
|
18
|
+
import { activity, type Io } from "./output.ts";
|
|
19
|
+
|
|
20
|
+
/** Walk up from `cwd` to the directory containing `jr2.config.ts`; `undefined` if there is none. */
|
|
21
|
+
export function findRoot(cwd: string): string | undefined {
|
|
22
|
+
let dir = cwd;
|
|
23
|
+
for (;;) {
|
|
24
|
+
if (existsSync(join(dir, "jr2.config.ts"))) return dir;
|
|
25
|
+
const parent = dirname(dir);
|
|
26
|
+
if (parent === dir) return undefined;
|
|
27
|
+
dir = parent;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** `findRoot`, for the callers that cannot proceed without one. */
|
|
32
|
+
export function resolveRoot(cwd: string): string {
|
|
33
|
+
const root = findRoot(cwd);
|
|
34
|
+
if (!root) throw new Error("not inside a jr2 instance — no jr2.config.ts found walking up from cwd");
|
|
35
|
+
return root;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Where to talk, as whom, and how to hang up (the port-forward lives only as long as the verb). */
|
|
39
|
+
export type Target = { url: string; token?: string; close?: () => void };
|
|
40
|
+
|
|
41
|
+
export type TargetOptions = {
|
|
42
|
+
/** Explicit orchestrator address (`--url`); with `JR2_URL` the escape hatch past the kube path. */
|
|
43
|
+
url?: string;
|
|
44
|
+
/** Kube namespace override (`-n`). Default: `config.name`, then the instance folder's name. */
|
|
45
|
+
namespace?: string;
|
|
46
|
+
/** Kube context override (`--context`). Default: the current kubectl context. */
|
|
47
|
+
context?: string;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** The parseArgs options every run-verb shares — how a target is addressed (ADR-0019). */
|
|
51
|
+
export const TARGET_ARGS = {
|
|
52
|
+
url: { type: "string" },
|
|
53
|
+
namespace: { type: "string", short: "n" },
|
|
54
|
+
context: { type: "string" },
|
|
55
|
+
} as const;
|
|
56
|
+
|
|
57
|
+
/** Pull the target-addressing flags out of a strict:false parseArgs `values` bag. */
|
|
58
|
+
export function targetOptions(values: Record<string, unknown>): TargetOptions {
|
|
59
|
+
return {
|
|
60
|
+
url: values.url as string | undefined,
|
|
61
|
+
namespace: values.namespace as string | undefined,
|
|
62
|
+
context: values.context as string | undefined,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Resolve the orchestrator a run-verb talks to (see the module doc for the full story). */
|
|
67
|
+
export async function resolveTarget(io: Io, opts: TargetOptions): Promise<Target> {
|
|
68
|
+
const explicit = opts.url ?? io.env.JR2_URL;
|
|
69
|
+
if (explicit) {
|
|
70
|
+
activity(io, `→ ${explicit}`);
|
|
71
|
+
return { url: explicit, token: io.env.JR2_TOKEN };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const root = resolveRoot(io.cwd);
|
|
75
|
+
const config = await loadConfig(root);
|
|
76
|
+
const namespace = opts.namespace ?? config?.name ?? basename(root);
|
|
77
|
+
const kube = io.kube ?? kubectlKube;
|
|
78
|
+
const context = opts.context ?? (await kube.currentContext());
|
|
79
|
+
if (!context) {
|
|
80
|
+
throw new Error("no kube context — `kind create cluster` (or point kubectl at one), then `jr2 up`");
|
|
81
|
+
}
|
|
82
|
+
activity(io, `→ context ${context} / namespace ${namespace}`);
|
|
83
|
+
|
|
84
|
+
const ctx = opts.context ? { context: opts.context } : {};
|
|
85
|
+
// Two faults, two fixes (ADR-0019). The Secret read is the first thing that crosses the network,
|
|
86
|
+
// so it is where a dead ADDRESS is caught; an empty answer from a cluster that DID reply is the
|
|
87
|
+
// identity being absent, which is the one `jr2 up` fixes. Telling a user to check a context that
|
|
88
|
+
// is correct because their VPN is down costs a debugging session.
|
|
89
|
+
let secret: string | undefined;
|
|
90
|
+
try {
|
|
91
|
+
secret =
|
|
92
|
+
io.env.JR2_TOKEN ??
|
|
93
|
+
(await kube.readSecret({ namespace, name: INSTANCE_SECRET, key: "JR2_INSTANCE_TOKEN", ...ctx }));
|
|
94
|
+
} catch (e) {
|
|
95
|
+
throw new Error(`cannot reach the cluster — context ${context}: ${(e as Error).message}`);
|
|
96
|
+
}
|
|
97
|
+
const token = secret;
|
|
98
|
+
if (!token) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`not deployed here — right context? (context ${context}, namespace ${namespace}; \`jr2 up\` deploys)`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
const fwd = await kube.portForward({ namespace, service: ORCHESTRATOR_SERVICE, port: ORCHESTRATOR_PORT, ...ctx });
|
|
104
|
+
return { url: fwd.url, token, close: fwd.close };
|
|
105
|
+
}
|