@barekey/cli 0.4.0 → 0.5.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 +53 -12
- package/bun.lock +9 -3
- package/dist/auth-provider.js +7 -4
- package/dist/command-utils.js +6 -6
- package/dist/commands/audit.d.ts +2 -0
- package/dist/commands/audit.js +47 -0
- package/dist/commands/auth.js +22 -7
- package/dist/commands/billing.d.ts +2 -0
- package/dist/commands/billing.js +62 -0
- package/dist/commands/env.js +157 -125
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +32 -0
- package/dist/commands/org.d.ts +2 -0
- package/dist/commands/org.js +85 -0
- package/dist/commands/project.d.ts +2 -0
- package/dist/commands/project.js +99 -0
- package/dist/commands/stage.d.ts +2 -0
- package/dist/commands/stage.js +125 -0
- package/dist/commands/target-prompts.d.ts +184 -0
- package/dist/commands/target-prompts.js +312 -0
- package/dist/commands/typegen.d.ts +2 -2
- package/dist/commands/typegen.js +57 -32
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/context/session-id.d.ts +11 -0
- package/dist/context/session-id.js +14 -0
- package/dist/contracts/index.d.ts +491 -0
- package/dist/contracts/index.js +307 -0
- package/dist/credentials-store.js +70 -11
- package/dist/http.d.ts +34 -0
- package/dist/http.js +56 -2
- package/dist/index.js +12 -0
- package/dist/runtime-config.js +14 -26
- package/dist/typegen/core.d.ts +45 -0
- package/dist/typegen/core.js +219 -0
- package/dist/types.d.ts +5 -3
- package/package.json +2 -2
- package/src/auth-provider.ts +8 -5
- package/src/command-utils.ts +6 -6
- package/src/commands/audit.ts +63 -0
- package/src/commands/auth.ts +32 -37
- package/src/commands/billing.ts +73 -0
- package/src/commands/env.ts +211 -218
- package/src/commands/init.ts +47 -0
- package/src/commands/org.ts +104 -0
- package/src/commands/project.ts +130 -0
- package/src/commands/stage.ts +167 -0
- package/src/commands/target-prompts.ts +357 -0
- package/src/commands/typegen.ts +71 -45
- package/src/constants.ts +1 -1
- package/src/context/session-id.ts +14 -0
- package/src/contracts/index.ts +370 -0
- package/src/credentials-store.ts +86 -12
- package/src/http.ts +78 -2
- package/src/index.ts +12 -0
- package/src/runtime-config.ts +19 -32
- package/src/typegen/core.ts +311 -0
- package/src/types.ts +5 -3
- package/test/command-utils.test.ts +47 -0
- package/test/credentials-store.test.ts +40 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type TypegenManifest } from "../contracts/index.js";
|
|
2
|
+
export type CliTypegenMode = "semantic" | "minimal";
|
|
3
|
+
export type CliRuntimeMode = "centralized" | "standalone";
|
|
4
|
+
export type CliTypegenResult = {
|
|
5
|
+
written: boolean;
|
|
6
|
+
path: string;
|
|
7
|
+
serverPath: string;
|
|
8
|
+
publicPath: string;
|
|
9
|
+
manifestVersion: string;
|
|
10
|
+
};
|
|
11
|
+
type TypegenIdentity = {
|
|
12
|
+
baseUrl: string | null;
|
|
13
|
+
orgSlug: string;
|
|
14
|
+
projectSlug: string;
|
|
15
|
+
stageSlug: string;
|
|
16
|
+
typegenMode: CliTypegenMode;
|
|
17
|
+
runtimeMode: CliRuntimeMode;
|
|
18
|
+
localEnvRoot: string | null;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Renders the generated SDK declaration files for one manifest.
|
|
22
|
+
*
|
|
23
|
+
* @param manifest The decoded typegen manifest.
|
|
24
|
+
* @param mode The requested typegen mode.
|
|
25
|
+
* @returns The rendered server and public declaration contents.
|
|
26
|
+
* @remarks The CLI owns this rendering so it no longer depends on SDK runtime helpers.
|
|
27
|
+
* @lastModified 2026-03-19
|
|
28
|
+
* @author GPT-5.4
|
|
29
|
+
*/
|
|
30
|
+
export declare function renderGeneratedTypesForManifest(manifest: TypegenManifest, mode: CliTypegenMode): {
|
|
31
|
+
serverContents: string;
|
|
32
|
+
publicContents: string;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Writes generated SDK declaration files into the installed `@barekey/sdk` package.
|
|
36
|
+
*
|
|
37
|
+
* @param manifest The decoded typegen manifest.
|
|
38
|
+
* @param identity The manifest identity used for metadata freshness.
|
|
39
|
+
* @returns The typegen write result.
|
|
40
|
+
* @remarks This preserves the existing generated file locations without importing the SDK package at runtime.
|
|
41
|
+
* @lastModified 2026-03-19
|
|
42
|
+
* @author GPT-5.4
|
|
43
|
+
*/
|
|
44
|
+
export declare function writeInstalledSdkGeneratedTypes(manifest: TypegenManifest, identity: TypegenIdentity): Promise<CliTypegenResult>;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { access, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { Schema } from "effect";
|
|
5
|
+
import { TypegenManifestSchema } from "../contracts/index.js";
|
|
6
|
+
const MANIFEST_VERSION_PATTERN = /\/\* barekey-manifest-version: ([^\n]+) \*\//;
|
|
7
|
+
const TypegenMetadataSchema = Schema.Struct({
|
|
8
|
+
version: Schema.Number,
|
|
9
|
+
last: Schema.String,
|
|
10
|
+
baseUrl: Schema.NullOr(Schema.String),
|
|
11
|
+
orgSlug: Schema.NullOr(Schema.String),
|
|
12
|
+
projectSlug: Schema.NullOr(Schema.String),
|
|
13
|
+
stageSlug: Schema.NullOr(Schema.String),
|
|
14
|
+
typegenMode: Schema.NullOr(Schema.Literal("semantic", "minimal")),
|
|
15
|
+
runtimeMode: Schema.NullOr(Schema.Literal("centralized", "standalone")),
|
|
16
|
+
localEnvRoot: Schema.NullOr(Schema.String),
|
|
17
|
+
});
|
|
18
|
+
function renderLinearMilestones(milestones) {
|
|
19
|
+
if (milestones.length === 0) {
|
|
20
|
+
return "readonly []";
|
|
21
|
+
}
|
|
22
|
+
return `readonly [${milestones
|
|
23
|
+
.map((milestone) => `readonly [${JSON.stringify(milestone.at)}, ${String(milestone.percentage)}]`)
|
|
24
|
+
.join(", ")}]`;
|
|
25
|
+
}
|
|
26
|
+
function renderRolloutMetadataType(row) {
|
|
27
|
+
const renderedMilestones = renderLinearMilestones(row.rolloutMilestones ?? []);
|
|
28
|
+
if (row.rolloutFunction === "step") {
|
|
29
|
+
return `Step<${renderedMilestones}>`;
|
|
30
|
+
}
|
|
31
|
+
if (row.rolloutFunction === "ease_in_out") {
|
|
32
|
+
return `EaseInOut<${renderedMilestones}>`;
|
|
33
|
+
}
|
|
34
|
+
return `Linear<${renderedMilestones}>`;
|
|
35
|
+
}
|
|
36
|
+
function renderVariableType(row, mode) {
|
|
37
|
+
if (mode === "minimal") {
|
|
38
|
+
return row.typeScriptType;
|
|
39
|
+
}
|
|
40
|
+
const descriptor = `{ Type: ${row.typeScriptType}; Kind: ${JSON.stringify(row.kind)}; Visibility: ${JSON.stringify(row.visibility)}; Rollout: ${row.kind === "rollout" ? renderRolloutMetadataType(row) : "never"} }`;
|
|
41
|
+
return `Env<${descriptor}>`;
|
|
42
|
+
}
|
|
43
|
+
function buildGeneratedTypesContents(manifest, input) {
|
|
44
|
+
const mapLines = manifest.variables
|
|
45
|
+
.slice()
|
|
46
|
+
.filter(input.include)
|
|
47
|
+
.sort((left, right) => left.name.localeCompare(right.name))
|
|
48
|
+
.map((row) => ` ${JSON.stringify(row.name)}: ${renderVariableType(row, input.mode)};`)
|
|
49
|
+
.join("\n");
|
|
50
|
+
const importLine = input.mode === "semantic"
|
|
51
|
+
? `import type { EaseInOut, Env, Linear, Step } from "${input.typeModulePath}";\n\n`
|
|
52
|
+
: "";
|
|
53
|
+
return `/* eslint-disable */\n/* This file is generated by barekey typegen. */\n/* barekey-manifest-version: ${manifest.manifestVersion} */\n\n${importLine}declare module "${input.declaredModulePath}" {\n interface ${input.interfaceName} {\n${mapLines.length > 0 ? mapLines : ""}\n }\n}\n\nexport {};\n`;
|
|
54
|
+
}
|
|
55
|
+
function readManifestVersion(contents) {
|
|
56
|
+
return contents?.match(MANIFEST_VERSION_PATTERN)?.[1]?.trim() ?? null;
|
|
57
|
+
}
|
|
58
|
+
async function readTextFile(filePath) {
|
|
59
|
+
try {
|
|
60
|
+
return await readFile(filePath, "utf8");
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
const nodeError = error;
|
|
64
|
+
if (nodeError?.code === "ENOENT") {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function writeTextFileAtomic(filePath, value) {
|
|
71
|
+
const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
72
|
+
try {
|
|
73
|
+
await writeFile(temporaryPath, value, "utf8");
|
|
74
|
+
await rename(temporaryPath, filePath);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
try {
|
|
78
|
+
await unlink(temporaryPath);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// Best-effort cleanup only.
|
|
82
|
+
}
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function resolveInstalledSdkTypegenTarget() {
|
|
87
|
+
const require = createRequire(path.join(process.cwd(), "__barekey_typegen__.cjs"));
|
|
88
|
+
let resolvedEntrypoint;
|
|
89
|
+
try {
|
|
90
|
+
resolvedEntrypoint = require.resolve("@barekey/sdk/package.json");
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
let current = path.dirname(resolvedEntrypoint);
|
|
96
|
+
while (true) {
|
|
97
|
+
const candidatePackageJson = path.join(current, "package.json");
|
|
98
|
+
const rawPackageJson = await readTextFile(candidatePackageJson);
|
|
99
|
+
if (rawPackageJson !== null) {
|
|
100
|
+
try {
|
|
101
|
+
const parsed = JSON.parse(rawPackageJson);
|
|
102
|
+
if (parsed.name === "@barekey/sdk") {
|
|
103
|
+
const serverGeneratedTypesPath = path.join(current, "generated.server.d.ts");
|
|
104
|
+
const publicGeneratedTypesPath = path.join(current, "generated.public.d.ts");
|
|
105
|
+
await access(serverGeneratedTypesPath);
|
|
106
|
+
await access(publicGeneratedTypesPath);
|
|
107
|
+
return {
|
|
108
|
+
packageRoot: current,
|
|
109
|
+
serverGeneratedTypesPath,
|
|
110
|
+
publicGeneratedTypesPath,
|
|
111
|
+
typegenMetadataPath: path.join(current, "typegen.json"),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// Keep walking upward.
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const parent = path.dirname(current);
|
|
120
|
+
if (parent === current) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
current = parent;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function buildTypegenMetadataContents(lastGeneratedAt, identity) {
|
|
127
|
+
return `${JSON.stringify({
|
|
128
|
+
version: 1,
|
|
129
|
+
last: lastGeneratedAt.toISOString(),
|
|
130
|
+
baseUrl: identity.baseUrl,
|
|
131
|
+
orgSlug: identity.orgSlug,
|
|
132
|
+
projectSlug: identity.projectSlug,
|
|
133
|
+
stageSlug: identity.stageSlug,
|
|
134
|
+
typegenMode: identity.typegenMode,
|
|
135
|
+
runtimeMode: identity.runtimeMode,
|
|
136
|
+
localEnvRoot: identity.localEnvRoot,
|
|
137
|
+
}, null, 2)}\n`;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Renders the generated SDK declaration files for one manifest.
|
|
141
|
+
*
|
|
142
|
+
* @param manifest The decoded typegen manifest.
|
|
143
|
+
* @param mode The requested typegen mode.
|
|
144
|
+
* @returns The rendered server and public declaration contents.
|
|
145
|
+
* @remarks The CLI owns this rendering so it no longer depends on SDK runtime helpers.
|
|
146
|
+
* @lastModified 2026-03-19
|
|
147
|
+
* @author GPT-5.4
|
|
148
|
+
*/
|
|
149
|
+
export function renderGeneratedTypesForManifest(manifest, mode) {
|
|
150
|
+
const decodedManifest = Schema.decodeUnknownSync(TypegenManifestSchema)(manifest);
|
|
151
|
+
return {
|
|
152
|
+
serverContents: buildGeneratedTypesContents(decodedManifest, {
|
|
153
|
+
mode,
|
|
154
|
+
typeModulePath: "./dist/types.js",
|
|
155
|
+
declaredModulePath: "./dist/types.js",
|
|
156
|
+
interfaceName: "BarekeyGeneratedTypeMap",
|
|
157
|
+
include: () => true,
|
|
158
|
+
}),
|
|
159
|
+
publicContents: buildGeneratedTypesContents(decodedManifest, {
|
|
160
|
+
mode,
|
|
161
|
+
typeModulePath: "./dist/public-types.js",
|
|
162
|
+
declaredModulePath: "./dist/public-types.js",
|
|
163
|
+
interfaceName: "BarekeyPublicGeneratedTypeMap",
|
|
164
|
+
include: (row) => row.visibility === "public",
|
|
165
|
+
}),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Writes generated SDK declaration files into the installed `@barekey/sdk` package.
|
|
170
|
+
*
|
|
171
|
+
* @param manifest The decoded typegen manifest.
|
|
172
|
+
* @param identity The manifest identity used for metadata freshness.
|
|
173
|
+
* @returns The typegen write result.
|
|
174
|
+
* @remarks This preserves the existing generated file locations without importing the SDK package at runtime.
|
|
175
|
+
* @lastModified 2026-03-19
|
|
176
|
+
* @author GPT-5.4
|
|
177
|
+
*/
|
|
178
|
+
export async function writeInstalledSdkGeneratedTypes(manifest, identity) {
|
|
179
|
+
const target = await resolveInstalledSdkTypegenTarget();
|
|
180
|
+
if (target === null) {
|
|
181
|
+
return {
|
|
182
|
+
written: false,
|
|
183
|
+
path: "",
|
|
184
|
+
serverPath: "",
|
|
185
|
+
publicPath: "",
|
|
186
|
+
manifestVersion: manifest.manifestVersion,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const [existingServerContents, existingPublicContents] = await Promise.all([
|
|
190
|
+
readTextFile(target.serverGeneratedTypesPath),
|
|
191
|
+
readTextFile(target.publicGeneratedTypesPath),
|
|
192
|
+
]);
|
|
193
|
+
const rendered = renderGeneratedTypesForManifest(manifest, identity.typegenMode);
|
|
194
|
+
if (existingServerContents === rendered.serverContents &&
|
|
195
|
+
existingPublicContents === rendered.publicContents &&
|
|
196
|
+
readManifestVersion(existingServerContents) === manifest.manifestVersion &&
|
|
197
|
+
readManifestVersion(existingPublicContents) === manifest.manifestVersion) {
|
|
198
|
+
await writeTextFileAtomic(target.typegenMetadataPath, buildTypegenMetadataContents(new Date(), identity));
|
|
199
|
+
return {
|
|
200
|
+
written: false,
|
|
201
|
+
path: target.serverGeneratedTypesPath,
|
|
202
|
+
serverPath: target.serverGeneratedTypesPath,
|
|
203
|
+
publicPath: target.publicGeneratedTypesPath,
|
|
204
|
+
manifestVersion: manifest.manifestVersion,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
await Promise.all([
|
|
208
|
+
writeTextFileAtomic(target.serverGeneratedTypesPath, rendered.serverContents),
|
|
209
|
+
writeTextFileAtomic(target.publicGeneratedTypesPath, rendered.publicContents),
|
|
210
|
+
]);
|
|
211
|
+
await writeTextFileAtomic(target.typegenMetadataPath, buildTypegenMetadataContents(new Date(), identity));
|
|
212
|
+
return {
|
|
213
|
+
written: true,
|
|
214
|
+
path: target.serverGeneratedTypesPath,
|
|
215
|
+
serverPath: target.serverGeneratedTypesPath,
|
|
216
|
+
publicPath: target.publicGeneratedTypesPath,
|
|
217
|
+
manifestVersion: manifest.manifestVersion,
|
|
218
|
+
};
|
|
219
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -4,10 +4,12 @@ export type CliCredentials = {
|
|
|
4
4
|
accessTokenExpiresAtMs: number;
|
|
5
5
|
refreshTokenExpiresAtMs: number;
|
|
6
6
|
clerkUserId: string;
|
|
7
|
-
orgId: string;
|
|
8
|
-
orgSlug: string;
|
|
7
|
+
orgId: string | null;
|
|
8
|
+
orgSlug: string | null;
|
|
9
|
+
lastOrgId?: string | null;
|
|
10
|
+
lastOrgSlug?: string | null;
|
|
9
11
|
};
|
|
10
12
|
export type CliConfig = {
|
|
11
13
|
baseUrl: string;
|
|
12
|
-
|
|
14
|
+
activeSessionId: string;
|
|
13
15
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barekey/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Barekey command line interface",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"packageManager": "bun@1.2.22",
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@clack/prompts": "^0.11.0",
|
|
18
|
-
"@barekey/sdk": "^0.5.0",
|
|
19
18
|
"commander": "^14.0.1",
|
|
19
|
+
"effect": "3.19.19",
|
|
20
20
|
"open": "^10.2.0",
|
|
21
21
|
"picocolors": "^1.1.1"
|
|
22
22
|
},
|
package/src/auth-provider.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { postJson } from "./http.js";
|
|
2
2
|
import type { CliCredentials } from "./types.js";
|
|
3
3
|
import { loadConfig, loadCredentials, saveCredentials } from "./credentials-store.js";
|
|
4
|
+
import { buildSessionId } from "./context/session-id.js";
|
|
4
5
|
|
|
5
6
|
type CliAuthProvider = {
|
|
6
7
|
getAccessToken(): Promise<string>;
|
|
@@ -13,7 +14,7 @@ export function createCliAuthProvider(): CliAuthProvider {
|
|
|
13
14
|
|
|
14
15
|
async function readCurrentCredentials(): Promise<{
|
|
15
16
|
baseUrl: string;
|
|
16
|
-
|
|
17
|
+
sessionId: string;
|
|
17
18
|
credentials: CliCredentials;
|
|
18
19
|
}> {
|
|
19
20
|
const config = await loadConfig();
|
|
@@ -21,7 +22,7 @@ export function createCliAuthProvider(): CliAuthProvider {
|
|
|
21
22
|
throw new Error("Not logged in. Run barekey login first.");
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
const credentials = await loadCredentials(config.
|
|
25
|
+
const credentials = await loadCredentials(config.activeSessionId);
|
|
25
26
|
if (credentials === null) {
|
|
26
27
|
throw new Error("CLI credentials are missing. Run barekey login again.");
|
|
27
28
|
}
|
|
@@ -30,13 +31,13 @@ export function createCliAuthProvider(): CliAuthProvider {
|
|
|
30
31
|
|
|
31
32
|
return {
|
|
32
33
|
baseUrl: config.baseUrl,
|
|
33
|
-
|
|
34
|
+
sessionId: config.activeSessionId,
|
|
34
35
|
credentials,
|
|
35
36
|
};
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
async function refreshIfNeeded(): Promise<CliCredentials> {
|
|
39
|
-
const { baseUrl,
|
|
40
|
+
const { baseUrl, credentials } = await readCurrentCredentials();
|
|
40
41
|
const now = Date.now();
|
|
41
42
|
if (!forceRefresh && credentials.accessTokenExpiresAtMs > now + 10_000) {
|
|
42
43
|
return credentials;
|
|
@@ -66,9 +67,11 @@ export function createCliAuthProvider(): CliAuthProvider {
|
|
|
66
67
|
clerkUserId: refreshed.clerkUserId,
|
|
67
68
|
orgId: refreshed.orgId,
|
|
68
69
|
orgSlug: refreshed.orgSlug,
|
|
70
|
+
lastOrgId: refreshed.orgId,
|
|
71
|
+
lastOrgSlug: refreshed.orgSlug,
|
|
69
72
|
};
|
|
70
73
|
|
|
71
|
-
await saveCredentials(
|
|
74
|
+
await saveCredentials(buildSessionId(baseUrl, refreshed.clerkUserId), nextCredentials);
|
|
72
75
|
cachedCredentials = nextCredentials;
|
|
73
76
|
forceRefresh = false;
|
|
74
77
|
return nextCredentials;
|
package/src/command-utils.ts
CHANGED
|
@@ -49,14 +49,14 @@ export async function requireLocalSession(): Promise<LocalSession> {
|
|
|
49
49
|
throw new Error("Not logged in. Run barekey auth login first.");
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
const credentials = await loadCredentials(config.
|
|
52
|
+
const credentials = await loadCredentials(config.activeSessionId);
|
|
53
53
|
if (credentials === null) {
|
|
54
54
|
throw new Error("Saved credentials not found. Run barekey auth login again.");
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
return {
|
|
58
58
|
baseUrl: config.baseUrl,
|
|
59
|
-
accountId: config.
|
|
59
|
+
accountId: config.activeSessionId,
|
|
60
60
|
credentials,
|
|
61
61
|
};
|
|
62
62
|
}
|
|
@@ -74,14 +74,14 @@ export async function resolveTarget(
|
|
|
74
74
|
|
|
75
75
|
const projectSlug = options.project?.trim() || runtime?.config.project || "";
|
|
76
76
|
const stageSlug = options.stage?.trim() || runtime?.config.environment || "";
|
|
77
|
-
const orgSlug = options.org?.trim() || runtime?.config.org ||
|
|
77
|
+
const orgSlug = options.org?.trim() || runtime?.config.org || "";
|
|
78
78
|
|
|
79
|
-
if (!isStandalone && (projectSlug.length === 0 || stageSlug.length === 0)) {
|
|
79
|
+
if (!isStandalone && (orgSlug.length === 0 || projectSlug.length === 0 || stageSlug.length === 0)) {
|
|
80
80
|
const hint = runtime
|
|
81
|
-
? `Found ${runtime.path} but project/environment is incomplete.`
|
|
81
|
+
? `Found ${runtime.path} but organization/project/environment is incomplete.`
|
|
82
82
|
: "No barekey.json found in current directory tree.";
|
|
83
83
|
throw new Error(
|
|
84
|
-
`${hint}
|
|
84
|
+
`${hint} Run barekey init or pass --org/--project/--stage.`,
|
|
85
85
|
);
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
|
|
3
|
+
import { createCliAuthProvider } from "../auth-provider.js";
|
|
4
|
+
import { requireLocalSession, toJsonOutput, type EnvTargetOptions } from "../command-utils.js";
|
|
5
|
+
import { AuditListResponseSchema } from "../contracts/index.js";
|
|
6
|
+
import { postJson } from "../http.js";
|
|
7
|
+
import { promptForOrganizationSlug } from "./target-prompts.js";
|
|
8
|
+
|
|
9
|
+
async function runAuditList(
|
|
10
|
+
options: EnvTargetOptions & {
|
|
11
|
+
limit?: string;
|
|
12
|
+
json?: boolean;
|
|
13
|
+
},
|
|
14
|
+
) {
|
|
15
|
+
const local = await requireLocalSession();
|
|
16
|
+
const authProvider = createCliAuthProvider();
|
|
17
|
+
const accessToken = await authProvider.getAccessToken();
|
|
18
|
+
const orgSlug = await promptForOrganizationSlug(options.org);
|
|
19
|
+
const limit = options.limit ? Number.parseInt(options.limit, 10) : 25;
|
|
20
|
+
|
|
21
|
+
const response = await postJson({
|
|
22
|
+
baseUrl: local.baseUrl,
|
|
23
|
+
path: "/v1/cli/audit/list",
|
|
24
|
+
accessToken,
|
|
25
|
+
payload: {
|
|
26
|
+
orgSlug,
|
|
27
|
+
projectSlug: options.project?.trim() || null,
|
|
28
|
+
limit: Number.isFinite(limit) ? limit : 25,
|
|
29
|
+
},
|
|
30
|
+
schema: AuditListResponseSchema,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (options.json) {
|
|
34
|
+
toJsonOutput(true, response);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (response.items.length === 0) {
|
|
39
|
+
console.log("No audit events found.");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (const item of response.items) {
|
|
44
|
+
console.log(`${new Date(item.occurredAtMs).toISOString()} ${item.category} ${item.title}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function registerAuditCommands(program: Command): void {
|
|
49
|
+
const audit = program.command("audit").description("Audit history");
|
|
50
|
+
|
|
51
|
+
audit
|
|
52
|
+
.command("list")
|
|
53
|
+
.description("List recent audit events")
|
|
54
|
+
.option("--org <slug>", "Organization slug")
|
|
55
|
+
.option("--project <slug>", "Project slug")
|
|
56
|
+
.option("--limit <count>", "Maximum number of events", "25")
|
|
57
|
+
.option("--json", "Machine-readable output", false)
|
|
58
|
+
.action(
|
|
59
|
+
async (options: EnvTargetOptions & { limit?: string; json?: boolean }) => {
|
|
60
|
+
await runAuditList(options);
|
|
61
|
+
},
|
|
62
|
+
);
|
|
63
|
+
}
|
package/src/commands/auth.ts
CHANGED
|
@@ -7,12 +7,19 @@ import pc from "picocolors";
|
|
|
7
7
|
import open from "open";
|
|
8
8
|
|
|
9
9
|
import { createCliAuthProvider } from "../auth-provider.js";
|
|
10
|
+
import { buildSessionId } from "../context/session-id.js";
|
|
10
11
|
import {
|
|
11
12
|
clearConfig,
|
|
12
13
|
deleteCredentials,
|
|
13
14
|
saveConfig,
|
|
14
15
|
saveCredentials,
|
|
15
16
|
} from "../credentials-store.js";
|
|
17
|
+
import {
|
|
18
|
+
CliSessionResponseSchema,
|
|
19
|
+
DevicePollResponseSchema,
|
|
20
|
+
DeviceStartResponseSchema,
|
|
21
|
+
RefreshResponseSchema,
|
|
22
|
+
} from "../contracts/index.js";
|
|
16
23
|
import { getJson, postJson } from "../http.js";
|
|
17
24
|
import { requireLocalSession, resolveBaseUrl, toJsonOutput } from "../command-utils.js";
|
|
18
25
|
|
|
@@ -60,18 +67,13 @@ async function runLogin(options: { baseUrl?: string }): Promise<void> {
|
|
|
60
67
|
let pollActive = false;
|
|
61
68
|
|
|
62
69
|
try {
|
|
63
|
-
const started = await postJson
|
|
64
|
-
deviceCode: string;
|
|
65
|
-
userCode: string;
|
|
66
|
-
verificationUri: string;
|
|
67
|
-
intervalSec: number;
|
|
68
|
-
expiresInSec: number;
|
|
69
|
-
}>({
|
|
70
|
+
const started = await postJson({
|
|
70
71
|
baseUrl,
|
|
71
72
|
path: "/v1/cli/device/start",
|
|
72
73
|
payload: {
|
|
73
74
|
clientName: resolveClientName(),
|
|
74
75
|
},
|
|
76
|
+
schema: DeviceStartResponseSchema,
|
|
75
77
|
});
|
|
76
78
|
const verificationUri = resolveVerificationUri(baseUrl, started.verificationUri);
|
|
77
79
|
|
|
@@ -93,27 +95,13 @@ async function runLogin(options: { baseUrl?: string }): Promise<void> {
|
|
|
93
95
|
pollActive = true;
|
|
94
96
|
|
|
95
97
|
while (Date.now() < expiresAtMs) {
|
|
96
|
-
const poll = await postJson
|
|
97
|
-
| {
|
|
98
|
-
status: "pending";
|
|
99
|
-
intervalSec: number;
|
|
100
|
-
}
|
|
101
|
-
| {
|
|
102
|
-
status: "approved";
|
|
103
|
-
accessToken: string;
|
|
104
|
-
refreshToken: string;
|
|
105
|
-
accessTokenExpiresAtMs: number;
|
|
106
|
-
refreshTokenExpiresAtMs: number;
|
|
107
|
-
orgId: string;
|
|
108
|
-
orgSlug: string;
|
|
109
|
-
clerkUserId: string;
|
|
110
|
-
}
|
|
111
|
-
>({
|
|
98
|
+
const poll = await postJson({
|
|
112
99
|
baseUrl,
|
|
113
100
|
path: "/v1/cli/device/poll",
|
|
114
101
|
payload: {
|
|
115
102
|
deviceCode: started.deviceCode,
|
|
116
103
|
},
|
|
104
|
+
schema: DevicePollResponseSchema,
|
|
117
105
|
});
|
|
118
106
|
|
|
119
107
|
if (poll.status === "pending") {
|
|
@@ -121,24 +109,36 @@ async function runLogin(options: { baseUrl?: string }): Promise<void> {
|
|
|
121
109
|
continue;
|
|
122
110
|
}
|
|
123
111
|
|
|
124
|
-
const
|
|
125
|
-
await saveCredentials(accountId, {
|
|
112
|
+
const refreshed = RefreshResponseSchema.make({
|
|
126
113
|
accessToken: poll.accessToken,
|
|
127
114
|
refreshToken: poll.refreshToken,
|
|
128
115
|
accessTokenExpiresAtMs: poll.accessTokenExpiresAtMs,
|
|
129
116
|
refreshTokenExpiresAtMs: poll.refreshTokenExpiresAtMs,
|
|
130
|
-
clerkUserId: poll.clerkUserId,
|
|
131
117
|
orgId: poll.orgId,
|
|
132
118
|
orgSlug: poll.orgSlug,
|
|
119
|
+
clerkUserId: poll.clerkUserId,
|
|
120
|
+
});
|
|
121
|
+
const sessionId = buildSessionId(baseUrl, refreshed.clerkUserId);
|
|
122
|
+
|
|
123
|
+
await saveCredentials(sessionId, {
|
|
124
|
+
accessToken: refreshed.accessToken,
|
|
125
|
+
refreshToken: refreshed.refreshToken,
|
|
126
|
+
accessTokenExpiresAtMs: refreshed.accessTokenExpiresAtMs,
|
|
127
|
+
refreshTokenExpiresAtMs: refreshed.refreshTokenExpiresAtMs,
|
|
128
|
+
clerkUserId: refreshed.clerkUserId,
|
|
129
|
+
orgId: refreshed.orgId,
|
|
130
|
+
orgSlug: refreshed.orgSlug,
|
|
131
|
+
lastOrgId: refreshed.orgId,
|
|
132
|
+
lastOrgSlug: refreshed.orgSlug,
|
|
133
133
|
});
|
|
134
134
|
await saveConfig({
|
|
135
135
|
baseUrl,
|
|
136
|
-
|
|
136
|
+
activeSessionId: sessionId,
|
|
137
137
|
});
|
|
138
138
|
|
|
139
139
|
pollSpinner.stop("Login approved");
|
|
140
140
|
pollActive = false;
|
|
141
|
-
outro(`Logged in as ${pc.bold(
|
|
141
|
+
outro(`Logged in as ${pc.bold(refreshed.clerkUserId)}.`);
|
|
142
142
|
return;
|
|
143
143
|
}
|
|
144
144
|
|
|
@@ -158,7 +158,7 @@ async function runLogin(options: { baseUrl?: string }): Promise<void> {
|
|
|
158
158
|
|
|
159
159
|
async function runLogout(): Promise<void> {
|
|
160
160
|
const local = await requireLocalSession();
|
|
161
|
-
await postJson
|
|
161
|
+
await postJson({
|
|
162
162
|
baseUrl: local.baseUrl,
|
|
163
163
|
path: "/v1/cli/logout",
|
|
164
164
|
payload: {
|
|
@@ -174,15 +174,11 @@ async function runWhoami(options: { json?: boolean }): Promise<void> {
|
|
|
174
174
|
const local = await requireLocalSession();
|
|
175
175
|
const authProvider = createCliAuthProvider();
|
|
176
176
|
const accessToken = await authProvider.getAccessToken();
|
|
177
|
-
const session = await getJson
|
|
178
|
-
clerkUserId: string;
|
|
179
|
-
orgId: string;
|
|
180
|
-
orgSlug: string;
|
|
181
|
-
source: "clerk" | "cli";
|
|
182
|
-
}>({
|
|
177
|
+
const session = await getJson({
|
|
183
178
|
baseUrl: local.baseUrl,
|
|
184
179
|
path: "/v1/cli/session",
|
|
185
180
|
accessToken,
|
|
181
|
+
schema: CliSessionResponseSchema,
|
|
186
182
|
});
|
|
187
183
|
|
|
188
184
|
if (options.json) {
|
|
@@ -191,7 +187,7 @@ async function runWhoami(options: { json?: boolean }): Promise<void> {
|
|
|
191
187
|
}
|
|
192
188
|
|
|
193
189
|
console.log(`${pc.bold("User")}: ${session.clerkUserId}`);
|
|
194
|
-
console.log(`${pc.bold("
|
|
190
|
+
console.log(`${pc.bold("Current org")}: ${session.orgSlug}`);
|
|
195
191
|
console.log(`${pc.bold("Source")}: ${session.source}`);
|
|
196
192
|
}
|
|
197
193
|
|
|
@@ -221,7 +217,6 @@ export function registerAuthCommands(program: Command): void {
|
|
|
221
217
|
await runWhoami(options);
|
|
222
218
|
});
|
|
223
219
|
|
|
224
|
-
// Backward-compatible top-level auth aliases.
|
|
225
220
|
program
|
|
226
221
|
.command("login")
|
|
227
222
|
.description("Alias for barekey auth login")
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
|
|
3
|
+
import { createCliAuthProvider } from "../auth-provider.js";
|
|
4
|
+
import { requireLocalSession, toJsonOutput, type EnvTargetOptions } from "../command-utils.js";
|
|
5
|
+
import { BillingCatalogResponseSchema, BillingStatusResponseSchema } from "../contracts/index.js";
|
|
6
|
+
import { getJson, postJson } from "../http.js";
|
|
7
|
+
import { promptForOrganizationSlug } from "./target-prompts.js";
|
|
8
|
+
|
|
9
|
+
async function runBillingCatalog(options: { json?: boolean }) {
|
|
10
|
+
const local = await requireLocalSession();
|
|
11
|
+
const authProvider = createCliAuthProvider();
|
|
12
|
+
const accessToken = await authProvider.getAccessToken();
|
|
13
|
+
const response = await getJson({
|
|
14
|
+
baseUrl: local.baseUrl,
|
|
15
|
+
path: "/v1/cli/billing/catalog",
|
|
16
|
+
accessToken,
|
|
17
|
+
schema: BillingCatalogResponseSchema,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
if (options.json) {
|
|
21
|
+
toJsonOutput(true, response);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.log(`Plans: ${response.variants.length}`);
|
|
26
|
+
console.log(`Metered features: ${Object.values(response.featureIds).join(", ")}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function runBillingStatus(options: EnvTargetOptions & { json?: boolean }) {
|
|
30
|
+
const local = await requireLocalSession();
|
|
31
|
+
const authProvider = createCliAuthProvider();
|
|
32
|
+
const accessToken = await authProvider.getAccessToken();
|
|
33
|
+
const orgSlug = await promptForOrganizationSlug(options.org);
|
|
34
|
+
const response = await postJson({
|
|
35
|
+
baseUrl: local.baseUrl,
|
|
36
|
+
path: "/v1/cli/billing/status",
|
|
37
|
+
accessToken,
|
|
38
|
+
payload: {
|
|
39
|
+
orgSlug,
|
|
40
|
+
},
|
|
41
|
+
schema: BillingStatusResponseSchema,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (options.json) {
|
|
45
|
+
toJsonOutput(true, response);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log(`Tier: ${response.currentTier ?? "none"}`);
|
|
50
|
+
console.log(`Product: ${response.currentProductId ?? "none"}`);
|
|
51
|
+
console.log(`Can manage billing: ${response.canManageBilling ? "yes" : "no"}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function registerBillingCommands(program: Command): void {
|
|
55
|
+
const billing = program.command("billing").description("Billing information");
|
|
56
|
+
|
|
57
|
+
billing
|
|
58
|
+
.command("catalog")
|
|
59
|
+
.description("Show the public billing catalog")
|
|
60
|
+
.option("--json", "Machine-readable output", false)
|
|
61
|
+
.action(async (options: { json?: boolean }) => {
|
|
62
|
+
await runBillingCatalog(options);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
billing
|
|
66
|
+
.command("status")
|
|
67
|
+
.description("Show billing status for an organization")
|
|
68
|
+
.option("--org <slug>", "Organization slug")
|
|
69
|
+
.option("--json", "Machine-readable output", false)
|
|
70
|
+
.action(async (options: EnvTargetOptions & { json?: boolean }) => {
|
|
71
|
+
await runBillingStatus(options);
|
|
72
|
+
});
|
|
73
|
+
}
|