layero 0.6.0 → 0.6.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/README.md +24 -0
- package/dist/api.js +9 -0
- package/dist/bin/layero.js +50 -0
- package/dist/commands/hooks.js +80 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -53,6 +53,7 @@ re-run `layero deploy`, get a new preview URL each time.
|
|
|
53
53
|
| `layero deploy` | Auto-detect framework, pack cwd, build, ship. |
|
|
54
54
|
| `layero deploys list` | List recent deploys. |
|
|
55
55
|
| `layero rollback` | Re-activate the previous successful deploy. |
|
|
56
|
+
| `layero hooks list/create/delete` | Manage deploy hooks (URL tokens that trigger builds from CMS / cron / external CI). |
|
|
56
57
|
| `layero token` | Manage the auth token directly. |
|
|
57
58
|
|
|
58
59
|
Run `layero <cmd> --help` for full options.
|
|
@@ -98,6 +99,29 @@ Run `layero <cmd> --help` for full options.
|
|
|
98
99
|
| `hugo.{toml,yaml,json}` or `config.*` with Hugo markers (`baseURL`, `[markup]`, …) | hugo | `hugo --gc --minify` (no install needed) | `public` |
|
|
99
100
|
| any `.html` at root, no `package.json` | static | `true` (no-op) | `.` |
|
|
100
101
|
|
|
102
|
+
## Deploy hooks — webhook URLs that trigger builds
|
|
103
|
+
|
|
104
|
+
When something *other than you* should kick a build — a headless CMS
|
|
105
|
+
publishing content, a cron job, an external CI pipeline — create a
|
|
106
|
+
deploy hook. You get back an opaque URL; whoever POSTs to it fires a
|
|
107
|
+
deploy.
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# Inside a linked project directory:
|
|
111
|
+
layero hooks create strapi-content # preview-target, default branch
|
|
112
|
+
layero hooks create publish --prod # production-target hook
|
|
113
|
+
layero hooks create staging --branch=dev # explicit branch
|
|
114
|
+
layero hooks list
|
|
115
|
+
layero hooks delete <id> # revoke immediately
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The created URL looks like `https://api.layero.ru/hooks/<token>`. Paste
|
|
119
|
+
it into Strapi / Sanity / Contentful / Decap CMS / GitHub Actions / a
|
|
120
|
+
cron job — any tool that can POST to a URL. Token = credential; rotate
|
|
121
|
+
by `delete` + `create`. There is no per-token rate limit yet; rely on
|
|
122
|
+
the platform's natural in-flight-commit dedup if the same commit gets
|
|
123
|
+
fired more than once.
|
|
124
|
+
|
|
101
125
|
## Bring-your-own-build (`--prebuilt`)
|
|
102
126
|
|
|
103
127
|
If you already build your site yourself — in CI, via a desktop tool like
|
package/dist/api.js
CHANGED
|
@@ -81,6 +81,15 @@ export class ApiClient {
|
|
|
81
81
|
rollbackProject(projectId, input) {
|
|
82
82
|
return this.request("POST", `/projects/${projectId}/rollback`, input);
|
|
83
83
|
}
|
|
84
|
+
listDeployHooks(projectId) {
|
|
85
|
+
return this.request("GET", `/projects/${projectId}/deploy-hooks`);
|
|
86
|
+
}
|
|
87
|
+
createDeployHook(projectId, input) {
|
|
88
|
+
return this.request("POST", `/projects/${projectId}/deploy-hooks`, input);
|
|
89
|
+
}
|
|
90
|
+
deleteDeployHook(projectId, hookId) {
|
|
91
|
+
return this.request("DELETE", `/projects/${projectId}/deploy-hooks/${hookId}`);
|
|
92
|
+
}
|
|
84
93
|
startDeviceAuth() {
|
|
85
94
|
return this.request("POST", "/auth/cli/device");
|
|
86
95
|
}
|
package/dist/bin/layero.js
CHANGED
|
@@ -11,6 +11,7 @@ import { linkCmd } from "../commands/link.js";
|
|
|
11
11
|
import { tokenSetCmd } from "../commands/token.js";
|
|
12
12
|
import { deployCmd } from "../commands/deploy.js";
|
|
13
13
|
import { deploysListCmd, rollbackCmd } from "../commands/deploys.js";
|
|
14
|
+
import { hooksCreateCmd, hooksDeleteCmd, hooksListCmd } from "../commands/hooks.js";
|
|
14
15
|
import { loginCmd } from "../commands/login.js";
|
|
15
16
|
import { orgsListCmd } from "../commands/orgs.js";
|
|
16
17
|
import { initCmd } from "../commands/init.js";
|
|
@@ -74,6 +75,55 @@ async function main() {
|
|
|
74
75
|
.action(async (opts) => {
|
|
75
76
|
await deploysListCmd(opts);
|
|
76
77
|
});
|
|
78
|
+
const hooks = program
|
|
79
|
+
.command("hooks")
|
|
80
|
+
.description("Manage deploy hooks — URL tokens that trigger builds from CMS / cron / external CI.");
|
|
81
|
+
hooks
|
|
82
|
+
.command("list")
|
|
83
|
+
.description("List deploy hooks for the linked project.")
|
|
84
|
+
.option("--project <id>", "target project id (default: linked .layero/project.json)")
|
|
85
|
+
.action(async (opts) => {
|
|
86
|
+
try {
|
|
87
|
+
await hooksListCmd(opts);
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
console.error(String(err?.message ?? err));
|
|
91
|
+
process.exitCode = 1;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
hooks
|
|
95
|
+
.command("create <name>")
|
|
96
|
+
.description("Create a new deploy hook. Prints a URL — paste it into Strapi / Sanity / "
|
|
97
|
+
+ "Contentful / GitHub Actions / cron as a POST webhook.")
|
|
98
|
+
.option("--project <id>", "target project id (default: linked .layero/project.json)")
|
|
99
|
+
.option("--branch <name>", "branch to deploy when fired (default: project default_branch, evaluated at fire time)")
|
|
100
|
+
.option("--prod", "fire the hook against the production environment (default: preview)")
|
|
101
|
+
.addHelpText("after", "\nExamples:\n"
|
|
102
|
+
+ " $ layero hooks create strapi-content # preview-target hook for default branch\n"
|
|
103
|
+
+ " $ layero hooks create publish --prod # production-target hook for default branch\n"
|
|
104
|
+
+ " $ layero hooks create staging --branch=dev # any branch, preview environment")
|
|
105
|
+
.action(async (name, opts) => {
|
|
106
|
+
try {
|
|
107
|
+
await hooksCreateCmd(name, opts);
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
console.error(String(err?.message ?? err));
|
|
111
|
+
process.exitCode = 1;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
hooks
|
|
115
|
+
.command("delete <id>")
|
|
116
|
+
.description("Revoke a deploy hook. The URL stops working immediately.")
|
|
117
|
+
.option("--project <id>", "target project id (default: linked .layero/project.json)")
|
|
118
|
+
.action(async (id, opts) => {
|
|
119
|
+
try {
|
|
120
|
+
await hooksDeleteCmd(id, opts);
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
console.error(String(err?.message ?? err));
|
|
124
|
+
process.exitCode = 1;
|
|
125
|
+
}
|
|
126
|
+
});
|
|
77
127
|
program
|
|
78
128
|
.command("rollback")
|
|
79
129
|
.description("Re-activate the previous successful deploy on the project's default branch.")
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { ApiClient, ApiError } from "../api.js";
|
|
3
|
+
import { loadConfig } from "../config.js";
|
|
4
|
+
import { loadProjectConfig } from "../project-config.js";
|
|
5
|
+
async function resolveProjectId(opts) {
|
|
6
|
+
if (opts.project) {
|
|
7
|
+
// Accepts an id directly; UUID format check is server-side.
|
|
8
|
+
return opts.project;
|
|
9
|
+
}
|
|
10
|
+
const linked = await loadProjectConfig(process.cwd());
|
|
11
|
+
if (linked?.project_id) {
|
|
12
|
+
return linked.project_id;
|
|
13
|
+
}
|
|
14
|
+
throw new Error("no project linked in cwd — pass --project <id> or run `layero deploy` "
|
|
15
|
+
+ "from a project directory once to link it.");
|
|
16
|
+
}
|
|
17
|
+
async function makeClient() {
|
|
18
|
+
const cfg = await loadConfig();
|
|
19
|
+
if (!cfg.token) {
|
|
20
|
+
throw new Error("not logged in. run `layero login` first.");
|
|
21
|
+
}
|
|
22
|
+
return new ApiClient(cfg);
|
|
23
|
+
}
|
|
24
|
+
export async function hooksListCmd(opts) {
|
|
25
|
+
const api = await makeClient();
|
|
26
|
+
const projectId = await resolveProjectId(opts);
|
|
27
|
+
const hooks = await api.listDeployHooks(projectId);
|
|
28
|
+
if (hooks.length === 0) {
|
|
29
|
+
console.log("no deploy hooks yet — `layero hooks create <name>` to add one.");
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
for (const h of hooks) {
|
|
33
|
+
const targetTag = h.target === "production" ? chalk.red("[prod]") : chalk.cyan("[preview]");
|
|
34
|
+
const branchTag = h.branch ? chalk.dim(`branch=${h.branch}`) : chalk.dim("branch=default");
|
|
35
|
+
const last = h.last_triggered_at
|
|
36
|
+
? `fired ${new Date(h.last_triggered_at).toISOString()}`
|
|
37
|
+
: "never fired";
|
|
38
|
+
console.log(`${targetTag} ${chalk.bold(h.name)} ${branchTag} ${chalk.dim("(" + last + ")")}`);
|
|
39
|
+
console.log(` ${chalk.dim(h.id)}`);
|
|
40
|
+
console.log(` ${h.url}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export async function hooksCreateCmd(name, opts) {
|
|
44
|
+
if (!name || !name.trim()) {
|
|
45
|
+
throw new Error("name is required: `layero hooks create <name>`");
|
|
46
|
+
}
|
|
47
|
+
const api = await makeClient();
|
|
48
|
+
const projectId = await resolveProjectId(opts);
|
|
49
|
+
const hook = await api.createDeployHook(projectId, {
|
|
50
|
+
name: name.trim(),
|
|
51
|
+
branch: opts.branch ?? null,
|
|
52
|
+
target: opts.prod ? "production" : "preview",
|
|
53
|
+
});
|
|
54
|
+
const targetTag = hook.target === "production" ? chalk.red("[prod]") : chalk.cyan("[preview]");
|
|
55
|
+
console.log(`${chalk.green("✓")} created ${targetTag} ${chalk.bold(hook.name)}`);
|
|
56
|
+
console.log(` ${chalk.dim(hook.id)}`);
|
|
57
|
+
console.log(` ${chalk.bold(hook.url)}`);
|
|
58
|
+
console.log(chalk.dim("\n Paste this URL into your CMS / cron / external CI as a POST webhook. "
|
|
59
|
+
+ "Anyone with the URL can fire a build — treat it like a secret. "
|
|
60
|
+
+ "Rotate by deleting and creating a new one."));
|
|
61
|
+
}
|
|
62
|
+
export async function hooksDeleteCmd(hookId, opts) {
|
|
63
|
+
if (!hookId) {
|
|
64
|
+
throw new Error("hook id is required: `layero hooks delete <id>`");
|
|
65
|
+
}
|
|
66
|
+
const api = await makeClient();
|
|
67
|
+
const projectId = await resolveProjectId(opts);
|
|
68
|
+
try {
|
|
69
|
+
await api.deleteDeployHook(projectId, hookId);
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
if (err instanceof ApiError && err.status === 404) {
|
|
73
|
+
console.error(chalk.yellow(`no hook ${hookId} on this project (already deleted?)`));
|
|
74
|
+
process.exitCode = 1;
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
console.log(`${chalk.green("✓")} hook ${chalk.dim(hookId)} revoked`);
|
|
80
|
+
}
|