@headband/cli 0.6.1 → 0.7.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 +22 -10
- package/dist/index.js +190 -64
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -187,7 +187,8 @@ has its own indexes and its own keys.
|
|
|
187
187
|
|
|
188
188
|
```bash
|
|
189
189
|
headband branch list [--project <id>] [--json]
|
|
190
|
-
headband branch create <name> --empty [--parent <name>] [--expires-in <2h|7d>] [--project <id>] [--json]
|
|
190
|
+
headband branch create <name> [--empty] [--parent <name>] [--expires-in <2h|7d>] [--project <id>] [--json]
|
|
191
|
+
headband branch ensure <name> [--from <parent>] [--expires-in <7d>] [--env-out <file>] [--project <id>] [--json]
|
|
191
192
|
headband branch delete <name> [--project <id>] [--yes] [--json]
|
|
192
193
|
headband branch protect <name> [--project <id>] [--json]
|
|
193
194
|
headband branch unprotect <name> [--project <id>] [--json]
|
|
@@ -197,13 +198,25 @@ headband branch checkout <name> [--project <id>] [--json]
|
|
|
197
198
|
|
|
198
199
|
- The project comes from `--project`, else the directory's checkout (below).
|
|
199
200
|
With neither, the command is a usage error.
|
|
200
|
-
- `create`
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
201
|
+
- `create` forks the parent (`--parent`, default: the primary branch): the
|
|
202
|
+
new branch starts with the parent's indexes, documents and settings,
|
|
203
|
+
copy-on-write. `--empty` makes a branch with no indexes instead. A parent
|
|
204
|
+
with an index that cannot fork (Typesense, Visor, your own engine) fails
|
|
205
|
+
with exit `6` (code `fork_not_supported`). Until forking is on for your
|
|
206
|
+
organization, a fork fails with exit `6` (code `fork_not_available`), and
|
|
207
|
+
until branches other than main open, every `create` fails with exit `6`
|
|
208
|
+
(code `branches_not_open`). `--expires-in` deletes the branch on its own
|
|
209
|
+
after that long. Forking by default is new in CLI 0.7.0: before it,
|
|
210
|
+
`create` needed `--empty`.
|
|
211
|
+
- `ensure` is for preview deploys, such as a Vercel build step. It forks the
|
|
212
|
+
branch from `--from` (default: the primary) unless it exists, pushes its
|
|
213
|
+
expiry `--expires-in` out (default `7d`), mints a search key and an admin
|
|
214
|
+
key on it, and writes `HEADBAND_URL`, `HEADBAND_PROJECT_ID`,
|
|
215
|
+
`HEADBAND_BRANCH`, `HEADBAND_BRANCH_ID`, `HEADBAND_SEARCH_KEY`,
|
|
216
|
+
`HEADBAND_ADMIN_KEY` and one `HEADBAND_<ENGINE>_URL` per engine to
|
|
217
|
+
`--env-out <file>`, replacing any `HEADBAND_*` lines there. Without
|
|
218
|
+
`--env-out` it prints them. The name is made branch-safe first, so a git
|
|
219
|
+
branch name works as is. It refuses the primary branch (CLI 0.7.0).
|
|
207
220
|
- `delete` removes the branch with its indexes, keys and engines. It asks
|
|
208
221
|
first. The primary branch, a protected branch, a branch with live child
|
|
209
222
|
branches and a branch with Sync-owned indexes are refused (exit `6`).
|
|
@@ -269,8 +282,7 @@ error, so a name is never matched across projects by accident.
|
|
|
269
282
|
project of their own and would ignore it. The exception is `keys create`, which
|
|
270
283
|
sends the project in its body (see Keys).
|
|
271
284
|
|
|
272
|
-
|
|
273
|
-
parent), `reset`, `rollback`, `restore` and `set-primary`.
|
|
285
|
+
Not available yet: `reset`, `rollback`, `restore` and `set-primary`.
|
|
274
286
|
|
|
275
287
|
### Keys
|
|
276
288
|
|
package/dist/index.js
CHANGED
|
@@ -42,8 +42,8 @@ __export(index_exports, {
|
|
|
42
42
|
syncClientFromFlags: () => syncClientFromFlags
|
|
43
43
|
});
|
|
44
44
|
module.exports = __toCommonJS(index_exports);
|
|
45
|
-
var import_fs11 = require("fs");
|
|
46
45
|
var import_fs12 = require("fs");
|
|
46
|
+
var import_fs13 = require("fs");
|
|
47
47
|
var import_path11 = require("path");
|
|
48
48
|
var import_prompts2 = __toESM(require("prompts"));
|
|
49
49
|
var import_picocolors3 = __toESM(require("picocolors"));
|
|
@@ -528,7 +528,7 @@ function errorMessageOf(status, errorBody) {
|
|
|
528
528
|
}
|
|
529
529
|
return message;
|
|
530
530
|
}
|
|
531
|
-
var defaultSleep = (ms) => new Promise((
|
|
531
|
+
var defaultSleep = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
532
532
|
async function readJson(response) {
|
|
533
533
|
const text = await response.text();
|
|
534
534
|
if (!text) return {};
|
|
@@ -548,7 +548,7 @@ async function apiRequest(baseUrl, path2, init2 = {}) {
|
|
|
548
548
|
headers.set("authorization", `Bearer ${init2.apiKey}`);
|
|
549
549
|
}
|
|
550
550
|
const base = normalizeUrl(baseUrl);
|
|
551
|
-
const { rateLimitRetries = 0, sleep = defaultSleep, apiKey: _apiKey, ...fetchInit } = init2;
|
|
551
|
+
const { rateLimitRetries = 0, sleep: sleep2 = defaultSleep, apiKey: _apiKey, ...fetchInit } = init2;
|
|
552
552
|
void _apiKey;
|
|
553
553
|
let response;
|
|
554
554
|
try {
|
|
@@ -561,7 +561,7 @@ async function apiRequest(baseUrl, path2, init2 = {}) {
|
|
|
561
561
|
const wait = response.status === 429 ? retryAfterSeconds(response) : null;
|
|
562
562
|
if (wait === null || attempt >= rateLimitRetries) break;
|
|
563
563
|
await response.body?.cancel().catch(() => void 0);
|
|
564
|
-
await
|
|
564
|
+
await sleep2(wait * 1e3);
|
|
565
565
|
}
|
|
566
566
|
} catch (error) {
|
|
567
567
|
const cause = error?.cause;
|
|
@@ -600,7 +600,7 @@ function createApiClient(url, profile) {
|
|
|
600
600
|
}
|
|
601
601
|
|
|
602
602
|
// src/auth.ts
|
|
603
|
-
var defaultSleep2 = (ms) => new Promise((
|
|
603
|
+
var defaultSleep2 = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
604
604
|
function openBrowser(url) {
|
|
605
605
|
try {
|
|
606
606
|
if (process.platform === "darwin") {
|
|
@@ -615,7 +615,7 @@ function openBrowser(url) {
|
|
|
615
615
|
}
|
|
616
616
|
async function runDeviceFlow(url, io, options = {}) {
|
|
617
617
|
const baseUrl = normalizeUrl(url);
|
|
618
|
-
const
|
|
618
|
+
const sleep2 = options.sleep || defaultSleep2;
|
|
619
619
|
const now = options.now || Date.now;
|
|
620
620
|
const device = await apiRequest(baseUrl, "/api/cli/device", {
|
|
621
621
|
method: "POST",
|
|
@@ -630,7 +630,7 @@ async function runDeviceFlow(url, io, options = {}) {
|
|
|
630
630
|
let interval = device.data.interval;
|
|
631
631
|
const deadline = now() + device.data.expiresIn * 1e3;
|
|
632
632
|
while (now() < deadline) {
|
|
633
|
-
await
|
|
633
|
+
await sleep2(interval * 1e3);
|
|
634
634
|
try {
|
|
635
635
|
const token = await apiRequest(baseUrl, "/api/cli/token", {
|
|
636
636
|
method: "POST",
|
|
@@ -685,6 +685,7 @@ async function login(url, io, options = {}) {
|
|
|
685
685
|
}
|
|
686
686
|
|
|
687
687
|
// src/branch.ts
|
|
688
|
+
var import_fs4 = require("fs");
|
|
688
689
|
var import_path5 = require("path");
|
|
689
690
|
|
|
690
691
|
// src/output.ts
|
|
@@ -894,14 +895,22 @@ var PART_2 = /* @__PURE__ */ new Set(["reset", "rollback", "restore", "set-prima
|
|
|
894
895
|
var BRANCH_USAGE = [
|
|
895
896
|
"Usage:",
|
|
896
897
|
" headband branch list [--project <id>] [--json]",
|
|
897
|
-
" headband branch create <name> --empty [--parent <name>] [--expires-in <2h|7d>] [--project <id>] [--json]",
|
|
898
|
+
" headband branch create <name> [--empty] [--parent <name>] [--expires-in <2h|7d>] [--project <id>] [--json]",
|
|
899
|
+
" headband branch ensure <name> [--from <parent>] [--expires-in <7d>] [--env-out <file>] [--project <id>] [--json]",
|
|
898
900
|
" headband branch delete <name> [--project <id>] [--yes] [--json]",
|
|
899
901
|
" headband branch protect <name> [--project <id>] [--json]",
|
|
900
902
|
" headband branch unprotect <name> [--project <id>] [--json]",
|
|
901
903
|
" headband branch expire <name> (--in <2h|7d> | --at <iso> | --clear) [--project <id>] [--json]",
|
|
902
904
|
" headband branch checkout <name> [--project <id>] [--json]",
|
|
903
|
-
" headband branch
|
|
904
|
-
"
|
|
905
|
+
" headband branch reset|rollback|restore|set-primary <name> not available yet",
|
|
906
|
+
"",
|
|
907
|
+
"create without --empty forks the parent (default: main): its indexes, documents and settings,",
|
|
908
|
+
"copy-on-write. --empty makes a branch with no indexes.",
|
|
909
|
+
"",
|
|
910
|
+
"ensure is for preview deploys: it forks the branch unless it exists, pushes its expiry out",
|
|
911
|
+
"(--expires-in, default 7d), mints a search key and an admin key for it, and writes HEADBAND_*",
|
|
912
|
+
"variables to --env-out (or prints them). The name is made branch-safe first: feature/Foo_Bar",
|
|
913
|
+
"becomes feature/foo_bar. It refuses the primary branch.",
|
|
905
914
|
"",
|
|
906
915
|
"The project comes from --project, else the directory's checkout (.headband/project.json).",
|
|
907
916
|
"Durations take s, m, h, d or w: 30m, 2h, 7d.",
|
|
@@ -1000,10 +1009,7 @@ async function runBranch(args, flags, deps) {
|
|
|
1000
1009
|
const action = args[0];
|
|
1001
1010
|
const json = flags["--json"] === true;
|
|
1002
1011
|
if (action && PART_2.has(action)) {
|
|
1003
|
-
throw new UsageError(
|
|
1004
|
-
`headband branch ${action} is coming with bucket storage.`,
|
|
1005
|
-
"not_available"
|
|
1006
|
-
);
|
|
1012
|
+
throw new UsageError(`headband branch ${action} is not available yet.`, "not_available");
|
|
1007
1013
|
}
|
|
1008
1014
|
const client = createApiClient(auth.url, { apiKey: auth.apiKey });
|
|
1009
1015
|
const checkout = () => checkoutFor(cwd, auth.profile?.orgId);
|
|
@@ -1032,18 +1038,35 @@ async function runBranch(args, flags, deps) {
|
|
|
1032
1038
|
if (error instanceof ApiError && error.code === "branches_not_open") {
|
|
1033
1039
|
throw restate(error, "Branches other than main are not open yet.");
|
|
1034
1040
|
}
|
|
1035
|
-
|
|
1036
|
-
throw restate(
|
|
1037
|
-
error,
|
|
1038
|
-
"Forking arrives with bucket storage. Pass --empty for an empty branch."
|
|
1039
|
-
);
|
|
1040
|
-
}
|
|
1041
|
-
throw error;
|
|
1041
|
+
throw restateCreate(error);
|
|
1042
1042
|
}
|
|
1043
1043
|
if (json) printJson(io, response);
|
|
1044
1044
|
else printTable(io, [response.data], COLUMNS);
|
|
1045
1045
|
return;
|
|
1046
1046
|
}
|
|
1047
|
+
if (action === "ensure") {
|
|
1048
|
+
const result = await ensureBranch(client, {
|
|
1049
|
+
name: branchNameFrom(requiredName(args[1])),
|
|
1050
|
+
project: projectFor(flags, checkout),
|
|
1051
|
+
from: stringFlag2(flags, "--from"),
|
|
1052
|
+
expiresInMs: parseDuration(stringFlag2(flags, "--expires-in") ?? "7d"),
|
|
1053
|
+
url: auth.url
|
|
1054
|
+
});
|
|
1055
|
+
const envOut = stringFlag2(flags, "--env-out");
|
|
1056
|
+
if (envOut && envOut !== "-") writeEnvFile((0, import_path5.resolve)(cwd, envOut), result.env);
|
|
1057
|
+
if (json) {
|
|
1058
|
+
printJson(io, { branch: result.branch, created: result.created, env: result.env });
|
|
1059
|
+
} else if (envOut && envOut !== "-") {
|
|
1060
|
+
const engines = result.branch.instances?.length ?? 0;
|
|
1061
|
+
io.stdout(
|
|
1062
|
+
`${result.created ? "Forked" : "Reused"} branch ${result.branch.name} (${engines} engine${engines === 1 ? "" : "s"}), expires ${result.branch.expiresAt}.`
|
|
1063
|
+
);
|
|
1064
|
+
io.stdout(`Wrote ${Object.keys(result.env).length} HEADBAND_* variables to ${envOut}.`);
|
|
1065
|
+
} else {
|
|
1066
|
+
io.stdout(dotenv(result.env));
|
|
1067
|
+
}
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1047
1070
|
if (action === "delete") {
|
|
1048
1071
|
const name = requiredName(args[1]);
|
|
1049
1072
|
const project = projectFor(flags, checkout);
|
|
@@ -1115,9 +1138,109 @@ async function runBranch(args, flags, deps) {
|
|
|
1115
1138
|
io.stdout(BRANCH_USAGE);
|
|
1116
1139
|
throw new UsageError("Unknown branch command");
|
|
1117
1140
|
}
|
|
1141
|
+
function restateCreate(error) {
|
|
1142
|
+
if (error instanceof ApiError && error.code === "branches_not_open") {
|
|
1143
|
+
return restate(error, "Branches other than main are not open yet.");
|
|
1144
|
+
}
|
|
1145
|
+
if (error instanceof ApiError && error.code === "fork_not_available") {
|
|
1146
|
+
return restate(error, "Forking is not open yet. Pass --empty for an empty branch.");
|
|
1147
|
+
}
|
|
1148
|
+
return error;
|
|
1149
|
+
}
|
|
1150
|
+
function branchNameFrom(ref) {
|
|
1151
|
+
const name = ref.trim().toLowerCase().replace(/[^a-z0-9._/-]+/g, "-").slice(0, 63).replace(/^[^a-z0-9]+/, "").replace(/[^a-z0-9]+$/, "");
|
|
1152
|
+
if (!name) throw new UsageError(`"${ref}" has no letters or digits to make a branch name from.`);
|
|
1153
|
+
return name;
|
|
1154
|
+
}
|
|
1155
|
+
var ENSURE_WAIT_MS = 6e4;
|
|
1156
|
+
var ENSURE_POLL_MS = 2e3;
|
|
1157
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1158
|
+
var engineVar = (engine) => `HEADBAND_${engine.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_URL`;
|
|
1159
|
+
async function ensureBranch(client, o, now = Date.now) {
|
|
1160
|
+
var _a;
|
|
1161
|
+
const path2 = `/v1/branches/${encodeURIComponent(o.name)}?project=${encodeURIComponent(o.project)}`;
|
|
1162
|
+
const read = async () => {
|
|
1163
|
+
try {
|
|
1164
|
+
return (await client.get(path2)).data;
|
|
1165
|
+
} catch (error) {
|
|
1166
|
+
if (error instanceof ApiError && error.status === 404 && error.code === "branch_not_found") {
|
|
1167
|
+
return null;
|
|
1168
|
+
}
|
|
1169
|
+
throw error;
|
|
1170
|
+
}
|
|
1171
|
+
};
|
|
1172
|
+
const expiresAt = () => new Date(now() + o.expiresInMs).toISOString();
|
|
1173
|
+
let branch = await read();
|
|
1174
|
+
let created = false;
|
|
1175
|
+
if (!branch) {
|
|
1176
|
+
const body = {
|
|
1177
|
+
name: o.name,
|
|
1178
|
+
projectId: o.project,
|
|
1179
|
+
expiresAt: expiresAt()
|
|
1180
|
+
};
|
|
1181
|
+
if (o.from) body.parent = o.from;
|
|
1182
|
+
try {
|
|
1183
|
+
branch = (await client.post("/v1/branches", body)).data;
|
|
1184
|
+
created = true;
|
|
1185
|
+
} catch (error) {
|
|
1186
|
+
if (!(error instanceof ApiError && error.code === "branch_exists"))
|
|
1187
|
+
throw restateCreate(error);
|
|
1188
|
+
branch = await read();
|
|
1189
|
+
if (!branch) throw error;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
if (branch.primary) {
|
|
1193
|
+
throw new UsageError(
|
|
1194
|
+
`${branch.name} is the primary branch. ensure is for preview branches; run it only on preview builds.`
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1197
|
+
for (const until = now() + ENSURE_WAIT_MS; branch.state === "creating" && now() < until; ) {
|
|
1198
|
+
await sleep(ENSURE_POLL_MS);
|
|
1199
|
+
branch = await read() ?? branch;
|
|
1200
|
+
}
|
|
1201
|
+
if (branch.state !== "ready") {
|
|
1202
|
+
throw new CliError(`Branch ${branch.name} is ${branch.state}, not ready. Try again shortly.`, {
|
|
1203
|
+
exitCode: EXIT_CODES.conflict,
|
|
1204
|
+
code: "branch_not_ready"
|
|
1205
|
+
});
|
|
1206
|
+
}
|
|
1207
|
+
if (!created && !branch.protected) {
|
|
1208
|
+
branch = {
|
|
1209
|
+
...branch,
|
|
1210
|
+
...(await client.patch(path2, { expiresAt: expiresAt() })).data
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
if (!branch.instances)
|
|
1214
|
+
branch = { ...branch, ...(await client.get(path2)).data };
|
|
1215
|
+
const mint = async (type) => (await client.post("/v1/keys", {
|
|
1216
|
+
name: `preview ${branch.name} ${type}`,
|
|
1217
|
+
type,
|
|
1218
|
+
projectId: o.project,
|
|
1219
|
+
branch: branch.id
|
|
1220
|
+
})).data.apiKey;
|
|
1221
|
+
const env = {
|
|
1222
|
+
HEADBAND_URL: o.url,
|
|
1223
|
+
HEADBAND_PROJECT_ID: o.project,
|
|
1224
|
+
HEADBAND_BRANCH: branch.name,
|
|
1225
|
+
HEADBAND_BRANCH_ID: branch.id,
|
|
1226
|
+
HEADBAND_SEARCH_KEY: await mint("search"),
|
|
1227
|
+
HEADBAND_ADMIN_KEY: await mint("admin")
|
|
1228
|
+
};
|
|
1229
|
+
for (const i of branch.instances ?? []) env[_a = engineVar(i.engine)] ?? (env[_a] = i.endpoint);
|
|
1230
|
+
return { branch, created, env };
|
|
1231
|
+
}
|
|
1232
|
+
var quote = (v) => /^[A-Za-z0-9_./:@+-]*$/.test(v) ? v : JSON.stringify(v);
|
|
1233
|
+
function dotenv(env) {
|
|
1234
|
+
return Object.entries(env).map(([k, v]) => `${k}=${quote(v)}`).join("\n");
|
|
1235
|
+
}
|
|
1236
|
+
function writeEnvFile(file, env) {
|
|
1237
|
+
const kept = (0, import_fs4.existsSync)(file) ? (0, import_fs4.readFileSync)(file, "utf8").split("\n").filter((line) => line.trim() !== "" && !/^\s*(export\s+)?HEADBAND_[A-Z0-9_]*=/.test(line)) : [];
|
|
1238
|
+
(0, import_fs4.writeFileSync)(file, [...kept, dotenv(env)].join("\n") + "\n", { mode: 384 });
|
|
1239
|
+
(0, import_fs4.chmodSync)(file, 384);
|
|
1240
|
+
}
|
|
1118
1241
|
|
|
1119
1242
|
// src/config-load.ts
|
|
1120
|
-
var
|
|
1243
|
+
var import_fs5 = require("fs");
|
|
1121
1244
|
var import_path6 = require("path");
|
|
1122
1245
|
var import_url = require("url");
|
|
1123
1246
|
var import_jiti = require("jiti");
|
|
@@ -1253,7 +1376,7 @@ var SYNC_CONFIG_SHAPE = 'a headband.config.ts whose default export is defineConf
|
|
|
1253
1376
|
function findConfigFile(cwd) {
|
|
1254
1377
|
for (const name of CONFIG_FILENAMES) {
|
|
1255
1378
|
const path2 = (0, import_path6.join)(cwd, name);
|
|
1256
|
-
if ((0,
|
|
1379
|
+
if ((0, import_fs5.existsSync)(path2)) return path2;
|
|
1257
1380
|
}
|
|
1258
1381
|
return void 0;
|
|
1259
1382
|
}
|
|
@@ -1373,10 +1496,10 @@ function missingAdapterError(command, why) {
|
|
|
1373
1496
|
|
|
1374
1497
|
// src/docs.ts
|
|
1375
1498
|
var import_path7 = require("path");
|
|
1376
|
-
var
|
|
1499
|
+
var import_fs6 = require("fs");
|
|
1377
1500
|
var DOCS_BATCH_SIZE = 1e3;
|
|
1378
1501
|
function parseDocumentsFile(path2) {
|
|
1379
|
-
const content = (0,
|
|
1502
|
+
const content = (0, import_fs6.readFileSync)(path2, "utf-8");
|
|
1380
1503
|
const ext = (0, import_path7.extname)(path2).toLowerCase();
|
|
1381
1504
|
if (ext === ".jsonl") {
|
|
1382
1505
|
return content.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => JSON.parse(line));
|
|
@@ -1501,7 +1624,7 @@ function refusedWalk(error) {
|
|
|
1501
1624
|
var DECLARE_WAIT_MS = 15 * 6e4;
|
|
1502
1625
|
var DECLARE_POLL_MS = 3e3;
|
|
1503
1626
|
async function declareExportKey(client, index, io, opts = {}) {
|
|
1504
|
-
const
|
|
1627
|
+
const sleep2 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
1505
1628
|
const now = opts.now ?? Date.now;
|
|
1506
1629
|
const probe = () => client.get(`/v1/documents?index=${encodeURIComponent(index)}&limit=1&cursor=`);
|
|
1507
1630
|
let refused;
|
|
@@ -1547,7 +1670,7 @@ async function declareExportKey(client, index, io, opts = {}) {
|
|
|
1547
1670
|
`\`${pk}\` is still not declared on ${index} after ${Math.round((opts.waitMs ?? DECLARE_WAIT_MS) / 6e4)} minutes. Visor may still be rebuilding it: run headband docs export again later.`
|
|
1548
1671
|
);
|
|
1549
1672
|
}
|
|
1550
|
-
await
|
|
1673
|
+
await sleep2(DECLARE_POLL_MS);
|
|
1551
1674
|
}
|
|
1552
1675
|
}
|
|
1553
1676
|
function exportRefusalAdvice(error, index) {
|
|
@@ -2167,7 +2290,7 @@ async function runSwitchEngine(client, opts, io, chatter) {
|
|
|
2167
2290
|
);
|
|
2168
2291
|
return;
|
|
2169
2292
|
}
|
|
2170
|
-
const
|
|
2293
|
+
const sleep2 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
2171
2294
|
let lastPhase = "";
|
|
2172
2295
|
for (; ; ) {
|
|
2173
2296
|
if (view.phase !== lastPhase) {
|
|
@@ -2175,7 +2298,7 @@ async function runSwitchEngine(client, opts, io, chatter) {
|
|
|
2175
2298
|
chatter.stdout(`${opts.uid}: ${PHASES[view.phase] ?? view.phase}`);
|
|
2176
2299
|
}
|
|
2177
2300
|
if (view.status === "succeeded" || view.status === "failed") break;
|
|
2178
|
-
await
|
|
2301
|
+
await sleep2(opts.pollMs ?? 3e3);
|
|
2179
2302
|
const status = await client.get(path2);
|
|
2180
2303
|
if (!status.switch || status.switch.switchId !== view.switchId) {
|
|
2181
2304
|
throw new CliError("The switch is no longer listed for this index.", {
|
|
@@ -2312,11 +2435,11 @@ function planMigration(config, applied, opts) {
|
|
|
2312
2435
|
}
|
|
2313
2436
|
|
|
2314
2437
|
// src/schema.ts
|
|
2315
|
-
var
|
|
2438
|
+
var import_fs8 = require("fs");
|
|
2316
2439
|
var import_path9 = require("path");
|
|
2317
2440
|
|
|
2318
2441
|
// ../sync/src/migrations.ts
|
|
2319
|
-
var
|
|
2442
|
+
var import_fs7 = require("fs");
|
|
2320
2443
|
var import_path8 = require("path");
|
|
2321
2444
|
var FILE_NAME_RE = /^(\d{4})_(.+)\.json$/;
|
|
2322
2445
|
function sanitizeSlug(slug) {
|
|
@@ -2324,7 +2447,7 @@ function sanitizeSlug(slug) {
|
|
|
2324
2447
|
return sanitized || "migration";
|
|
2325
2448
|
}
|
|
2326
2449
|
function generateMigrationFile(dir, ops, meta, now = () => /* @__PURE__ */ new Date()) {
|
|
2327
|
-
(0,
|
|
2450
|
+
(0, import_fs7.mkdirSync)(dir, { recursive: true });
|
|
2328
2451
|
const existing = readMigrations(dir);
|
|
2329
2452
|
const nextVersion = existing.length === 0 ? 1 : existing[existing.length - 1].version + 1;
|
|
2330
2453
|
const safeSlug = sanitizeSlug(meta.slug);
|
|
@@ -2337,13 +2460,13 @@ function generateMigrationFile(dir, ops, meta, now = () => /* @__PURE__ */ new D
|
|
|
2337
2460
|
...meta.targetHashes ? { targetHashes: meta.targetHashes } : {},
|
|
2338
2461
|
ops
|
|
2339
2462
|
};
|
|
2340
|
-
(0,
|
|
2463
|
+
(0, import_fs7.writeFileSync)(path2, JSON.stringify(file, null, 2));
|
|
2341
2464
|
return path2;
|
|
2342
2465
|
}
|
|
2343
2466
|
function readMigrations(dir) {
|
|
2344
2467
|
let entries;
|
|
2345
2468
|
try {
|
|
2346
|
-
entries = (0,
|
|
2469
|
+
entries = (0, import_fs7.readdirSync)(dir);
|
|
2347
2470
|
} catch {
|
|
2348
2471
|
return [];
|
|
2349
2472
|
}
|
|
@@ -2352,7 +2475,7 @@ function readMigrations(dir) {
|
|
|
2352
2475
|
const match = FILE_NAME_RE.exec(entry);
|
|
2353
2476
|
if (!match) continue;
|
|
2354
2477
|
const path2 = (0, import_path8.join)(dir, entry);
|
|
2355
|
-
const raw = (0,
|
|
2478
|
+
const raw = (0, import_fs7.readFileSync)(path2, "utf-8");
|
|
2356
2479
|
let parsed;
|
|
2357
2480
|
try {
|
|
2358
2481
|
parsed = JSON.parse(raw);
|
|
@@ -2382,7 +2505,7 @@ function pendingMigrations(files, appliedVersion) {
|
|
|
2382
2505
|
var BASE_BACKOFF_MS = 500;
|
|
2383
2506
|
var DEFAULT_MAX_RETRIES = 5;
|
|
2384
2507
|
function defaultSleep3(ms) {
|
|
2385
|
-
return new Promise((
|
|
2508
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
2386
2509
|
}
|
|
2387
2510
|
var SchemaCasError = class extends Error {
|
|
2388
2511
|
constructor(message) {
|
|
@@ -2549,17 +2672,17 @@ function printOpsTable(io, ops) {
|
|
|
2549
2672
|
}
|
|
2550
2673
|
function readAppliedMarker(dir) {
|
|
2551
2674
|
const path2 = (0, import_path9.join)(dir, APPLIED_MARKER_FILE);
|
|
2552
|
-
if (!(0,
|
|
2675
|
+
if (!(0, import_fs8.existsSync)(path2)) return 0;
|
|
2553
2676
|
try {
|
|
2554
|
-
const parsed = JSON.parse((0,
|
|
2677
|
+
const parsed = JSON.parse((0, import_fs8.readFileSync)(path2, "utf-8"));
|
|
2555
2678
|
return typeof parsed.version === "number" ? parsed.version : 0;
|
|
2556
2679
|
} catch {
|
|
2557
2680
|
return 0;
|
|
2558
2681
|
}
|
|
2559
2682
|
}
|
|
2560
2683
|
function writeAppliedMarker(dir, version) {
|
|
2561
|
-
(0,
|
|
2562
|
-
(0,
|
|
2684
|
+
(0, import_fs8.mkdirSync)(dir, { recursive: true });
|
|
2685
|
+
(0, import_fs8.writeFileSync)((0, import_path9.join)(dir, APPLIED_MARKER_FILE), JSON.stringify({ version }, null, 2));
|
|
2563
2686
|
}
|
|
2564
2687
|
function configHashes(config, index) {
|
|
2565
2688
|
const def = config.indexes.find((d) => d.name === index);
|
|
@@ -3068,7 +3191,7 @@ async function runSchemaForget(deps) {
|
|
|
3068
3191
|
}
|
|
3069
3192
|
|
|
3070
3193
|
// src/sync.ts
|
|
3071
|
-
var
|
|
3194
|
+
var import_fs9 = require("fs");
|
|
3072
3195
|
var import_path10 = require("path");
|
|
3073
3196
|
var import_crypto = require("crypto");
|
|
3074
3197
|
var import_pg = require("pg");
|
|
@@ -3359,7 +3482,7 @@ function rowOutcomes(rows, resolved, failures) {
|
|
|
3359
3482
|
return { done, retry, deadLetter };
|
|
3360
3483
|
}
|
|
3361
3484
|
function defaultSleep4(ms) {
|
|
3362
|
-
return new Promise((
|
|
3485
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
3363
3486
|
}
|
|
3364
3487
|
function errorMessage(err) {
|
|
3365
3488
|
if (!(err instanceof Error)) return String(err);
|
|
@@ -3399,7 +3522,7 @@ async function runSyncWorker(opts) {
|
|
|
3399
3522
|
batchSize = DEFAULT_BATCH_SIZE,
|
|
3400
3523
|
stopSignal,
|
|
3401
3524
|
now = Date.now,
|
|
3402
|
-
sleep = defaultSleep4,
|
|
3525
|
+
sleep: sleep2 = defaultSleep4,
|
|
3403
3526
|
onHeartbeat,
|
|
3404
3527
|
log = (message) => console.warn(message)
|
|
3405
3528
|
} = opts;
|
|
@@ -3426,7 +3549,7 @@ async function runSyncWorker(opts) {
|
|
|
3426
3549
|
log(`[headband] could not record this worker run: ${errorMessage(err)}`);
|
|
3427
3550
|
}
|
|
3428
3551
|
};
|
|
3429
|
-
const backoff = (attempt) =>
|
|
3552
|
+
const backoff = (attempt) => sleep2(Math.min(failureDelayMs * 2 ** (attempt - 1), MAX_FAILURE_DELAY_MS));
|
|
3430
3553
|
const processBatch = async (batch) => {
|
|
3431
3554
|
const changes = batch.map((row) => ({
|
|
3432
3555
|
model: row.model,
|
|
@@ -3468,7 +3591,7 @@ async function runSyncWorker(opts) {
|
|
|
3468
3591
|
const rows = await outbox.claim(batchSize);
|
|
3469
3592
|
if (rows.length === 0) {
|
|
3470
3593
|
if (!loop) break;
|
|
3471
|
-
await
|
|
3594
|
+
await sleep2(idleDelayMs);
|
|
3472
3595
|
continue;
|
|
3473
3596
|
}
|
|
3474
3597
|
let outcome;
|
|
@@ -3627,8 +3750,8 @@ async function runSyncInit(deps) {
|
|
|
3627
3750
|
deps.io.stdout(ddl);
|
|
3628
3751
|
if (deps.write) {
|
|
3629
3752
|
const path2 = (0, import_path10.join)(deps.cwd ?? process.cwd(), OUTBOX_SQL_RELATIVE_PATH);
|
|
3630
|
-
(0,
|
|
3631
|
-
(0,
|
|
3753
|
+
(0, import_fs9.mkdirSync)((0, import_path10.join)(deps.cwd ?? process.cwd(), "headband"), { recursive: true });
|
|
3754
|
+
(0, import_fs9.writeFileSync)(path2, `${ddl}
|
|
3632
3755
|
`);
|
|
3633
3756
|
deps.io.stdout(`Wrote ${path2}`);
|
|
3634
3757
|
}
|
|
@@ -4014,10 +4137,10 @@ async function runSelfHostedSyncStatus(deps) {
|
|
|
4014
4137
|
}
|
|
4015
4138
|
function runSyncScaffoldDocker(deps) {
|
|
4016
4139
|
const path2 = (0, import_path10.join)(deps.cwd, DOCKERFILE_NAME);
|
|
4017
|
-
if ((0,
|
|
4140
|
+
if ((0, import_fs9.existsSync)(path2) && !deps.force) {
|
|
4018
4141
|
throw new CliError(`${DOCKERFILE_NAME} already exists at ${path2}. Pass --force to overwrite.`);
|
|
4019
4142
|
}
|
|
4020
|
-
(0,
|
|
4143
|
+
(0, import_fs9.writeFileSync)(path2, dockerfileContent(deps.cliVersion));
|
|
4021
4144
|
deps.io.stdout(`Wrote ${path2}`);
|
|
4022
4145
|
deps.io.stdout("Add this service to your docker-compose.yml:");
|
|
4023
4146
|
deps.io.stdout(COMPOSE_SNIPPET);
|
|
@@ -4194,10 +4317,10 @@ ${CONFIG_NOTE}`,
|
|
|
4194
4317
|
};
|
|
4195
4318
|
|
|
4196
4319
|
// src/sync-indexes.ts
|
|
4197
|
-
var
|
|
4320
|
+
var import_fs11 = require("fs");
|
|
4198
4321
|
|
|
4199
4322
|
// src/sync-sources.ts
|
|
4200
|
-
var
|
|
4323
|
+
var import_fs10 = require("fs");
|
|
4201
4324
|
var import_pg2 = require("pg");
|
|
4202
4325
|
|
|
4203
4326
|
// ../../src/lib/sync/copy.ts
|
|
@@ -4264,7 +4387,7 @@ var SOURCE_USAGE = [
|
|
|
4264
4387
|
function readCaFile(deps, file) {
|
|
4265
4388
|
let text;
|
|
4266
4389
|
try {
|
|
4267
|
-
text = (deps.readFile ?? ((p) => (0,
|
|
4390
|
+
text = (deps.readFile ?? ((p) => (0, import_fs10.readFileSync)(p, "utf8")))(file);
|
|
4268
4391
|
} catch {
|
|
4269
4392
|
throw new CliError("Cannot read the --ca-file.");
|
|
4270
4393
|
}
|
|
@@ -4905,7 +5028,7 @@ var PREVIEW_MAX_IDS = 20;
|
|
|
4905
5028
|
var ATTENTION_INDEX = /* @__PURE__ */ new Set(["error", "needs_rebuild", "awaiting_setup", "degraded"]);
|
|
4906
5029
|
var ATTENTION_SOURCE = /* @__PURE__ */ new Set(["error", "awaiting_setup"]);
|
|
4907
5030
|
var path = (id, suffix = "") => `/v1/sync/indexes/${encodeURIComponent(id)}${suffix}`;
|
|
4908
|
-
function readDefinition(file, readFile = (p) => (0,
|
|
5031
|
+
function readDefinition(file, readFile = (p) => (0, import_fs11.readFileSync)(p, "utf8")) {
|
|
4909
5032
|
let text;
|
|
4910
5033
|
try {
|
|
4911
5034
|
text = readFile(file);
|
|
@@ -5324,6 +5447,8 @@ var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
|
5324
5447
|
"--branch",
|
|
5325
5448
|
"--parent",
|
|
5326
5449
|
"--expires-in",
|
|
5450
|
+
"--env-out",
|
|
5451
|
+
"--from",
|
|
5327
5452
|
"--in",
|
|
5328
5453
|
"--at",
|
|
5329
5454
|
"--level",
|
|
@@ -5496,12 +5621,13 @@ Commands:
|
|
|
5496
5621
|
projects create <name> --engine <meilisearch|typesense|elasticsearch|visor> [--json]
|
|
5497
5622
|
projects delete <id> [--yes] [--json]
|
|
5498
5623
|
branch list [--project <id>] [--json]
|
|
5499
|
-
branch create <name> --empty [--parent <name>] [--expires-in <2h|7d>] [--project <id>] [--json]
|
|
5624
|
+
branch create <name> [--empty] [--parent <name>] [--expires-in <2h|7d>] [--project <id>] [--json]
|
|
5625
|
+
branch ensure <name> [--from <parent>] [--expires-in <7d>] [--env-out <file>] [--project <id>] [--json]
|
|
5500
5626
|
branch delete <name> [--project <id>] [--yes] [--json]
|
|
5501
5627
|
branch protect|unprotect <name> [--project <id>] [--json]
|
|
5502
5628
|
branch expire <name> (--in <2h|7d> | --at <iso> | --clear) [--project <id>] [--json]
|
|
5503
5629
|
branch checkout <name> [--project <id>] [--json]
|
|
5504
|
-
branch reset|rollback|restore|set-primary (
|
|
5630
|
+
branch reset|rollback|restore|set-primary (not available yet)
|
|
5505
5631
|
keys list [--json]
|
|
5506
5632
|
keys create <name> --type <admin|search> [--project <id>] [--branch <name>] [--instances <id,id>] [--indexes <uid,uid>] [--permissions <a,b>] [--expires <30d|ISO date>] [--json]
|
|
5507
5633
|
keys revoke <id> [--yes] [--json]
|
|
@@ -6054,7 +6180,7 @@ async function runIndexes(args, flags, io, confirm, cwd) {
|
|
|
6054
6180
|
}
|
|
6055
6181
|
if (settingsAction === "set") {
|
|
6056
6182
|
const file = requiredFlag(flags, "--file");
|
|
6057
|
-
const settings = JSON.parse((0,
|
|
6183
|
+
const settings = JSON.parse((0, import_fs13.readFileSync)(file, "utf-8"));
|
|
6058
6184
|
const response = await client.patch(`/v1/indexes/${encode(uid)}/settings`, settings);
|
|
6059
6185
|
printJson(io, response);
|
|
6060
6186
|
return;
|
|
@@ -6115,7 +6241,7 @@ async function runDocsExport(flags, context, cwd) {
|
|
|
6115
6241
|
return;
|
|
6116
6242
|
}
|
|
6117
6243
|
const partial = `${out}.partial`;
|
|
6118
|
-
const fd = (0,
|
|
6244
|
+
const fd = (0, import_fs12.openSync)(partial, "w", 384);
|
|
6119
6245
|
let exported;
|
|
6120
6246
|
try {
|
|
6121
6247
|
exported = await exportDocuments(client, {
|
|
@@ -6123,26 +6249,26 @@ async function runDocsExport(flags, context, cwd) {
|
|
|
6123
6249
|
format,
|
|
6124
6250
|
batchSize,
|
|
6125
6251
|
write: (line) => {
|
|
6126
|
-
(0,
|
|
6252
|
+
(0, import_fs12.writeSync)(fd, `${line}
|
|
6127
6253
|
`);
|
|
6128
6254
|
},
|
|
6129
6255
|
onPage: (count, total) => io.stderr(`Exported ${count}${total === null ? "" : `/${total}`} documents`),
|
|
6130
6256
|
finishOnError: true
|
|
6131
6257
|
});
|
|
6132
|
-
(0,
|
|
6258
|
+
(0, import_fs12.closeSync)(fd);
|
|
6133
6259
|
} catch (error) {
|
|
6134
|
-
(0,
|
|
6260
|
+
(0, import_fs12.closeSync)(fd);
|
|
6135
6261
|
const kept = error instanceof ExportInterruptedError ? error.exported : 0;
|
|
6136
6262
|
if (kept > 0) {
|
|
6137
6263
|
io.stderr(
|
|
6138
6264
|
`Kept the ${kept} ${kept === 1 ? "document" : "documents"} exported before the error in ${partial}. ${out} was not written.`
|
|
6139
6265
|
);
|
|
6140
6266
|
} else {
|
|
6141
|
-
(0,
|
|
6267
|
+
(0, import_fs12.rmSync)(partial, { force: true });
|
|
6142
6268
|
}
|
|
6143
6269
|
throw exportFailure(error instanceof ExportInterruptedError ? error.cause : error, index);
|
|
6144
6270
|
}
|
|
6145
|
-
(0,
|
|
6271
|
+
(0, import_fs12.renameSync)(partial, out);
|
|
6146
6272
|
if (boolFlag(flags, "--json")) printJson(io, { index, out, format, exported });
|
|
6147
6273
|
else io.stdout(`Exported ${exported} documents to ${out}.`);
|
|
6148
6274
|
}
|
|
@@ -6513,7 +6639,7 @@ async function runSync(args, flags, io, confirm, cwd) {
|
|
|
6513
6639
|
printCommandHelp("sync", io);
|
|
6514
6640
|
throw new UsageError("Unknown sync command");
|
|
6515
6641
|
}
|
|
6516
|
-
var CLI_VERSION = true ? "0.
|
|
6642
|
+
var CLI_VERSION = true ? "0.7.0" : "dev";
|
|
6517
6643
|
async function run(argv, context = { io: defaultIO }) {
|
|
6518
6644
|
if (argv.length === 1 && (argv[0] === "--version" || argv[0] === "-v" || argv[0] === "version")) {
|
|
6519
6645
|
context.io.stdout(CLI_VERSION);
|
|
@@ -6615,7 +6741,7 @@ async function main(argv, context = { io: defaultIO }) {
|
|
|
6615
6741
|
function stdoutLineWriter(line) {
|
|
6616
6742
|
if (process.stdout.write(`${line}
|
|
6617
6743
|
`)) return;
|
|
6618
|
-
return new Promise((
|
|
6744
|
+
return new Promise((resolve3) => process.stdout.once("drain", () => resolve3()));
|
|
6619
6745
|
}
|
|
6620
6746
|
function isDirectRun() {
|
|
6621
6747
|
return typeof require !== "undefined" && typeof module !== "undefined" && require.main === module;
|