@barekey/cli 0.1.0 → 0.1.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/.github/workflows/publish.yml +59 -0
- package/AGENTS.md +9 -0
- package/README.md +5 -4
- package/bun.lock +59 -0
- package/dist/commands/env-helpers.d.ts +10 -0
- package/dist/commands/env-helpers.js +61 -0
- package/dist/commands/env.js +47 -12
- package/dist/commands/typegen.js +1 -1
- package/dist/index.js +0 -0
- package/package.json +4 -2
- package/src/commands/env-helpers.ts +87 -0
- package/src/commands/env.ts +86 -14
- package/src/commands/typegen.ts +1 -1
- package/test/env-helpers.test.ts +64 -0
- package/dist/typegen.d.ts +0 -20
- package/dist/typegen.js +0 -14
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
name: Publish package
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- master
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
id-token: write
|
|
12
|
+
|
|
13
|
+
concurrency:
|
|
14
|
+
group: publish-${{ github.ref }}
|
|
15
|
+
cancel-in-progress: false
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
publish:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- name: Check out repository
|
|
22
|
+
uses: actions/checkout@v5
|
|
23
|
+
|
|
24
|
+
- name: Set up Bun
|
|
25
|
+
uses: oven-sh/setup-bun@v2
|
|
26
|
+
with:
|
|
27
|
+
bun-version: 1.2.22
|
|
28
|
+
|
|
29
|
+
- name: Set up Node.js
|
|
30
|
+
uses: actions/setup-node@v4
|
|
31
|
+
with:
|
|
32
|
+
node-version: 24
|
|
33
|
+
registry-url: https://registry.npmjs.org
|
|
34
|
+
|
|
35
|
+
- name: Install dependencies
|
|
36
|
+
run: bun install --frozen-lockfile
|
|
37
|
+
|
|
38
|
+
- name: Build package
|
|
39
|
+
run: bun run build
|
|
40
|
+
|
|
41
|
+
- name: Check if version is already published
|
|
42
|
+
id: published
|
|
43
|
+
shell: bash
|
|
44
|
+
run: |
|
|
45
|
+
set -euo pipefail
|
|
46
|
+
package_name="$(node -p "require('./package.json').name")"
|
|
47
|
+
package_version="$(node -p "require('./package.json').version")"
|
|
48
|
+
version_exists="$(node -e "const https = require('node:https'); const url = 'https://registry.npmjs.org/' + encodeURIComponent(require('./package.json').name); https.get(url, (response) => { if (response.statusCode === 404) { console.log('false'); response.resume(); return; } if (response.statusCode !== 200) { console.error('Unexpected registry status:', response.statusCode); process.exit(1); } let data = ''; response.on('data', (chunk) => data += chunk); response.on('end', () => { const metadata = JSON.parse(data); console.log(Boolean(metadata.versions && metadata.versions[require('./package.json').version])); }); }).on('error', (error) => { console.error(error); process.exit(1); });")"
|
|
49
|
+
echo "package_name=${package_name}" >> "$GITHUB_OUTPUT"
|
|
50
|
+
echo "package_version=${package_version}" >> "$GITHUB_OUTPUT"
|
|
51
|
+
echo "version_exists=${version_exists}" >> "$GITHUB_OUTPUT"
|
|
52
|
+
|
|
53
|
+
- name: Publish to npm
|
|
54
|
+
if: steps.published.outputs.version_exists != 'true'
|
|
55
|
+
run: npm publish --access public --provenance
|
|
56
|
+
|
|
57
|
+
- name: Report skipped publish
|
|
58
|
+
if: steps.published.outputs.version_exists == 'true'
|
|
59
|
+
run: echo "${{ steps.published.outputs.package_name }}@${{ steps.published.outputs.package_version }} is already published; skipping."
|
package/AGENTS.md
ADDED
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ CLI for logging into Barekey, managing environment variables, and pulling resolv
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
8
|
+
bun add -g @barekey/cli
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
## Quickstart
|
|
@@ -30,7 +30,8 @@ barekey env get-many --names DATABASE_URL,FEATURE_FLAG --org acme --project api
|
|
|
30
30
|
## Development
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
bun install
|
|
34
|
+
bun run build
|
|
35
|
+
bun run typecheck
|
|
36
|
+
bun test
|
|
36
37
|
```
|
package/bun.lock
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"configVersion": 1,
|
|
4
|
+
"workspaces": {
|
|
5
|
+
"": {
|
|
6
|
+
"name": "@barekey/cli",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@barekey/sdk": "^0.1.3",
|
|
9
|
+
"@clack/prompts": "^0.11.0",
|
|
10
|
+
"commander": "^14.0.1",
|
|
11
|
+
"open": "^10.2.0",
|
|
12
|
+
"picocolors": "^1.1.1",
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/node": "^24.10.1",
|
|
16
|
+
"typescript": "^5.9.3",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
"packages": {
|
|
21
|
+
"@barekey/sdk": ["@barekey/sdk@0.1.3", "", {}, "sha512-Y5defUYsa6r6GnfR7i0zNnKVI6gXp/GJihVO7+kpijCA61Hnf5hheBTZQyEUma+G1U7MxLcQM/g+FNSHSOXeYQ=="],
|
|
22
|
+
|
|
23
|
+
"@clack/core": ["@clack/core@0.5.0", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow=="],
|
|
24
|
+
|
|
25
|
+
"@clack/prompts": ["@clack/prompts@0.11.0", "", { "dependencies": { "@clack/core": "0.5.0", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw=="],
|
|
26
|
+
|
|
27
|
+
"@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="],
|
|
28
|
+
|
|
29
|
+
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
|
|
30
|
+
|
|
31
|
+
"commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
|
|
32
|
+
|
|
33
|
+
"default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="],
|
|
34
|
+
|
|
35
|
+
"default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="],
|
|
36
|
+
|
|
37
|
+
"define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="],
|
|
38
|
+
|
|
39
|
+
"is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="],
|
|
40
|
+
|
|
41
|
+
"is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="],
|
|
42
|
+
|
|
43
|
+
"is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="],
|
|
44
|
+
|
|
45
|
+
"open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],
|
|
46
|
+
|
|
47
|
+
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
|
48
|
+
|
|
49
|
+
"run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="],
|
|
50
|
+
|
|
51
|
+
"sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
|
|
52
|
+
|
|
53
|
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
54
|
+
|
|
55
|
+
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
|
56
|
+
|
|
57
|
+
"wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type CliRolloutFunction = "linear" | "step" | "ease_in_out";
|
|
2
|
+
export type CliVisibility = "private" | "public";
|
|
3
|
+
export type CliRolloutMilestone = {
|
|
4
|
+
at: string;
|
|
5
|
+
percentage: number;
|
|
6
|
+
};
|
|
7
|
+
export declare function collectOptionValues(value: string, previous?: Array<string>): Array<string>;
|
|
8
|
+
export declare function parseRolloutFunction(value: string | undefined): CliRolloutFunction;
|
|
9
|
+
export declare function parseRolloutMilestones(values: Array<string> | undefined): Array<CliRolloutMilestone>;
|
|
10
|
+
export declare function parseVisibility(value: string | undefined): CliVisibility;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export function collectOptionValues(value, previous = []) {
|
|
2
|
+
return [...previous, value];
|
|
3
|
+
}
|
|
4
|
+
export function parseRolloutFunction(value) {
|
|
5
|
+
if (value === undefined) {
|
|
6
|
+
return "linear";
|
|
7
|
+
}
|
|
8
|
+
if (value === "linear" || value === "step" || value === "ease_in_out") {
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
throw new Error("--function must be one of: linear, step, ease_in_out.");
|
|
12
|
+
}
|
|
13
|
+
export function parseRolloutMilestones(values) {
|
|
14
|
+
if (values === undefined || values.length === 0) {
|
|
15
|
+
return [
|
|
16
|
+
{
|
|
17
|
+
at: new Date().toISOString(),
|
|
18
|
+
percentage: 0,
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
}
|
|
22
|
+
const milestones = values.map((rawValue) => {
|
|
23
|
+
const separatorIndex = rawValue.lastIndexOf("=");
|
|
24
|
+
if (separatorIndex <= 0 || separatorIndex >= rawValue.length - 1) {
|
|
25
|
+
throw new Error(`Invalid --point value "${rawValue}". Use ISO_TIMESTAMP=PERCENTAGE, for example 2026-03-12T18:00:00Z=50.`);
|
|
26
|
+
}
|
|
27
|
+
const at = rawValue.slice(0, separatorIndex).trim();
|
|
28
|
+
const percentageValue = rawValue.slice(separatorIndex + 1).trim();
|
|
29
|
+
const atMs = Date.parse(at);
|
|
30
|
+
const percentage = Number(percentageValue);
|
|
31
|
+
if (!Number.isFinite(atMs)) {
|
|
32
|
+
throw new Error(`Invalid rollout point time "${at}". Use an ISO 8601 timestamp.`);
|
|
33
|
+
}
|
|
34
|
+
if (!Number.isFinite(percentage) || percentage < 0 || percentage > 100) {
|
|
35
|
+
throw new Error(`Invalid rollout point percentage "${percentageValue}". Use a number between 0 and 100.`);
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
atMs,
|
|
39
|
+
milestone: {
|
|
40
|
+
at: new Date(atMs).toISOString(),
|
|
41
|
+
percentage,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
milestones.sort((left, right) => left.atMs - right.atMs);
|
|
46
|
+
for (let index = 1; index < milestones.length; index += 1) {
|
|
47
|
+
if ((milestones[index]?.atMs ?? 0) <= (milestones[index - 1]?.atMs ?? 0)) {
|
|
48
|
+
throw new Error("Rollout points must be strictly increasing by time.");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return milestones.map((entry) => entry.milestone);
|
|
52
|
+
}
|
|
53
|
+
export function parseVisibility(value) {
|
|
54
|
+
if (value === undefined || value === "private") {
|
|
55
|
+
return "private";
|
|
56
|
+
}
|
|
57
|
+
if (value === "public") {
|
|
58
|
+
return "public";
|
|
59
|
+
}
|
|
60
|
+
throw new Error("--visibility must be one of: private, public.");
|
|
61
|
+
}
|
package/dist/commands/env.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { writeFile } from "node:fs/promises";
|
|
2
2
|
import { cancel, confirm, isCancel } from "@clack/prompts";
|
|
3
|
-
import { BarekeyClient } from "@barekey/sdk";
|
|
3
|
+
import { BarekeyClient } from "@barekey/sdk/server";
|
|
4
4
|
import pc from "picocolors";
|
|
5
5
|
import { createCliAuthProvider } from "../auth-provider.js";
|
|
6
6
|
import { addTargetOptions, dotenvEscape, parseChance, requireLocalSession, resolveTarget, toJsonOutput, } from "../command-utils.js";
|
|
7
7
|
import { postJson } from "../http.js";
|
|
8
|
+
import { collectOptionValues, parseRolloutFunction, parseRolloutMilestones, parseVisibility, } from "./env-helpers.js";
|
|
8
9
|
function createEnvClient(input) {
|
|
9
10
|
const organization = input.organization?.trim();
|
|
10
11
|
if (!organization) {
|
|
@@ -94,7 +95,7 @@ async function runEnvList(options) {
|
|
|
94
95
|
const rolloutSuffix = row.kind === "rollout"
|
|
95
96
|
? ` ${pc.dim(`${row.rolloutFunction ?? "linear"}(${row.rolloutMilestones?.length ?? 0} milestones)`)}`
|
|
96
97
|
: "";
|
|
97
|
-
console.log(`${row.name} ${pc.dim(row.kind)} ${pc.dim(row.declaredType)}${chanceSuffix}${rolloutSuffix}`);
|
|
98
|
+
console.log(`${row.name} ${pc.dim(row.visibility)} ${pc.dim(row.kind)} ${pc.dim(row.declaredType)}${chanceSuffix}${rolloutSuffix}`);
|
|
98
99
|
}
|
|
99
100
|
}
|
|
100
101
|
async function runEnvWrite(operation, name, value, options) {
|
|
@@ -102,21 +103,47 @@ async function runEnvWrite(operation, name, value, options) {
|
|
|
102
103
|
const target = await resolveTarget(options, local);
|
|
103
104
|
const authProvider = createCliAuthProvider();
|
|
104
105
|
const accessToken = await authProvider.getAccessToken();
|
|
105
|
-
|
|
106
|
+
if (options.ab !== undefined && options.rollout !== undefined) {
|
|
107
|
+
throw new Error("Use either --ab or --rollout, not both.");
|
|
108
|
+
}
|
|
109
|
+
const hasRolloutPoints = (options.point?.length ?? 0) > 0;
|
|
110
|
+
if (options.rollout === undefined && (options.function !== undefined || hasRolloutPoints)) {
|
|
111
|
+
throw new Error("--function and --point can only be used together with --rollout.");
|
|
112
|
+
}
|
|
113
|
+
if (options.ab !== undefined && (options.function !== undefined || hasRolloutPoints)) {
|
|
114
|
+
throw new Error("--function and --point are only supported for --rollout, not --ab.");
|
|
115
|
+
}
|
|
116
|
+
if (options.rollout !== undefined && options.chance !== undefined) {
|
|
117
|
+
throw new Error("--chance only applies to --ab.");
|
|
118
|
+
}
|
|
119
|
+
const entry = options.rollout !== undefined
|
|
106
120
|
? {
|
|
107
121
|
name,
|
|
108
|
-
|
|
122
|
+
visibility: parseVisibility(options.visibility),
|
|
123
|
+
kind: "rollout",
|
|
109
124
|
declaredType: options.type ?? "string",
|
|
110
125
|
valueA: value,
|
|
111
|
-
valueB: options.
|
|
112
|
-
|
|
126
|
+
valueB: options.rollout,
|
|
127
|
+
rolloutFunction: parseRolloutFunction(options.function),
|
|
128
|
+
rolloutMilestones: parseRolloutMilestones(options.point),
|
|
113
129
|
}
|
|
114
|
-
:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
130
|
+
: options.ab !== undefined
|
|
131
|
+
? {
|
|
132
|
+
name,
|
|
133
|
+
visibility: parseVisibility(options.visibility),
|
|
134
|
+
kind: "ab_roll",
|
|
135
|
+
declaredType: options.type ?? "string",
|
|
136
|
+
valueA: value,
|
|
137
|
+
valueB: options.ab,
|
|
138
|
+
chance: parseChance(options.chance),
|
|
139
|
+
}
|
|
140
|
+
: {
|
|
141
|
+
name,
|
|
142
|
+
visibility: parseVisibility(options.visibility),
|
|
143
|
+
kind: "secret",
|
|
144
|
+
declaredType: options.type ?? "string",
|
|
145
|
+
value,
|
|
146
|
+
};
|
|
120
147
|
const result = await postJson({
|
|
121
148
|
baseUrl: local.baseUrl,
|
|
122
149
|
path: "/v1/env/write",
|
|
@@ -257,7 +284,11 @@ export function registerEnvCommands(program) {
|
|
|
257
284
|
.argument("<name>", "Variable name")
|
|
258
285
|
.argument("<value>", "Variable value")
|
|
259
286
|
.option("--ab <value-b>", "Second value for ab_roll")
|
|
287
|
+
.option("--rollout <value-b>", "Second value for rollout")
|
|
260
288
|
.option("--chance <number>", "A-branch probability between 0 and 1")
|
|
289
|
+
.option("--function <name>", "Rollout interpolation function (linear, step, ease_in_out)")
|
|
290
|
+
.option("--point <at=percentage>", "Rollout milestone, repeatable. Example: 2026-03-12T18:00:00Z=50", collectOptionValues, [])
|
|
291
|
+
.option("--visibility <visibility>", "Variable visibility: private|public", "private")
|
|
261
292
|
.option("--type <type>", "Declared value type", "string")
|
|
262
293
|
.option("--json", "Machine-readable output", false)).action(async (name, value, options) => {
|
|
263
294
|
await runEnvWrite("create_only", name, value, options);
|
|
@@ -268,7 +299,11 @@ export function registerEnvCommands(program) {
|
|
|
268
299
|
.argument("<name>", "Variable name")
|
|
269
300
|
.argument("<value>", "Variable value")
|
|
270
301
|
.option("--ab <value-b>", "Second value for ab_roll")
|
|
302
|
+
.option("--rollout <value-b>", "Second value for rollout")
|
|
271
303
|
.option("--chance <number>", "A-branch probability between 0 and 1")
|
|
304
|
+
.option("--function <name>", "Rollout interpolation function (linear, step, ease_in_out)")
|
|
305
|
+
.option("--point <at=percentage>", "Rollout milestone, repeatable. Example: 2026-03-12T18:00:00Z=50", collectOptionValues, [])
|
|
306
|
+
.option("--visibility <visibility>", "Variable visibility: private|public", "private")
|
|
272
307
|
.option("--type <type>", "Declared value type", "string")
|
|
273
308
|
.option("--json", "Machine-readable output", false)).action(async (name, value, options) => {
|
|
274
309
|
await runEnvWrite("upsert", name, value, options);
|
package/dist/commands/typegen.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BarekeyClient } from "@barekey/sdk";
|
|
1
|
+
import { BarekeyClient } from "@barekey/sdk/server";
|
|
2
2
|
import { addTargetOptions, requireLocalSession, resolveTarget, } from "../command-utils.js";
|
|
3
3
|
async function runTypegen(options) {
|
|
4
4
|
const local = await requireLocalSession();
|
package/dist/index.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barekey/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Barekey command line interface",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,11 +9,13 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"build": "tsc -p tsconfig.json",
|
|
11
11
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
12
|
+
"test": "bun test test",
|
|
12
13
|
"start": "node dist/index.js"
|
|
13
14
|
},
|
|
15
|
+
"packageManager": "bun@1.2.22",
|
|
14
16
|
"dependencies": {
|
|
15
17
|
"@clack/prompts": "^0.11.0",
|
|
16
|
-
"@barekey/sdk": "^0.1.
|
|
18
|
+
"@barekey/sdk": "^0.1.3",
|
|
17
19
|
"commander": "^14.0.1",
|
|
18
20
|
"open": "^10.2.0",
|
|
19
21
|
"picocolors": "^1.1.1"
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export type CliRolloutFunction = "linear" | "step" | "ease_in_out";
|
|
2
|
+
|
|
3
|
+
export type CliVisibility = "private" | "public";
|
|
4
|
+
|
|
5
|
+
export type CliRolloutMilestone = {
|
|
6
|
+
at: string;
|
|
7
|
+
percentage: number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function collectOptionValues(
|
|
11
|
+
value: string,
|
|
12
|
+
previous: Array<string> = [],
|
|
13
|
+
): Array<string> {
|
|
14
|
+
return [...previous, value];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function parseRolloutFunction(value: string | undefined): CliRolloutFunction {
|
|
18
|
+
if (value === undefined) {
|
|
19
|
+
return "linear";
|
|
20
|
+
}
|
|
21
|
+
if (value === "linear" || value === "step" || value === "ease_in_out") {
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
throw new Error("--function must be one of: linear, step, ease_in_out.");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function parseRolloutMilestones(
|
|
28
|
+
values: Array<string> | undefined,
|
|
29
|
+
): Array<CliRolloutMilestone> {
|
|
30
|
+
if (values === undefined || values.length === 0) {
|
|
31
|
+
return [
|
|
32
|
+
{
|
|
33
|
+
at: new Date().toISOString(),
|
|
34
|
+
percentage: 0,
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const milestones = values.map((rawValue) => {
|
|
40
|
+
const separatorIndex = rawValue.lastIndexOf("=");
|
|
41
|
+
if (separatorIndex <= 0 || separatorIndex >= rawValue.length - 1) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Invalid --point value "${rawValue}". Use ISO_TIMESTAMP=PERCENTAGE, for example 2026-03-12T18:00:00Z=50.`,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const at = rawValue.slice(0, separatorIndex).trim();
|
|
48
|
+
const percentageValue = rawValue.slice(separatorIndex + 1).trim();
|
|
49
|
+
const atMs = Date.parse(at);
|
|
50
|
+
const percentage = Number(percentageValue);
|
|
51
|
+
if (!Number.isFinite(atMs)) {
|
|
52
|
+
throw new Error(`Invalid rollout point time "${at}". Use an ISO 8601 timestamp.`);
|
|
53
|
+
}
|
|
54
|
+
if (!Number.isFinite(percentage) || percentage < 0 || percentage > 100) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Invalid rollout point percentage "${percentageValue}". Use a number between 0 and 100.`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
atMs,
|
|
62
|
+
milestone: {
|
|
63
|
+
at: new Date(atMs).toISOString(),
|
|
64
|
+
percentage,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
milestones.sort((left, right) => left.atMs - right.atMs);
|
|
70
|
+
for (let index = 1; index < milestones.length; index += 1) {
|
|
71
|
+
if ((milestones[index]?.atMs ?? 0) <= (milestones[index - 1]?.atMs ?? 0)) {
|
|
72
|
+
throw new Error("Rollout points must be strictly increasing by time.");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return milestones.map((entry) => entry.milestone);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function parseVisibility(value: string | undefined): CliVisibility {
|
|
80
|
+
if (value === undefined || value === "private") {
|
|
81
|
+
return "private";
|
|
82
|
+
}
|
|
83
|
+
if (value === "public") {
|
|
84
|
+
return "public";
|
|
85
|
+
}
|
|
86
|
+
throw new Error("--visibility must be one of: private, public.");
|
|
87
|
+
}
|
package/src/commands/env.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { writeFile } from "node:fs/promises";
|
|
2
2
|
|
|
3
3
|
import { cancel, confirm, isCancel } from "@clack/prompts";
|
|
4
|
-
import { BarekeyClient } from "@barekey/sdk";
|
|
4
|
+
import { BarekeyClient } from "@barekey/sdk/server";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
import pc from "picocolors";
|
|
7
7
|
|
|
@@ -16,6 +16,14 @@ import {
|
|
|
16
16
|
type EnvTargetOptions,
|
|
17
17
|
} from "../command-utils.js";
|
|
18
18
|
import { postJson } from "../http.js";
|
|
19
|
+
import {
|
|
20
|
+
collectOptionValues,
|
|
21
|
+
parseRolloutFunction,
|
|
22
|
+
parseRolloutMilestones,
|
|
23
|
+
parseVisibility,
|
|
24
|
+
type CliRolloutFunction,
|
|
25
|
+
type CliVisibility,
|
|
26
|
+
} from "./env-helpers.js";
|
|
19
27
|
|
|
20
28
|
function createEnvClient(input: {
|
|
21
29
|
organization: string | undefined;
|
|
@@ -117,12 +125,13 @@ async function runEnvList(options: EnvTargetOptions & { json?: boolean }): Promi
|
|
|
117
125
|
const response = await postJson<{
|
|
118
126
|
variables: Array<{
|
|
119
127
|
name: string;
|
|
128
|
+
visibility: CliVisibility;
|
|
120
129
|
kind: "secret" | "ab_roll" | "rollout";
|
|
121
130
|
declaredType: "string" | "boolean" | "int64" | "float" | "date" | "json";
|
|
122
131
|
createdAtMs: number;
|
|
123
132
|
updatedAtMs: number;
|
|
124
133
|
chance: number | null;
|
|
125
|
-
rolloutFunction:
|
|
134
|
+
rolloutFunction: CliRolloutFunction | null;
|
|
126
135
|
rolloutMilestones: Array<{ at: string; percentage: number }> | null;
|
|
127
136
|
}>;
|
|
128
137
|
}>({
|
|
@@ -155,7 +164,7 @@ async function runEnvList(options: EnvTargetOptions & { json?: boolean }): Promi
|
|
|
155
164
|
)}`
|
|
156
165
|
: "";
|
|
157
166
|
console.log(
|
|
158
|
-
`${row.name} ${pc.dim(row.kind)} ${pc.dim(row.declaredType)}${chanceSuffix}${rolloutSuffix}`,
|
|
167
|
+
`${row.name} ${pc.dim(row.visibility)} ${pc.dim(row.kind)} ${pc.dim(row.declaredType)}${chanceSuffix}${rolloutSuffix}`,
|
|
159
168
|
);
|
|
160
169
|
}
|
|
161
170
|
}
|
|
@@ -166,7 +175,11 @@ async function runEnvWrite(
|
|
|
166
175
|
value: string,
|
|
167
176
|
options: EnvTargetOptions & {
|
|
168
177
|
ab?: string;
|
|
178
|
+
rollout?: string;
|
|
179
|
+
function?: string;
|
|
180
|
+
point?: Array<string>;
|
|
169
181
|
chance?: string;
|
|
182
|
+
visibility?: CliVisibility;
|
|
170
183
|
type?: "string" | "boolean" | "int64" | "float" | "date" | "json";
|
|
171
184
|
json?: boolean;
|
|
172
185
|
},
|
|
@@ -176,22 +189,49 @@ async function runEnvWrite(
|
|
|
176
189
|
const authProvider = createCliAuthProvider();
|
|
177
190
|
const accessToken = await authProvider.getAccessToken();
|
|
178
191
|
|
|
192
|
+
if (options.ab !== undefined && options.rollout !== undefined) {
|
|
193
|
+
throw new Error("Use either --ab or --rollout, not both.");
|
|
194
|
+
}
|
|
195
|
+
const hasRolloutPoints = (options.point?.length ?? 0) > 0;
|
|
196
|
+
if (options.rollout === undefined && (options.function !== undefined || hasRolloutPoints)) {
|
|
197
|
+
throw new Error("--function and --point can only be used together with --rollout.");
|
|
198
|
+
}
|
|
199
|
+
if (options.ab !== undefined && (options.function !== undefined || hasRolloutPoints)) {
|
|
200
|
+
throw new Error("--function and --point are only supported for --rollout, not --ab.");
|
|
201
|
+
}
|
|
202
|
+
if (options.rollout !== undefined && options.chance !== undefined) {
|
|
203
|
+
throw new Error("--chance only applies to --ab.");
|
|
204
|
+
}
|
|
205
|
+
|
|
179
206
|
const entry =
|
|
180
|
-
options.
|
|
207
|
+
options.rollout !== undefined
|
|
181
208
|
? {
|
|
182
209
|
name,
|
|
183
|
-
|
|
210
|
+
visibility: parseVisibility(options.visibility),
|
|
211
|
+
kind: "rollout" as const,
|
|
184
212
|
declaredType: options.type ?? "string",
|
|
185
213
|
valueA: value,
|
|
186
|
-
valueB: options.
|
|
187
|
-
|
|
214
|
+
valueB: options.rollout,
|
|
215
|
+
rolloutFunction: parseRolloutFunction(options.function),
|
|
216
|
+
rolloutMilestones: parseRolloutMilestones(options.point),
|
|
188
217
|
}
|
|
189
|
-
:
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
218
|
+
: options.ab !== undefined
|
|
219
|
+
? {
|
|
220
|
+
name,
|
|
221
|
+
visibility: parseVisibility(options.visibility),
|
|
222
|
+
kind: "ab_roll" as const,
|
|
223
|
+
declaredType: options.type ?? "string",
|
|
224
|
+
valueA: value,
|
|
225
|
+
valueB: options.ab,
|
|
226
|
+
chance: parseChance(options.chance),
|
|
227
|
+
}
|
|
228
|
+
: {
|
|
229
|
+
name,
|
|
230
|
+
visibility: parseVisibility(options.visibility),
|
|
231
|
+
kind: "secret" as const,
|
|
232
|
+
declaredType: options.type ?? "string",
|
|
233
|
+
value,
|
|
234
|
+
};
|
|
195
235
|
|
|
196
236
|
const result = await postJson<{
|
|
197
237
|
createdCount: number;
|
|
@@ -315,7 +355,7 @@ async function runEnvPull(
|
|
|
315
355
|
const response = await postJson<{
|
|
316
356
|
values: Array<{
|
|
317
357
|
name: string;
|
|
318
|
-
kind: "secret" | "ab_roll";
|
|
358
|
+
kind: "secret" | "ab_roll" | "rollout";
|
|
319
359
|
declaredType: "string" | "boolean" | "int64" | "float" | "date" | "json";
|
|
320
360
|
value: string;
|
|
321
361
|
}>;
|
|
@@ -411,7 +451,19 @@ export function registerEnvCommands(program: Command): void {
|
|
|
411
451
|
.argument("<name>", "Variable name")
|
|
412
452
|
.argument("<value>", "Variable value")
|
|
413
453
|
.option("--ab <value-b>", "Second value for ab_roll")
|
|
454
|
+
.option("--rollout <value-b>", "Second value for rollout")
|
|
414
455
|
.option("--chance <number>", "A-branch probability between 0 and 1")
|
|
456
|
+
.option(
|
|
457
|
+
"--function <name>",
|
|
458
|
+
"Rollout interpolation function (linear, step, ease_in_out)",
|
|
459
|
+
)
|
|
460
|
+
.option(
|
|
461
|
+
"--point <at=percentage>",
|
|
462
|
+
"Rollout milestone, repeatable. Example: 2026-03-12T18:00:00Z=50",
|
|
463
|
+
collectOptionValues,
|
|
464
|
+
[],
|
|
465
|
+
)
|
|
466
|
+
.option("--visibility <visibility>", "Variable visibility: private|public", "private")
|
|
415
467
|
.option("--type <type>", "Declared value type", "string")
|
|
416
468
|
.option("--json", "Machine-readable output", false),
|
|
417
469
|
).action(
|
|
@@ -420,7 +472,11 @@ export function registerEnvCommands(program: Command): void {
|
|
|
420
472
|
value: string,
|
|
421
473
|
options: EnvTargetOptions & {
|
|
422
474
|
ab?: string;
|
|
475
|
+
rollout?: string;
|
|
476
|
+
function?: string;
|
|
477
|
+
point?: Array<string>;
|
|
423
478
|
chance?: string;
|
|
479
|
+
visibility?: CliVisibility;
|
|
424
480
|
type?: "string" | "boolean" | "int64" | "float" | "date" | "json";
|
|
425
481
|
json?: boolean;
|
|
426
482
|
},
|
|
@@ -436,7 +492,19 @@ export function registerEnvCommands(program: Command): void {
|
|
|
436
492
|
.argument("<name>", "Variable name")
|
|
437
493
|
.argument("<value>", "Variable value")
|
|
438
494
|
.option("--ab <value-b>", "Second value for ab_roll")
|
|
495
|
+
.option("--rollout <value-b>", "Second value for rollout")
|
|
439
496
|
.option("--chance <number>", "A-branch probability between 0 and 1")
|
|
497
|
+
.option(
|
|
498
|
+
"--function <name>",
|
|
499
|
+
"Rollout interpolation function (linear, step, ease_in_out)",
|
|
500
|
+
)
|
|
501
|
+
.option(
|
|
502
|
+
"--point <at=percentage>",
|
|
503
|
+
"Rollout milestone, repeatable. Example: 2026-03-12T18:00:00Z=50",
|
|
504
|
+
collectOptionValues,
|
|
505
|
+
[],
|
|
506
|
+
)
|
|
507
|
+
.option("--visibility <visibility>", "Variable visibility: private|public", "private")
|
|
440
508
|
.option("--type <type>", "Declared value type", "string")
|
|
441
509
|
.option("--json", "Machine-readable output", false),
|
|
442
510
|
).action(
|
|
@@ -445,7 +513,11 @@ export function registerEnvCommands(program: Command): void {
|
|
|
445
513
|
value: string,
|
|
446
514
|
options: EnvTargetOptions & {
|
|
447
515
|
ab?: string;
|
|
516
|
+
rollout?: string;
|
|
517
|
+
function?: string;
|
|
518
|
+
point?: Array<string>;
|
|
448
519
|
chance?: string;
|
|
520
|
+
visibility?: CliVisibility;
|
|
449
521
|
type?: "string" | "boolean" | "int64" | "float" | "date" | "json";
|
|
450
522
|
json?: boolean;
|
|
451
523
|
},
|
package/src/commands/typegen.ts
CHANGED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
parseRolloutFunction,
|
|
5
|
+
parseRolloutMilestones,
|
|
6
|
+
parseVisibility,
|
|
7
|
+
} from "../src/commands/env-helpers";
|
|
8
|
+
|
|
9
|
+
describe("parseVisibility", () => {
|
|
10
|
+
test("defaults to private when missing", () => {
|
|
11
|
+
expect(parseVisibility(undefined)).toBe("private");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("accepts public visibility", () => {
|
|
15
|
+
expect(parseVisibility("public")).toBe("public");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("rejects unsupported visibility values", () => {
|
|
19
|
+
expect(() => parseVisibility("internal")).toThrow(
|
|
20
|
+
"--visibility must be one of: private, public.",
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("parseRolloutFunction", () => {
|
|
26
|
+
test("defaults to linear", () => {
|
|
27
|
+
expect(parseRolloutFunction(undefined)).toBe("linear");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("rejects unsupported rollout functions", () => {
|
|
31
|
+
expect(() => parseRolloutFunction("curve")).toThrow(
|
|
32
|
+
"--function must be one of: linear, step, ease_in_out.",
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("parseRolloutMilestones", () => {
|
|
38
|
+
test("sorts valid rollout milestones chronologically", () => {
|
|
39
|
+
expect(
|
|
40
|
+
parseRolloutMilestones([
|
|
41
|
+
"2026-03-12T19:00:00Z=100",
|
|
42
|
+
"2026-03-12T18:00:00Z=50",
|
|
43
|
+
]),
|
|
44
|
+
).toEqual([
|
|
45
|
+
{
|
|
46
|
+
at: "2026-03-12T18:00:00.000Z",
|
|
47
|
+
percentage: 50,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
at: "2026-03-12T19:00:00.000Z",
|
|
51
|
+
percentage: 100,
|
|
52
|
+
},
|
|
53
|
+
]);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("rejects duplicate rollout milestone times", () => {
|
|
57
|
+
expect(() =>
|
|
58
|
+
parseRolloutMilestones([
|
|
59
|
+
"2026-03-12T18:00:00Z=25",
|
|
60
|
+
"2026-03-12T18:00:00Z=50",
|
|
61
|
+
]),
|
|
62
|
+
).toThrow("Rollout points must be strictly increasing by time.");
|
|
63
|
+
});
|
|
64
|
+
});
|
package/dist/typegen.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export type TypegenManifest = {
|
|
2
|
-
orgId: string;
|
|
3
|
-
orgSlug: string;
|
|
4
|
-
projectSlug: string;
|
|
5
|
-
stageSlug: string;
|
|
6
|
-
generatedAtMs: number;
|
|
7
|
-
manifestVersion: string;
|
|
8
|
-
variables: Array<{
|
|
9
|
-
name: string;
|
|
10
|
-
kind: "secret" | "ab_roll" | "rollout";
|
|
11
|
-
declaredType: "string" | "boolean" | "int64" | "float" | "date" | "json";
|
|
12
|
-
required: boolean;
|
|
13
|
-
updatedAtMs: number;
|
|
14
|
-
typeScriptType: string;
|
|
15
|
-
}>;
|
|
16
|
-
};
|
|
17
|
-
export declare function writeTypegenFile(input: {
|
|
18
|
-
manifest: TypegenManifest;
|
|
19
|
-
outPath: string;
|
|
20
|
-
}): Promise<void>;
|
package/dist/typegen.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { writeFile } from "node:fs/promises";
|
|
2
|
-
export async function writeTypegenFile(input) {
|
|
3
|
-
const keys = input.manifest.variables
|
|
4
|
-
.map((row) => row.name)
|
|
5
|
-
.sort((left, right) => left.localeCompare(right));
|
|
6
|
-
const unionLines = keys.map((key) => ` | ${JSON.stringify(key)}`).join("\n");
|
|
7
|
-
const mapLines = input.manifest.variables
|
|
8
|
-
.slice()
|
|
9
|
-
.sort((left, right) => left.name.localeCompare(right.name))
|
|
10
|
-
.map((row) => ` ${JSON.stringify(row.name)}: ${row.typeScriptType};`)
|
|
11
|
-
.join("\n");
|
|
12
|
-
const contents = `/* eslint-disable */\n/* This file is generated by barekey typegen. */\n\nimport type { BarekeyTemporalInstant } from "@barekey/sdk";\nimport "@barekey/sdk";\n\ndeclare module "@barekey/sdk" {\n interface BarekeyGeneratedTypeMap {\n${mapLines}\n }\n}\n\nexport type BarekeyKnownKey =\n${unionLines.length > 0 ? unionLines : " never"};\n\nexport const barekeyManifestVersion = ${JSON.stringify(input.manifest.manifestVersion)};\n`;
|
|
13
|
-
await writeFile(input.outPath, contents, "utf8");
|
|
14
|
-
}
|