@wervt/cli 0.2.0 → 0.2.1
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/dist/index.mjs +116 -7
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { appendFile, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, join, relative, resolve } from "node:path";
|
|
4
4
|
import { styleText } from "node:util";
|
|
5
5
|
import * as p from "@clack/prompts";
|
|
@@ -71,6 +71,52 @@ async function saveConfig(config) {
|
|
|
71
71
|
return file;
|
|
72
72
|
}
|
|
73
73
|
//#endregion
|
|
74
|
+
//#region src/source.ts
|
|
75
|
+
async function git(dir, ...args) {
|
|
76
|
+
try {
|
|
77
|
+
const result = await x("git", args, {
|
|
78
|
+
nodeOptions: { cwd: dir },
|
|
79
|
+
throwOnError: false
|
|
80
|
+
});
|
|
81
|
+
return result.exitCode === 0 ? result.stdout.trim() || void 0 : void 0;
|
|
82
|
+
} catch {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** `git@github.com:owner/repo.git` and `https://…/repo.git` as `https://github.com/owner/repo`. */
|
|
87
|
+
function webUrl(remote) {
|
|
88
|
+
if (!remote) return void 0;
|
|
89
|
+
const ssh = remote.match(/^(?:ssh:\/\/)?git@([^:/]+)[:/](.+?)(?:\.git)?$/);
|
|
90
|
+
if (ssh) return `https://${ssh[1]}/${ssh[2]}`;
|
|
91
|
+
const https = remote.match(/^https:\/\/(?:[^@/]+@)?(.+?)(?:\.git)?$/);
|
|
92
|
+
return https ? `https://${https[1]}` : void 0;
|
|
93
|
+
}
|
|
94
|
+
async function deploySource(dir) {
|
|
95
|
+
const env = process.env;
|
|
96
|
+
const message = await git(dir, "log", "-1", "--format=%s");
|
|
97
|
+
if (env.GITHUB_ACTIONS === "true") {
|
|
98
|
+
const repo = `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}`;
|
|
99
|
+
return {
|
|
100
|
+
commit: env.GITHUB_SHA,
|
|
101
|
+
branch: env.GITHUB_HEAD_REF || env.GITHUB_REF_NAME,
|
|
102
|
+
message,
|
|
103
|
+
repo,
|
|
104
|
+
actor: env.GITHUB_ACTOR,
|
|
105
|
+
run: `${repo}/actions/runs/${env.GITHUB_RUN_ID}`
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const status = await git(dir, "status", "--porcelain", "--", ".");
|
|
109
|
+
const commit = await git(dir, "rev-parse", "HEAD");
|
|
110
|
+
return {
|
|
111
|
+
commit,
|
|
112
|
+
branch: await git(dir, "branch", "--show-current"),
|
|
113
|
+
message,
|
|
114
|
+
dirty: commit ? Boolean(status) : void 0,
|
|
115
|
+
repo: webUrl(await git(dir, "remote", "get-url", "origin")),
|
|
116
|
+
actor: await git(dir, "config", "user.name")
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
//#endregion
|
|
74
120
|
//#region src/index.ts
|
|
75
121
|
const connection = {
|
|
76
122
|
url: {
|
|
@@ -79,7 +125,7 @@ const connection = {
|
|
|
79
125
|
},
|
|
80
126
|
token: {
|
|
81
127
|
type: "string",
|
|
82
|
-
description: "
|
|
128
|
+
description: "Admin, read or deploy token (or WERVT_TOKEN)"
|
|
83
129
|
}
|
|
84
130
|
};
|
|
85
131
|
async function api(path, flags, init = {}) {
|
|
@@ -158,9 +204,13 @@ const deploy = defineCommand({
|
|
|
158
204
|
const bundle = await createBundle(dir);
|
|
159
205
|
spinner.stop(`Packed ${bundle.files} files (${(bundle.data.byteLength / 1024).toFixed(0)} kB)`);
|
|
160
206
|
spinner.start("Uploading");
|
|
207
|
+
const source = await deploySource(dir);
|
|
161
208
|
const res = await api(`/v1/apps/${name}/deploys`, args, {
|
|
162
209
|
method: "POST",
|
|
163
|
-
headers: {
|
|
210
|
+
headers: {
|
|
211
|
+
"content-type": "application/gzip",
|
|
212
|
+
"x-wervt-source": encodeURIComponent(JSON.stringify(source))
|
|
213
|
+
},
|
|
164
214
|
body: new Uint8Array(bundle.data)
|
|
165
215
|
});
|
|
166
216
|
const labels = {
|
|
@@ -184,6 +234,7 @@ const deploy = defineCommand({
|
|
|
184
234
|
if (event.done) {
|
|
185
235
|
spinner.stop(`Release ${event.release}`);
|
|
186
236
|
p.outro(`Deployed → ${event.url}`);
|
|
237
|
+
if (process.env.GITHUB_OUTPUT) await appendFile(process.env.GITHUB_OUTPUT, `url=${event.url}\nrelease=${event.release}\n`);
|
|
187
238
|
return;
|
|
188
239
|
}
|
|
189
240
|
spinner.message(labels[event.step] ?? event.step);
|
|
@@ -236,7 +287,18 @@ const releases = defineCommand({
|
|
|
236
287
|
},
|
|
237
288
|
async run({ args }) {
|
|
238
289
|
const info = await (await api(`/v1/apps/${args.app}`, args)).json();
|
|
239
|
-
|
|
290
|
+
const history = new Map(info.history?.map((entry) => [entry.release, entry]));
|
|
291
|
+
for (const release of [...info.releases].reverse()) {
|
|
292
|
+
const { source, deployedBy } = history.get(release) ?? {};
|
|
293
|
+
const details = [
|
|
294
|
+
source?.commit ? `${source.commit.slice(0, 7)}${source.dirty ? "+" : ""}` : void 0,
|
|
295
|
+
source?.branch,
|
|
296
|
+
source?.actor ?? deployedBy,
|
|
297
|
+
source?.message && styleText("dim", source.message)
|
|
298
|
+
].filter(Boolean);
|
|
299
|
+
const active = release === info.release ? styleText("green", " (active)") : "";
|
|
300
|
+
console.log(`${release}${active}${details.length ? ` ${details.join(" ")}` : ""}`);
|
|
301
|
+
}
|
|
240
302
|
}
|
|
241
303
|
});
|
|
242
304
|
const activate = defineCommand({
|
|
@@ -378,7 +440,53 @@ const env = defineCommand({
|
|
|
378
440
|
})
|
|
379
441
|
}) : await api(`/v1/apps/${args.app}/env`, args)).json();
|
|
380
442
|
console.log(keys.length ? keys.join("\n") : "No variables set.");
|
|
381
|
-
if (Object.keys(set).length || unset.length) console.log("Applies
|
|
443
|
+
if (Object.keys(set).length || unset.length) console.log("Applies from the next request.");
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
const tokens = defineCommand({
|
|
447
|
+
meta: {
|
|
448
|
+
name: "tokens",
|
|
449
|
+
description: "List, create or revoke an app's deploy tokens (for CI)"
|
|
450
|
+
},
|
|
451
|
+
args: {
|
|
452
|
+
app: {
|
|
453
|
+
type: "positional",
|
|
454
|
+
required: true,
|
|
455
|
+
description: "App name"
|
|
456
|
+
},
|
|
457
|
+
create: {
|
|
458
|
+
type: "string",
|
|
459
|
+
description: "Create a token with this name, e.g. github"
|
|
460
|
+
},
|
|
461
|
+
revoke: {
|
|
462
|
+
type: "string",
|
|
463
|
+
description: "Revoke the token with this id"
|
|
464
|
+
},
|
|
465
|
+
...connection
|
|
466
|
+
},
|
|
467
|
+
async run({ args }) {
|
|
468
|
+
const path = `/v1/apps/${args.app}/tokens`;
|
|
469
|
+
if (args.create) {
|
|
470
|
+
const token = await (await api(path, args, {
|
|
471
|
+
method: "POST",
|
|
472
|
+
headers: { "content-type": "application/json" },
|
|
473
|
+
body: JSON.stringify({ name: args.create })
|
|
474
|
+
})).json();
|
|
475
|
+
console.error(`Deploy token "${token.name}" for ${args.app} (shown once):`);
|
|
476
|
+
console.log(token.token);
|
|
477
|
+
console.error(styleText("dim", "It can deploy this app and nothing else. Store it as the WERVT_TOKEN secret."));
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
if (args.revoke) {
|
|
481
|
+
await api(`${path}/${args.revoke}`, args, { method: "DELETE" });
|
|
482
|
+
return console.log(`Revoked ${args.revoke}`);
|
|
483
|
+
}
|
|
484
|
+
const list = await (await api(path, args)).json();
|
|
485
|
+
if (!list.length) return console.log(`No deploy tokens for ${args.app}.`);
|
|
486
|
+
for (const token of list) {
|
|
487
|
+
const used = token.lastUsedAt ? `used ${token.lastUsedAt.slice(0, 10)}` : "never used";
|
|
488
|
+
console.log(`${token.id} ${token.name.padEnd(20)} created ${token.createdAt.slice(0, 10)} ${used}`);
|
|
489
|
+
}
|
|
382
490
|
}
|
|
383
491
|
});
|
|
384
492
|
const remove = defineCommand({
|
|
@@ -506,12 +614,12 @@ const login = defineCommand({
|
|
|
506
614
|
token: {
|
|
507
615
|
type: "string",
|
|
508
616
|
required: true,
|
|
509
|
-
description: "
|
|
617
|
+
description: "Admin, read or deploy token"
|
|
510
618
|
}
|
|
511
619
|
},
|
|
512
620
|
async run({ args }) {
|
|
513
621
|
const res = await fetch(`${args.url.replace(/\/$/, "")}/v1/apps`, { headers: { authorization: `Bearer ${args.token}` } });
|
|
514
|
-
if (!res.ok) throw new Error(`Login failed: ${res.status} ${res.statusText}`);
|
|
622
|
+
if (!res.ok && res.status !== 403) throw new Error(`Login failed: ${res.status} ${res.statusText}`);
|
|
515
623
|
const file = await saveConfig({
|
|
516
624
|
url: args.url,
|
|
517
625
|
token: args.token
|
|
@@ -534,6 +642,7 @@ runMain(defineCommand({
|
|
|
534
642
|
reprovision,
|
|
535
643
|
access,
|
|
536
644
|
env,
|
|
645
|
+
tokens,
|
|
537
646
|
delete: remove,
|
|
538
647
|
login
|
|
539
648
|
}
|