@kryd/cli 0.1.0 → 0.2.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 +5 -4
- package/dist/index.js +207 -7
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -10,15 +10,15 @@ npm install -g @kryd/cli # then use the `kryd` command
|
|
|
10
10
|
npx @kryd/cli <command>
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
Requires Node.js ≥ 22.
|
|
13
|
+
Requires Node.js ≥ 22 and git (the CLI shells out to git for `kryd init` and `kryd push`).
|
|
14
14
|
|
|
15
15
|
## Quickstart
|
|
16
16
|
|
|
17
17
|
```sh
|
|
18
18
|
kryd login # sign in via the browser
|
|
19
19
|
kryd init # link this folder to a Kryd project (+ a `kryd` git remote)
|
|
20
|
-
|
|
21
|
-
kryd logs # follow the linked project's latest deploy
|
|
20
|
+
kryd push # push the current branch → build → deploy → live, streamed
|
|
21
|
+
kryd logs # follow the linked project's latest deploy (kryd push already does)
|
|
22
22
|
kryd logs <project> --runtime # tail the live app's stdout/stderr
|
|
23
23
|
```
|
|
24
24
|
|
|
@@ -30,7 +30,8 @@ Once you've run `kryd init` in a directory, the commands below work **arg-less**
|
|
|
30
30
|
|---|---|
|
|
31
31
|
| `kryd login` / `logout` / `whoami` | Authenticate the CLI (browser flow); the token is stored in `~/.kryd`. |
|
|
32
32
|
| `kryd init` | Link the current repo to a Kryd project + register the deploy webhook + add a `kryd` git remote. |
|
|
33
|
-
| `kryd
|
|
33
|
+
| `kryd push [branch]` | Push to the `kryd` remote and follow the deploy it triggers (defaults to the current branch). |
|
|
34
|
+
| `kryd deploy [project]` | Re-deploy the production-branch HEAD already on the forge — no new commit. |
|
|
34
35
|
| `kryd logs [target]` | Follow a deploy's build/deploy log (`--runtime` tails the live container instead). |
|
|
35
36
|
| `kryd rollback [project] [deployment]` | Roll back to a previous successful deploy — no rebuild. |
|
|
36
37
|
| `kryd db create \| detach [project]` | Attach / tear down managed Postgres (shared or bring-your-own). |
|
package/dist/index.js
CHANGED
|
@@ -15144,6 +15144,32 @@ async function listDeployments(apiUrl, token, projectId) {
|
|
|
15144
15144
|
}
|
|
15145
15145
|
return (await res.json()).items;
|
|
15146
15146
|
}
|
|
15147
|
+
function findDeploymentsForCommit(deployments, commitSha, branch) {
|
|
15148
|
+
return deployments.filter((d) => d.commitSha === commitSha && d.branch === branch);
|
|
15149
|
+
}
|
|
15150
|
+
async function pollForNewDeployment(fetchDeployments, match, opts) {
|
|
15151
|
+
const intervalMs = opts?.intervalMs ?? 2e3;
|
|
15152
|
+
const timeoutMs = opts?.timeoutMs ?? 6e4;
|
|
15153
|
+
const maxConsecutiveErrors = opts?.maxConsecutiveErrors ?? 5;
|
|
15154
|
+
const sleep = opts?.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
15155
|
+
const started = Date.now();
|
|
15156
|
+
let consecutiveErrors = 0;
|
|
15157
|
+
for (; ; ) {
|
|
15158
|
+
try {
|
|
15159
|
+
const items = await fetchDeployments();
|
|
15160
|
+
consecutiveErrors = 0;
|
|
15161
|
+
const fresh = findDeploymentsForCommit(items, match.commitSha, match.branch).filter(
|
|
15162
|
+
(d) => !match.knownIds.has(d.id)
|
|
15163
|
+
);
|
|
15164
|
+
const found = fresh[fresh.length - 1];
|
|
15165
|
+
if (found) return found;
|
|
15166
|
+
} catch (err) {
|
|
15167
|
+
if (++consecutiveErrors > maxConsecutiveErrors) throw err;
|
|
15168
|
+
}
|
|
15169
|
+
if (Date.now() - started >= timeoutMs) return null;
|
|
15170
|
+
await sleep(intervalMs);
|
|
15171
|
+
}
|
|
15172
|
+
}
|
|
15147
15173
|
function dataFromFrame(frame) {
|
|
15148
15174
|
const data = frame.split("\n").filter((l) => l.startsWith("data:")).map((l) => l.slice(5).replace(/^ /, "")).join("\n");
|
|
15149
15175
|
return data.length > 0 ? data : void 0;
|
|
@@ -15301,7 +15327,7 @@ function inspectProject(cwd) {
|
|
|
15301
15327
|
}
|
|
15302
15328
|
|
|
15303
15329
|
// src/git.ts
|
|
15304
|
-
import { execFileSync } from "node:child_process";
|
|
15330
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
15305
15331
|
function authenticatedRemoteUrl(cloneUrl, username, token) {
|
|
15306
15332
|
const prefix = "https://";
|
|
15307
15333
|
if (!cloneUrl.startsWith(prefix)) return cloneUrl;
|
|
@@ -15330,6 +15356,43 @@ function configureGitRemote(cwd, remote, url2) {
|
|
|
15330
15356
|
return { status: "unavailable" };
|
|
15331
15357
|
}
|
|
15332
15358
|
}
|
|
15359
|
+
function probe(cwd, args) {
|
|
15360
|
+
try {
|
|
15361
|
+
const value = execFileSync("git", args, {
|
|
15362
|
+
cwd,
|
|
15363
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
15364
|
+
}).toString().trim();
|
|
15365
|
+
return { status: "ok", value };
|
|
15366
|
+
} catch (err) {
|
|
15367
|
+
if (err.code === "ENOENT") return { status: "no-git" };
|
|
15368
|
+
return { status: "failed" };
|
|
15369
|
+
}
|
|
15370
|
+
}
|
|
15371
|
+
function gitAvailable(cwd) {
|
|
15372
|
+
const res = probe(cwd, ["rev-parse", "--is-inside-work-tree"]);
|
|
15373
|
+
if (res.status === "no-git") return res;
|
|
15374
|
+
if (res.status !== "ok" || res.value !== "true") return { status: "not-a-repo" };
|
|
15375
|
+
return { status: "ok" };
|
|
15376
|
+
}
|
|
15377
|
+
function currentBranch(cwd) {
|
|
15378
|
+
return probe(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
15379
|
+
}
|
|
15380
|
+
function headSha(cwd, ref) {
|
|
15381
|
+
return probe(cwd, ["rev-parse", ref]);
|
|
15382
|
+
}
|
|
15383
|
+
function hasRemote(cwd, remote) {
|
|
15384
|
+
const res = probe(cwd, ["remote"]);
|
|
15385
|
+
if (res.status !== "ok") return res;
|
|
15386
|
+
return { status: "ok", value: res.value.split(/\s+/).filter(Boolean).includes(remote) };
|
|
15387
|
+
}
|
|
15388
|
+
function pushBranch(cwd, remote, branch, extraArgs) {
|
|
15389
|
+
const res = spawnSync("git", ["push", remote, branch, ...extraArgs], {
|
|
15390
|
+
cwd,
|
|
15391
|
+
stdio: "inherit"
|
|
15392
|
+
});
|
|
15393
|
+
if (res.status === 0) return { status: "ok" };
|
|
15394
|
+
return { status: "failed", code: res.status ?? 1 };
|
|
15395
|
+
}
|
|
15333
15396
|
|
|
15334
15397
|
// src/index.ts
|
|
15335
15398
|
function reportError(err) {
|
|
@@ -15458,20 +15521,22 @@ async function runInit(opts) {
|
|
|
15458
15521
|
case "added":
|
|
15459
15522
|
case "updated":
|
|
15460
15523
|
nextStep = `${remote.status === "added" ? "Added" : "Updated"} git remote "${remote.remote}" (with your push credential).
|
|
15461
|
-
Next: git push ${remote.remote} ${branch}
|
|
15524
|
+
Next: kryd push # push to deploy (same as \`git push ${remote.remote} ${branch}\`)
|
|
15462
15525
|
`;
|
|
15463
15526
|
break;
|
|
15464
15527
|
case "not-a-repo":
|
|
15465
15528
|
nextStep = `No git repo here yet. To deploy (the URL carries your push token \u2014 keep it private):
|
|
15466
15529
|
git init && git add -A && git commit -m "init"
|
|
15467
15530
|
git remote add kryd ${authedUrl}
|
|
15468
|
-
|
|
15531
|
+
kryd push
|
|
15469
15532
|
`;
|
|
15470
15533
|
break;
|
|
15471
15534
|
case "unavailable":
|
|
15472
|
-
nextStep =
|
|
15535
|
+
nextStep = gitAvailable(cwd).status === "no-git" ? `git is not installed, so the deploy remote could not be configured.
|
|
15536
|
+
Install git (https://git-scm.com/downloads), then re-run \`kryd init\` here.
|
|
15537
|
+
` : `Add the remote, then push to deploy (the URL carries your push token \u2014 keep it private):
|
|
15473
15538
|
git remote add kryd ${authedUrl}
|
|
15474
|
-
|
|
15539
|
+
kryd push
|
|
15475
15540
|
`;
|
|
15476
15541
|
break;
|
|
15477
15542
|
}
|
|
@@ -15605,6 +15670,135 @@ async function runDeploy(opts) {
|
|
|
15605
15670
|
reportError(err);
|
|
15606
15671
|
}
|
|
15607
15672
|
}
|
|
15673
|
+
async function runPush(opts) {
|
|
15674
|
+
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
15675
|
+
const token = loadConfig().token;
|
|
15676
|
+
if (!token) {
|
|
15677
|
+
process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
|
|
15678
|
+
process.exitCode = 1;
|
|
15679
|
+
return;
|
|
15680
|
+
}
|
|
15681
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
15682
|
+
const available = gitAvailable(cwd);
|
|
15683
|
+
if (available.status === "no-git") {
|
|
15684
|
+
process.stderr.write(
|
|
15685
|
+
"git is not installed, and `kryd push` needs it to deploy.\nInstall git (https://git-scm.com/downloads), then try again.\n"
|
|
15686
|
+
);
|
|
15687
|
+
process.exitCode = 1;
|
|
15688
|
+
return;
|
|
15689
|
+
}
|
|
15690
|
+
if (available.status !== "ok") {
|
|
15691
|
+
process.stderr.write(
|
|
15692
|
+
"Not a git repository \u2014 run `kryd push` from the root of your app's repo.\n"
|
|
15693
|
+
);
|
|
15694
|
+
process.exitCode = 1;
|
|
15695
|
+
return;
|
|
15696
|
+
}
|
|
15697
|
+
const remote = hasRemote(cwd, "kryd");
|
|
15698
|
+
if (remote.status !== "ok" || !remote.value) {
|
|
15699
|
+
process.stderr.write(
|
|
15700
|
+
'No "kryd" git remote here. Run `kryd init` to link this project and configure it.\n'
|
|
15701
|
+
);
|
|
15702
|
+
process.exitCode = 1;
|
|
15703
|
+
return;
|
|
15704
|
+
}
|
|
15705
|
+
if (opts.branch?.startsWith("-")) {
|
|
15706
|
+
process.stderr.write(
|
|
15707
|
+
`"${opts.branch}" is not a branch. Pass the branch before \`--\`, e.g. \`kryd push main -- ${opts.branch}\`.
|
|
15708
|
+
`
|
|
15709
|
+
);
|
|
15710
|
+
process.exitCode = 1;
|
|
15711
|
+
return;
|
|
15712
|
+
}
|
|
15713
|
+
let branch = opts.branch;
|
|
15714
|
+
if (!branch) {
|
|
15715
|
+
const current = currentBranch(cwd);
|
|
15716
|
+
if (current.status !== "ok") {
|
|
15717
|
+
process.stderr.write("Could not determine the current branch \u2014 pass one, e.g. `kryd push main`.\n");
|
|
15718
|
+
process.exitCode = 1;
|
|
15719
|
+
return;
|
|
15720
|
+
}
|
|
15721
|
+
if (current.value === "HEAD") {
|
|
15722
|
+
process.stderr.write(
|
|
15723
|
+
"You are on a detached HEAD \u2014 pass the branch to push explicitly, e.g. `kryd push main`.\n"
|
|
15724
|
+
);
|
|
15725
|
+
process.exitCode = 1;
|
|
15726
|
+
return;
|
|
15727
|
+
}
|
|
15728
|
+
branch = current.value;
|
|
15729
|
+
}
|
|
15730
|
+
const gitArgs = opts.gitArgs ?? [];
|
|
15731
|
+
if (branch.includes(":") || branch.startsWith("+")) {
|
|
15732
|
+
const pushed = pushBranch(cwd, "kryd", branch, gitArgs);
|
|
15733
|
+
if (pushed.status === "failed") {
|
|
15734
|
+
process.exitCode = pushed.code;
|
|
15735
|
+
return;
|
|
15736
|
+
}
|
|
15737
|
+
process.stdout.write(
|
|
15738
|
+
"Pushed. `kryd push` does not follow refspec pushes \u2014 use `kryd logs` to watch a deploy.\n"
|
|
15739
|
+
);
|
|
15740
|
+
return;
|
|
15741
|
+
}
|
|
15742
|
+
try {
|
|
15743
|
+
const sha = headSha(cwd, branch);
|
|
15744
|
+
if (sha.status !== "ok") {
|
|
15745
|
+
process.stderr.write(`Unknown branch "${branch}" \u2014 nothing to push.
|
|
15746
|
+
`);
|
|
15747
|
+
process.exitCode = 1;
|
|
15748
|
+
return;
|
|
15749
|
+
}
|
|
15750
|
+
const commitSha = sha.value;
|
|
15751
|
+
const projectId = resolveProjectId(void 0, opts.cwd);
|
|
15752
|
+
if (!projectId) {
|
|
15753
|
+
process.stderr.write("tip: run `kryd init` here to scope `kryd push` to this project.\n");
|
|
15754
|
+
}
|
|
15755
|
+
const fetchDeployments = () => listDeployments(apiUrl, token, projectId ?? void 0);
|
|
15756
|
+
let known = null;
|
|
15757
|
+
try {
|
|
15758
|
+
known = findDeploymentsForCommit(await fetchDeployments(), commitSha, branch);
|
|
15759
|
+
} catch {
|
|
15760
|
+
known = null;
|
|
15761
|
+
}
|
|
15762
|
+
const pushed = pushBranch(cwd, "kryd", branch, gitArgs);
|
|
15763
|
+
if (pushed.status === "failed") {
|
|
15764
|
+
process.exitCode = pushed.code;
|
|
15765
|
+
return;
|
|
15766
|
+
}
|
|
15767
|
+
if (known === null) {
|
|
15768
|
+
process.stdout.write(
|
|
15769
|
+
"Pushed. Could not reach the API to follow the deploy \u2014 try `kryd logs`.\n"
|
|
15770
|
+
);
|
|
15771
|
+
return;
|
|
15772
|
+
}
|
|
15773
|
+
process.stdout.write("Waiting for the deploy to start\u2026\n");
|
|
15774
|
+
const deployment = await pollForNewDeployment(
|
|
15775
|
+
fetchDeployments,
|
|
15776
|
+
{ commitSha, branch, knownIds: new Set(known.map((d) => d.id)) },
|
|
15777
|
+
{
|
|
15778
|
+
...opts.sleep ? { sleep: opts.sleep } : {},
|
|
15779
|
+
...opts.pollTimeoutMs !== void 0 ? { timeoutMs: opts.pollTimeoutMs } : {}
|
|
15780
|
+
}
|
|
15781
|
+
);
|
|
15782
|
+
if (!deployment) {
|
|
15783
|
+
const previous = known[0];
|
|
15784
|
+
if (previous) {
|
|
15785
|
+
const outcome = previous.status === "live" ? "is already deployed" : `has already been deployed once (that deploy ended \`${previous.status}\`)`;
|
|
15786
|
+
process.stdout.write(
|
|
15787
|
+
`Nothing new to push \u2014 ${commitSha.slice(0, 7)} ${outcome} (\`kryd logs ${previous.id}\` to review it).
|
|
15788
|
+
`
|
|
15789
|
+
);
|
|
15790
|
+
} else {
|
|
15791
|
+
process.stdout.write(
|
|
15792
|
+
"Pushed, but no deploy has started yet. Follow it with `kryd logs`.\n" + (projectId ? "" : "If this keeps happening, re-run `kryd init` to link this folder.\n")
|
|
15793
|
+
);
|
|
15794
|
+
}
|
|
15795
|
+
return;
|
|
15796
|
+
}
|
|
15797
|
+
await followDeploy(apiUrl, token, deployment.id);
|
|
15798
|
+
} catch (err) {
|
|
15799
|
+
reportError(err);
|
|
15800
|
+
}
|
|
15801
|
+
}
|
|
15608
15802
|
async function runRollback(opts) {
|
|
15609
15803
|
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
15610
15804
|
const token = loadConfig().token;
|
|
@@ -15811,7 +16005,7 @@ async function runAiEnable(opts) {
|
|
|
15811
16005
|
const status = await enableAi(apiUrl, token, { projectId: project });
|
|
15812
16006
|
process.stdout.write(
|
|
15813
16007
|
`AI enabled for ${project}.
|
|
15814
|
-
` + (status.url ? `Endpoint: ${status.url}
|
|
16008
|
+
` + (status.url ? `Endpoint: ${status.url} (OpenAI-compatible base URL \u2014 use it as-is)
|
|
15815
16009
|
` : "") + `AI_GATEWAY_URL + AI_GATEWAY_TOKEN will be injected on your next deploy (kryd deploy).
|
|
15816
16010
|
`
|
|
15817
16011
|
);
|
|
@@ -15849,7 +16043,7 @@ async function runStorageDetach(opts) {
|
|
|
15849
16043
|
reportError(err);
|
|
15850
16044
|
}
|
|
15851
16045
|
}
|
|
15852
|
-
var CLI_VERSION = true ? "0.
|
|
16046
|
+
var CLI_VERSION = true ? "0.2.0" : "0.0.0-dev";
|
|
15853
16047
|
var program = new Command();
|
|
15854
16048
|
program.name("kryd").description("Kryd CLI").version(CLI_VERSION);
|
|
15855
16049
|
program.command("login").description("Sign in via the browser (default) and store a token").option("--email <email>", "account email (with --password; non-interactive escape hatch)").option("--password <password>", "account password (with --email; visible in `ps` \u2014 prefer the browser flow)").option(
|
|
@@ -15876,6 +16070,11 @@ program.command("logs [target]").description(
|
|
|
15876
16070
|
).option("--api-url <url>", "control-plane API base URL").action(
|
|
15877
16071
|
(target, opts) => opts.runtime ? runRuntimeLogs({ project: target, since: opts.since, apiUrl: opts.apiUrl }) : runLogs({ apiUrl: opts.apiUrl, deployment: target })
|
|
15878
16072
|
);
|
|
16073
|
+
program.command("push [branch]").description(
|
|
16074
|
+
"Push to the kryd remote and follow the deploy it triggers (defaults to the current branch)"
|
|
16075
|
+
).option("--api-url <url>", "control-plane API base URL").action(
|
|
16076
|
+
(branch, opts, cmd) => runPush({ ...opts, branch, gitArgs: cmd.args.slice(1) })
|
|
16077
|
+
);
|
|
15879
16078
|
program.command("deploy [project]").description("Trigger a deploy of the project's production branch and follow it live").option("--api-url <url>", "control-plane API base URL").action((project, opts) => runDeploy({ ...opts, project }));
|
|
15880
16079
|
program.command("rollback [project] [deployment]").description("Roll back to a previous successful deploy (no rebuild) and follow it live").option("--api-url <url>", "control-plane API base URL").action(
|
|
15881
16080
|
(project, deployment, opts) => runRollback({ ...opts, project, deployment })
|
|
@@ -15910,6 +16109,7 @@ export {
|
|
|
15910
16109
|
runLogin,
|
|
15911
16110
|
runLogout,
|
|
15912
16111
|
runLogs,
|
|
16112
|
+
runPush,
|
|
15913
16113
|
runRollback,
|
|
15914
16114
|
runRuntimeLogs,
|
|
15915
16115
|
runStorageCreate,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kryd/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Kryd CLI — push a React / Vite / Next.js app to the effortless EU-sovereign deploy stack on Scaleway: git push → live with SSL, one-click managed Postgres & object storage, and an EU-hosted AI gateway already wired in.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"kryd",
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
"typescript": "^5.6.3",
|
|
52
52
|
"vitest": "^2.1.8",
|
|
53
53
|
"@kryd/shared-types": "0.0.0",
|
|
54
|
-
"@kryd/config-
|
|
55
|
-
"@kryd/config-
|
|
54
|
+
"@kryd/config-eslint": "0.0.0",
|
|
55
|
+
"@kryd/config-ts": "0.0.0"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --external:commander --define:__KRYD_VERSION__=\\\"$npm_package_version\\\" --outfile=dist/index.js",
|